evm icon indicating copy to clipboard operation
evm copied to clipboard

Gas estimation for CREATE opcodes with l64 rule gives incorrect results.

Open jnaviask opened this issue 4 years ago • 1 comments

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;
  }
}

jnaviask avatar Jan 13 '21 20:01 jnaviask