Claude fixes for Glam-6

- Misc 8037 fixes
This commit is contained in:
Marius van der Wijden 2026-06-24 17:30:57 +02:00
parent dd073df1cd
commit 0b699f5791
4 changed files with 97 additions and 31 deletions

View file

@ -745,6 +745,13 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
// Execute the transaction's creation.
ret, _, result, vmerr = st.evm.Create(msg.From, msg.Data, st.gasRemaining.ForwardAll(), value)
st.gasRemaining.Absorb(result)
// EIP-8037: a successful creation to a pre-existing (alive) leaf creates
// no new account, so refund the intrinsic NEW_ACCOUNT state gas to the
// reservoir (fork.py: refunded when created_target_alive). The failure
// case is handled by the vmerr branch below.
if rules.IsAmsterdam && vmerr == nil && st.evm.CreateTargetWasAlive() {
st.gasRemaining.RefundState(params.AccountCreationSize * st.evm.Context.CostPerStateByte)
}
} else {
// Increment the nonce for the next transaction.
st.state.SetNonce(msg.From, st.state.GetNonce(msg.From)+1, tracing.NonceChangeEoACall)
@ -930,13 +937,13 @@ func (st *stateTransition) settleGas(rules params.Rules, floorDataGas uint64) (g
}
if rules.IsAmsterdam {
// EIP-7623/7976: the calldata floor applies to the block-level regular
// gas dimension as well, mirroring its effect on the receipt gas. The
// spec accumulates max(tx_regular_gas, calldata_floor) into
// block_gas_used, so the block must never count fewer regular units
// than the floor the sender was charged.
blockRegularGas := max(txRegularGas, floorDataGas)
if err = st.gp.ChargeGasAmsterdam(blockRegularGas, txStateGas, gasUsed); err != nil {
// EIP-7778: the block-level gas accounting uses the gross regular gas
// (tx_gas_used_before_refund - tx_state_gas) and ignores both the
// EIP-3529 refund and the EIP-7623/7976 calldata floor. The floor and
// refund only affect the receipt scalar (tx_gas_used) and the sender's
// ETH refund, not what the block charges (fork.py: block_gas_used +=
// tx_regular_gas).
if err = st.gp.ChargeGasAmsterdam(txRegularGas, txStateGas, gasUsed); err != nil {
return 0, 0, err
}
} else {
@ -998,7 +1005,7 @@ func (st *stateTransition) validateAuthorization(auth *types.SetCodeAuthorizatio
// - the delegation-indicator portion (AuthorizationCreationSize × CPSB) is
// refunded when this auth writes no new indicator bytes (the authority is
// already delegated, or the auth clears the delegation).
func (st *stateTransition) applyAuthorization(rules params.Rules, auth *types.SetCodeAuthorization) error {
func (st *stateTransition) applyAuthorization(rules params.Rules, auth *types.SetCodeAuthorization, preDelegated map[common.Address]bool) error {
authority, err := st.validateAuthorization(auth)
if err != nil {
if rules.IsAmsterdam {
@ -1018,24 +1025,39 @@ func (st *stateTransition) applyAuthorization(rules params.Rules, auth *types.Se
st.state.AddRefund(params.CallNewAccountGas - params.TxAuthTupleGas)
}
} else {
// EIP-8037 (spec apply_authorization): refund the per-auth intrinsic
// state charge for state that does not actually get newly created.
// EIP-8037/8038 (spec set_delegation): refund the per-auth intrinsic
// state charge for state that is not actually newly created.
//
// - NEW_ACCOUNT is refunded when the authority account already exists
// (account_exists), since no new account is created.
// delegated_now reflects prior authorizations applied this tx; the
// pre-transaction delegation is captured on first touch of the
// authority.
delegatedNow := curDelegated
delegatedBeforeTx, seen := preDelegated[authority]
if !seen {
delegatedBeforeTx = curDelegated
preDelegated[authority] = delegatedBeforeTx
}
// - NEW_ACCOUNT (+ worst-case ACCOUNT_WRITE regular) is refunded when
// the authority account leaf already exists, since no new account
// is created.
if st.state.Exist(authority) {
st.gasRemaining.RefundState(params.AccountCreationSize * st.evm.Context.CostPerStateByte)
// EIP-8038: the worst-case ACCOUNT_WRITE charged per authorization in
// the intrinsic cost is refunded to the regular refund counter when
// the authority account already exists (no new account is created).
st.state.AddRefund(params.AccountWriteAmsterdam)
}
// - AUTH_BASE is refunded when no new delegation-indicator bytes are
// written: either the authority already carries code/delegation
// (code_hash != EMPTY, i.e. curDelegated) or this auth clears the
// delegation (auth.address == 0). Exactly one refund per auth.
if curDelegated || auth.Address == (common.Address{}) {
st.gasRemaining.RefundState(params.AuthorizationCreationSize * st.evm.Context.CostPerStateByte)
// - AUTH_BASE refill, mirroring set_delegation:
authBase := params.AuthorizationCreationSize * st.evm.Context.CostPerStateByte
if auth.Address == (common.Address{}) {
// Clearing: refund AUTH_BASE; refund a second time when removing a
// delegation that was created earlier in this same transaction
// (delegated now but not before the tx).
st.gasRemaining.RefundState(authBase)
if delegatedNow && !delegatedBeforeTx {
st.gasRemaining.RefundState(authBase)
}
} else if delegatedNow || delegatedBeforeTx {
// Setting: refund AUTH_BASE when no new indicator bytes are written
// (the authority already carries a delegation now or pre-tx).
st.gasRemaining.RefundState(authBase)
}
}
@ -1058,8 +1080,14 @@ func (st *stateTransition) applyAuthorization(rules params.Rules, auth *types.Se
// applyAuthorizations applies an EIP-7702 code delegation to the state.
func (st *stateTransition) applyAuthorizations(rules params.Rules, auths []types.SetCodeAuthorization) {
// preDelegated records each authority's delegation state in the
// pre-transaction state, captured on the first authorization that touches
// it. EIP-8037 distinguishes a delegation that existed before the
// transaction (delegated_before_tx) from one created earlier in the same
// transaction (delegated_now) when refilling AUTH_BASE.
preDelegated := make(map[common.Address]bool)
for _, auth := range auths {
st.applyAuthorization(rules, &auth)
st.applyAuthorization(rules, &auth, preDelegated)
}
}

View file

@ -127,6 +127,13 @@ type EVM struct {
// order when the sub-call fails (spec generic_call credit_state_gas_refund).
callNewAccountChargedTemp bool
// createTargetAliveTemp records, for the most recent successful create()
// frame, whether the target account leaf was already alive (non-empty)
// before the create. opCreate/opCreate2 read it to refund the EIP-8037
// NEW_ACCOUNT state gas on a successful create to a pre-existing leaf
// (spec create_message: refund when target_alive).
createTargetAliveTemp bool
// precompiles holds the precompiled contracts for the current epoch
precompiles map[common.Address]PrecompiledContract
@ -491,6 +498,13 @@ func (evm *EVM) StaticCall(caller common.Address, addr common.Address, input []b
return ret, exitGas, err
}
// CreateTargetWasAlive reports whether the most recent successful create()
// frame targeted an already-alive (non-empty) account leaf. Used by the
// transaction-level creation path to apply the EIP-8037 NEW_ACCOUNT refund.
func (evm *EVM) CreateTargetWasAlive() bool {
return evm.createTargetAliveTemp
}
// create creates a new contract using code as deployment code.
func (evm *EVM) create(caller common.Address, code []byte, gas GasBudget, value *uint256.Int, address common.Address, typ OpCode) (ret []byte, createAddress common.Address, result GasBudget, err error) {
// Depth check execution. Fail if we're trying to execute above the
@ -552,6 +566,15 @@ func (evm *EVM) create(caller common.Address, code []byte, gas GasBudget, value
// address collision while regular gas is burnt.
return nil, common.Address{}, halt, ErrContractAddressCollision
}
// EIP-8037: record whether the target leaf was already alive (non-empty)
// before the create. The collision check above already accessed the
// account, so this read adds nothing new to the block access list. A
// successful create to an already-alive leaf creates no new account, so
// opCreate/opCreate2 refunds the NEW_ACCOUNT charge. Captured here (rather
// than eagerly in the opcode) so early-failure paths that never reach the
// target — depth, insufficient balance — do not record a spurious access.
targetAlive := !evm.StateDB.Empty(address)
// 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
// account with non-zero balance.
@ -613,7 +636,10 @@ func (evm *EVM) create(caller common.Address, code []byte, gas GasBudget, value
return ret, address, exit, err
}
// Either success, or pre-Homestead ErrCodeStoreOutOfGas (gas preserved).
// Both packaged as a success-form GasBudget.
// Both packaged as a success-form GasBudget. Record target aliveness for
// the EIP-8037 NEW_ACCOUNT refund (set here, after any nested creates ran,
// so it reflects this frame's target rather than a deeper one).
evm.createTargetAliveTemp = targetAlive
return ret, address, contract.Gas.ExitSuccess(), err
}

View file

@ -293,4 +293,9 @@ func (g *GasBudget) Absorb(child GasBudget) {
g.UsedRegularGas += child.UsedRegularGas
g.StateGas = child.StateGas
g.UsedStateGas += child.UsedStateGas
// A successful child propagates its spilled state gas to the parent so a
// later parent halt burns it (spec incorporate_child_on_success). On revert
// or halt the child's leftover SpilledStateGas is already zero (the exit
// form refilled it), so this is a no-op there.
g.SpilledStateGas += child.SpilledStateGas
}

View file

@ -663,6 +663,9 @@ func opCreate(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
child := scope.Contract.forwardGas(forward, evm.Config.Tracer, tracing.GasChangeCallContractCreation)
res, addr, result, suberr := evm.Create(scope.Contract.Address(), input, child, &value)
// EIP-8037: a successful create to an already-alive target leaf creates no
// new account; create() records that in createTargetAliveTemp.
targetWasAlive := evm.chainRules.IsAmsterdam && suberr == nil && evm.createTargetAliveTemp
// 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
@ -679,10 +682,10 @@ func opCreate(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
// Refund the leftover gas back to current frame
scope.Contract.refundGas(result, evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded)
// EIP-8037: no account was created on any failure path, so refund the
// account-creation state gas charged before the opcode ran (gasCreateEip8037)
// in LIFO order (regular gas up to the spilled amount, then the reservoir).
if evm.chainRules.IsAmsterdam && suberr != nil {
// EIP-8037: refund the account-creation state gas charged before the opcode
// ran (gasCreateEip8037) in LIFO order when no new account leaf is created:
// any failure path, or a success where the target leaf already existed.
if evm.chainRules.IsAmsterdam && (suberr != nil || targetWasAlive) {
scope.Contract.Gas.CreditStateRefund(params.AccountCreationSize * evm.Context.CostPerStateByte)
}
@ -707,8 +710,12 @@ func opCreate2(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
// reuse size int for stackvalue
stackvalue := size
child := scope.Contract.forwardGas(forward, evm.Config.Tracer, tracing.GasChangeCallContractCreation2)
res, addr, result, suberr := evm.Create2(scope.Contract.Address(), input, child, &endowment, &salt)
// EIP-8037: a successful create to an already-alive target leaf creates no
// new account; create() records that in createTargetAliveTemp.
targetWasAlive := evm.chainRules.IsAmsterdam && suberr == nil && evm.createTargetAliveTemp
// Push item on the stack based on the returned error.
if suberr != nil {
stackvalue.Clear()
@ -720,10 +727,10 @@ func opCreate2(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
// Refund the leftover gas back to current frame
scope.Contract.refundGas(result, evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded)
// EIP-8037: no account was created on any failure path, so refund the
// account-creation state gas charged before the opcode ran (gasCreate2Eip8037)
// in LIFO order (regular gas up to the spilled amount, then the reservoir).
if evm.chainRules.IsAmsterdam && suberr != nil {
// EIP-8037: refund the account-creation state gas charged before the opcode
// ran (gasCreate2Eip8037) in LIFO order when no new account leaf is created:
// any failure path, or a success where the target leaf already existed.
if evm.chainRules.IsAmsterdam && (suberr != nil || targetWasAlive) {
scope.Contract.Gas.CreditStateRefund(params.AccountCreationSize * evm.Context.CostPerStateByte)
}