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
}
// 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)
}

View file

@ -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 {

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.
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 {

View file

@ -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)

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)
}
// 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)

View file

@ -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 (