core: fixed issues from devnet

This commit is contained in:
Marius van der Wijden 2026-03-05 19:29:57 +01:00
parent e483205878
commit 0140060d96
5 changed files with 91 additions and 31 deletions

View file

@ -87,11 +87,21 @@ func (gp *GasPool) ReturnGasAmsterdam(txGasLimit, txRegular, txState, receiptGas
gp.cumulativeUsed += receiptGasUsed
newUsed := max(gp.cumulativeRegular, gp.cumulativeState)
blockGasIncrement := newUsed - oldUsed
returned := txGasLimit - blockGasIncrement
if gp.remaining > math.MaxUint64-returned {
return fmt.Errorf("%w: remaining: %d, returned: %d", ErrGasLimitOverflow, gp.remaining, returned)
if blockGasIncrement > txGasLimit {
// State gas via reservoir model can push the dimensional cost above
// the tx gas limit. Consume the excess from the pool.
excess := blockGasIncrement - txGasLimit
if gp.remaining < excess {
return fmt.Errorf("%w: remaining: %d, excess: %d", ErrGasLimitReached, gp.remaining, excess)
}
gp.remaining -= excess
} else {
returned := txGasLimit - blockGasIncrement
if gp.remaining > math.MaxUint64-returned {
return fmt.Errorf("%w: remaining: %d, returned: %d", ErrGasLimitOverflow, gp.remaining, returned)
}
gp.remaining += returned
}
gp.remaining += returned
return nil
}

View file

@ -536,7 +536,6 @@ func (evm *EVM) create(caller common.Address, code []byte, gas GasCosts, value *
if err != nil {
return nil, common.Address{}, gas, err
}
// Charge the contract creation init gas in verkle mode
if evm.chainRules.IsEIP4762 {
statelessGas := evm.AccessEvents.ContractCreatePreCheckGas(address, gas.RegularGas)
@ -620,7 +619,8 @@ func (evm *EVM) create(caller common.Address, code []byte, gas GasCosts, value *
// State gas charges must persist for block 2D gas accounting even
// though the state is reverted. Only zero regular gas as penalty.
isCodeValidation := evm.chainRules.IsAmsterdam &&
(errors.Is(err, ErrMaxCodeSizeExceeded) || errors.Is(err, ErrInvalidCode))
(errors.Is(err, ErrMaxCodeSizeExceeded) || errors.Is(err, ErrInvalidCode) ||
(err == ErrCodeStoreOutOfGas && contract.Gas.TotalStateGasCharged > savedTotalStateGas))
if !isRevert {
if isCodeValidation {
contract.Gas.RegularGas = 0
@ -646,8 +646,13 @@ func (evm *EVM) initNewContract(contract *Contract, address common.Address) ([]b
return ret, err
}
// Charge code storage gas BEFORE validation checks so that state gas is
// properly accounted for in block gas even if the code is invalid (EIP-7954).
// Check code validity once upfront.
codeErr := CheckMaxCodeSize(&evm.chainRules, uint64(len(ret)))
if codeErr == nil && len(ret) >= 1 && ret[0] == 0xEF && evm.chainRules.IsLondon {
codeErr = ErrInvalidCode
}
// Charge code storage gas.
if !evm.chainRules.IsEIP4762 {
createDataGas := GasCosts{RegularGas: uint64(len(ret)) * params.CreateDataGas}
if evm.chainRules.IsAmsterdam {
@ -658,6 +663,13 @@ func (evm *EVM) initNewContract(contract *Contract, address common.Address) ([]b
}
}
if !contract.UseGas(createDataGas, evm.Config.Tracer, tracing.GasChangeCallCodeStorage) {
if evm.chainRules.IsAmsterdam && codeErr == nil {
// Valid code that merely ran out of gas: track state gas for
// block accounting (EIP-8037).
contract.Gas.TotalStateGasCharged += createDataGas.StateGas
contract.Gas.RegularGas = 0
contract.Gas.StateGas = 0
}
return ret, ErrCodeStoreOutOfGas
}
} else {
@ -668,14 +680,8 @@ func (evm *EVM) initNewContract(contract *Contract, address common.Address) ([]b
}
}
// Check whether the max code size has been exceeded, assign err if the case.
if err := CheckMaxCodeSize(&evm.chainRules, uint64(len(ret))); err != nil {
return ret, err
}
// Reject code starting with 0xEF if EIP-3541 is enabled.
if len(ret) >= 1 && ret[0] == 0xEF && evm.chainRules.IsLondon {
return ret, ErrInvalidCode
if codeErr != nil {
return ret, codeErr
}
if len(ret) > 0 {

View file

@ -18,7 +18,6 @@ package vm
import (
"errors"
"fmt"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/math"
@ -596,12 +595,13 @@ func gasCreateEip8037(evm *EVM, contract *Contract, stack *Stack, mem *Memory, m
if overflow {
return GasCosts{}, ErrGasUintOverflow
}
if size > params.MaxInitCodeSize {
return GasCosts{}, fmt.Errorf("%w: size %d", ErrMaxInitCodeSizeExceeded, size)
}
// Since size <= params.MaxInitCodeSize, these multiplication cannot overflow
words := (size + 31) / 32
stateGas := params.AccountCreationSize * evm.Context.CostPerGasByte * words
// Cap word gas at MaxInitCodeSizeAmsterdam to avoid overflow.
// The actual init code size check happens in create() for graceful failure.
wordSize := min(size, params.MaxInitCodeSizeAmsterdam)
words := (wordSize + 31) / 32
// Account creation is a fixed state gas cost, not proportional to init code size.
// Code storage state gas is charged separately in initNewContract.
stateGas := params.AccountCreationSize * evm.Context.CostPerGasByte
// CREATE uses InitCodeWordGas (EIP-3860); Keccak256WordGas is only for CREATE2.
wordGas := params.InitCodeWordGas * words
return GasCosts{RegularGas: gas + wordGas, StateGas: stateGas}, nil
@ -616,12 +616,13 @@ func gasCreate2Eip8037(evm *EVM, contract *Contract, stack *Stack, mem *Memory,
if overflow {
return GasCosts{}, ErrGasUintOverflow
}
if size > params.MaxInitCodeSize {
return GasCosts{}, fmt.Errorf("%w: size %d", ErrMaxInitCodeSizeExceeded, size)
}
// Since size <= params.MaxInitCodeSize, these multiplication cannot overflow
words := (size + 31) / 32
stateGas := params.AccountCreationSize * evm.Context.CostPerGasByte * words
// Cap word gas at MaxInitCodeSizeAmsterdam to avoid overflow.
// The actual init code size check happens in create() for graceful failure.
wordSize := min(size, params.MaxInitCodeSizeAmsterdam)
words := (wordSize + 31) / 32
// Account creation is a fixed state gas cost, not proportional to init code size.
// Code storage state gas is charged separately in initNewContract.
stateGas := params.AccountCreationSize * evm.Context.CostPerGasByte
// CREATE2 charges both InitCodeWordGas (EIP-3860) and Keccak256WordGas (for address hashing).
wordGas := (params.InitCodeWordGas + params.Keccak256WordGas) * words
return GasCosts{RegularGas: gas + wordGas, StateGas: stateGas}, nil

View file

@ -28,13 +28,17 @@ func (g GasCosts) Sum() uint64 {
return g.RegularGas + g.StateGas
}
// Sub returns true if the operation would underflow
// Underflow returns true if the operation would underflow.
// When state gas exceeds the reservoir, the excess spills to regular gas.
// The check accounts for regular gas already consumed by b.RegularGas.
func (g GasCosts) Underflow(b GasCosts) bool {
if b.RegularGas > g.RegularGas {
return true
}
if b.StateGas > g.StateGas {
if b.StateGas > g.RegularGas {
spillover := b.StateGas - g.StateGas
remainingRegular := g.RegularGas - b.RegularGas
if spillover > remainingRegular {
return true
}
}

View file

@ -17,6 +17,7 @@
package vm
import (
"errors"
"math"
"github.com/ethereum/go-ethereum/common"
@ -661,6 +662,15 @@ func opCreate(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
gas.RegularGas -= gas.RegularGas / 64
}
// EIP-7954: check init code size after gas is charged (by the gas function)
// but before execution. This aborts the caller's execution, ensuring all
// regular gas is consumed while the state gas spill is tracked.
if evm.chainRules.IsAmsterdam {
if err := CheckMaxInitCodeSize(&evm.chainRules, uint64(len(input))); err != nil {
return nil, err
}
}
// reuse size int for stackvalue
stackvalue := size
@ -684,6 +694,15 @@ func opCreate(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
}
scope.Stack.push(&stackvalue)
// For inner CREATEs that fail code validation (EIP-7954 + EIP-8037):
// the state was reverted so no state growth occurred. Don't propagate
// the code storage state gas to the parent's block gas accounting.
// Restore the parent's state gas reservoir since it was not consumed.
if evm.chainRules.IsAmsterdam && (errors.Is(suberr, ErrMaxCodeSizeExceeded) || errors.Is(suberr, ErrInvalidCode)) {
returnGas.TotalStateGasCharged = 0
returnGas.RevertedStateGasSpill = 0
returnGas.StateGas = stateGas
}
scope.Contract.RefundGas(returnGas, evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded)
if suberr == ErrExecutionReverted {
@ -708,6 +727,16 @@ func opCreate2(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
// Apply EIP150
gas.RegularGas -= gas.RegularGas / 64
// EIP-7954: check init code size after gas is charged (by the gas function)
// but before execution. This aborts the caller's execution, ensuring all
// regular gas is consumed while the state gas spill is tracked.
if evm.chainRules.IsAmsterdam {
if err := CheckMaxInitCodeSize(&evm.chainRules, uint64(len(input))); err != nil {
return nil, err
}
}
// Pass caller's state gas (reservoir) to child and zero it out to avoid
// double-counting when the unused portion is refunded on return.
stateGas := scope.Contract.Gas.StateGas
@ -724,6 +753,16 @@ func opCreate2(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
stackvalue.SetBytes(addr.Bytes())
}
scope.Stack.push(&stackvalue)
// For inner CREATEs that fail code validation (EIP-7954 + EIP-8037):
// the state was reverted so no state growth occurred. Don't propagate
// the code storage state gas to the parent's block gas accounting.
// Restore the parent's state gas reservoir since it was not consumed.
if evm.chainRules.IsAmsterdam && (errors.Is(suberr, ErrMaxCodeSizeExceeded) || errors.Is(suberr, ErrInvalidCode)) {
returnGas.TotalStateGasCharged = 0
returnGas.RevertedStateGasSpill = 0
returnGas.StateGas = stateGas
}
scope.Contract.RefundGas(returnGas, evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded)
if suberr == ErrExecutionReverted {