evm
evm copied to clipboard
Gas estimation for CREATE opcodes with l64 rule gives incorrect results.
I've been benchmarking gas usage and was having trouble with a contract that makes a CREATE call internally -- the gas estimation came out far higher than expected. The results I was getting were similar to here: https://github.com/paritytech/frontier/pull/252#issuecomment-754700523
And to clarify, by l64
I mean the protocol defined here, to pass gas into subcontracts: https://github.com/ethereum/EIPs/issues/150
Investigation shows the following cost occurring in the gasometer at l64
time:
2021-01-13 15:26:24.970 TRACE http.worker20 evm:Running opcode: Err(Create), Pre gas-left: 4294945964
2021-01-13 15:26:24.970 TRACE http.worker20 evm:Opcode costs: memory_gas: 36, gas_cost: 32000, total used gas: 231
2021-01-13 15:26:24.970 TRACE http.worker20 evm:Recording l64 cost: 67108030
2021-01-13 15:26:24.971 TRACE http.worker20 evm:Running opcode: Ok(Push(1)), Pre gas-left: 4227805934
I note that the l64 cost is approximately equal to the amount of gas remaining / 64. My suspicion is that, because the gasometer runs the code with a MAX_U256 starting gas, the l64
calls are being improperly estimated when compared with the amount of starting gas used in practice. Note that this starting gas comes from the following line in frontier: https://github.com/paritytech/frontier/blob/master/client/rpc/src/eth.rs#L760, but should theoretically be unneeded for the purposes of estimation.
If useful, here is the entire contract I've been using for testing, specifically the spawn()
call on the CreateContract
.
As a quick point of comparison, the gas estimate I get with EIP-150 enabled is 67227213
, but if I set call_l64_after_gas: false
in the config, I instead get 119183
-- much closer if not exactly the amount of gas used when the call is actually executed.
pragma solidity ^0.5.0;
contract SubContract {
constructor() public payable { }
function getAddress() external view returns (address ownAddress) {
return address(this);
}
function getValue() external view returns (uint) {
return address(this).balance;
}
}
contract CreateContract {
address public deployed;
constructor() public { }
function spawn() external returns (SubContract subAddress) {
SubContract result = new SubContract();
deployed = address(result);
return result;
}
function spawnWithValue() external payable returns (SubContract subAddress) {
SubContract result = (new SubContract).value(msg.value)();
deployed = address(result);
return result;
}
}