diff --git a/core/gaspool.go b/core/gaspool.go index 418b09e810..b4380e3e90 100644 --- a/core/gaspool.go +++ b/core/gaspool.go @@ -73,18 +73,21 @@ func (gp *GasPool) ReturnGas(returned uint64, gasUsed uint64) error { return nil } -// ReturnGasAmsterdam handles 2D gas accounting for Amsterdam (EIP-8037). -// It undoes the SubGas deduction fully and accumulates per-dimension block totals. -func (gp *GasPool) ReturnGasAmsterdam(returned, txRegular, txState, receiptGasUsed uint64) error { - if gp.remaining > math.MaxUint64-returned { - return fmt.Errorf("%w: remaining: %d, returned: %d", ErrGasLimitOverflow, gp.remaining, returned) - } - // Undo SubGas deduction fully (Amsterdam uses cumulative tracking) - gp.remaining += returned - // Accumulate 2D block dimensions +// ReturnGasAmsterdam calculates the new remaining gas in the pool after the +// execution of a message. +func (gp *GasPool) ReturnGasAmsterdam(txRegular, txState, receiptGasUsed uint64) error { gp.cumulativeRegular += txRegular gp.cumulativeState += txState gp.cumulativeUsed += receiptGasUsed + + blockUsed := max(gp.cumulativeRegular, gp.cumulativeState) + if gp.initial < blockUsed { + return fmt.Errorf("%w: block gas overflow: initial %d, used %d (regular: %d, state: %d)", + ErrGasLimitReached, gp.initial, blockUsed, gp.cumulativeRegular, gp.cumulativeState) + } + // TX inclusion: only the regular dimension is checked when deciding + // whether the next transaction fits. + gp.remaining = gp.initial - gp.cumulativeRegular return nil } @@ -132,6 +135,12 @@ func (gp *GasPool) Set(other *GasPool) { gp.cumulativeState = other.cumulativeState } +// AmsterdamDimensions returns the per-dimension cumulative gas values +// for 2D gas accounting (EIP-8037). +func (gp *GasPool) AmsterdamDimensions() (regular, state uint64) { + return gp.cumulativeRegular, gp.cumulativeState +} + func (gp *GasPool) String() string { return fmt.Sprintf("initial: %d, remaining: %d, cumulative used: %d", gp.initial, gp.remaining, gp.cumulativeUsed) } diff --git a/core/state_transition.go b/core/state_transition.go index 9c19f17d94..87626f5937 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -608,7 +608,7 @@ func (st *stateTransition) execute() (*ExecutionResult, error) { 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 { + if err := st.gp.ReturnGasAmsterdam(txRegular, txState, st.gasUsed()); err != nil { return nil, err } } else { diff --git a/core/vm/evm.go b/core/vm/evm.go index 4f187265d7..38b8cd4f23 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -479,27 +479,30 @@ func (evm *EVM) StaticCall(caller common.Address, addr common.Address, input []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, address common.Address, typ OpCode) (ret []byte, createAddress common.Address, leftOverGas GasCosts, gasUsed GasUsed, err error) { - if evm.Config.Tracer != nil { - evm.captureBegin(evm.depth, typ, caller, address, code, gas, value.ToBig()) - defer func(startGas GasCosts) { - evm.captureEnd(evm.depth, startGas, leftOverGas, ret, err) - }(gas) - } +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, used GasUsed, err error) { // Depth check execution. Fail if we're trying to execute above the // limit. + var nonce uint64 if evm.depth > int(params.CallCreateDepth) { err = ErrDepth } else if !evm.Context.CanTransfer(evm.StateDB, caller, value) { err = ErrInsufficientBalance } else { - nonce := evm.StateDB.GetNonce(caller) + nonce = evm.StateDB.GetNonce(caller) if nonce+1 < nonce { err = ErrNonceUintOverflow - } else { - evm.StateDB.SetNonce(caller, nonce+1, tracing.NonceChangeContractCreator) } } + + if err == nil { + evm.StateDB.SetNonce(caller, nonce+1, tracing.NonceChangeContractCreator) + } + if evm.Config.Tracer != nil { + evm.captureBegin(evm.depth, typ, caller, address, code, gas, value.ToBig()) + defer func(startGas GasCosts) { + evm.captureEnd(evm.depth, startGas, leftOverGas, ret, err) + }(gas) + } if err != nil { return nil, common.Address{}, gas, GasUsed{}, err } @@ -534,8 +537,10 @@ 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(gas.RegularGas, 0, tracing.GasChangeCallFailedExecution) } + // Burn all gas on collision + collisionUsed := GasUsed{RegularGasUsed: gas.RegularGas} gas.RegularGas = 0 - return nil, common.Address{}, gas, GasUsed{}, ErrContractAddressCollision + return nil, common.Address{}, gas, collisionUsed, 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 @@ -580,6 +585,9 @@ func (evm *EVM) create(caller common.Address, code []byte, gas GasCosts, value * if err != nil && (evm.chainRules.IsHomestead || err != ErrCodeStoreOutOfGas) { evm.StateDB.RevertToSnapshot(snapshot) if err != ErrExecutionReverted { + 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 } @@ -601,35 +609,39 @@ func (evm *EVM) initNewContract(contract *Contract, address common.Address) ([]b return ret, ErrInvalidCode } - if !evm.chainRules.IsEIP4762 { - if evm.chainRules.IsAmsterdam { - // EIP-8037: Split code deposit into state gas (code storage) and - // regular gas (keccak256 hashing). - stateGas := GasCosts{StateGas: uint64(len(ret)) * evm.Context.CostPerGasByte} - if !contract.UseGas(stateGas, evm.Config.Tracer, tracing.GasChangeCallCodeStorage) { - return ret, ErrCodeStoreOutOfGas - } - regularGas := GasCosts{RegularGas: toWordSize(uint64(len(ret))) * params.Keccak256WordGas} - if !contract.UseGas(regularGas, evm.Config.Tracer, tracing.GasChangeCallCodeStorage) { - return ret, ErrCodeStoreOutOfGas - } - } else { - createDataGas := GasCosts{RegularGas: uint64(len(ret)) * params.CreateDataGas} - if !contract.UseGas(createDataGas, evm.Config.Tracer, tracing.GasChangeCallCodeStorage) { - return ret, ErrCodeStoreOutOfGas - } + if evm.chainRules.IsAmsterdam { + // Check max code size BEFORE charging gas so over-max code + // does not consume state gas (which would inflate tx_state). + if err := CheckMaxCodeSize(&evm.chainRules, uint64(len(ret))); err != nil { + return ret, err } - } else { + // EIP-8037: Charge regular gas (keccak256 hash) first, then state gas + // (code storage). Regular-before-state prevents reservoir inflation. + regularGas := GasCosts{RegularGas: toWordSize(uint64(len(ret))) * params.Keccak256WordGas} + if !contract.UseGas(regularGas, evm.Config.Tracer, tracing.GasChangeCallCodeStorage) { + return ret, ErrCodeStoreOutOfGas + } + stateGas := GasCosts{StateGas: uint64(len(ret)) * evm.Context.CostPerGasByte} + if !contract.UseGas(stateGas, evm.Config.Tracer, tracing.GasChangeCallCodeStorage) { + return ret, ErrCodeStoreOutOfGas + } + } else if evm.chainRules.IsEIP4762 { consumed, wanted := evm.AccessEvents.CodeChunksRangeGas(address, 0, uint64(len(ret)), uint64(len(ret)), true, contract.Gas.RegularGas) contract.UseGas(GasCosts{RegularGas: consumed}, evm.Config.Tracer, tracing.GasChangeWitnessCodeChunk) if len(ret) > 0 && (consumed < wanted) { return ret, ErrCodeStoreOutOfGas } - } - - // Verify max code size after gas calculation. - if err := CheckMaxCodeSize(&evm.chainRules, uint64(len(ret))); err != nil { - return ret, err + if err := CheckMaxCodeSize(&evm.chainRules, uint64(len(ret))); err != nil { + return ret, err + } + } else { + createDataGas := GasCosts{RegularGas: uint64(len(ret)) * params.CreateDataGas} + if !contract.UseGas(createDataGas, evm.Config.Tracer, tracing.GasChangeCallCodeStorage) { + return ret, ErrCodeStoreOutOfGas + } + if err := CheckMaxCodeSize(&evm.chainRules, uint64(len(ret))); err != nil { + return ret, err + } } if len(ret) > 0 { diff --git a/core/vm/gas_table.go b/core/vm/gas_table.go index c8c3ca132f..04ce34d6a7 100644 --- a/core/vm/gas_table.go +++ b/core/vm/gas_table.go @@ -477,11 +477,14 @@ func gasCallCodeIntrinsic(evm *EVM, contract *Contract, stack *Stack, mem *Memor return 0, err } var ( - gas uint64 - overflow bool + gas uint64 + overflow bool + transfersValue = !stack.Back(2).IsZero() ) - if stack.Back(2).Sign() != 0 && !evm.chainRules.IsEIP4762 { - gas += params.CallValueTransferGas + if transfersValue { + if !evm.chainRules.IsEIP4762 { + gas += params.CallValueTransferGas + } } if gas, overflow = math.SafeAdd(gas, memoryGas); overflow { return 0, ErrGasUintOverflow @@ -505,8 +508,11 @@ func gasSelfdestruct(evm *EVM, contract *Contract, stack *Stack, mem *Memory, me // EIP150 homestead gas reprice fork: if evm.chainRules.IsEIP150 { gas = params.SelfdestructGasEIP150 - var address = common.Address(stack.Back(0).Bytes20()) + if gas > contract.Gas.RegularGas { + return GasCosts{RegularGas: gas}, nil + } + var address = common.Address(stack.Back(0).Bytes20()) if evm.chainRules.IsEIP158 { // if empty and transfers value if evm.StateDB.Empty(address) && evm.StateDB.GetBalance(contract.Address()).Sign() != 0 { @@ -639,17 +645,13 @@ func gasSStore8037(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memo } if original == current { if original == (common.Hash{}) { // create slot (2.1.1) - // EIP-8037: Charge state gas first (before regular gas), matching the - // spec's charge_state_gas → charge_gas ordering. This ensures that - // state_gas_used is recorded even if the subsequent regular gas charge - // fails with OOG. - stateGas := GasCosts{StateGas: params.StorageCreationSize * evm.Context.CostPerGasByte} - 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 + // EIP-8037: Return both regular and state gas. The interpreter + // charges regular gas before state gas, preventing reservoir + // inflation when the regular charge OOGs. + return GasCosts{ + RegularGas: cost.RegularGas + params.SstoreResetGasEIP2200 - params.ColdSloadCostEIP2929, + StateGas: params.StorageCreationSize * evm.Context.CostPerGasByte, + }, nil } if value == (common.Hash{}) { // delete slot (2.1.2b) evm.StateDB.AddRefund(params.SstoreClearsScheduleRefundEIP3529) diff --git a/core/vm/interpreter.go b/core/vm/interpreter.go index 66d567cfeb..14b244d3a4 100644 --- a/core/vm/interpreter.go +++ b/core/vm/interpreter.go @@ -226,7 +226,20 @@ func (evm *EVM) Run(contract *Contract, input []byte, readOnly bool) (ret []byte return nil, fmt.Errorf("%w: %v", ErrOutOfGas, err) } // for tracing: this gas consumption event is emitted below in the debug section. - if contract.Gas.Underflow(dynamicCost) { + if evm.chainRules.IsAmsterdam && dynamicCost.StateGas > 0 { + // EIP-8037: charge regular gas before state gas. + if contract.Gas.RegularGas < dynamicCost.RegularGas { + return nil, ErrOutOfGas + } + contract.GasUsed.RegularGasUsed += dynamicCost.RegularGas + contract.Gas.RegularGas -= dynamicCost.RegularGas + stateOnly := GasCosts{StateGas: dynamicCost.StateGas} + if contract.Gas.Underflow(stateOnly) { + return nil, ErrOutOfGas + } + contract.GasUsed.Add(stateOnly) + contract.Gas.Sub(stateOnly) + } else if contract.Gas.Underflow(dynamicCost) { return nil, ErrOutOfGas } else { contract.GasUsed.Add(dynamicCost) diff --git a/core/vm/operations_acl.go b/core/vm/operations_acl.go index e6ff32dec6..86d0c07639 100644 --- a/core/vm/operations_acl.go +++ b/core/vm/operations_acl.go @@ -243,6 +243,10 @@ func makeSelfdestructGasFn(refundsEnabled bool) gasFunc { return GasCosts{}, ErrOutOfGas } } + if gas > contract.Gas.RegularGas { + return GasCosts{RegularGas: gas}, nil + } + // if empty and transfers value if evm.StateDB.Empty(address) && evm.StateDB.GetBalance(contract.Address()).Sign() != 0 { gas += params.CreateBySelfdestructGas @@ -349,6 +353,10 @@ func makeCallVariantGasCallEIP7702(intrinsicFunc intrinsicGasFunc) gasFunc { // part of the dynamic gas. This will ensure it is correctly reported to // tracers. contract.Gas.RegularGas += eip2929Cost + eip7702Cost + // Undo the RegularGasUsed increments from the direct UseGas charges, + // since this gas will be re-charged via the returned cost. + contract.GasUsed.RegularGasUsed -= eip2929Cost + contract.GasUsed.RegularGasUsed -= eip7702Cost // Aggregate the gas costs from all components, including EIP-2929, EIP-7702, // the CALL opcode itself, and the cost incurred by nested calls. @@ -394,15 +402,12 @@ func makeCallVariantGasCallEIP8037(intrinsicFunc intrinsicGasFunc, stateGasFunc if err != nil { return GasCosts{}, err } - // Early OOG check before stateful operations. - if contract.Gas.RegularGas < intrinsicCost { - return GasCosts{}, ErrOutOfGas - } - // Compute state gas (new account creation as state gas). - stateGas, err := stateGasFunc(evm, contract, stack, mem, memorySize) - if err != nil { - return GasCosts{}, err + // Charge intrinsic cost directly (regular gas). This must happen + // BEFORE state gas to prevent reservoir inflation, and also serves + // as the OOG guard before stateful operations. + if !contract.UseGas(GasCosts{RegularGas: intrinsicCost}, evm.Config.Tracer, tracing.GasChangeCallOpCode) { + return GasCosts{}, ErrOutOfGas } // EIP-7702 delegation check. @@ -418,8 +423,11 @@ func makeCallVariantGasCallEIP8037(intrinsicFunc intrinsicGasFunc, stateGasFunc } } - // Charge state gas directly before callGas computation. State gas that - // spills to regular gas must reduce the gas available for callGasTemp. + // Compute and charge state gas (new account creation) AFTER regular gas. + stateGas, err := stateGasFunc(evm, contract, stack, mem, memorySize) + if err != nil { + return GasCosts{}, err + } if stateGas.StateGas > 0 { stateGasCost := GasCosts{StateGas: stateGas.StateGas} if contract.Gas.Underflow(stateGasCost) { @@ -430,16 +438,15 @@ func makeCallVariantGasCallEIP8037(intrinsicFunc intrinsicGasFunc, stateGasFunc } // Calculate the gas budget for the nested call (63/64 rule). - 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 } - // Temporarily add gas charges back for tracer reporting. - contract.Gas.RegularGas += eip2929Cost + eip7702Cost - // Undo GasUsed increments from direct UseGas charges. - contract.GasUsed.RegularGasUsed -= eip2929Cost - contract.GasUsed.RegularGasUsed -= eip7702Cost + // Temporarily undo direct regular charges for tracer reporting. + // The interpreter will charge the returned totalCost. + contract.Gas.RegularGas += eip2929Cost + eip7702Cost + intrinsicCost + contract.GasUsed.RegularGasUsed -= eip2929Cost + eip7702Cost + intrinsicCost // Aggregate total cost. var (