core: misc fixes (claude)

This commit is contained in:
MariusVanDerWijden 2026-04-29 01:50:27 +02:00
parent 95574903c0
commit 87a4f9c674
9 changed files with 287 additions and 65 deletions

View file

@ -19,6 +19,7 @@ package state
import (
"fmt"
"maps"
"os"
"slices"
"sort"
@ -251,7 +252,7 @@ func (j *journal) stateChangedBytes(revid int, stateObjects map[common.Address]*
var subcallBytes int64
visit := func(e journalEntry) {
switch e := e.(type) {
case createContractChange:
case createObjectChange:
created[e.account] = true
case codeChange:
codeChanged[e.account] = true
@ -262,19 +263,27 @@ func (j *journal) stateChangedBytes(revid int, stateObjects map[common.Address]*
}
}
}
pos := rev.journalIndex
for _, child := range rev.closedChildren {
for ; pos < child.start; pos++ {
if excludeSubcalls {
// Walk only this frame's own entries, skipping closed child ranges.
pos := rev.journalIndex
for _, child := range rev.closedChildren {
for ; pos < child.start; pos++ {
visit(j.entries[pos])
}
pos = child.end
}
for ; pos < len(j.entries); pos++ {
visit(j.entries[pos])
}
if !excludeSubcalls {
// Add the cached cost for this subcall.
subcallBytes += j.stateBytesCharged[child.start]
} else {
// Walk all entries (including subcall ranges) to compute the full
// diff for this frame. The caller is responsible for subtracting
// `already_paid` (= state_gas_used so far) before charging the
// difference, matching the spec's `this_call_cost = growth_cost -
// already_paid` formula.
for pos := rev.journalIndex; pos < len(j.entries); pos++ {
visit(j.entries[pos])
}
pos = child.end
}
for ; pos < len(j.entries); pos++ {
visit(j.entries[pos])
}
var totalBytes int64
@ -319,6 +328,12 @@ func (j *journal) stateChangedBytes(revid int, stateObjects map[common.Address]*
// Cache so the parent can look up this frame's total cost.
j.stateBytesCharged[rev.journalIndex] = totalBytes
if os.Getenv("DEBUG_8037") != "" {
fmt.Fprintf(os.Stderr, " scb(rev=%d,idx=%d,!s=%v): cre=%d sl=%d cc=%d sub=%d tot=%d cls=%v\n",
revid, rev.journalIndex, excludeSubcalls,
len(created), len(slots), len(codeChanged),
subcallBytes, totalBytes, rev.closedChildren)
}
return totalBytes
}

View file

@ -21,6 +21,7 @@ import (
"fmt"
"math"
"math/big"
"os"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/tracing"
@ -557,6 +558,13 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
if !sufficient {
return nil, fmt.Errorf("%w: have %d, want %d", ErrIntrinsicGas, st.gasRemaining.RegularGas, cost.RegularGas)
}
if rules.IsAmsterdam {
// EIP-8037: intrinsic state gas is deducted from the reservoir but
// does NOT count toward StateGasUsed (it is added back later via
// tx_state_gas = intrinsic_state + state_gas_used). Reset only the
// state-gas tracker; regular intrinsic stays accounted.
st.gasRemaining.StateGasUsed -= cost.StateGas
}
if t := st.evm.Config.Tracer; t != nil && t.OnGasChange != nil {
if rules.IsAmsterdam {
t.OnGasChange(msg.GasLimit, st.gasRemaining.RegularGas+st.gasRemaining.StateGas, tracing.GasChangeTxIntrinsicGas)
@ -599,23 +607,31 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
vmerr error // vm errors do not effect consensus and are therefore not assigned to err
)
// Take a snapshot for gas calculation
outerSnapshot := st.state.Snapshot()
var execGasUsed vm.GasUsed
if contractCreation {
ret, _, st.gasRemaining, vmerr = st.evm.Create(msg.From, msg.Data, st.gasRemaining, value)
} else {
if !contractCreation {
// Increment the nonce for the next transaction.
st.state.SetNonce(msg.From, st.state.GetNonce(msg.From)+1, tracing.NonceChangeEoACall)
// Apply EIP-7702 authorizations.
// Apply EIP-7702 authorizations BEFORE the outer state-gas snapshot
// so that the authorization code-write does not appear in the diff
// (it's already paid for by the per-auth intrinsic state gas).
if msg.SetCodeAuthorizations != nil {
for _, auth := range msg.SetCodeAuthorizations {
// Note errors are ignored, we simply skip invalid authorizations here.
st.applyAuthorization(rules, &auth)
}
}
}
// Take a snapshot for gas calculation. For CREATE txs, the account
// creation in evm.Create() will be inside this snapshot and bubble up
// via cached child frames; we subtract the intrinsic-covered portion
// at frame end. For CALL txs, auths have already been applied so their
// code changes are not in the diff.
outerSnapshot := st.state.Snapshot()
if contractCreation {
ret, _, st.gasRemaining, vmerr = st.evm.Create(msg.From, msg.Data, st.gasRemaining, value)
} else {
// Perform convenience warming of sender's delegation target. Although the
// sender is already warmed in Prepare(..), it's possible a delegation to
@ -636,12 +652,23 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
outerBytes := st.state.StateChangedBytes(outerSnapshot, false)
// Refund state gas for selfdestructed accounts.
outerBytes -= st.state.SelfDestructRefundBytes()
st.gasRemaining.Charge(vm.GasCosts{StateGas: outerBytes * int64(st.evm.Context.CostPerStateByte)})
} else {
if execGasUsed.StateGas > 0 {
st.gasRemaining.StateGas += uint64(execGasUsed.StateGas)
// For contract-creation txs the intrinsic already paid for the
// account creation; subtract it so we don't double-charge.
if contractCreation {
outerBytes -= int64(params.AccountCreationSize)
}
execGasUsed.StateGas = 0
// EIP-8037 spec: this_call_cost = growth_cost - already_paid.
alreadyPaid := st.gasRemaining.StateGasUsed
thisCallCost := outerBytes*int64(st.evm.Context.CostPerStateByte) - alreadyPaid
st.gasRemaining.Charge(vm.GasCosts{StateGas: thisCallCost})
} else {
// On top-level error, restore state-gas reservoir and reset
// the state-gas-used counter; state changes are reverted, no
// state was grown (matches execution-specs fork.py:1055).
if st.gasRemaining.StateGasUsed > 0 {
st.gasRemaining.StateGas += uint64(st.gasRemaining.StateGasUsed)
}
st.gasRemaining.StateGasUsed = 0
}
}
@ -672,12 +699,20 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
returned := st.returnGas()
if rules.IsAmsterdam {
// EIP-8037: 2D gas accounting for Amsterdam.
// tx_regular = initialBudget.RegularGas - gasRemaining.RegularGas
// tx_state = initialBudget.StateGas - gasRemaining.StateGas
txRegular := st.initialBudget.RegularGas - st.gasRemaining.RegularGas
// EIP-8037: tx_regular = intrinsic_regular + execution_regular_gas_used
// (RegularGasUsed already includes intrinsic since it stays counted).
// tx_state = intrinsic_state + execution_state_gas_used (StateGasUsed
// excludes intrinsic — it was undone after the intrinsic Charge).
txRegular := st.gasRemaining.RegularGasUsed
txRegular = max(txRegular, floorDataGas)
txState := st.initialBudget.StateGas - st.gasRemaining.StateGas
txState := uint64(int64(cost.StateGas) + st.gasRemaining.StateGasUsed)
if int64(cost.StateGas)+st.gasRemaining.StateGasUsed < 0 {
txState = 0
}
if os.Getenv("DEBUG_8037") != "" {
fmt.Fprintf(os.Stderr, "state_transition: txRegular=%d, txState=%d (cost.StateGas=%d, StateGasUsed=%d), gasUsed=%d\n",
txRegular, txState, cost.StateGas, st.gasRemaining.StateGasUsed, st.gasUsed())
}
if err := st.gp.ReturnGasAmsterdam(txRegular, txState, st.gasUsed()); err != nil {
return nil, err
}

View file

@ -173,8 +173,14 @@ func enable3529(jt *JumpTable) {
// enable8037 enables EIP-8037 SSTORE repricing: the regular-gas portion of
// new slot creation and same-tx 0→X→0 reset is reduced; the state-gas
// portion is charged/refunded at frame-end via the journal.
//
// Also reprices CREATE/CREATE2 base regular gas from GAS_CREATE (32,000) to
// CREATE_BASE_AMSTERDAM (9,000); the 112 × CPSB state-gas portion is charged
// at frame end via the journal walker.
func enable8037(jt *JumpTable) {
jt[SSTORE].dynamicGas = gasSStoreEIP8037
jt[CREATE].constantGas = params.CreateGasAmsterdam
jt[CREATE2].constantGas = params.CreateGasAmsterdam
}
// enable3198 applies EIP-3198 (BASEFEE Opcode)

View file

@ -18,7 +18,9 @@ package vm
import (
"errors"
"fmt"
"math/big"
"os"
"sync/atomic"
"github.com/ethereum/go-ethereum/common"
@ -318,11 +320,31 @@ func (evm *EVM) Call(caller common.Address, addr common.Address, input []byte, g
}
gas.Exhaust()
}
// EIP-8037: on error, return the child's state-gas-used to its
// reservoir (state was rolled back, no state was grown), and reset
// state_gas_used to 0 so it isn't propagated to the parent.
if evm.chainRules.IsAmsterdam && gas.StateGasUsed > 0 {
gas.StateGas += uint64(gas.StateGasUsed)
gas.StateGasUsed = 0
}
} else {
if evm.chainRules.IsAmsterdam {
// Charge callee's state changes to the callee's gas.
// EIP-8037 spec: this_call_cost = growth_cost - already_paid,
// where already_paid is the sum of state_gas_used by successful
// descendants (tracked in gas.StateGasUsed).
if os.Getenv("DEBUG_8037") != "" {
fmt.Fprintf(os.Stderr, "Call to %v depth=%d: closing snapshot2=%d (gas before charge: %v, StateGasUsed=%d)\n",
addr, evm.depth, snapshot2, gas, gas.StateGasUsed)
}
bytesCharged := evm.StateDB.StateChangedBytes(snapshot2, false)
stateGasCost := GasCosts{StateGas: bytesCharged * int64(evm.Context.CostPerStateByte)}
alreadyPaid := gas.StateGasUsed
thisCallCost := bytesCharged*int64(evm.Context.CostPerStateByte) - alreadyPaid
stateGasCost := GasCosts{StateGas: thisCallCost}
if os.Getenv("DEBUG_8037") != "" {
fmt.Fprintf(os.Stderr, " bytesCharged=%d, alreadyPaid=%d, thisCallCost=%d, canAfford=%v\n",
bytesCharged, alreadyPaid, thisCallCost, gas.CanAfford(stateGasCost))
}
if !gas.CanAfford(stateGasCost) {
evm.StateDB.RevertToSnapshot(snapshot1)
gas.Exhaust()
@ -366,7 +388,10 @@ func (evm *EVM) CallCode(caller common.Address, addr common.Address, input []byt
if !evm.Context.CanTransfer(evm.StateDB, caller, value) {
return nil, gas, ErrInsufficientBalance
}
var snapshot = evm.StateDB.Snapshot()
// EIP-8037: two-snapshot pattern (matches Call/DelegateCall) to avoid
// double counting subcall state-gas in the parent's frame computation.
snapshot1 := evm.StateDB.Snapshot()
snapshot2 := evm.StateDB.Snapshot()
// It is allowed to call precompiles, even via delegatecall
if p, isPrecompile := evm.precompile(addr); isPrecompile {
@ -380,24 +405,45 @@ func (evm *EVM) CallCode(caller common.Address, addr common.Address, input []byt
gas = contract.Gas
}
if err != nil {
evm.StateDB.RevertToSnapshot(snapshot)
evm.StateDB.RevertToSnapshot(snapshot1)
if err != ErrExecutionReverted {
if evm.Config.Tracer != nil && evm.Config.Tracer.OnGasChange != nil {
evm.Config.Tracer.OnGasChange(gas.RegularGas, 0, tracing.GasChangeCallFailedExecution)
}
gas.Exhaust()
}
// EIP-8037: on error, return state-gas-used to reservoir.
if evm.chainRules.IsAmsterdam && gas.StateGasUsed > 0 {
gas.StateGas += uint64(gas.StateGasUsed)
gas.StateGasUsed = 0
}
} else {
if evm.chainRules.IsAmsterdam {
bytesCharged := evm.StateDB.StateChangedBytes(snapshot, false)
stateGasCost := GasCosts{StateGas: bytesCharged * int64(evm.Context.CostPerStateByte)}
if os.Getenv("DEBUG_8037") != "" {
fmt.Fprintf(os.Stderr, "CallCode to %v depth=%d (gas before charge: %v, StateGasUsed=%d)\n",
addr, evm.depth, gas, gas.StateGasUsed)
}
bytesCharged := evm.StateDB.StateChangedBytes(snapshot2, false)
alreadyPaid := gas.StateGasUsed
thisCallCost := bytesCharged*int64(evm.Context.CostPerStateByte) - alreadyPaid
stateGasCost := GasCosts{StateGas: thisCallCost}
if os.Getenv("DEBUG_8037") != "" {
fmt.Fprintf(os.Stderr, " bytesCharged=%d, alreadyPaid=%d, thisCallCost=%d, canAfford=%v\n",
bytesCharged, alreadyPaid, thisCallCost, gas.CanAfford(stateGasCost))
}
if !gas.CanAfford(stateGasCost) {
gas.Exhaust()
return ret, gas, ErrOutOfGas
}
gas.Charge(stateGasCost)
}
evm.StateDB.CloseSnapshot(snapshot)
evm.StateDB.CloseSnapshot(snapshot2)
if evm.chainRules.IsAmsterdam {
// Cache snapshot1's own bytes (no setup work for callcode),
// excluding the snapshot2 subcall which was already charged.
evm.StateDB.StateChangedBytes(snapshot1, true)
}
evm.StateDB.CloseSnapshot(snapshot1)
}
return ret, gas, err
}
@ -420,7 +466,11 @@ func (evm *EVM) DelegateCall(originCaller common.Address, caller common.Address,
if evm.depth > int(params.CallCreateDepth) {
return nil, gas, ErrDepth
}
var snapshot = evm.StateDB.Snapshot()
// EIP-8037: two-snapshot pattern (matches Call). snapshot1 covers
// any caller-frame setup (none for delegatecall, kept for symmetry);
// snapshot2 covers the callee execution.
snapshot1 := evm.StateDB.Snapshot()
snapshot2 := evm.StateDB.Snapshot()
// It is allowed to call precompiles, even via delegatecall
if p, isPrecompile := evm.precompile(addr); isPrecompile {
@ -435,24 +485,45 @@ func (evm *EVM) DelegateCall(originCaller common.Address, caller common.Address,
gas = contract.Gas
}
if err != nil {
evm.StateDB.RevertToSnapshot(snapshot)
evm.StateDB.RevertToSnapshot(snapshot1)
if err != ErrExecutionReverted {
if evm.Config.Tracer != nil && evm.Config.Tracer.OnGasChange != nil {
evm.Config.Tracer.OnGasChange(gas.RegularGas, 0, tracing.GasChangeCallFailedExecution)
}
gas.Exhaust()
}
// EIP-8037: on error, return state-gas-used to reservoir.
if evm.chainRules.IsAmsterdam && gas.StateGasUsed > 0 {
gas.StateGas += uint64(gas.StateGasUsed)
gas.StateGasUsed = 0
}
} else {
if evm.chainRules.IsAmsterdam {
bytesCharged := evm.StateDB.StateChangedBytes(snapshot, false)
stateGasCost := GasCosts{StateGas: bytesCharged * int64(evm.Context.CostPerStateByte)}
if os.Getenv("DEBUG_8037") != "" {
fmt.Fprintf(os.Stderr, "DelegateCall to %v depth=%d (gas before charge: %v, StateGasUsed=%d)\n",
addr, evm.depth, gas, gas.StateGasUsed)
}
bytesCharged := evm.StateDB.StateChangedBytes(snapshot2, false)
alreadyPaid := gas.StateGasUsed
thisCallCost := bytesCharged*int64(evm.Context.CostPerStateByte) - alreadyPaid
stateGasCost := GasCosts{StateGas: thisCallCost}
if os.Getenv("DEBUG_8037") != "" {
fmt.Fprintf(os.Stderr, " bytesCharged=%d, alreadyPaid=%d, thisCallCost=%d, canAfford=%v\n",
bytesCharged, alreadyPaid, thisCallCost, gas.CanAfford(stateGasCost))
}
if !gas.CanAfford(stateGasCost) {
gas.Exhaust()
return ret, gas, ErrOutOfGas
}
gas.Charge(stateGasCost)
}
evm.StateDB.CloseSnapshot(snapshot)
evm.StateDB.CloseSnapshot(snapshot2)
if evm.chainRules.IsAmsterdam {
// Cache snapshot1's own bytes (= 0 for delegatecall, no setup),
// excluding the snapshot2 subcall which was already charged.
evm.StateDB.StateChangedBytes(snapshot1, true)
}
evm.StateDB.CloseSnapshot(snapshot1)
}
return ret, gas, err
@ -509,6 +580,10 @@ func (evm *EVM) StaticCall(caller common.Address, addr common.Address, input []b
}
gas.Exhaust()
}
if evm.chainRules.IsAmsterdam && gas.StateGasUsed > 0 {
gas.StateGas += uint64(gas.StateGasUsed)
gas.StateGasUsed = 0
}
} else {
evm.StateDB.CloseSnapshot(snapshot)
}
@ -619,11 +694,21 @@ func (evm *EVM) create(caller common.Address, code []byte, gas GasBudget, value
if err != ErrExecutionReverted {
contract.UseGas(GasCosts{RegularGas: contract.Gas.RegularGas}, evm.Config.Tracer, tracing.GasChangeCallFailedExecution)
}
// EIP-8037: on error, return the child contract's state-gas-used
// to its reservoir (state was rolled back). Don't propagate
// state_gas_used to the parent.
if evm.chainRules.IsAmsterdam && contract.Gas.StateGasUsed > 0 {
contract.Gas.StateGas += uint64(contract.Gas.StateGasUsed)
contract.Gas.StateGasUsed = 0
}
} else {
if evm.chainRules.IsAmsterdam {
// Charge initcode's state changes to the created contract's gas.
// EIP-8037 spec: this_call_cost = growth_cost - already_paid.
bytesCharged := evm.StateDB.StateChangedBytes(snapshot2, false)
stateGasCost := GasCosts{StateGas: bytesCharged * int64(evm.Context.CostPerStateByte)}
alreadyPaid := contract.Gas.StateGasUsed
thisCallCost := bytesCharged*int64(evm.Context.CostPerStateByte) - alreadyPaid
stateGasCost := GasCosts{StateGas: thisCallCost}
if !contract.Gas.CanAfford(stateGasCost) {
evm.StateDB.RevertToSnapshot(snapshot1)
contract.Gas.Exhaust()

View file

@ -430,16 +430,21 @@ func gasCallIntrinsic(evm *EVM, contract *Contract, stack *Stack, mem *Memory, m
return GasCosts{}, ErrOutOfGas
}
// Stateful check
var stateGas uint64
if evm.chainRules.IsEIP158 {
if transfersValue && evm.StateDB.Empty(address) {
// EIP-8037: under Amsterdam the regular-gas portion of GAS_NEW_ACCOUNT
// is removed (covered by CALL_VALUE 9000); the state-gas portion of
// 112 × CPSB is charged at frame end via the journal walker.
if !evm.chainRules.IsAmsterdam {
var stateGas uint64
if evm.chainRules.IsEIP158 {
if transfersValue && evm.StateDB.Empty(address) {
stateGas += params.CallNewAccountGas
}
} else if !evm.StateDB.Exist(address) {
stateGas += params.CallNewAccountGas
}
} else if !evm.StateDB.Exist(address) {
stateGas += params.CallNewAccountGas
}
if gas, overflow = math.SafeAdd(gas, stateGas); overflow {
return GasCosts{}, ErrGasUintOverflow
if gas, overflow = math.SafeAdd(gas, stateGas); overflow {
return GasCosts{}, ErrGasUintOverflow
}
}
return GasCosts{RegularGas: gas}, nil
}
@ -489,13 +494,18 @@ func gasSelfdestruct(evm *EVM, contract *Contract, stack *Stack, mem *Memory, me
gas = params.SelfdestructGasEIP150
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 {
// EIP-8037: CreateBySelfdestructGas (25000 regular) is removed under
// Amsterdam — account creation is now charged via state gas at frame
// end via the journal walker.
if !evm.chainRules.IsAmsterdam {
if evm.chainRules.IsEIP158 {
// if empty and transfers value
if evm.StateDB.Empty(address) && evm.StateDB.GetBalance(contract.Address()).Sign() != 0 {
gas += params.CreateBySelfdestructGas
}
} else if !evm.StateDB.Exist(address) {
gas += params.CreateBySelfdestructGas
}
} else if !evm.StateDB.Exist(address) {
gas += params.CreateBySelfdestructGas
}
}

View file

@ -59,6 +59,13 @@ type GasBudget struct {
// Tracks the gas refunds in this call frame. Needed so we can
// revert the refunds if the call frame reverts.
StateGasRefund uint64
// EIP-8037 per-dimension usage trackers. RegularGasUsed is incremented
// only for regular-gas charges; StateGasUsed is incremented for the
// full state-gas portion of a charge regardless of whether it was
// satisfied from the reservoir or spilled into RegularGas.
RegularGasUsed uint64
StateGasUsed int64
}
// NewGasBudgetReg creates a GasBudget with the given initial regular gas allowance.
@ -76,13 +83,22 @@ func (g GasBudget) Used(initial GasBudget) uint64 {
return (initial.RegularGas + initial.StateGas) - (g.RegularGas + g.StateGas)
}
// Exhaust burns the remaining regular gas on exceptional halt.
// Exhaust burns the remaining regular gas on exceptional halt. The full
// remaining regular gas is moved to RegularGasUsed so the per-tx tally
// reflects the burn (per spec process_message exceptional-halt branch).
func (g *GasBudget) Exhaust() {
g.RegularGasUsed += g.RegularGas
g.RegularGas = 0
}
func (g *GasBudget) Copy() GasBudget {
return GasBudget{RegularGas: g.RegularGas, StateGas: g.StateGas}
return GasBudget{
RegularGas: g.RegularGas,
StateGas: g.StateGas,
StateGasRefund: g.StateGasRefund,
RegularGasUsed: g.RegularGasUsed,
StateGasUsed: g.StateGasUsed,
}
}
// String returns a visual representation of the gas budget vector.
@ -117,6 +133,8 @@ func (g *GasBudget) Charge(cost GasCosts) (uint64, bool) {
return prior, false
}
g.RegularGas -= cost.RegularGas
g.RegularGasUsed += cost.RegularGas
g.StateGasUsed += cost.StateGas
if cost.StateGas < 0 {
g.StateGas -= uint64(cost.StateGas)
return prior, true
@ -132,10 +150,14 @@ func (g *GasBudget) Charge(cost GasCosts) (uint64, bool) {
}
// Refund adds the given gas budget back. It returns the pre-refund regular gas
// value and whether the budget was actually changed.
// value and whether the budget was actually changed. Used trackers from the
// other budget are accumulated so child-frame state-gas usage propagates to
// the caller.
func (g *GasBudget) Refund(other GasBudget) (uint64, bool) {
prior := g.RegularGas
g.RegularGas += other.RegularGas
g.StateGas += other.StateGas
g.RegularGasUsed += other.RegularGasUsed
g.StateGasUsed += other.StateGasUsed
return prior, other.RegularGas != 0 || other.StateGas != 0
}

View file

@ -669,7 +669,12 @@ func opCreate(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
scope.Contract.UseGas(GasCosts{RegularGas: gas}, evm.Config.Tracer, tracing.GasChangeCallContractCreation)
res, addr, returnGas, suberr := evm.Create(scope.Contract.Address(), input, NewGasBudgetReg(gas), &value)
// EIP-8037: gas given to child via Create will be tracked there and
// propagated via RefundGas. Uncount here to avoid double-counting.
scope.Contract.Gas.RegularGasUsed -= gas
childStateGas := scope.Contract.Gas.StateGas
scope.Contract.Gas.StateGas = 0
res, addr, returnGas, suberr := evm.Create(scope.Contract.Address(), input, NewGasBudget(gas, childStateGas), &value)
// Push item on the stack based on the returned error. If the ruleset is
// homestead we must check for CodeStoreOutOfGasError (homestead only
// rule) and treat as an error, if the ruleset is frontier we must
@ -708,9 +713,14 @@ func opCreate2(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
// Apply EIP150
gas -= gas / 64
scope.Contract.UseGas(GasCosts{RegularGas: gas}, evm.Config.Tracer, tracing.GasChangeCallContractCreation2)
// EIP-8037: gas given to child via Create2 will be tracked there and
// propagated via RefundGas. Uncount here to avoid double-counting.
scope.Contract.Gas.RegularGasUsed -= gas
childStateGas := scope.Contract.Gas.StateGas
scope.Contract.Gas.StateGas = 0
// reuse size int for stackvalue
stackvalue := size
res, addr, returnGas, suberr := evm.Create2(scope.Contract.Address(), input, NewGasBudgetReg(gas),
res, addr, returnGas, suberr := evm.Create2(scope.Contract.Address(), input, NewGasBudget(gas, childStateGas),
&endowment, &salt)
// Push item on the stack based on the returned error.
if suberr != nil {
@ -744,10 +754,24 @@ func opCall(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
if evm.readOnly && !value.IsZero() {
return nil, ErrWriteProtection
}
// EIP-8037: callGasTemp was already added to RegularGasUsed via the
// dynamicGas mechanism, but the child will track its own usage and
// propagate via RefundGas. Uncount it here to avoid double-counting.
scope.Contract.Gas.RegularGasUsed -= evm.callGasTemp
if !value.IsZero() {
gas += params.CallStipend
// EIP-8037: the stipend is "free" gas given to the child; it is
// not charged to the caller's regular_gas_used. The child will
// nevertheless track stipend usage via charge_gas and propagate
// via RefundGas, so subtract it here so the caller doesn't pay
// for stipend usage (matches spec MessageCallGas semantics).
scope.Contract.Gas.RegularGasUsed -= params.CallStipend
}
ret, returnGas, err := evm.Call(scope.Contract.Address(), toAddr, args, NewGasBudgetReg(gas), &value)
// EIP-8037: pass the parent's full state-gas reservoir to the child;
// reset parent's reservoir to zero. Leftover comes back via RefundGas.
childStateGas := scope.Contract.Gas.StateGas
scope.Contract.Gas.StateGas = 0
ret, returnGas, err := evm.Call(scope.Contract.Address(), toAddr, args, NewGasBudget(gas, childStateGas), &value)
if err != nil {
temp.Clear()
@ -777,11 +801,15 @@ func opCallCode(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
// Get arguments from the memory.
args := scope.Memory.GetPtr(inOffset.Uint64(), inSize.Uint64())
scope.Contract.Gas.RegularGasUsed -= evm.callGasTemp
if !value.IsZero() {
gas += params.CallStipend
scope.Contract.Gas.RegularGasUsed -= params.CallStipend
}
ret, returnGas, err := evm.CallCode(scope.Contract.Address(), toAddr, args, NewGasBudgetReg(gas), &value)
childStateGas := scope.Contract.Gas.StateGas
scope.Contract.Gas.StateGas = 0
ret, returnGas, err := evm.CallCode(scope.Contract.Address(), toAddr, args, NewGasBudget(gas, childStateGas), &value)
if err != nil {
temp.Clear()
} else {
@ -810,7 +838,10 @@ func opDelegateCall(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
// Get arguments from the memory.
args := scope.Memory.GetPtr(inOffset.Uint64(), inSize.Uint64())
ret, returnGas, err := evm.DelegateCall(scope.Contract.Caller(), scope.Contract.Address(), toAddr, args, NewGasBudgetReg(gas), scope.Contract.value)
scope.Contract.Gas.RegularGasUsed -= evm.callGasTemp
childStateGas := scope.Contract.Gas.StateGas
scope.Contract.Gas.StateGas = 0
ret, returnGas, err := evm.DelegateCall(scope.Contract.Caller(), scope.Contract.Address(), toAddr, args, NewGasBudget(gas, childStateGas), scope.Contract.value)
if err != nil {
temp.Clear()
} else {
@ -839,7 +870,10 @@ func opStaticCall(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
// Get arguments from the memory.
args := scope.Memory.GetPtr(inOffset.Uint64(), inSize.Uint64())
ret, returnGas, err := evm.StaticCall(scope.Contract.Address(), toAddr, args, NewGasBudgetReg(gas))
scope.Contract.Gas.RegularGasUsed -= evm.callGasTemp
childStateGas := scope.Contract.Gas.StateGas
scope.Contract.Gas.StateGas = 0
ret, returnGas, err := evm.StaticCall(scope.Contract.Address(), toAddr, args, NewGasBudget(gas, childStateGas))
if err != nil {
temp.Clear()
} else {

View file

@ -196,6 +196,7 @@ func (evm *EVM) Run(contract *Contract, input []byte, readOnly bool) (ret []byte
return nil, ErrOutOfGas
} else {
contract.Gas.RegularGas -= cost
contract.Gas.RegularGasUsed += cost
}
// All ops with a dynamic memory usage also has a dynamic gas cost.
@ -229,6 +230,7 @@ func (evm *EVM) Run(contract *Contract, input []byte, readOnly bool) (ret []byte
return nil, ErrOutOfGas
} else {
contract.Gas.RegularGas -= dynamicCost.RegularGas
contract.Gas.RegularGasUsed += dynamicCost.RegularGas
}
}

View file

@ -186,6 +186,10 @@ func makeCallVariantGasCallEIP2929(oldCalculator gasFunc, addressPosition int) g
// outside of this function, as part of the dynamic gas, and that will make it
// also become correctly reported to tracers.
contract.Gas.RegularGas += coldCost
// Also undo the RegularGasUsed bookkeeping so coldCost isn't
// double-counted (it will be re-added by the dynamicGas
// mechanism via the returned cost).
contract.Gas.RegularGasUsed -= coldCost
gas := gasCost.RegularGas
var overflow bool
@ -318,8 +322,13 @@ func makeSelfdestructGasFn(refundsEnabled bool) gasFunc {
}
}
// if empty and transfers value
if evm.StateDB.Empty(address) && evm.StateDB.GetBalance(contract.Address()).Sign() != 0 {
gas += params.CreateBySelfdestructGas
// EIP-8037: under Amsterdam the regular-gas portion of
// CreateBySelfdestructGas (25,000) is removed; account creation
// is charged via state gas at frame end.
if !evm.chainRules.IsAmsterdam {
if evm.StateDB.Empty(address) && evm.StateDB.GetBalance(contract.Address()).Sign() != 0 {
gas += params.CreateBySelfdestructGas
}
}
if refundsEnabled && !evm.StateDB.HasSelfDestructed(contract.Address()) {
evm.StateDB.AddRefund(params.SelfdestructRefundGas)
@ -411,6 +420,10 @@ func makeCallVariantGasCallEIP7702(intrinsicFunc gasFunc) gasFunc {
// part of the dynamic gas. This will ensure it is correctly reported to
// tracers.
contract.Gas.RegularGas += eip2929Cost + eip7702Cost
// Also undo the RegularGasUsed bookkeeping so eip2929/7702 costs aren't
// double-counted (they will be re-added by the dynamicGas mechanism via
// the returned cost).
contract.Gas.RegularGasUsed -= eip2929Cost + eip7702Cost
// Aggregate the gas costs from all components, including EIP-2929, EIP-7702,
// the CALL opcode itself, and the cost incurred by nested calls.