core/vm: update to newest tests, rework gas mechanism

This commit is contained in:
Marius van der Wijden 2026-03-19 14:52:15 +01:00
parent 41feef2a84
commit e5d453c086
14 changed files with 125 additions and 81 deletions

View file

@ -5,10 +5,10 @@
# https://github.com/ethereum/execution-spec-tests/releases/download/v5.1.0
a3192784375acec7eaec492799d5c5d0c47a2909a3cc40178898e4ecd20cc416 fixtures_develop.tar.gz
# version:spec-tests-bal v5.4.0
# version:spec-tests-bal v5.5.1
# https://github.com/ethereum/execution-spec-tests/releases
# https://github.com/ethereum/execution-spec-tests/releases/download/bal%40v5.4.0
be300cbc5dcae213850bc5688d8fd0f572785ec64801f28653e1c01a03805127 fixtures_bal.tar.gz
# https://github.com/ethereum/execution-spec-tests/releases/download/bal%40v5.5.1
79f81379bc456b9f05d4e7298eba939855d0147b525cd2cadd1206513284ab9e fixtures_bal.tar.gz
# version:golang 1.25.7
# https://go.dev/dl/

View file

@ -298,7 +298,7 @@ func ProcessBeaconBlockRoot(beaconRoot common.Hash, evm *vm.EVM) bal.StateMutati
}
evm.SetTxContext(NewEVMTxContext(msg))
evm.StateDB.AddAddressToAccessList(params.BeaconRootsAddress)
_, _, _ = evm.Call(msg.From, *msg.To, msg.Data, vm.GasCosts{RegularGas: 30_000_000}, common.U2560)
_, _, _, _ = evm.Call(msg.From, *msg.To, msg.Data, vm.GasCosts{RegularGas: 30_000_000}, common.U2560)
return evm.StateDB.Finalise(true)
}
@ -322,7 +322,7 @@ func ProcessParentBlockHash(prevHash common.Hash, evm *vm.EVM) bal.StateMutation
}
evm.SetTxContext(NewEVMTxContext(msg))
evm.StateDB.AddAddressToAccessList(params.HistoryStorageAddress)
_, _, err := evm.Call(msg.From, *msg.To, msg.Data, vm.GasCosts{RegularGas: 30_000_000}, common.U2560)
_, _, _, err := evm.Call(msg.From, *msg.To, msg.Data, vm.GasCosts{RegularGas: 30_000_000}, common.U2560)
if err != nil {
panic(err)
}
@ -362,7 +362,7 @@ func processRequestsSystemCall(requests *[][]byte, evm *vm.EVM, requestType byte
}
evm.SetTxContext(NewEVMTxContext(msg))
evm.StateDB.AddAddressToAccessList(addr)
ret, _, err := evm.Call(msg.From, *msg.To, msg.Data, vm.GasCosts{RegularGas: 30_000_000}, common.U2560)
ret, _, _, err := evm.Call(msg.From, *msg.To, msg.Data, vm.GasCosts{RegularGas: 30_000_000}, common.U2560)
mut := evm.StateDB.Finalise(true)
if err != nil {
return nil, fmt.Errorf("system call failed to execute: %v", err)

View file

@ -545,8 +545,9 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
vmerr error // vm errors do not effect consensus and are therefore not assigned to err
authRefund uint64
)
var execGasUsed vm.GasUsed
if contractCreation {
ret, _, st.gasRemaining, vmerr = st.evm.Create(msg.From, msg.Data, st.gasRemaining, value)
ret, _, st.gasRemaining, execGasUsed, vmerr = st.evm.Create(msg.From, msg.Data, st.gasRemaining, value)
} else {
// Increment the nonce for the next transaction.
st.state.SetNonce(msg.From, st.state.GetNonce(msg.From)+1, tracing.NonceChangeEoACall)
@ -570,19 +571,13 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
}
// Execute the transaction's call.
ret, st.gasRemaining, vmerr = st.evm.Call(msg.From, st.to(), msg.Data, st.gasRemaining, value)
ret, st.gasRemaining, execGasUsed, vmerr = st.evm.Call(msg.From, st.to(), msg.Data, st.gasRemaining, value)
}
// Record the gas used excluding gas refunds. This value represents the actual
// gas allowance required to complete execution.
peakGasUsed := st.gasUsed()
// EIP-8037: Capture pre-refund remaining for 2D gas accounting.
var preRefundRemaining uint64
if rules.IsAmsterdam {
preRefundRemaining = st.gasRemaining.Sum()
}
// Compute refund counter, capped to a refund quotient.
st.gasRemaining.RegularGas += st.calcRefund()
if rules.IsPrague {
@ -607,10 +602,11 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
if rules.IsAmsterdam {
// EIP-8037: 2D gas accounting for Amsterdam.
// tx_state = adjusted_intrinsic_state + exec_state_used (spec: set_delegation adjusts intrinsic)
// tx_regular = total_dimensional_used - tx_state
txState := (gas.StateGas - authRefund) + st.gasRemaining.StateGasCharged
txRegular := (msg.GasLimit - preRefundRemaining) - txState
// tx_regular = intrinsic_regular + exec_regular_gas_used
// tx_state = intrinsic_state (adjusted) + exec_state_gas_used
// These are tracked independently, not derived from remaining gas.
txState := (gas.StateGas - authRefund) + execGasUsed.StateGasCharged
txRegular := gas.RegularGas + execGasUsed.RegularGasUsed
txRegular = max(txRegular, floorDataGas)
if err := st.gp.ReturnGasAmsterdam(returned, txRegular, txState, st.gasUsed()); err != nil {
return nil, err

View file

@ -42,8 +42,9 @@ type Contract struct {
IsDeployment bool
IsSystemCall bool
Gas GasCosts
value *uint256.Int
Gas GasCosts
GasUsed GasUsed // EIP-8037: canonical per-frame gas usage accumulator
value *uint256.Int
}
// NewContract returns a new contract environment for the execution of EVM.
@ -133,19 +134,21 @@ func (c *Contract) UseGas(gas GasCosts, logger *tracing.Hooks, reason tracing.Ga
if logger != nil && logger.OnGasChange != nil && reason != tracing.GasChangeIgnored {
logger.OnGasChange(c.Gas.RegularGas, c.Gas.RegularGas-gas.RegularGas, reason)
}
c.GasUsed.Add(gas)
c.Gas.Sub(gas)
return true
}
// RefundGas refunds gas to the contract
func (c *Contract) RefundGas(err error, gas GasCosts, logger *tracing.Hooks, reason tracing.GasChangeReason) {
// RefundGas refunds gas to the contract. gasUsed carries the child frame's
// accumulated gas usage metrics (EIP-8037), incorporated on both success and error.
func (c *Contract) RefundGas(err error, gas GasCosts, gasUsed GasUsed, logger *tracing.Hooks, reason tracing.GasChangeReason) {
// If the preceding call errored, return the state gas
// to the parent call
if err != nil {
gas.StateGas += gas.StateGasCharged
gas.StateGasCharged = 0
gas.StateGas += gasUsed.StateGasCharged
gasUsed.StateGasCharged = 0
}
if gas.RegularGas == 0 && gas.StateGas == 0 && gas.StateGasCharged == 0 {
if gas.RegularGas == 0 && gas.StateGas == 0 && gasUsed.StateGasCharged == 0 && gasUsed.RegularGasUsed == 0 {
return
}
if logger != nil && logger.OnGasChange != nil && reason != tracing.GasChangeIgnored {
@ -153,7 +156,8 @@ func (c *Contract) RefundGas(err error, gas GasCosts, logger *tracing.Hooks, rea
}
c.Gas.RegularGas += gas.RegularGas
c.Gas.StateGas = gas.StateGas
c.Gas.StateGasCharged += gas.StateGasCharged
c.GasUsed.StateGasCharged += gasUsed.StateGasCharged
c.GasUsed.RegularGasUsed += gasUsed.RegularGasUsed
}
// Address returns the contracts address

View file

@ -238,7 +238,7 @@ func isSystemCall(caller common.Address) bool {
// parameters. It also handles any necessary value transfer required and takse
// the necessary steps to create accounts and reverses the state in case of an
// execution error or failed value transfer.
func (evm *EVM) Call(caller common.Address, addr common.Address, input []byte, gas GasCosts, value *uint256.Int) (ret []byte, leftOverGas GasCosts, err error) {
func (evm *EVM) Call(caller common.Address, addr common.Address, input []byte, gas GasCosts, value *uint256.Int) (ret []byte, leftOverGas GasCosts, gasUsed GasUsed, err error) {
// Capture the tracer start/end events in debug mode
if evm.Config.Tracer != nil {
evm.captureBegin(evm.depth, CALL, caller, addr, input, gas, value.ToBig())
@ -248,37 +248,30 @@ func (evm *EVM) Call(caller common.Address, addr common.Address, input []byte, g
}
// Fail if we're trying to execute above the call depth limit
if evm.depth > int(params.CallCreateDepth) {
return nil, gas, ErrDepth
return nil, gas, GasUsed{}, ErrDepth
}
syscall := isSystemCall(caller)
// Fail if we're trying to transfer more than the available balance.
if !syscall && !value.IsZero() && !evm.Context.CanTransfer(evm.StateDB, caller, value) {
return nil, gas, ErrInsufficientBalance
return nil, gas, GasUsed{}, ErrInsufficientBalance
}
snapshot := evm.StateDB.Snapshot()
p, isPrecompile := evm.precompile(addr)
if !evm.StateDB.Exist(addr) {
if !isPrecompile && evm.chainRules.IsEIP4762 && !isSystemCall(caller) {
// Add proof of absence to witness
// At this point, the read costs have already been charged, either because this
// is a direct tx call, in which case it's covered by the intrinsic gas, or because
// of a CALL instruction, in which case BASIC_DATA has been added to the access
// list in write mode. If there is enough gas paying for the addition of the code
// hash leaf to the access list, then account creation will proceed unimpaired.
// Thus, only pay for the creation of the code hash leaf here.
wgas := evm.AccessEvents.CodeHashGas(addr, true, gas.RegularGas, false)
if gas.RegularGas < wgas {
evm.StateDB.RevertToSnapshot(snapshot)
return nil, GasCosts{}, ErrOutOfGas
return nil, GasCosts{}, GasUsed{}, ErrOutOfGas
}
gas.RegularGas -= wgas
}
if !isPrecompile && evm.chainRules.IsEIP158 && value.IsZero() {
// Calling a non-existing account, don't do anything.
return nil, gas, nil
return nil, gas, GasUsed{}, nil
}
evm.StateDB.CreateAccount(addr)
}
@ -294,7 +287,9 @@ func (evm *EVM) Call(caller common.Address, addr common.Address, input []byte, g
if evm.chainRules.IsAmsterdam {
stateDB = evm.StateDB
}
gasBefore := gas.RegularGas
ret, gas.RegularGas, err = RunPrecompiledContract(stateDB, p, addr, input, gas.RegularGas, evm.Config.Tracer)
gasUsed.RegularGasUsed += gasBefore - gas.RegularGas
} else {
// Initialise a new contract and set the code that is to be used by the EVM.
code := evm.resolveCode(addr)
@ -307,6 +302,7 @@ func (evm *EVM) Call(caller common.Address, addr common.Address, input []byte, g
contract.SetCallCode(evm.resolveCodeHash(addr), code)
ret, err = evm.Run(contract, input, false)
gas = contract.Gas
gasUsed = contract.GasUsed
}
}
// When an error was returned by the EVM or when setting the creation code
@ -319,10 +315,11 @@ func (evm *EVM) Call(caller common.Address, addr common.Address, input []byte, g
if evm.Config.Tracer != nil && evm.Config.Tracer.OnGasChange != nil {
evm.Config.Tracer.OnGasChange(gas.RegularGas, 0, tracing.GasChangeCallFailedExecution)
}
gasUsed.RegularGasUsed += gas.RegularGas
gas.RegularGas = 0
}
}
return ret, gas, err
return ret, gas, gasUsed, err
}
// CallCode executes the contract associated with the addr with the given input
@ -332,7 +329,7 @@ func (evm *EVM) Call(caller common.Address, addr common.Address, input []byte, g
//
// CallCode differs from Call in the sense that it executes the given address'
// code with the caller as context.
func (evm *EVM) CallCode(caller common.Address, addr common.Address, input []byte, gas GasCosts, value *uint256.Int) (ret []byte, leftOverGas GasCosts, err error) {
func (evm *EVM) CallCode(caller common.Address, addr common.Address, input []byte, gas GasCosts, value *uint256.Int) (ret []byte, leftOverGas GasCosts, gasUsed GasUsed, err error) {
// Invoke tracer hooks that signal entering/exiting a call frame
if evm.Config.Tracer != nil {
evm.captureBegin(evm.depth, CALLCODE, caller, addr, input, gas, value.ToBig())
@ -342,14 +339,14 @@ func (evm *EVM) CallCode(caller common.Address, addr common.Address, input []byt
}
// Fail if we're trying to execute above the call depth limit
if evm.depth > int(params.CallCreateDepth) {
return nil, gas, ErrDepth
return nil, gas, GasUsed{}, ErrDepth
}
// Fail if we're trying to transfer more than the available balance
// Note although it's noop to transfer X ether to caller itself. But
// if caller doesn't have enough balance, it would be an error to allow
// over-charging itself. So the check here is necessary.
if !evm.Context.CanTransfer(evm.StateDB, caller, value) {
return nil, gas, ErrInsufficientBalance
return nil, gas, GasUsed{}, ErrInsufficientBalance
}
var snapshot = evm.StateDB.Snapshot()
@ -359,7 +356,9 @@ func (evm *EVM) CallCode(caller common.Address, addr common.Address, input []byt
if evm.chainRules.IsAmsterdam {
stateDB = evm.StateDB
}
gasBefore := gas.RegularGas
ret, gas.RegularGas, err = RunPrecompiledContract(stateDB, p, addr, input, gas.RegularGas, evm.Config.Tracer)
gasUsed.RegularGasUsed += gasBefore - gas.RegularGas
} else {
// Initialise a new contract and set the code that is to be used by the EVM.
// The contract is a scoped environment for this execution context only.
@ -367,6 +366,7 @@ func (evm *EVM) CallCode(caller common.Address, addr common.Address, input []byt
contract.SetCallCode(evm.resolveCodeHash(addr), evm.resolveCode(addr))
ret, err = evm.Run(contract, input, false)
gas = contract.Gas
gasUsed = contract.GasUsed
}
if err != nil {
evm.StateDB.RevertToSnapshot(snapshot)
@ -375,10 +375,11 @@ func (evm *EVM) CallCode(caller common.Address, addr common.Address, input []byt
if evm.Config.Tracer != nil && evm.Config.Tracer.OnGasChange != nil {
evm.Config.Tracer.OnGasChange(gas.RegularGas, 0, tracing.GasChangeCallFailedExecution)
}
gasUsed.RegularGasUsed += gas.RegularGas
gas.RegularGas = 0
}
}
return ret, gas, err
return ret, gas, gasUsed, err
}
// DelegateCall executes the contract associated with the addr with the given input
@ -386,7 +387,7 @@ func (evm *EVM) CallCode(caller common.Address, addr common.Address, input []byt
//
// DelegateCall differs from CallCode in the sense that it executes the given address'
// code with the caller as context and the caller is set to the caller of the caller.
func (evm *EVM) DelegateCall(originCaller common.Address, caller common.Address, addr common.Address, input []byte, gas GasCosts, value *uint256.Int) (ret []byte, leftOverGas GasCosts, err error) {
func (evm *EVM) DelegateCall(originCaller common.Address, caller common.Address, addr common.Address, input []byte, gas GasCosts, value *uint256.Int) (ret []byte, leftOverGas GasCosts, gasUsed GasUsed, err error) {
// Invoke tracer hooks that signal entering/exiting a call frame
if evm.Config.Tracer != nil {
// DELEGATECALL inherits value from parent call
@ -397,7 +398,7 @@ func (evm *EVM) DelegateCall(originCaller common.Address, caller common.Address,
}
// Fail if we're trying to execute above the call depth limit
if evm.depth > int(params.CallCreateDepth) {
return nil, gas, ErrDepth
return nil, gas, GasUsed{}, ErrDepth
}
var snapshot = evm.StateDB.Snapshot()
@ -407,7 +408,9 @@ func (evm *EVM) DelegateCall(originCaller common.Address, caller common.Address,
if evm.chainRules.IsAmsterdam {
stateDB = evm.StateDB
}
gasBefore := gas.RegularGas
ret, gas.RegularGas, err = RunPrecompiledContract(stateDB, p, addr, input, gas.RegularGas, evm.Config.Tracer)
gasUsed.RegularGasUsed += gasBefore - gas.RegularGas
} else {
// Initialise a new contract and make initialise the delegate values
//
@ -416,6 +419,7 @@ func (evm *EVM) DelegateCall(originCaller common.Address, caller common.Address,
contract.SetCallCode(evm.resolveCodeHash(addr), evm.resolveCode(addr))
ret, err = evm.Run(contract, input, false)
gas = contract.Gas
gasUsed = contract.GasUsed
}
if err != nil {
evm.StateDB.RevertToSnapshot(snapshot)
@ -424,17 +428,18 @@ func (evm *EVM) DelegateCall(originCaller common.Address, caller common.Address,
if evm.Config.Tracer != nil && evm.Config.Tracer.OnGasChange != nil {
evm.Config.Tracer.OnGasChange(gas.RegularGas, 0, tracing.GasChangeCallFailedExecution)
}
gasUsed.RegularGasUsed += gas.RegularGas
gas.RegularGas = 0
}
}
return ret, gas, err
return ret, gas, gasUsed, err
}
// StaticCall executes the contract associated with the addr with the given input
// as parameters while disallowing any modifications to the state during the call.
// Opcodes that attempt to perform such modifications will result in exceptions
// instead of performing the modifications.
func (evm *EVM) StaticCall(caller common.Address, addr common.Address, input []byte, gas GasCosts) (ret []byte, leftOverGas GasCosts, err error) {
func (evm *EVM) StaticCall(caller common.Address, addr common.Address, input []byte, gas GasCosts) (ret []byte, leftOverGas GasCosts, gasUsed GasUsed, err error) {
// Invoke tracer hooks that signal entering/exiting a call frame
if evm.Config.Tracer != nil {
evm.captureBegin(evm.depth, STATICCALL, caller, addr, input, gas, nil)
@ -444,7 +449,7 @@ func (evm *EVM) StaticCall(caller common.Address, addr common.Address, input []b
}
// Fail if we're trying to execute above the call depth limit
if evm.depth > int(params.CallCreateDepth) {
return nil, gas, ErrDepth
return nil, gas, GasUsed{}, ErrDepth
}
// We take a snapshot here. This is a bit counter-intuitive, and could probably be skipped.
// However, even a staticcall is considered a 'touch'. On mainnet, static calls were introduced
@ -464,7 +469,9 @@ func (evm *EVM) StaticCall(caller common.Address, addr common.Address, input []b
if evm.chainRules.IsAmsterdam {
stateDB = evm.StateDB
}
gasBefore := gas.RegularGas
ret, gas.RegularGas, err = RunPrecompiledContract(stateDB, p, addr, input, gas.RegularGas, evm.Config.Tracer)
gasUsed.RegularGasUsed += gasBefore - gas.RegularGas
} else {
// Initialise a new contract and set the code that is to be used by the EVM.
// The contract is a scoped environment for this execution context only.
@ -476,6 +483,7 @@ func (evm *EVM) StaticCall(caller common.Address, addr common.Address, input []b
// when we're in Homestead this also counts for code storage gas errors.
ret, err = evm.Run(contract, input, true)
gas = contract.Gas
gasUsed = contract.GasUsed
}
if err != nil {
evm.StateDB.RevertToSnapshot(snapshot)
@ -484,14 +492,15 @@ func (evm *EVM) StaticCall(caller common.Address, addr common.Address, input []b
if evm.Config.Tracer != nil && evm.Config.Tracer.OnGasChange != nil {
evm.Config.Tracer.OnGasChange(gas.RegularGas, 0, tracing.GasChangeCallFailedExecution)
}
gasUsed.RegularGasUsed += gas.RegularGas
gas.RegularGas = 0
}
}
return ret, gas, err
return ret, gas, gasUsed, err
}
// create creates a new contract using code as deployment code.
func (evm *EVM) create(caller common.Address, code []byte, gas GasCosts, value *uint256.Int, address common.Address, typ OpCode) (ret []byte, createAddress common.Address, leftOverGas GasCosts, err error) {
func (evm *EVM) create(caller common.Address, code []byte, gas GasCosts, value *uint256.Int, address common.Address, typ OpCode) (ret []byte, createAddress common.Address, leftOverGas GasCosts, gasUsed GasUsed, err error) {
// Depth check execution. Fail if we're trying to execute above the
// limit.
var nonce uint64
@ -516,13 +525,13 @@ func (evm *EVM) create(caller common.Address, code []byte, gas GasCosts, value *
}(gas)
}
if err != nil {
return nil, common.Address{}, gas, err
return nil, common.Address{}, gas, GasUsed{}, err
}
// Charge the contract creation init gas in verkle mode
if evm.chainRules.IsEIP4762 {
statelessGas := evm.AccessEvents.ContractCreatePreCheckGas(address, gas.RegularGas)
if statelessGas > gas.RegularGas {
return nil, common.Address{}, GasCosts{}, ErrOutOfGas
return nil, common.Address{}, GasCosts{}, GasUsed{}, ErrOutOfGas
}
if evm.Config.Tracer != nil && evm.Config.Tracer.OnGasChange != nil {
evm.Config.Tracer.OnGasChange(gas.RegularGas, gas.RegularGas-statelessGas, tracing.GasChangeWitnessContractCollisionCheck)
@ -550,7 +559,7 @@ func (evm *EVM) create(caller common.Address, code []byte, gas GasCosts, value *
evm.Config.Tracer.OnGasChange(gas.RegularGas, 0, tracing.GasChangeCallFailedExecution)
}
gas.RegularGas = 0
return nil, common.Address{}, gas, ErrContractAddressCollision
return nil, common.Address{}, gas, GasUsed{}, ErrContractAddressCollision
}
// Create a new account on the state only if the object was not present.
// It might be possible the contract code is deployed to a pre-existent
@ -573,7 +582,7 @@ func (evm *EVM) create(caller common.Address, code []byte, gas GasCosts, value *
if evm.chainRules.IsEIP4762 {
consumed, wanted := evm.AccessEvents.ContractCreateInitGas(address, gas.RegularGas)
if consumed < wanted {
return nil, common.Address{}, GasCosts{}, ErrOutOfGas
return nil, common.Address{}, GasCosts{}, GasUsed{}, ErrOutOfGas
}
if evm.Config.Tracer != nil && evm.Config.Tracer.OnGasChange != nil {
evm.Config.Tracer.OnGasChange(gas.RegularGas, gas.RegularGas-consumed, tracing.GasChangeWitnessContractInit)
@ -601,10 +610,11 @@ func (evm *EVM) create(caller common.Address, code []byte, gas GasCosts, value *
if evm.Config.Tracer != nil && evm.Config.Tracer.OnGasChange != nil {
evm.Config.Tracer.OnGasChange(contract.Gas.RegularGas, 0, tracing.GasChangeCallFailedExecution)
}
contract.GasUsed.RegularGasUsed += contract.Gas.RegularGas
contract.Gas.RegularGas = 0
}
}
return ret, address, contract.Gas, err
return ret, address, contract.Gas, contract.GasUsed, err
}
// initNewContract runs a new contract's creation code, performs checks on the
@ -661,7 +671,7 @@ func (evm *EVM) initNewContract(contract *Contract, address common.Address) ([]b
}
// Create creates a new contract using code as deployment code.
func (evm *EVM) Create(caller common.Address, code []byte, gas GasCosts, value *uint256.Int) (ret []byte, contractAddr common.Address, leftOverGas GasCosts, err error) {
func (evm *EVM) Create(caller common.Address, code []byte, gas GasCosts, value *uint256.Int) (ret []byte, contractAddr common.Address, leftOverGas GasCosts, gasUsed GasUsed, err error) {
contractAddr = crypto.CreateAddress(caller, evm.StateDB.GetNonce(caller))
return evm.create(caller, code, gas, value, contractAddr, CREATE)
}
@ -670,7 +680,7 @@ func (evm *EVM) Create(caller common.Address, code []byte, gas GasCosts, value *
//
// The different between Create2 with Create is Create2 uses keccak256(0xff ++ msg.sender ++ salt ++ keccak256(init_code))[12:]
// instead of the usual sender-and-nonce-hash as the address where the contract is initialized at.
func (evm *EVM) Create2(caller common.Address, code []byte, gas GasCosts, endowment *uint256.Int, salt *uint256.Int) (ret []byte, contractAddr common.Address, leftOverGas GasCosts, err error) {
func (evm *EVM) Create2(caller common.Address, code []byte, gas GasCosts, endowment *uint256.Int, salt *uint256.Int) (ret []byte, contractAddr common.Address, leftOverGas GasCosts, gasUsed GasUsed, err error) {
inithash := crypto.Keccak256Hash(code)
contractAddr = crypto.CreateAddress2(caller, salt.Bytes32(), inithash[:])
return evm.create(caller, code, gas, endowment, contractAddr, CREATE2)

View file

@ -710,6 +710,7 @@ func gasSStore8037(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memo
if contract.Gas.Underflow(stateGas) {
return GasCosts{}, errors.New("out of gas for state gas")
}
contract.GasUsed.Add(stateGas)
contract.Gas.Sub(stateGas)
return GasCosts{RegularGas: cost.RegularGas + params.SstoreResetGasEIP2200 - params.ColdSloadCostEIP2929}, nil
}

View file

@ -98,7 +98,7 @@ func TestEIP2200(t *testing.T) {
}
evm := NewEVM(vmctx, statedb, params.AllEthashProtocolChanges, Config{ExtraEips: []int{2200}})
_, gas, err := evm.Call(common.Address{}, address, nil, GasCosts{RegularGas: tt.gaspool}, new(uint256.Int))
_, gas, _, err := evm.Call(common.Address{}, address, nil, GasCosts{RegularGas: tt.gaspool}, new(uint256.Int))
if !errors.Is(err, tt.failure) {
t.Errorf("test %d: failure mismatch: have %v, want %v", i, err, tt.failure)
}
@ -154,7 +154,7 @@ func TestCreateGas(t *testing.T) {
evm := NewEVM(vmctx, statedb, params.AllEthashProtocolChanges, config)
var startGas = uint64(testGas)
ret, gas, err := evm.Call(common.Address{}, address, nil, GasCosts{RegularGas: startGas}, new(uint256.Int))
ret, gas, _, err := evm.Call(common.Address{}, address, nil, GasCosts{RegularGas: startGas}, new(uint256.Int))
if err != nil {
return false
}

View file

@ -5,11 +5,24 @@ import "fmt"
type GasCosts struct {
RegularGas uint64
StateGas uint64
}
// StateGasCharged tracks the cumulative state gas charged during execution.
// GasUsed tracks per-frame gas usage metrics for EIP-8037 2D block gas accounting.
type GasUsed struct {
// RegularGasUsed accumulates all gas charged via charge_gas() in the spec:
// State-gas spillover (when StateGas is exhausted and the excess
// is paid from RegularGas) does NOT increment RegularGasUsed.
RegularGasUsed uint64
// StateGasCharged accumulates all gas charged via charge_state_gas()
// On child error the charged state gas is restored to the parent's state gas reservoir.
StateGasCharged uint64
}
func (g *GasUsed) Add(cost GasCosts) {
g.RegularGasUsed += cost.RegularGas
g.StateGasCharged += cost.StateGas
}
func (g GasCosts) Max() uint64 {
return max(g.RegularGas, g.StateGas)
}
@ -38,11 +51,11 @@ func (g GasCosts) Underflow(b GasCosts) bool {
// Sub doesn't check for underflows
func (g *GasCosts) Sub(b GasCosts) {
g.RegularGas -= b.RegularGas
g.StateGasCharged += b.StateGas
if b.StateGas > g.StateGas {
diff := b.StateGas - g.StateGas
g.StateGas = 0
g.RegularGas -= diff
// Note: spillover does NOT increment RegularGasUsed, matching the spec
} else {
g.StateGas -= b.StateGas
}
@ -52,7 +65,6 @@ func (g *GasCosts) Sub(b GasCosts) {
func (g *GasCosts) Add(b GasCosts) {
g.RegularGas += b.RegularGas
g.StateGas += b.StateGas
g.StateGasCharged += b.StateGasCharged
}
func (g GasCosts) String() string {

View file

@ -674,8 +674,10 @@ func opCreate(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
stackvalue := size
scope.Contract.UseGas(GasCosts{RegularGas: gas.RegularGas}, evm.Config.Tracer, tracing.GasChangeCallContractCreation)
// UseGas inflates RegularGasUsed, so we need to undo that here.
scope.Contract.GasUsed.RegularGasUsed -= gas.RegularGas
res, addr, returnGas, suberr := evm.Create(scope.Contract.Address(), input, GasCosts{RegularGas: gas.RegularGas, StateGas: scope.Contract.Gas.StateGas}, &value)
res, addr, returnGas, childGasUsed, suberr := evm.Create(scope.Contract.Address(), input, GasCosts{RegularGas: gas.RegularGas, StateGas: scope.Contract.Gas.StateGas}, &value)
// Push item on the stack based on the returned error. If the ruleset is
// homestead we must check for CodeStoreOutOfGasError (homestead only
// rule) and treat as an error, if the ruleset is frontier we must
@ -689,7 +691,7 @@ func opCreate(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
}
scope.Stack.push(&stackvalue)
scope.Contract.RefundGas(suberr, returnGas, evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded)
scope.Contract.RefundGas(suberr, returnGas, childGasUsed, evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded)
if suberr == ErrExecutionReverted {
evm.returnData = res // set REVERT data to return data buffer
@ -724,9 +726,11 @@ func opCreate2(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
}
scope.Contract.UseGas(GasCosts{RegularGas: gas.RegularGas}, evm.Config.Tracer, tracing.GasChangeCallContractCreation2)
// UseGas inflates RegularGasUsed, so we need to undo that here.
scope.Contract.GasUsed.RegularGasUsed -= gas.RegularGas
// reuse size int for stackvalue
stackvalue := size
res, addr, returnGas, suberr := evm.Create2(scope.Contract.Address(), input, GasCosts{RegularGas: gas.RegularGas, StateGas: scope.Contract.Gas.StateGas},
res, addr, returnGas, childGasUsed, suberr := evm.Create2(scope.Contract.Address(), input, GasCosts{RegularGas: gas.RegularGas, StateGas: scope.Contract.Gas.StateGas},
&endowment, &salt)
// Push item on the stack based on the returned error.
if suberr != nil {
@ -736,7 +740,7 @@ func opCreate2(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
}
scope.Stack.push(&stackvalue)
scope.Contract.RefundGas(suberr, returnGas, evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded)
scope.Contract.RefundGas(suberr, returnGas, childGasUsed, evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded)
if suberr == ErrExecutionReverted {
evm.returnData = res // set REVERT data to return data buffer
@ -764,7 +768,10 @@ func opCall(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
if !value.IsZero() {
gas += params.CallStipend
}
ret, returnGas, err := evm.Call(scope.Contract.Address(), toAddr, args, GasCosts{RegularGas: gas, StateGas: scope.Contract.Gas.StateGas}, &value)
// We need to substract the gas here, otherwise its double counted by UseGas
scope.Contract.GasUsed.RegularGasUsed -= gas
ret, returnGas, childGasUsed, err := evm.Call(scope.Contract.Address(), toAddr, args, GasCosts{RegularGas: gas, StateGas: scope.Contract.Gas.StateGas}, &value)
if err != nil {
temp.Clear()
@ -776,7 +783,7 @@ func opCall(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
scope.Memory.Set(retOffset.Uint64(), retSize.Uint64(), ret)
}
scope.Contract.RefundGas(err, returnGas, evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded)
scope.Contract.RefundGas(err, returnGas, childGasUsed, evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded)
evm.returnData = ret
return ret, nil
@ -797,8 +804,10 @@ func opCallCode(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
if !value.IsZero() {
gas += params.CallStipend
}
// We need to substract the gas here, otherwise its double counted by UseGas
scope.Contract.GasUsed.RegularGasUsed -= gas
ret, returnGas, err := evm.CallCode(scope.Contract.Address(), toAddr, args, GasCosts{RegularGas: gas, StateGas: scope.Contract.Gas.StateGas}, &value)
ret, returnGas, childGasUsed, err := evm.CallCode(scope.Contract.Address(), toAddr, args, GasCosts{RegularGas: gas, StateGas: scope.Contract.Gas.StateGas}, &value)
if err != nil {
temp.Clear()
} else {
@ -809,7 +818,7 @@ func opCallCode(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
scope.Memory.Set(retOffset.Uint64(), retSize.Uint64(), ret)
}
scope.Contract.RefundGas(err, returnGas, evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded)
scope.Contract.RefundGas(err, returnGas, childGasUsed, evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded)
evm.returnData = ret
return ret, nil
@ -827,8 +836,10 @@ func opDelegateCall(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
// Get arguments from the memory.
args := scope.Memory.GetPtr(inOffset.Uint64(), inSize.Uint64())
stateGas := scope.Contract.Gas.StateGas
ret, returnGas, err := evm.DelegateCall(scope.Contract.Caller(), scope.Contract.Address(), toAddr, args, GasCosts{RegularGas: gas, StateGas: stateGas}, scope.Contract.value)
// We need to substract the gas here, otherwise its double counted by UseGas
scope.Contract.GasUsed.RegularGasUsed -= gas
ret, returnGas, childGasUsed, err := evm.DelegateCall(scope.Contract.Caller(), scope.Contract.Address(), toAddr, args, GasCosts{RegularGas: gas, StateGas: scope.Contract.Gas.StateGas}, scope.Contract.value)
if err != nil {
temp.Clear()
} else {
@ -839,7 +850,7 @@ func opDelegateCall(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
scope.Memory.Set(retOffset.Uint64(), retSize.Uint64(), ret)
}
scope.Contract.RefundGas(err, returnGas, evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded)
scope.Contract.RefundGas(err, returnGas, childGasUsed, evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded)
evm.returnData = ret
return ret, nil
@ -857,8 +868,10 @@ func opStaticCall(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
// Get arguments from the memory.
args := scope.Memory.GetPtr(inOffset.Uint64(), inSize.Uint64())
stateGas := scope.Contract.Gas.StateGas
ret, returnGas, err := evm.StaticCall(scope.Contract.Address(), toAddr, args, GasCosts{RegularGas: gas, StateGas: stateGas})
// We need to substract the gas here, otherwise its double counted by UseGas
scope.Contract.GasUsed.RegularGasUsed -= gas
ret, returnGas, childGasUsed, err := evm.StaticCall(scope.Contract.Address(), toAddr, args, GasCosts{RegularGas: gas, StateGas: scope.Contract.Gas.StateGas})
if err != nil {
temp.Clear()
} else {
@ -869,7 +882,7 @@ func opStaticCall(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
scope.Memory.Set(retOffset.Uint64(), retSize.Uint64(), ret)
}
scope.Contract.RefundGas(err, returnGas, evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded)
scope.Contract.RefundGas(err, returnGas, childGasUsed, evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded)
evm.returnData = ret
return ret, nil

View file

@ -196,6 +196,7 @@ func (evm *EVM) Run(contract *Contract, input []byte, readOnly bool) (ret []byte
return nil, ErrOutOfGas
} else {
contract.Gas.RegularGas -= cost
contract.GasUsed.RegularGasUsed += cost // EIP-8037: track constant gas
}
// All ops with a dynamic memory usage also has a dynamic gas cost.
@ -228,6 +229,7 @@ func (evm *EVM) Run(contract *Contract, input []byte, readOnly bool) (ret []byte
if contract.Gas.Underflow(dynamicCost) {
return nil, ErrOutOfGas
} else {
contract.GasUsed.Add(dynamicCost)
contract.Gas.Sub(dynamicCost)
}
}

View file

@ -55,7 +55,7 @@ func TestLoopInterrupt(t *testing.T) {
timeout := make(chan bool)
go func(evm *EVM) {
_, _, err := evm.Call(common.Address{}, address, nil, GasCosts{RegularGas: math.MaxUint64}, new(uint256.Int))
_, _, _, err := evm.Call(common.Address{}, address, nil, GasCosts{RegularGas: math.MaxUint64}, new(uint256.Int))
errChannel <- err
}(evm)

View file

@ -307,6 +307,7 @@ func makeCallVariantGasCall(oldCalculatorStateful, oldCalculatorStateless gasFun
if contract.Gas.Underflow(stateGasCost) {
return GasCosts{}, ErrOutOfGas
}
contract.GasUsed.Add(stateGasCost)
contract.Gas.Sub(stateGasCost)
}
@ -327,10 +328,15 @@ func makeCallVariantGasCall(oldCalculatorStateful, oldCalculatorStateless gasFun
if overflow {
return GasCosts{}, ErrGasUintOverflow
}
// EIP-8037: undo the RegularGasUsed increment from the direct UseGas
// charge, since this gas will be re-charged via the returned cost.
contract.GasUsed.RegularGasUsed -= eip2929Gas
contract.Gas.RegularGas, overflow = math.SafeAdd(contract.Gas.RegularGas, eip7702Gas)
if overflow {
return GasCosts{}, ErrGasUintOverflow
}
contract.GasUsed.RegularGasUsed -= eip7702Gas
var totalCost uint64
totalCost, overflow = math.SafeAdd(eip2929Gas, eip7702Gas)

View file

@ -142,7 +142,7 @@ func Execute(code, input []byte, cfg *Config) ([]byte, *state.StateDB, error) {
// set the receiver's (the executing contract) code for execution.
cfg.State.SetCode(address, code, tracing.CodeChangeUnspecified)
// Call the code with the given configuration.
ret, leftOverGas, err := vmenv.Call(
ret, leftOverGas, _, err := vmenv.Call(
cfg.Origin,
common.BytesToAddress([]byte("contract")),
input,
@ -177,7 +177,7 @@ func Create(input []byte, cfg *Config) ([]byte, common.Address, uint64, error) {
// - reset transient storage(eip 1153)
cfg.State.Prepare(rules, cfg.Origin, cfg.Coinbase, nil, vm.ActivePrecompiles(rules), nil)
// Call the code with the given configuration.
code, address, leftOverGas, err := vmenv.Create(
code, address, leftOverGas, _, err := vmenv.Create(
cfg.Origin,
input,
vm.GasCosts{RegularGas: cfg.GasLimit},
@ -211,7 +211,7 @@ func Call(address common.Address, input []byte, cfg *Config) ([]byte, uint64, er
statedb.Prepare(rules, cfg.Origin, cfg.Coinbase, &address, vm.ActivePrecompiles(rules), nil)
// Call the code with the given configuration.
ret, leftOverGas, err := vmenv.Call(
ret, leftOverGas, _, err := vmenv.Call(
cfg.Origin,
address,
input,

View file

@ -316,7 +316,7 @@ func runBenchmark(b *testing.B, t *StateTest) {
start := time.Now()
// Execute the message.
_, leftOverGas, err := evm.Call(sender.Address(), *msg.To, msg.Data, vm.GasCosts{RegularGas: msg.GasLimit}, uint256.MustFromBig(msg.Value))
_, leftOverGas, _, err := evm.Call(sender.Address(), *msg.To, msg.Data, vm.GasCosts{RegularGas: msg.GasLimit}, uint256.MustFromBig(msg.Value))
if err != nil {
b.Error(err)
return