core: apply changes from bal-devnet-3 branch

This commit is contained in:
Marius van der Wijden 2026-04-08 13:05:25 +02:00
parent 4e46fb1b8d
commit 223f1c50ce
6 changed files with 120 additions and 77 deletions

View file

@ -73,18 +73,21 @@ func (gp *GasPool) ReturnGas(returned uint64, gasUsed uint64) error {
return nil return nil
} }
// ReturnGasAmsterdam handles 2D gas accounting for Amsterdam (EIP-8037). // ReturnGasAmsterdam calculates the new remaining gas in the pool after the
// It undoes the SubGas deduction fully and accumulates per-dimension block totals. // execution of a message.
func (gp *GasPool) ReturnGasAmsterdam(returned, txRegular, txState, receiptGasUsed uint64) error { func (gp *GasPool) ReturnGasAmsterdam(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
gp.cumulativeRegular += txRegular gp.cumulativeRegular += txRegular
gp.cumulativeState += txState gp.cumulativeState += txState
gp.cumulativeUsed += receiptGasUsed 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 return nil
} }
@ -132,6 +135,12 @@ func (gp *GasPool) Set(other *GasPool) {
gp.cumulativeState = other.cumulativeState 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 { func (gp *GasPool) String() string {
return fmt.Sprintf("initial: %d, remaining: %d, cumulative used: %d", gp.initial, gp.remaining, gp.cumulativeUsed) return fmt.Sprintf("initial: %d, remaining: %d, cumulative used: %d", gp.initial, gp.remaining, gp.cumulativeUsed)
} }

View file

@ -608,7 +608,7 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
txState := (gas.StateGas - authRefund) + execGasUsed.StateGasCharged txState := (gas.StateGas - authRefund) + execGasUsed.StateGasCharged
txRegular := gas.RegularGas + execGasUsed.RegularGasUsed 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(txRegular, txState, st.gasUsed()); err != nil {
return nil, err return nil, err
} }
} else { } else {

View file

@ -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. // 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) { 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) {
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)
}
// 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
if evm.depth > int(params.CallCreateDepth) { if evm.depth > int(params.CallCreateDepth) {
err = ErrDepth err = ErrDepth
} else if !evm.Context.CanTransfer(evm.StateDB, caller, value) { } else if !evm.Context.CanTransfer(evm.StateDB, caller, value) {
err = ErrInsufficientBalance err = ErrInsufficientBalance
} else { } else {
nonce := evm.StateDB.GetNonce(caller) nonce = evm.StateDB.GetNonce(caller)
if nonce+1 < nonce { if nonce+1 < nonce {
err = ErrNonceUintOverflow 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 { if err != nil {
return nil, common.Address{}, gas, GasUsed{}, err 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 { 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)
} }
// Burn all gas on collision
collisionUsed := GasUsed{RegularGasUsed: gas.RegularGas}
gas.RegularGas = 0 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. // 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
@ -580,6 +585,9 @@ func (evm *EVM) create(caller common.Address, code []byte, gas GasCosts, value *
if err != nil && (evm.chainRules.IsHomestead || err != ErrCodeStoreOutOfGas) { if err != nil && (evm.chainRules.IsHomestead || err != ErrCodeStoreOutOfGas) {
evm.StateDB.RevertToSnapshot(snapshot) evm.StateDB.RevertToSnapshot(snapshot)
if err != ErrExecutionReverted { 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.GasUsed.RegularGasUsed += contract.Gas.RegularGas
contract.Gas.RegularGas = 0 contract.Gas.RegularGas = 0
} }
@ -601,35 +609,39 @@ func (evm *EVM) initNewContract(contract *Contract, address common.Address) ([]b
return ret, ErrInvalidCode return ret, ErrInvalidCode
} }
if !evm.chainRules.IsEIP4762 { if evm.chainRules.IsAmsterdam {
if evm.chainRules.IsAmsterdam { // Check max code size BEFORE charging gas so over-max code
// EIP-8037: Split code deposit into state gas (code storage) and // does not consume state gas (which would inflate tx_state).
// regular gas (keccak256 hashing). if err := CheckMaxCodeSize(&evm.chainRules, uint64(len(ret))); err != nil {
stateGas := GasCosts{StateGas: uint64(len(ret)) * evm.Context.CostPerGasByte} return ret, err
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
}
} }
} 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) 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) contract.UseGas(GasCosts{RegularGas: consumed}, evm.Config.Tracer, tracing.GasChangeWitnessCodeChunk)
if len(ret) > 0 && (consumed < wanted) { if len(ret) > 0 && (consumed < wanted) {
return ret, ErrCodeStoreOutOfGas return ret, ErrCodeStoreOutOfGas
} }
} if err := CheckMaxCodeSize(&evm.chainRules, uint64(len(ret))); err != nil {
return ret, err
// Verify max code size after gas calculation. }
if err := CheckMaxCodeSize(&evm.chainRules, uint64(len(ret))); err != nil { } else {
return ret, err 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 { if len(ret) > 0 {

View file

@ -477,11 +477,14 @@ func gasCallCodeIntrinsic(evm *EVM, contract *Contract, stack *Stack, mem *Memor
return 0, err return 0, err
} }
var ( var (
gas uint64 gas uint64
overflow bool overflow bool
transfersValue = !stack.Back(2).IsZero()
) )
if stack.Back(2).Sign() != 0 && !evm.chainRules.IsEIP4762 { if transfersValue {
gas += params.CallValueTransferGas if !evm.chainRules.IsEIP4762 {
gas += params.CallValueTransferGas
}
} }
if gas, overflow = math.SafeAdd(gas, memoryGas); overflow { if gas, overflow = math.SafeAdd(gas, memoryGas); overflow {
return 0, ErrGasUintOverflow return 0, ErrGasUintOverflow
@ -505,8 +508,11 @@ func gasSelfdestruct(evm *EVM, contract *Contract, stack *Stack, mem *Memory, me
// EIP150 homestead gas reprice fork: // EIP150 homestead gas reprice fork:
if evm.chainRules.IsEIP150 { if evm.chainRules.IsEIP150 {
gas = params.SelfdestructGasEIP150 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 evm.chainRules.IsEIP158 {
// if empty and transfers value // if empty and transfers value
if evm.StateDB.Empty(address) && evm.StateDB.GetBalance(contract.Address()).Sign() != 0 { 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 == current {
if original == (common.Hash{}) { // create slot (2.1.1) if original == (common.Hash{}) { // create slot (2.1.1)
// EIP-8037: Charge state gas first (before regular gas), matching the // EIP-8037: Return both regular and state gas. The interpreter
// spec's charge_state_gas → charge_gas ordering. This ensures that // charges regular gas before state gas, preventing reservoir
// state_gas_used is recorded even if the subsequent regular gas charge // inflation when the regular charge OOGs.
// fails with OOG. return GasCosts{
stateGas := GasCosts{StateGas: params.StorageCreationSize * evm.Context.CostPerGasByte} RegularGas: cost.RegularGas + params.SstoreResetGasEIP2200 - params.ColdSloadCostEIP2929,
if contract.Gas.Underflow(stateGas) { StateGas: params.StorageCreationSize * evm.Context.CostPerGasByte,
return GasCosts{}, errors.New("out of gas for state gas") }, nil
}
contract.GasUsed.Add(stateGas)
contract.Gas.Sub(stateGas)
return GasCosts{RegularGas: cost.RegularGas + params.SstoreResetGasEIP2200 - params.ColdSloadCostEIP2929}, nil
} }
if value == (common.Hash{}) { // delete slot (2.1.2b) if value == (common.Hash{}) { // delete slot (2.1.2b)
evm.StateDB.AddRefund(params.SstoreClearsScheduleRefundEIP3529) evm.StateDB.AddRefund(params.SstoreClearsScheduleRefundEIP3529)

View file

@ -226,7 +226,20 @@ func (evm *EVM) Run(contract *Contract, input []byte, readOnly bool) (ret []byte
return nil, fmt.Errorf("%w: %v", ErrOutOfGas, err) return nil, fmt.Errorf("%w: %v", ErrOutOfGas, err)
} }
// for tracing: this gas consumption event is emitted below in the debug section. // 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 return nil, ErrOutOfGas
} else { } else {
contract.GasUsed.Add(dynamicCost) contract.GasUsed.Add(dynamicCost)

View file

@ -243,6 +243,10 @@ func makeSelfdestructGasFn(refundsEnabled bool) gasFunc {
return GasCosts{}, ErrOutOfGas return GasCosts{}, ErrOutOfGas
} }
} }
if gas > contract.Gas.RegularGas {
return GasCosts{RegularGas: gas}, nil
}
// if empty and transfers value // if empty and transfers value
if evm.StateDB.Empty(address) && evm.StateDB.GetBalance(contract.Address()).Sign() != 0 { if evm.StateDB.Empty(address) && evm.StateDB.GetBalance(contract.Address()).Sign() != 0 {
gas += params.CreateBySelfdestructGas 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 // part of the dynamic gas. This will ensure it is correctly reported to
// tracers. // tracers.
contract.Gas.RegularGas += eip2929Cost + eip7702Cost 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, // Aggregate the gas costs from all components, including EIP-2929, EIP-7702,
// the CALL opcode itself, and the cost incurred by nested calls. // the CALL opcode itself, and the cost incurred by nested calls.
@ -394,15 +402,12 @@ func makeCallVariantGasCallEIP8037(intrinsicFunc intrinsicGasFunc, stateGasFunc
if err != nil { if err != nil {
return GasCosts{}, err 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). // Charge intrinsic cost directly (regular gas). This must happen
stateGas, err := stateGasFunc(evm, contract, stack, mem, memorySize) // BEFORE state gas to prevent reservoir inflation, and also serves
if err != nil { // as the OOG guard before stateful operations.
return GasCosts{}, err if !contract.UseGas(GasCosts{RegularGas: intrinsicCost}, evm.Config.Tracer, tracing.GasChangeCallOpCode) {
return GasCosts{}, ErrOutOfGas
} }
// EIP-7702 delegation check. // EIP-7702 delegation check.
@ -418,8 +423,11 @@ func makeCallVariantGasCallEIP8037(intrinsicFunc intrinsicGasFunc, stateGasFunc
} }
} }
// Charge state gas directly before callGas computation. State gas that // Compute and charge state gas (new account creation) AFTER regular gas.
// spills to regular gas must reduce the gas available for callGasTemp. stateGas, err := stateGasFunc(evm, contract, stack, mem, memorySize)
if err != nil {
return GasCosts{}, err
}
if stateGas.StateGas > 0 { if stateGas.StateGas > 0 {
stateGasCost := GasCosts{StateGas: stateGas.StateGas} stateGasCost := GasCosts{StateGas: stateGas.StateGas}
if contract.Gas.Underflow(stateGasCost) { 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). // 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 { if err != nil {
return GasCosts{}, err return GasCosts{}, err
} }
// Temporarily add gas charges back for tracer reporting. // Temporarily undo direct regular charges for tracer reporting.
contract.Gas.RegularGas += eip2929Cost + eip7702Cost // The interpreter will charge the returned totalCost.
// Undo GasUsed increments from direct UseGas charges. contract.Gas.RegularGas += eip2929Cost + eip7702Cost + intrinsicCost
contract.GasUsed.RegularGasUsed -= eip2929Cost contract.GasUsed.RegularGasUsed -= eip2929Cost + eip7702Cost + intrinsicCost
contract.GasUsed.RegularGasUsed -= eip7702Cost
// Aggregate total cost. // Aggregate total cost.
var ( var (