silkrpc icon indicating copy to clipboard operation
silkrpc copied to clipboard

GasCost calculation mismatch for opCode CALL/STATICCALL with RPCdaemon

Open lupin012 opened this issue 3 years ago • 0 comments

Executing the debug_traceTransaction call using the following input:

{
  "jsonrpc":"2.0",
  "method":"debug_traceTransaction",
  "params":[
     "0xeb3803f052e817e41e381b5ac150d0967e37b2e298d9a1dc079750abeeafbead",
     {
       "disableStorage": false,
       "disableMemory": false,
       "disableStack": false
     }
  ],
  "id":1
}

we see a mismatch on gasCost calculation between RPCDaemon/erigon and Silkrpc/Silkworm/Evmone

Erigon: See core/vm/interpreter.go

//Static portion of gas cost = operation.constantGas // For tracing (ad esempio per la CALL e 100)

// Dynamic portion of gas // consume the gas and return an error if not enough gas is available. // cost is explicitly set so that the capture state defer method can get the proper cost if operation.dynamicGas != nil { var dynamicCost uint64 dynamicCost, err = operation.dynamicGas(in.evm, contract, locStack, mem, memorySize)

 cost += dynamicCost // total cost, for debug tracing
 if err != nil || !contract.UseGas(dynamicCost) {
    return nil, ErrOutOfGas
 }

}

So the gasCost is the sum between static part and dynamica one using the gas returned by operation.dynamicGas Then is called the Tracer: in.cfg.Tracer.CaptureState(in.evm, pc, op, gasCopy, cost, mem, locStack, in.returnData, contract, in.evm.depth, err)

The routine operation.dynamicGas() in our case is' : makeCallVariantGasCallEIP2929() in core/vm/operations_acl.go

In the routine contract.Gas (is the gasLeft) (GasLeft - static cost opcode) if !warmAccess decrease by coldCost (2500)

gas, err := oldCalculator() // decrease by 1/64 of the gas-left

if warmAccess return

// In case of a cold access, we temporarily add the cold charge back, and also // add it to the returned gas. By adding it to the return, it will be charged // outside of this function, as part of the dynamic gas, and that will make it // also become correctly reported to tracers.

Using the transaction reported below:

"pc":15411,"op":"DUP7","gas":77277,"gasCost":3,"depth":2 "pc":15412,"op":"GAS","gas":77274,"gasCost":2,"depth":2 "pc":15413,"op":"CALL","gas":77272,"gasCost":76106,"depth":2 ============== "pc":0,"op":"PUSH1","gas":73506,"gasCost":3,"depth":3 "pc":2,"op":"PUSH1","gas":73503,"gasCost":3,"depth":3 "pc":4,"op":"MSTORE","gas":73500,"gasCost":12,"depth":3

in makeCallVariantGasCallEIP2929: 77172 (contract.Gas is already decreased by opcode cost)

if !warmAccess 77172-2500 = 74672 (coldCost) 74672-(74672/64) = 73506 (old-calculator)

73506+2500=76006 After the routine it is summed the static parte (100) = 76106

Seems the gasCost calculation for CALL and any other opcode different. Is it correct


In silkrpc (tracer) with current interface using gas-left and nsg.gas it is not possible calculate gasCost of the CALL in the same way of ERIGON.

call_impl() in silkworm/third_party/evmone/lib/evmone/instructions_calls.cpp

in input we have:

  • gas = <gasLeft>
  • gasLeft = <gasLeft-costoOpcode>

if (state.rev >= EVMC_BERLIN && state.host.access_account(dst) == EVMC_ACCESS_COLD) decraese gas-left by 2500 (coldCost)

// assign msg.gas = gas

msg.gas = std::min(msg.gas, state.gas_left - state.gas_left / 64);

then it is executed the execute() where the tracer is called const auto result = state.host.call(msg);

the tracer is called with gas-left and msg.gas with same value. Because during ExecutionState the reset() is caled

using transaction data:

"depth":2,"gas":77277,"gasCost":3,"op":"DUP7","pc":15411 "depth":2,"gas":77274,"gasCost":2,"op":"GAS","pc":15412 "depth":2,"gas":77272,"gasCost":73506,"op":"CALL","pc":15413 -------------- "depth":3,"gas":73506,"gasCost":3,"op":"PUSH1","pc":0 "depth":3,"gas":73503,"gasCost":3,"op":"PUSH1","pc":2 "depth":3,"gas":73500,"gasCost":12,"op":"MSTORE","pc":4

in call_impl() 77172 ( OPcode cost is already decreased) gas = 77272 gasLeft = 77172

if (state.rev >= EVMC_BERLIN && state.host.access_account(dst) == EVMC_ACCESS_COLD) state.gas_left -= 2500 (74672)

msg.gas = min(74672, 74672-(74672/64)) // 73506

msg.gas = 73506 gas-left = 74672

const auto result = state.host.call(msg);

the tracer receives: msg.gas = 73506 gas-left = 73506

lupin012 avatar Jun 04 '22 09:06 lupin012