diff --git a/core/state_transition.go b/core/state_transition.go index 877ed0638f..a3a9efeaa4 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -242,12 +242,10 @@ func FloorDataGas(rules params.Rules, from common.Address, to *common.Address, v tokenCost = params.TxCostFloorPerToken } - // The floor is anchored to the transaction base cost. Under EIP-2780 that - // base is the per-resource decomposition (the same one used by the intrinsic - // gas), so the floor never undercuts the transaction's own base. + // The floor is anchored to the transaction base cost. floorBase := params.TxGas if rules.IsAmsterdam { - floorBase = intrinsicBaseGasEIP2780(from, to, value) + floorBase = params.TxBaseCost2780 } // Check for overflow if (math.MaxUint64-floorBase)/tokenCost < tokens { @@ -739,7 +737,7 @@ func (st *stateTransition) execute() (*ExecutionResult, error) { // If the contract creation failed, or the destination was pre-existing, // refund the account-creation state gas pre-charged in IntrinsicGas. if rules.IsAmsterdam && !creation { - st.gasRemaining.RefundState(params.AccountCreationSize * st.evm.Context.CostPerStateByte) + st.gasRemaining.RefundStateToReservoir(params.AccountCreationSize * st.evm.Context.CostPerStateByte) } } else { // Increment the nonce for the next transaction. @@ -755,6 +753,10 @@ func (st *stateTransition) execute() (*ExecutionResult, error) { // performing the resolution and warming. if addr, ok := types.ParseDelegation(st.state.GetCode(*msg.To)); ok { st.state.AddAddressToAccessList(addr) + // Record in BAL + if rules.IsAmsterdam { + st.state.GetCode(addr) + } } // EIP-2780: charge the transaction's top-level recipient costs. If the // budget cannot cover the charge, the top frame halts out of gas. @@ -968,7 +970,7 @@ func (st *stateTransition) applyAuthorization(rules params.Rules, auth *types.Se authority, err := st.validateAuthorization(auth) if err != nil { if rules.IsAmsterdam { - st.gasRemaining.RefundState((params.AccountCreationSize + params.AuthorizationCreationSize) * st.evm.Context.CostPerStateByte) + st.gasRemaining.RefundStateToReservoir((params.AccountCreationSize + params.AuthorizationCreationSize) * st.evm.Context.CostPerStateByte) st.state.AddRefund(params.AccountWriteAmsterdam) } return err @@ -981,7 +983,7 @@ func (st *stateTransition) applyAuthorization(rules params.Rules, auth *types.Se } } else { if st.state.Exist(authority) { - st.gasRemaining.RefundState(params.AccountCreationSize * st.evm.Context.CostPerStateByte) + st.gasRemaining.RefundStateToReservoir(params.AccountCreationSize * st.evm.Context.CostPerStateByte) st.state.AddRefund(params.AccountWriteAmsterdam) } authBase := params.AuthorizationCreationSize * st.evm.Context.CostPerStateByte @@ -993,17 +995,17 @@ func (st *stateTransition) applyAuthorization(rules params.Rules, auth *types.Se } if auth.Address == (common.Address{}) { // Clearing writes no indicator, refill this auth's state charge. - st.gasRemaining.RefundState(authBase) + st.gasRemaining.RefundStateToReservoir(authBase) // The indicator was created by an earlier auth within the same // transaction, refill the state charge as it's no longer justified. if curDelegated && !preDelegated { - st.gasRemaining.RefundState(authBase) + st.gasRemaining.RefundStateToReservoir(authBase) } } else if curDelegated || preDelegated { // The 23-byte slot is already occupied, overwriting it writes no // new bytes, refill the state charge. - st.gasRemaining.RefundState(authBase) + st.gasRemaining.RefundStateToReservoir(authBase) } } diff --git a/core/vm/gascosts.go b/core/vm/gascosts.go index 10b8eeb033..76a2c7af60 100644 --- a/core/vm/gascosts.go +++ b/core/vm/gascosts.go @@ -196,6 +196,22 @@ func (g *GasBudget) RefundState(s uint64) { g.UsedStateGas -= int64(s) } +// RefundStateToReservoir credits a state-gas refund directly to the +// reservoir, without repaying spilled regular gas first. +// +// Per the spec's set_delegation, authorization refunds (and the post-create +// new-account refund) are added to message.state_gas_reservoir directly, in +// contrast to the LIFO inline refunds handled by RefundState. The usage +// counter is decremented by the full amount, matching the spec's +// tx_state_gas = intrinsic_state + state_gas_used - state_refund and +// preserving the per-frame invariant: +// +// StateGas + UsedStateGas == initialStateGas + Spilled +func (g *GasBudget) RefundStateToReservoir(s uint64) { + g.StateGas += s + g.UsedStateGas -= int64(s) +} + // Forward drains `regular` regular gas and the entire state reservoir from // the parent's running budget and returns the initial GasBudget for a child // frame. The parent's UsedRegularGas is bumped by the forwarded amount so diff --git a/core/vm/operations_acl.go b/core/vm/operations_acl.go index 111cc6a00c..3669626d5a 100644 --- a/core/vm/operations_acl.go +++ b/core/vm/operations_acl.go @@ -400,7 +400,7 @@ func makeCallVariantGasCallEIP7702(intrinsicFunc intrinsicGasFunc, coldCost uint // Terminate the gas measurement if the leftover gas is not sufficient, // it can effectively prevent accessing the states in the following steps. // It's an essential safeguard before any stateful check. - if contract.Gas.RegularGas < intrinsicCost { + if !contract.chargeRegular(intrinsicCost, evm.Config.Tracer, tracing.GasChangeIgnored) { return GasCosts{}, ErrOutOfGas } @@ -422,7 +422,7 @@ func makeCallVariantGasCallEIP7702(intrinsicFunc intrinsicGasFunc, coldCost uint } // Calculate the gas budget for the nested call. The costs defined by // EIP-2929 and EIP-7702 have already been applied. - evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, contract.Gas.RegularGas, intrinsicCost, stack.back(0)) + evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, contract.Gas.RegularGas, 0, stack.back(0)) if err != nil { return GasCosts{}, err } @@ -430,11 +430,13 @@ func makeCallVariantGasCallEIP7702(intrinsicFunc intrinsicGasFunc, coldCost uint // adding it to the return, it will be charged outside of this function, as // part of the dynamic gas. This will ensure it is correctly reported to // tracers. - contract.Gas.RegularGas += eip2929Cost + eip7702Cost + contract.Gas.RegularGas += eip2929Cost + eip7702Cost + intrinsicCost + // Undo the RegularGasUsed increments from the direct UseGas charges, // since this gas will be re-charged via the returned cost. contract.Gas.UsedRegularGas -= eip2929Cost contract.Gas.UsedRegularGas -= eip7702Cost + contract.Gas.UsedRegularGas -= intrinsicCost // Aggregate the gas costs from all components, including EIP-2929, EIP-7702, // the CALL opcode itself, and the cost incurred by nested calls. diff --git a/params/protocol_params.go b/params/protocol_params.go index cace632931..c4a74b794a 100644 --- a/params/protocol_params.go +++ b/params/protocol_params.go @@ -101,13 +101,7 @@ const ( TxAccessListStorageKeyGas uint64 = 1900 // Per storage key specified in EIP 2930 access list TxAuthTupleGas uint64 = 12500 // Per auth tuple code specified in EIP-7702 - // RegularPerAuthBaseCost is defined in EIP-8037 as the sum of: - // - // - Calldata cost: 1,616 (101 bytes × 16) - // - Recovering authority address (ecRecover) - // - Reading nonce and code of authority (cold access) - // - Storing values in already warm account: 2 x WARM_ACCESS - RegularPerAuthBaseCost uint64 = 7500 + RegularPerAuthBaseCost uint64 = 7816 // As defined by EIP-8037 and EIP-8038 // EIP-2780: resource-based intrinsic transaction gas. TxBaseCost2780 uint64 = 12000