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 # https://github.com/ethereum/execution-spec-tests/releases/download/v5.1.0
a3192784375acec7eaec492799d5c5d0c47a2909a3cc40178898e4ecd20cc416 fixtures_develop.tar.gz 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
# https://github.com/ethereum/execution-spec-tests/releases/download/bal%40v5.4.0 # https://github.com/ethereum/execution-spec-tests/releases/download/bal%40v5.5.1
be300cbc5dcae213850bc5688d8fd0f572785ec64801f28653e1c01a03805127 fixtures_bal.tar.gz 79f81379bc456b9f05d4e7298eba939855d0147b525cd2cadd1206513284ab9e fixtures_bal.tar.gz
# version:golang 1.25.7 # version:golang 1.25.7
# https://go.dev/dl/ # 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.SetTxContext(NewEVMTxContext(msg))
evm.StateDB.AddAddressToAccessList(params.BeaconRootsAddress) 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) return evm.StateDB.Finalise(true)
} }
@ -322,7 +322,7 @@ func ProcessParentBlockHash(prevHash common.Hash, evm *vm.EVM) bal.StateMutation
} }
evm.SetTxContext(NewEVMTxContext(msg)) evm.SetTxContext(NewEVMTxContext(msg))
evm.StateDB.AddAddressToAccessList(params.HistoryStorageAddress) 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 { if err != nil {
panic(err) panic(err)
} }
@ -362,7 +362,7 @@ func processRequestsSystemCall(requests *[][]byte, evm *vm.EVM, requestType byte
} }
evm.SetTxContext(NewEVMTxContext(msg)) evm.SetTxContext(NewEVMTxContext(msg))
evm.StateDB.AddAddressToAccessList(addr) 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) mut := evm.StateDB.Finalise(true)
if err != nil { if err != nil {
return nil, fmt.Errorf("system call failed to execute: %v", err) 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 vmerr error // vm errors do not effect consensus and are therefore not assigned to err
authRefund uint64 authRefund uint64
) )
var execGasUsed vm.GasUsed
if contractCreation { 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 { } else {
// Increment the nonce for the next transaction. // Increment the nonce for the next transaction.
st.state.SetNonce(msg.From, st.state.GetNonce(msg.From)+1, tracing.NonceChangeEoACall) 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. // 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 // Record the gas used excluding gas refunds. This value represents the actual
// gas allowance required to complete execution. // gas allowance required to complete execution.
peakGasUsed := st.gasUsed() 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. // Compute refund counter, capped to a refund quotient.
st.gasRemaining.RegularGas += st.calcRefund() st.gasRemaining.RegularGas += st.calcRefund()
if rules.IsPrague { if rules.IsPrague {
@ -607,10 +602,11 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
if rules.IsAmsterdam { if rules.IsAmsterdam {
// EIP-8037: 2D gas accounting for Amsterdam. // EIP-8037: 2D gas accounting for Amsterdam.
// tx_state = adjusted_intrinsic_state + exec_state_used (spec: set_delegation adjusts intrinsic) // tx_regular = intrinsic_regular + exec_regular_gas_used
// tx_regular = total_dimensional_used - tx_state // tx_state = intrinsic_state (adjusted) + exec_state_gas_used
txState := (gas.StateGas - authRefund) + st.gasRemaining.StateGasCharged // These are tracked independently, not derived from remaining gas.
txRegular := (msg.GasLimit - preRefundRemaining) - txState txState := (gas.StateGas - authRefund) + execGasUsed.StateGasCharged
txRegular := gas.RegularGas + execGasUsed.RegularGasUsed
txRegular = max(txRegular, floorDataGas) txRegular = max(txRegular, floorDataGas)
if err := st.gp.ReturnGasAmsterdam(returned, txRegular, txState, st.gasUsed()); err != nil { if err := st.gp.ReturnGasAmsterdam(returned, txRegular, txState, st.gasUsed()); err != nil {
return nil, err return nil, err

View file

@ -42,8 +42,9 @@ type Contract struct {
IsDeployment bool IsDeployment bool
IsSystemCall bool IsSystemCall bool
Gas GasCosts Gas GasCosts
value *uint256.Int GasUsed GasUsed // EIP-8037: canonical per-frame gas usage accumulator
value *uint256.Int
} }
// NewContract returns a new contract environment for the execution of EVM. // 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 { if logger != nil && logger.OnGasChange != nil && reason != tracing.GasChangeIgnored {
logger.OnGasChange(c.Gas.RegularGas, c.Gas.RegularGas-gas.RegularGas, reason) logger.OnGasChange(c.Gas.RegularGas, c.Gas.RegularGas-gas.RegularGas, reason)
} }
c.GasUsed.Add(gas)
c.Gas.Sub(gas) c.Gas.Sub(gas)
return true return true
} }
// RefundGas refunds gas to the contract // RefundGas refunds gas to the contract. gasUsed carries the child frame's
func (c *Contract) RefundGas(err error, gas GasCosts, logger *tracing.Hooks, reason tracing.GasChangeReason) { // 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 // If the preceding call errored, return the state gas
// to the parent call // to the parent call
if err != nil { if err != nil {
gas.StateGas += gas.StateGasCharged gas.StateGas += gasUsed.StateGasCharged
gas.StateGasCharged = 0 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 return
} }
if logger != nil && logger.OnGasChange != nil && reason != tracing.GasChangeIgnored { 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.RegularGas += gas.RegularGas
c.Gas.StateGas = gas.StateGas 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 // 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 // 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 // the necessary steps to create accounts and reverses the state in case of an
// execution error or failed value transfer. // 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 // Capture the tracer start/end events in debug mode
if evm.Config.Tracer != nil { if evm.Config.Tracer != nil {
evm.captureBegin(evm.depth, CALL, caller, addr, input, gas, value.ToBig()) 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 // Fail if we're trying to execute above the call depth limit
if evm.depth > int(params.CallCreateDepth) { if evm.depth > int(params.CallCreateDepth) {
return nil, gas, ErrDepth return nil, gas, GasUsed{}, ErrDepth
} }
syscall := isSystemCall(caller) syscall := isSystemCall(caller)
// Fail if we're trying to transfer more than the available balance. // Fail if we're trying to transfer more than the available balance.
if !syscall && !value.IsZero() && !evm.Context.CanTransfer(evm.StateDB, caller, value) { if !syscall && !value.IsZero() && !evm.Context.CanTransfer(evm.StateDB, caller, value) {
return nil, gas, ErrInsufficientBalance return nil, gas, GasUsed{}, ErrInsufficientBalance
} }
snapshot := evm.StateDB.Snapshot() snapshot := evm.StateDB.Snapshot()
p, isPrecompile := evm.precompile(addr) p, isPrecompile := evm.precompile(addr)
if !evm.StateDB.Exist(addr) { if !evm.StateDB.Exist(addr) {
if !isPrecompile && evm.chainRules.IsEIP4762 && !isSystemCall(caller) { 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) wgas := evm.AccessEvents.CodeHashGas(addr, true, gas.RegularGas, false)
if gas.RegularGas < wgas { if gas.RegularGas < wgas {
evm.StateDB.RevertToSnapshot(snapshot) evm.StateDB.RevertToSnapshot(snapshot)
return nil, GasCosts{}, ErrOutOfGas return nil, GasCosts{}, GasUsed{}, ErrOutOfGas
} }
gas.RegularGas -= wgas gas.RegularGas -= wgas
} }
if !isPrecompile && evm.chainRules.IsEIP158 && value.IsZero() { if !isPrecompile && evm.chainRules.IsEIP158 && value.IsZero() {
// Calling a non-existing account, don't do anything. // Calling a non-existing account, don't do anything.
return nil, gas, nil return nil, gas, GasUsed{}, nil
} }
evm.StateDB.CreateAccount(addr) 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 { if evm.chainRules.IsAmsterdam {
stateDB = evm.StateDB stateDB = evm.StateDB
} }
gasBefore := gas.RegularGas
ret, gas.RegularGas, err = RunPrecompiledContract(stateDB, p, addr, input, gas.RegularGas, evm.Config.Tracer) ret, gas.RegularGas, err = RunPrecompiledContract(stateDB, p, addr, input, gas.RegularGas, evm.Config.Tracer)
gasUsed.RegularGasUsed += gasBefore - gas.RegularGas
} else { } else {
// Initialise a new contract and set the code that is to be used by the EVM. // Initialise a new contract and set the code that is to be used by the EVM.
code := evm.resolveCode(addr) 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) contract.SetCallCode(evm.resolveCodeHash(addr), code)
ret, err = evm.Run(contract, input, false) ret, err = evm.Run(contract, input, false)
gas = contract.Gas gas = contract.Gas
gasUsed = contract.GasUsed
} }
} }
// When an error was returned by the EVM or when setting the creation code // 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 { if evm.Config.Tracer != nil && evm.Config.Tracer.OnGasChange != nil {
evm.Config.Tracer.OnGasChange(gas.RegularGas, 0, tracing.GasChangeCallFailedExecution) evm.Config.Tracer.OnGasChange(gas.RegularGas, 0, tracing.GasChangeCallFailedExecution)
} }
gasUsed.RegularGasUsed += gas.RegularGas
gas.RegularGas = 0 gas.RegularGas = 0
} }
} }
return ret, gas, err return ret, gas, gasUsed, err
} }
// CallCode executes the contract associated with the addr with the given input // 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' // CallCode differs from Call in the sense that it executes the given address'
// code with the caller as context. // 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 // Invoke tracer hooks that signal entering/exiting a call frame
if evm.Config.Tracer != nil { if evm.Config.Tracer != nil {
evm.captureBegin(evm.depth, CALLCODE, caller, addr, input, gas, value.ToBig()) 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 // Fail if we're trying to execute above the call depth limit
if evm.depth > int(params.CallCreateDepth) { 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 // 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 // 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 // if caller doesn't have enough balance, it would be an error to allow
// over-charging itself. So the check here is necessary. // over-charging itself. So the check here is necessary.
if !evm.Context.CanTransfer(evm.StateDB, caller, value) { if !evm.Context.CanTransfer(evm.StateDB, caller, value) {
return nil, gas, ErrInsufficientBalance return nil, gas, GasUsed{}, ErrInsufficientBalance
} }
var snapshot = evm.StateDB.Snapshot() 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 { if evm.chainRules.IsAmsterdam {
stateDB = evm.StateDB stateDB = evm.StateDB
} }
gasBefore := gas.RegularGas
ret, gas.RegularGas, err = RunPrecompiledContract(stateDB, p, addr, input, gas.RegularGas, evm.Config.Tracer) ret, gas.RegularGas, err = RunPrecompiledContract(stateDB, p, addr, input, gas.RegularGas, evm.Config.Tracer)
gasUsed.RegularGasUsed += gasBefore - gas.RegularGas
} else { } else {
// Initialise a new contract and set the code that is to be used by the EVM. // 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. // 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)) contract.SetCallCode(evm.resolveCodeHash(addr), evm.resolveCode(addr))
ret, err = evm.Run(contract, input, false) ret, err = evm.Run(contract, input, false)
gas = contract.Gas gas = contract.Gas
gasUsed = contract.GasUsed
} }
if err != nil { if err != nil {
evm.StateDB.RevertToSnapshot(snapshot) 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 { if evm.Config.Tracer != nil && evm.Config.Tracer.OnGasChange != nil {
evm.Config.Tracer.OnGasChange(gas.RegularGas, 0, tracing.GasChangeCallFailedExecution) evm.Config.Tracer.OnGasChange(gas.RegularGas, 0, tracing.GasChangeCallFailedExecution)
} }
gasUsed.RegularGasUsed += gas.RegularGas
gas.RegularGas = 0 gas.RegularGas = 0
} }
} }
return ret, gas, err return ret, gas, gasUsed, err
} }
// DelegateCall executes the contract associated with the addr with the given input // 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' // 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. // 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 // Invoke tracer hooks that signal entering/exiting a call frame
if evm.Config.Tracer != nil { if evm.Config.Tracer != nil {
// DELEGATECALL inherits value from parent call // 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 // Fail if we're trying to execute above the call depth limit
if evm.depth > int(params.CallCreateDepth) { if evm.depth > int(params.CallCreateDepth) {
return nil, gas, ErrDepth return nil, gas, GasUsed{}, ErrDepth
} }
var snapshot = evm.StateDB.Snapshot() var snapshot = evm.StateDB.Snapshot()
@ -407,7 +408,9 @@ func (evm *EVM) DelegateCall(originCaller common.Address, caller common.Address,
if evm.chainRules.IsAmsterdam { if evm.chainRules.IsAmsterdam {
stateDB = evm.StateDB stateDB = evm.StateDB
} }
gasBefore := gas.RegularGas
ret, gas.RegularGas, err = RunPrecompiledContract(stateDB, p, addr, input, gas.RegularGas, evm.Config.Tracer) ret, gas.RegularGas, err = RunPrecompiledContract(stateDB, p, addr, input, gas.RegularGas, evm.Config.Tracer)
gasUsed.RegularGasUsed += gasBefore - gas.RegularGas
} else { } else {
// Initialise a new contract and make initialise the delegate values // 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)) contract.SetCallCode(evm.resolveCodeHash(addr), evm.resolveCode(addr))
ret, err = evm.Run(contract, input, false) ret, err = evm.Run(contract, input, false)
gas = contract.Gas gas = contract.Gas
gasUsed = contract.GasUsed
} }
if err != nil { if err != nil {
evm.StateDB.RevertToSnapshot(snapshot) 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 { if evm.Config.Tracer != nil && evm.Config.Tracer.OnGasChange != nil {
evm.Config.Tracer.OnGasChange(gas.RegularGas, 0, tracing.GasChangeCallFailedExecution) evm.Config.Tracer.OnGasChange(gas.RegularGas, 0, tracing.GasChangeCallFailedExecution)
} }
gasUsed.RegularGasUsed += gas.RegularGas
gas.RegularGas = 0 gas.RegularGas = 0
} }
} }
return ret, gas, err return ret, gas, gasUsed, err
} }
// StaticCall executes the contract associated with the addr with the given input // StaticCall executes the contract associated with the addr with the given input
// as parameters while disallowing any modifications to the state during the call. // as parameters while disallowing any modifications to the state during the call.
// Opcodes that attempt to perform such modifications will result in exceptions // Opcodes that attempt to perform such modifications will result in exceptions
// instead of performing the modifications. // 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 // Invoke tracer hooks that signal entering/exiting a call frame
if evm.Config.Tracer != nil { if evm.Config.Tracer != nil {
evm.captureBegin(evm.depth, STATICCALL, caller, addr, input, gas, 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 // Fail if we're trying to execute above the call depth limit
if evm.depth > int(params.CallCreateDepth) { 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. // 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 // 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 { if evm.chainRules.IsAmsterdam {
stateDB = evm.StateDB stateDB = evm.StateDB
} }
gasBefore := gas.RegularGas
ret, gas.RegularGas, err = RunPrecompiledContract(stateDB, p, addr, input, gas.RegularGas, evm.Config.Tracer) ret, gas.RegularGas, err = RunPrecompiledContract(stateDB, p, addr, input, gas.RegularGas, evm.Config.Tracer)
gasUsed.RegularGasUsed += gasBefore - gas.RegularGas
} else { } else {
// Initialise a new contract and set the code that is to be used by the EVM. // 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. // 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. // when we're in Homestead this also counts for code storage gas errors.
ret, err = evm.Run(contract, input, true) ret, err = evm.Run(contract, input, true)
gas = contract.Gas gas = contract.Gas
gasUsed = contract.GasUsed
} }
if err != nil { if err != nil {
evm.StateDB.RevertToSnapshot(snapshot) 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 { if evm.Config.Tracer != nil && evm.Config.Tracer.OnGasChange != nil {
evm.Config.Tracer.OnGasChange(gas.RegularGas, 0, tracing.GasChangeCallFailedExecution) evm.Config.Tracer.OnGasChange(gas.RegularGas, 0, tracing.GasChangeCallFailedExecution)
} }
gasUsed.RegularGasUsed += gas.RegularGas
gas.RegularGas = 0 gas.RegularGas = 0
} }
} }
return ret, gas, err return ret, gas, gasUsed, err
} }
// create creates a new contract using code as deployment code. // 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 // Depth check execution. Fail if we're trying to execute above the
// limit. // limit.
var nonce uint64 var nonce uint64
@ -516,13 +525,13 @@ func (evm *EVM) create(caller common.Address, code []byte, gas GasCosts, value *
}(gas) }(gas)
} }
if err != nil { 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 // Charge the contract creation init gas in verkle mode
if evm.chainRules.IsEIP4762 { if evm.chainRules.IsEIP4762 {
statelessGas := evm.AccessEvents.ContractCreatePreCheckGas(address, gas.RegularGas) statelessGas := evm.AccessEvents.ContractCreatePreCheckGas(address, gas.RegularGas)
if statelessGas > 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 { if evm.Config.Tracer != nil && evm.Config.Tracer.OnGasChange != nil {
evm.Config.Tracer.OnGasChange(gas.RegularGas, gas.RegularGas-statelessGas, tracing.GasChangeWitnessContractCollisionCheck) 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) evm.Config.Tracer.OnGasChange(gas.RegularGas, 0, tracing.GasChangeCallFailedExecution)
} }
gas.RegularGas = 0 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. // 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 // 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 { if evm.chainRules.IsEIP4762 {
consumed, wanted := evm.AccessEvents.ContractCreateInitGas(address, gas.RegularGas) consumed, wanted := evm.AccessEvents.ContractCreateInitGas(address, gas.RegularGas)
if consumed < wanted { 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 { if evm.Config.Tracer != nil && evm.Config.Tracer.OnGasChange != nil {
evm.Config.Tracer.OnGasChange(gas.RegularGas, gas.RegularGas-consumed, tracing.GasChangeWitnessContractInit) 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 { if evm.Config.Tracer != nil && evm.Config.Tracer.OnGasChange != nil {
evm.Config.Tracer.OnGasChange(contract.Gas.RegularGas, 0, tracing.GasChangeCallFailedExecution) evm.Config.Tracer.OnGasChange(contract.Gas.RegularGas, 0, tracing.GasChangeCallFailedExecution)
} }
contract.GasUsed.RegularGasUsed += contract.Gas.RegularGas
contract.Gas.RegularGas = 0 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 // 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. // 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)) contractAddr = crypto.CreateAddress(caller, evm.StateDB.GetNonce(caller))
return evm.create(caller, code, gas, value, contractAddr, CREATE) 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:] // 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. // 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) inithash := crypto.Keccak256Hash(code)
contractAddr = crypto.CreateAddress2(caller, salt.Bytes32(), inithash[:]) contractAddr = crypto.CreateAddress2(caller, salt.Bytes32(), inithash[:])
return evm.create(caller, code, gas, endowment, contractAddr, CREATE2) 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) { if contract.Gas.Underflow(stateGas) {
return GasCosts{}, errors.New("out of gas for state gas") return GasCosts{}, errors.New("out of gas for state gas")
} }
contract.GasUsed.Add(stateGas)
contract.Gas.Sub(stateGas) contract.Gas.Sub(stateGas)
return GasCosts{RegularGas: cost.RegularGas + params.SstoreResetGasEIP2200 - params.ColdSloadCostEIP2929}, nil 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}}) 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) { if !errors.Is(err, tt.failure) {
t.Errorf("test %d: failure mismatch: have %v, want %v", i, 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) evm := NewEVM(vmctx, statedb, params.AllEthashProtocolChanges, config)
var startGas = uint64(testGas) 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 { if err != nil {
return false return false
} }

View file

@ -5,11 +5,24 @@ import "fmt"
type GasCosts struct { type GasCosts struct {
RegularGas uint64 RegularGas uint64
StateGas 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 StateGasCharged uint64
} }
func (g *GasUsed) Add(cost GasCosts) {
g.RegularGasUsed += cost.RegularGas
g.StateGasCharged += cost.StateGas
}
func (g GasCosts) Max() uint64 { func (g GasCosts) Max() uint64 {
return max(g.RegularGas, g.StateGas) return max(g.RegularGas, g.StateGas)
} }
@ -38,11 +51,11 @@ func (g GasCosts) Underflow(b GasCosts) bool {
// Sub doesn't check for underflows // Sub doesn't check for underflows
func (g *GasCosts) Sub(b GasCosts) { func (g *GasCosts) Sub(b GasCosts) {
g.RegularGas -= b.RegularGas g.RegularGas -= b.RegularGas
g.StateGasCharged += b.StateGas
if b.StateGas > g.StateGas { if b.StateGas > g.StateGas {
diff := b.StateGas - g.StateGas diff := b.StateGas - g.StateGas
g.StateGas = 0 g.StateGas = 0
g.RegularGas -= diff g.RegularGas -= diff
// Note: spillover does NOT increment RegularGasUsed, matching the spec
} else { } else {
g.StateGas -= b.StateGas g.StateGas -= b.StateGas
} }
@ -52,7 +65,6 @@ func (g *GasCosts) Sub(b GasCosts) {
func (g *GasCosts) Add(b GasCosts) { func (g *GasCosts) Add(b GasCosts) {
g.RegularGas += b.RegularGas g.RegularGas += b.RegularGas
g.StateGas += b.StateGas g.StateGas += b.StateGas
g.StateGasCharged += b.StateGasCharged
} }
func (g GasCosts) String() string { func (g GasCosts) String() string {

View file

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

View file

@ -196,6 +196,7 @@ func (evm *EVM) Run(contract *Contract, input []byte, readOnly bool) (ret []byte
return nil, ErrOutOfGas return nil, ErrOutOfGas
} else { } else {
contract.Gas.RegularGas -= cost 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. // 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) { if contract.Gas.Underflow(dynamicCost) {
return nil, ErrOutOfGas return nil, ErrOutOfGas
} else { } else {
contract.GasUsed.Add(dynamicCost)
contract.Gas.Sub(dynamicCost) contract.Gas.Sub(dynamicCost)
} }
} }

View file

@ -55,7 +55,7 @@ func TestLoopInterrupt(t *testing.T) {
timeout := make(chan bool) timeout := make(chan bool)
go func(evm *EVM) { 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 errChannel <- err
}(evm) }(evm)

View file

@ -307,6 +307,7 @@ func makeCallVariantGasCall(oldCalculatorStateful, oldCalculatorStateless gasFun
if contract.Gas.Underflow(stateGasCost) { if contract.Gas.Underflow(stateGasCost) {
return GasCosts{}, ErrOutOfGas return GasCosts{}, ErrOutOfGas
} }
contract.GasUsed.Add(stateGasCost)
contract.Gas.Sub(stateGasCost) contract.Gas.Sub(stateGasCost)
} }
@ -327,10 +328,15 @@ func makeCallVariantGasCall(oldCalculatorStateful, oldCalculatorStateless gasFun
if overflow { if overflow {
return GasCosts{}, ErrGasUintOverflow 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) contract.Gas.RegularGas, overflow = math.SafeAdd(contract.Gas.RegularGas, eip7702Gas)
if overflow { if overflow {
return GasCosts{}, ErrGasUintOverflow return GasCosts{}, ErrGasUintOverflow
} }
contract.GasUsed.RegularGasUsed -= eip7702Gas
var totalCost uint64 var totalCost uint64
totalCost, overflow = math.SafeAdd(eip2929Gas, eip7702Gas) 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. // set the receiver's (the executing contract) code for execution.
cfg.State.SetCode(address, code, tracing.CodeChangeUnspecified) cfg.State.SetCode(address, code, tracing.CodeChangeUnspecified)
// Call the code with the given configuration. // Call the code with the given configuration.
ret, leftOverGas, err := vmenv.Call( ret, leftOverGas, _, err := vmenv.Call(
cfg.Origin, cfg.Origin,
common.BytesToAddress([]byte("contract")), common.BytesToAddress([]byte("contract")),
input, input,
@ -177,7 +177,7 @@ func Create(input []byte, cfg *Config) ([]byte, common.Address, uint64, error) {
// - reset transient storage(eip 1153) // - reset transient storage(eip 1153)
cfg.State.Prepare(rules, cfg.Origin, cfg.Coinbase, nil, vm.ActivePrecompiles(rules), nil) cfg.State.Prepare(rules, cfg.Origin, cfg.Coinbase, nil, vm.ActivePrecompiles(rules), nil)
// Call the code with the given configuration. // Call the code with the given configuration.
code, address, leftOverGas, err := vmenv.Create( code, address, leftOverGas, _, err := vmenv.Create(
cfg.Origin, cfg.Origin,
input, input,
vm.GasCosts{RegularGas: cfg.GasLimit}, 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) statedb.Prepare(rules, cfg.Origin, cfg.Coinbase, &address, vm.ActivePrecompiles(rules), nil)
// Call the code with the given configuration. // Call the code with the given configuration.
ret, leftOverGas, err := vmenv.Call( ret, leftOverGas, _, err := vmenv.Call(
cfg.Origin, cfg.Origin,
address, address,
input, input,

View file

@ -316,7 +316,7 @@ func runBenchmark(b *testing.B, t *StateTest) {
start := time.Now() start := time.Now()
// Execute the message. // 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 { if err != nil {
b.Error(err) b.Error(err)
return return