diff --git a/build/checksums.txt b/build/checksums.txt index d6a9f2b803..e6f4d3857e 100644 --- a/build/checksums.txt +++ b/build/checksums.txt @@ -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 v7.2.0 +# version:spec-tests-bal v7.3.1 # https://github.com/ethereum/execution-specs/releases -# https://github.com/ethereum/execution-specs/releases/download/tests-bal%40v7.2.0 -fc1d9ae174cdd5db789068839999e6f83666cc79f7dac36e973d7616d9a2e2cf fixtures_bal.tar.gz +# https://github.com/ethereum/execution-specs/releases/download/tests-bal%40v7.3.1 +3c9bd8799a506a96f74162863efdf5eaa00226e645db6523346fdb7c5ba0bf62 fixtures_bal.tar.gz # version:golang 1.25.10 # https://go.dev/dl/ diff --git a/core/state_transition.go b/core/state_transition.go index dac8123530..8dae706c8b 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -674,11 +674,6 @@ func (st *stateTransition) execute() (*ExecutionResult, error) { ret []byte vmerr error // vm errors do not effect consensus and are therefore not assigned to err result vm.GasBudget - - // Capture the forwarded regular-gas amount BEFORE ForwardAll consumes - // it, so Absorb can back out state-gas spillover from UsedRegularGas - // per EIP-8037. - forwarded = st.gasRemaining.RegularGas ) if contractCreation { // Check whether the init code size has been exceeded. @@ -687,7 +682,7 @@ func (st *stateTransition) execute() (*ExecutionResult, error) { } // Execute the transaction's creation. ret, _, result, vmerr = st.evm.Create(msg.From, msg.Data, st.gasRemaining.ForwardAll(), value) - st.gasRemaining.Absorb(result, forwarded) + st.gasRemaining.Absorb(result) // If the contract creation failed, refund the account-creation state // gas pre-charged in IntrinsicGas. @@ -711,7 +706,7 @@ func (st *stateTransition) execute() (*ExecutionResult, error) { } // Execute the transaction's call. ret, result, vmerr = st.evm.Call(msg.From, st.to(), msg.Data, st.gasRemaining.ForwardAll(), value) - st.gasRemaining.Absorb(result, forwarded) + st.gasRemaining.Absorb(result) } // Settle down the gas usage and refund the ETH back if any remaining @@ -821,7 +816,13 @@ func (st *stateTransition) settleGas(rules params.Rules, floorDataGas uint64) (g } if rules.IsAmsterdam { - if err = st.gp.ChargeGasAmsterdam(txRegularGas, txStateGas, gasUsed); err != nil { + // EIP-7623/7976: the calldata floor applies to the block-level regular + // gas dimension as well, mirroring its effect on the receipt gas. The + // spec accumulates max(tx_regular_gas, calldata_floor) into + // block_gas_used, so the block must never count fewer regular units + // than the floor the sender was charged. + blockRegularGas := max(txRegularGas, floorDataGas) + if err = st.gp.ChargeGasAmsterdam(blockRegularGas, txStateGas, gasUsed); err != nil { return 0, 0, err } } else { diff --git a/core/vm/contract.go b/core/vm/contract.go index 9155e9f84a..8b4114b84d 100644 --- a/core/vm/contract.go +++ b/core/vm/contract.go @@ -153,9 +153,9 @@ func (c *Contract) chargeState(s uint64, logger *tracing.Hooks, reason tracing.G } // refundGas absorbs a sub-call's leftover GasBudget into this contract's gas state. -func (c *Contract) refundGas(child GasBudget, forwarded uint64, logger *tracing.Hooks, reason tracing.GasChangeReason) { +func (c *Contract) refundGas(child GasBudget, logger *tracing.Hooks, reason tracing.GasChangeReason) { prior := c.Gas - c.Gas.Absorb(child, forwarded) + c.Gas.Absorb(child) if logger.HasGasHook() && reason != tracing.GasChangeIgnored { logger.EmitGasChange(prior.AsTracing(), c.Gas.AsTracing(), reason) } diff --git a/core/vm/eips.go b/core/vm/eips.go index ba7cbd7461..82ccf33df0 100644 --- a/core/vm/eips.go +++ b/core/vm/eips.go @@ -596,6 +596,8 @@ func enable7843(jt *JumpTable) { func enable8037(jt *JumpTable) { jt[CREATE].constantGas = params.CreateGasAmsterdam jt[CREATE2].constantGas = params.CreateGasAmsterdam + jt[CREATE].dynamicGas = gasCreateEip8037 + jt[CREATE2].dynamicGas = gasCreate2Eip8037 jt[SELFDESTRUCT].dynamicGas = gasSelfdestruct8037 jt[SSTORE].dynamicGas = gasSStore8037 } diff --git a/core/vm/evm.go b/core/vm/evm.go index e66dace3e0..7133047d6f 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -271,7 +271,7 @@ func (evm *EVM) Call(caller common.Address, addr common.Address, input []byte, g if !syscall && !value.IsZero() && !evm.Context.CanTransfer(evm.StateDB, caller, value) { return nil, gas, ErrInsufficientBalance } - snapshot, reservoir := evm.StateDB.Snapshot(), gas.StateGas + snapshot := evm.StateDB.Snapshot() p, isPrecompile := evm.precompile(addr) if !evm.StateDB.Exist(addr) { if !isPrecompile && evm.chainRules.IsEIP4762 && !isSystemCall(caller) { @@ -285,7 +285,7 @@ func (evm *EVM) Call(caller common.Address, addr common.Address, input []byte, g wgas := evm.AccessEvents.CodeHashGas(addr, true, gas.RegularGas, false) if _, ok := gas.ChargeRegular(wgas); !ok { evm.StateDB.RevertToSnapshot(snapshot) - return nil, gas.ExitHalt(reservoir), ErrOutOfGas + return nil, gas.ExitHalt(), ErrOutOfGas } } @@ -299,7 +299,7 @@ func (evm *EVM) Call(caller common.Address, addr common.Address, input []byte, g prev, ok := gas.ChargeState(params.AccountCreationSize * evm.Context.CostPerStateByte) if !ok { evm.StateDB.RevertToSnapshot(snapshot) - return nil, gas.ExitHalt(reservoir), ErrOutOfGas + return nil, gas.ExitHalt(), ErrOutOfGas } if evm.Config.Tracer.HasGasHook() { evm.Config.Tracer.EmitGasChange(prev.AsTracing(), gas.AsTracing(), tracing.GasChangeAccountCreation) @@ -330,7 +330,7 @@ func (evm *EVM) Call(caller common.Address, addr common.Address, input []byte, g } // Calculate the remaining gas at the end of frame - exitGas := gas.Exit(err, reservoir) + exitGas := gas.Exit(err) if err != nil { evm.StateDB.RevertToSnapshot(snapshot) @@ -372,7 +372,7 @@ func (evm *EVM) CallCode(caller common.Address, addr common.Address, input []byt if !evm.Context.CanTransfer(evm.StateDB, caller, value) { return nil, gas, ErrInsufficientBalance } - snapshot, reservoir := evm.StateDB.Snapshot(), gas.StateGas + snapshot := evm.StateDB.Snapshot() // It is allowed to call precompiles, even via delegatecall if p, isPrecompile := evm.precompile(addr); isPrecompile { @@ -387,7 +387,7 @@ func (evm *EVM) CallCode(caller common.Address, addr common.Address, input []byt } // Calculate the remaining gas at the end of frame - exitGas := gas.Exit(err, reservoir) + exitGas := gas.Exit(err) if err != nil { evm.StateDB.RevertToSnapshot(snapshot) @@ -418,7 +418,7 @@ func (evm *EVM) DelegateCall(originCaller common.Address, caller common.Address, if evm.depth > int(params.CallCreateDepth) { return nil, gas, ErrDepth } - snapshot, reservoir := evm.StateDB.Snapshot(), gas.StateGas + snapshot := evm.StateDB.Snapshot() // It is allowed to call precompiles, even via delegatecall if p, isPrecompile := evm.precompile(addr); isPrecompile { @@ -431,7 +431,7 @@ func (evm *EVM) DelegateCall(originCaller common.Address, caller common.Address, } // Calculate the remaining gas at the end of frame - exitGas := gas.Exit(err, reservoir) + exitGas := gas.Exit(err) if err != nil { evm.StateDB.RevertToSnapshot(snapshot) @@ -465,7 +465,7 @@ func (evm *EVM) StaticCall(caller common.Address, addr common.Address, input []b // after all empty accounts were deleted, so this is not required. However, if we omit this, // then certain tests start failing; stRevertTest/RevertPrecompiledTouchExactOOG.json. // We could change this, but for now it's left for legacy reasons - snapshot, reservoir := evm.StateDB.Snapshot(), gas.StateGas + snapshot := evm.StateDB.Snapshot() // We do an AddBalance of zero here, just in order to trigger a touch. // This doesn't matter on Mainnet, where all empties are gone at the time of Byzantium, @@ -483,7 +483,7 @@ func (evm *EVM) StaticCall(caller common.Address, addr common.Address, input []b } // Calculate the remaining gas at the end of frame - exitGas := gas.Exit(err, reservoir) + exitGas := gas.Exit(err) if err != nil { evm.StateDB.RevertToSnapshot(snapshot) if err != ErrExecutionReverted { @@ -521,14 +521,13 @@ func (evm *EVM) create(caller common.Address, code []byte, gas GasBudget, value } // Increment the caller's nonce after passing all validations evm.StateDB.SetNonce(caller, nonce+1, tracing.NonceChangeContractCreator) - reservoir := gas.StateGas // Charge the contract creation init gas in verkle mode if evm.chainRules.IsEIP4762 { statelessGas := evm.AccessEvents.ContractCreatePreCheckGas(address, gas.RegularGas) prior, ok := gas.Charge(GasCosts{RegularGas: statelessGas}) if !ok { - return nil, common.Address{}, gas.ExitHalt(reservoir), ErrOutOfGas + return nil, common.Address{}, gas.ExitHalt(), ErrOutOfGas } if evm.Config.Tracer.HasGasHook() { evm.Config.Tracer.EmitGasChange(prior.AsTracing(), gas.AsTracing(), tracing.GasChangeWitnessContractCollisionCheck) @@ -549,7 +548,7 @@ func (evm *EVM) create(caller common.Address, code []byte, gas GasBudget, value if evm.StateDB.GetNonce(address) != 0 || (contractHash != (common.Hash{}) && contractHash != types.EmptyCodeHash) || // non-empty code isEIP7610RejectedAccount(evm.ChainConfig().ChainID, address, evm.chainRules.IsEIP158) { - halt := gas.ExitHalt(reservoir) + halt := gas.ExitHalt() if evm.Config.Tracer.HasGasHook() { evm.Config.Tracer.EmitGasChange(gas.AsTracing(), halt.AsTracing(), tracing.GasChangeCallFailedExecution) } @@ -563,18 +562,9 @@ func (evm *EVM) create(caller common.Address, code []byte, gas GasBudget, value snapshot := evm.StateDB.Snapshot() if !evm.StateDB.Exist(address) { evm.StateDB.CreateAccount(address) - - if evm.chainRules.IsAmsterdam && evm.depth > 0 { - // Only charge state gas if we are not doing a create transaction. - // Prevents double charging with IntrinsicGas. - prev, ok := gas.ChargeState(params.AccountCreationSize * evm.Context.CostPerStateByte) - if !ok { - return nil, common.Address{}, gas.ExitHalt(reservoir), ErrOutOfGas - } - if evm.Config.Tracer.HasGasHook() { - evm.Config.Tracer.EmitGasChange(prev.AsTracing(), gas.AsTracing(), tracing.GasChangeAccountCreation) - } - } + // EIP-8037: the account-creation state gas is charged before the + // opcode runs (gasCreateEip8037) for CREATE/CREATE2 opcodes, and in + // IntrinsicGas for creation transactions, so there is no charge here. } // CreateContract means that regardless of whether the account previously existed // in the state trie or not, it _now_ becomes created as a _contract_ account. @@ -589,7 +579,7 @@ func (evm *EVM) create(caller common.Address, code []byte, gas GasBudget, value if evm.chainRules.IsEIP4762 { consumed, wanted := evm.AccessEvents.ContractCreateInitGas(address, gas.RegularGas) if consumed < wanted { - return nil, common.Address{}, gas.ExitHalt(reservoir), ErrOutOfGas + return nil, common.Address{}, gas.ExitHalt(), ErrOutOfGas } prior, _ := gas.Charge(GasCosts{RegularGas: consumed}) if evm.Config.Tracer.HasGasHook() { @@ -614,7 +604,7 @@ func (evm *EVM) create(caller common.Address, code []byte, gas GasBudget, value if err != nil && (evm.chainRules.IsHomestead || err != ErrCodeStoreOutOfGas) { evm.StateDB.RevertToSnapshot(snapshot) - exit := contract.Gas.Exit(err, reservoir) + exit := contract.Gas.Exit(err) if err != ErrExecutionReverted { if evm.Config.Tracer.HasGasHook() { evm.Config.Tracer.EmitGasChange(contract.Gas.AsTracing(), exit.AsTracing(), tracing.GasChangeCallFailedExecution) diff --git a/core/vm/gas_table.go b/core/vm/gas_table.go index 550375c9c0..5cf64a9844 100644 --- a/core/vm/gas_table.go +++ b/core/vm/gas_table.go @@ -367,6 +367,62 @@ func gasCreate2Eip3860(evm *EVM, contract *Contract, stack *Stack, mem *Memory, return GasCosts{RegularGas: gas}, nil } +// gasCreateEip8037 is the CREATE gas calculator for Amsterdam. It charges the +// account-creation cost as state gas (EIP-8037) here, before the opcode runs, +// so the 63/64 gas-forwarding split sees the post-charge regular gas. The +// charge is refunded to the reservoir in opCreate on any failure path that +// does not create an account. +func gasCreateEip8037(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) { + if evm.readOnly { + return GasCosts{}, ErrWriteProtection + } + gas, err := memoryGasCost(mem, memorySize) + if err != nil { + return GasCosts{}, err + } + size, overflow := stack.back(2).Uint64WithOverflow() + if overflow { + return GasCosts{}, ErrGasUintOverflow + } + if err := CheckMaxInitCodeSize(&evm.chainRules, size); err != nil { + return GasCosts{}, err + } + // Since size <= MaxInitCodeSizeAmsterdam, these multiplications cannot overflow. + wordGas := params.InitCodeWordGas * ((size + 31) / 32) + stateGas := params.AccountCreationSize * evm.Context.CostPerStateByte + return GasCosts{ + RegularGas: gas + wordGas, + StateGas: stateGas, + }, nil +} + +// gasCreate2Eip8037 is the CREATE2 gas calculator for Amsterdam. See +// gasCreateEip8037; CREATE2 additionally charges Keccak256WordGas for hashing +// the init code. +func gasCreate2Eip8037(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) { + if evm.readOnly { + return GasCosts{}, ErrWriteProtection + } + gas, err := memoryGasCost(mem, memorySize) + if err != nil { + return GasCosts{}, err + } + size, overflow := stack.back(2).Uint64WithOverflow() + if overflow { + return GasCosts{}, ErrGasUintOverflow + } + if err := CheckMaxInitCodeSize(&evm.chainRules, size); err != nil { + return GasCosts{}, err + } + // Since size <= MaxInitCodeSizeAmsterdam, these multiplications cannot overflow. + wordGas := (params.InitCodeWordGas + params.Keccak256WordGas) * ((size + 31) / 32) + stateGas := params.AccountCreationSize * evm.Context.CostPerStateByte + return GasCosts{ + RegularGas: gas + wordGas, + StateGas: stateGas, + }, nil +} + func gasExpFrontier(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) { expByteLen := uint64((stack.back(1).BitLen() + 7) / 8) diff --git a/core/vm/gascosts.go b/core/vm/gascosts.go index b1756ab5fe..98130b9ce5 100644 --- a/core/vm/gascosts.go +++ b/core/vm/gascosts.go @@ -226,9 +226,11 @@ func (g GasBudget) ExitRevert() GasBudget { // ExitHalt produces the leftover for an exceptional halt. // -// - state_gas_reservoir is reset back to its value at the start of the child frame -// - the gas_left initially given to the child is consumed (set to zero) -func (g GasBudget) ExitHalt(initStateReservoir uint64) GasBudget { +// Per the updated EIP-8037, only the regular gas_left is burned (folded into +// UsedRegularGas); the entire state-gas reservoir — including any portion that +// spilled into the regular pool during execution — is refunded to the caller's +// reservoir rather than reclassified as burned regular gas. +func (g GasBudget) ExitHalt() GasBudget { reservoir := int64(g.StateGas) + g.UsedStateGas if reservoir < 0 { // Reservoir should never be negative. By construction it equals @@ -237,17 +239,10 @@ func (g GasBudget) ExitHalt(initStateReservoir uint64) GasBudget { reservoir = 0 log.Warn("Negative reservoir at halt", "remaining", g.StateGas, "used", g.UsedStateGas) } - // The portion of state gas charged from regular gas is also burned - // together with the regular gas, rather than being returned to the - // parent's state-gas reservoir. - var spilled uint64 - if uint64(reservoir) > initStateReservoir { - spilled = uint64(reservoir) - initStateReservoir - } return GasBudget{ RegularGas: 0, - StateGas: initStateReservoir, - UsedRegularGas: g.UsedRegularGas + g.RegularGas + spilled, + StateGas: uint64(reservoir), + UsedRegularGas: g.UsedRegularGas + g.RegularGas, UsedStateGas: 0, } } @@ -261,33 +256,24 @@ func (g GasBudget) ExitHalt(initStateReservoir uint64) GasBudget { // // Soft validation failures (occurring BEFORE evm.Run) should call Preserved // directly instead of going through this dispatcher. -func (g GasBudget) Exit(err error, initStateReservoir uint64) GasBudget { +func (g GasBudget) Exit(err error) GasBudget { switch { case err == nil: return g.ExitSuccess() case err == ErrExecutionReverted: return g.ExitRevert() default: - return g.ExitHalt(initStateReservoir) + return g.ExitHalt() } } // Absorb merges a sub-call's leftover GasBudget into this (caller's) running -// budget. Additionally, it does an EIP-8037 spillover correction: -// state-gas that spilled into the regular pool inside the child frame is -// excluded from the UsedRegularGas. -// -// spillover = forwarded - child.RegularGas - child.UsedRegularGas -// -// forwarded is the regular-gas amount that was passed to the child at call -// entry (i.e., the regular initial of the child's GasBudget). -func (g *GasBudget) Absorb(child GasBudget, forwarded uint64) { - spillover := forwarded - child.RegularGas - child.UsedRegularGas - - g.UsedRegularGas -= child.RegularGas +// budget. Under the updated EIP-8037, state-gas no longer spills into the +// child's burned regular gas on halt, so the child's UsedRegularGas can be +// folded in directly without a spillover correction. +func (g *GasBudget) Absorb(child GasBudget) { g.RegularGas += child.RegularGas + g.UsedRegularGas += child.UsedRegularGas g.StateGas = child.StateGas g.UsedStateGas += child.UsedStateGas - - g.UsedRegularGas -= spillover } diff --git a/core/vm/instructions.go b/core/vm/instructions.go index 92c363a356..672a731358 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -677,7 +677,14 @@ func opCreate(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) { scope.Stack.push(&stackvalue) // Refund the leftover gas back to current frame - scope.Contract.refundGas(result, forward, evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded) + scope.Contract.refundGas(result, evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded) + + // EIP-8037: no account was created on any failure path, so refund the + // account-creation state gas charged before the opcode ran (gasCreateEip8037) + // back to the reservoir. + if evm.chainRules.IsAmsterdam && suberr != nil { + scope.Contract.Gas.RefundState(params.AccountCreationSize * evm.Context.CostPerStateByte) + } if suberr == ErrExecutionReverted { evm.returnData = res // set REVERT data to return data buffer @@ -711,7 +718,14 @@ func opCreate2(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) { scope.Stack.push(&stackvalue) // Refund the leftover gas back to current frame - scope.Contract.refundGas(result, forward, evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded) + scope.Contract.refundGas(result, evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded) + + // EIP-8037: no account was created on any failure path, so refund the + // account-creation state gas charged before the opcode ran (gasCreate2Eip8037) + // back to the reservoir. + if evm.chainRules.IsAmsterdam && suberr != nil { + scope.Contract.Gas.RefundState(params.AccountCreationSize * evm.Context.CostPerStateByte) + } if suberr == ErrExecutionReverted { evm.returnData = res // set REVERT data to return data buffer @@ -756,7 +770,7 @@ func opCall(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) { if err == nil || err == ErrExecutionReverted { scope.Memory.Set(retOffset.Uint64(), retSize.Uint64(), ret) } - scope.Contract.refundGas(result, gas, evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded) + scope.Contract.refundGas(result, evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded) evm.returnData = ret return ret, nil @@ -792,7 +806,7 @@ func opCallCode(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) { scope.Memory.Set(retOffset.Uint64(), retSize.Uint64(), ret) } - scope.Contract.refundGas(result, gas, evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded) + scope.Contract.refundGas(result, evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded) evm.returnData = ret return ret, nil @@ -824,7 +838,7 @@ func opDelegateCall(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) { if err == nil || err == ErrExecutionReverted { scope.Memory.Set(retOffset.Uint64(), retSize.Uint64(), ret) } - scope.Contract.refundGas(result, gas, evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded) + scope.Contract.refundGas(result, evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded) evm.returnData = ret return ret, nil @@ -857,7 +871,7 @@ func opStaticCall(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) { scope.Memory.Set(retOffset.Uint64(), retSize.Uint64(), ret) } - scope.Contract.refundGas(result, gas, evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded) + scope.Contract.refundGas(result, evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded) evm.returnData = ret return ret, nil