Claude fixes for Glam-6

- AL key gas
- 7708 remove burn logs
- Refund auths fully
- 8037 halt mechanics
- 8037 spill mechanics
This commit is contained in:
Marius van der Wijden 2026-06-24 16:22:47 +02:00
parent 9c7bd2abe5
commit 3523721bd4
5 changed files with 78 additions and 57 deletions

View file

@ -122,14 +122,22 @@ func IntrinsicGas(data []byte, accessList types.AccessList, authList []types.Set
if accessList != nil { if accessList != nil {
addresses := uint64(len(accessList)) addresses := uint64(len(accessList))
storageKeys := uint64(accessList.StorageKeys()) storageKeys := uint64(accessList.StorageKeys())
if (math.MaxUint64-gas.RegularGas)/params.TxAccessListAddressGas < addresses { // EIP-8038 ties the access-list base costs to the cold-access costs
// (TX_ACCESS_LIST_ADDRESS = COLD_ACCOUNT_ACCESS = 3000,
// TX_ACCESS_LIST_STORAGE_KEY = COLD_STORAGE_ACCESS = 3000) in Amsterdam.
addressGas, storageKeyGas := params.TxAccessListAddressGas, params.TxAccessListStorageKeyGas
if rules.IsAmsterdam {
addressGas = params.ColdAccountAccessAmsterdam
storageKeyGas = params.ColdStorageAccessAmsterdam
}
if (math.MaxUint64-gas.RegularGas)/addressGas < addresses {
return vm.GasCosts{}, ErrGasUintOverflow return vm.GasCosts{}, ErrGasUintOverflow
} }
gas.RegularGas += addresses * params.TxAccessListAddressGas gas.RegularGas += addresses * addressGas
if (math.MaxUint64-gas.RegularGas)/params.TxAccessListStorageKeyGas < storageKeys { if (math.MaxUint64-gas.RegularGas)/storageKeyGas < storageKeys {
return vm.GasCosts{}, ErrGasUintOverflow return vm.GasCosts{}, ErrGasUintOverflow
} }
gas.RegularGas += storageKeys * params.TxAccessListStorageKeyGas gas.RegularGas += storageKeys * storageKeyGas
// EIP-7981: access list data is charged in addition to the base charge. // EIP-7981: access list data is charged in addition to the base charge.
if rules.IsAmsterdam { if rules.IsAmsterdam {
@ -817,12 +825,6 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
} }
} }
// EIP-7708: Emit the ETH-burn logs
if rules.IsAmsterdam {
for _, log := range st.evm.StateDB.LogsForBurnAccounts() {
st.evm.StateDB.AddLog(log)
}
}
return &ExecutionResult{ return &ExecutionResult{
UsedGas: gasUsed, UsedGas: gasUsed,
MaxUsedGas: peakUsed, MaxUsedGas: peakUsed,
@ -999,12 +1001,14 @@ func (st *stateTransition) validateAuthorization(auth *types.SetCodeAuthorizatio
func (st *stateTransition) applyAuthorization(rules params.Rules, auth *types.SetCodeAuthorization) error { func (st *stateTransition) applyAuthorization(rules params.Rules, auth *types.SetCodeAuthorization) error {
authority, err := st.validateAuthorization(auth) authority, err := st.validateAuthorization(auth)
if err != nil { if err != nil {
// EIP-8037 (spec apply_authorization): an invalid authorization is if rules.IsAmsterdam {
// skipped without any state-gas refund. The per-auth intrinsic state // Spec set_delegation: an invalid authorization writes no state, so
// charge ((NEW_ACCOUNT + AUTH_BASE) * CPSB) was levied for every // the full per-auth intrinsic charge is refilled — NEW_ACCOUNT +
// authorization in the list regardless of validity, and only a // AUTH_BASE to the state reservoir and the worst-case ACCOUNT_WRITE
// successfully-applied authorization that avoids creating new state // to the regular refund counter.
// earns a refund below. Invalid auths therefore pay in full. st.gasRemaining.RefundState((params.AccountCreationSize + params.AuthorizationCreationSize) * st.evm.Context.CostPerStateByte)
st.state.AddRefund(params.AccountWriteAmsterdam)
}
return err return err
} }
prevDelegation, curDelegated := types.ParseDelegation(st.state.GetCode(authority)) prevDelegation, curDelegated := types.ParseDelegation(st.state.GetCode(authority))

View file

@ -698,9 +698,10 @@ func gasSStore8037(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memo
cost.StateGas = stateSetGas cost.StateGas = stateSetGas
} }
if current != newValue && original == newValue && original == (common.Hash{}) { if current != newValue && original == newValue && original == (common.Hash{}) {
// Slot set then cleared in the same tx: refund the state gas directly // Slot set then cleared in the same tx: refund the state gas in LIFO
// to the reservoir (not the gas_used/5-capped refund counter). // order (regular gas up to the spilled amount, then the reservoir),
contract.Gas.RefundState(stateSetGas) // not the gas_used/5-capped refund counter.
contract.Gas.CreditStateRefund(stateSetGas)
} }
return cost, nil return cost, nil
} }

View file

@ -65,6 +65,7 @@ type GasBudget struct {
StateGas uint64 // remaining state-gas reservoir (or leftover for caller to absorb) StateGas uint64 // remaining state-gas reservoir (or leftover for caller to absorb)
UsedRegularGas uint64 // gross regular gas consumed in this frame UsedRegularGas uint64 // gross regular gas consumed in this frame
UsedStateGas int64 // signed net state-gas consumed in this frame UsedStateGas int64 // signed net state-gas consumed in this frame
SpilledStateGas uint64 // state gas that spilled from the reservoir into regular gas in this frame
} }
// NewGasBudget initializes a fresh GasBudget for execution / forwarding, // NewGasBudget initializes a fresh GasBudget for execution / forwarding,
@ -117,6 +118,7 @@ func (g *GasBudget) Charge(cost GasCosts) (GasBudget, bool) {
spillover := cost.StateGas - g.StateGas spillover := cost.StateGas - g.StateGas
g.StateGas = 0 g.StateGas = 0
g.RegularGas -= spillover g.RegularGas -= spillover
g.SpilledStateGas += spillover
} else { } else {
g.StateGas -= cost.StateGas g.StateGas -= cost.StateGas
} }
@ -145,9 +147,10 @@ func (g *GasBudget) IsZero() bool {
return g.RegularGas == 0 && g.StateGas == 0 return g.RegularGas == 0 && g.StateGas == 0
} }
// RefundState applies an inline state-gas refund (e.g., SSTORE 0->A->0). // RefundState applies an inline state-gas refund directly to the reservoir
// The reservoir is credited and the signed usage counter is decremented // (e.g., a SetCode authorization on an already-existing leaf, or a top-level
// in lockstep, preserving the per-frame invariant: // transaction refund). The reservoir is credited and the signed usage counter
// is decremented in lockstep, preserving the per-frame invariant:
// //
// StateGas + UsedStateGas == initialStateGas + spillover_so_far // StateGas + UsedStateGas == initialStateGas + spillover_so_far
// //
@ -157,6 +160,24 @@ func (g *GasBudget) RefundState(s uint64) {
g.UsedStateGas -= int64(s) g.UsedStateGas -= int64(s)
} }
// CreditStateRefund applies an inline state-gas refund in LIFO order, mirroring
// the spec's credit_state_gas_refund. State-gas charges draw from the reservoir
// first and from regular gas last, so a refill credits the pool charged last
// first: regular gas up to the amount previously spilled, then the reservoir.
// This restores the exact pools the original charge drew from, so a probe
// sub-call that can only observe the reservoir sees a refund only when the
// reservoir actually had headroom.
//
// Used for SSTORE 0->A->0 restorations and NEW_ACCOUNT refunds on failed
// CALL/CREATE sub-calls (spec storage.py / system.py credit_state_gas_refund).
func (g *GasBudget) CreditStateRefund(s uint64) {
fromRegular := min(s, g.SpilledStateGas)
g.RegularGas += fromRegular
g.SpilledStateGas -= fromRegular
g.StateGas += s - fromRegular
g.UsedStateGas -= int64(s)
}
// Forward drains `regular` regular gas and the entire state reservoir from // Forward drains `regular` regular gas and the entire state reservoir from
// the parent's running budget and returns the initial GasBudget for a child // the parent's running budget and returns the initial GasBudget for a child
// frame. The parent's UsedRegularGas is bumped by the forwarded amount so // frame. The parent's UsedRegularGas is bumped by the forwarded amount so
@ -201,48 +222,44 @@ func (g GasBudget) ExitSuccess() GasBudget {
return g return g
} }
// ExitRevert produces the leftover for a REVERT exit. Per EIP-8037, all state // ExitRevert produces the leftover for a REVERT exit. It mirrors the spec's
// gas charged by the reverted frame is refunded to the caller's reservoir: // refill_frame_state_gas: the frame's state gas is rolled back in LIFO order —
// // the spilled portion is credited back to regular gas and the remainder to the
// leftover.StateGas = StateGas + UsedStateGas // reservoir — so the pools the charges drew from are restored. No gas is burned
// // on a revert.
// UsedStateGas is reset since the frame's state changes are discarded.
func (g GasBudget) ExitRevert() GasBudget { func (g GasBudget) ExitRevert() GasBudget {
reservoir := int64(g.StateGas) + g.UsedStateGas reservoir := int64(g.StateGas) + g.UsedStateGas - int64(g.SpilledStateGas)
if reservoir < 0 { if reservoir < 0 {
// Reservoir should never be negative. By construction it equals // Reservoir should never be negative. By construction it equals the
// the initial state-gas allocation plus any spillover to regular // initial state-gas allocation (spillover is returned to regular gas).
// gas.
reservoir = 0 reservoir = 0
log.Warn("Negative reservoir at revert", "remaining", g.StateGas, "used", g.UsedStateGas) log.Warn("Negative reservoir at revert", "remaining", g.StateGas, "used", g.UsedStateGas, "spilled", g.SpilledStateGas)
} }
return GasBudget{ return GasBudget{
RegularGas: g.RegularGas, RegularGas: g.RegularGas + g.SpilledStateGas,
StateGas: uint64(reservoir), StateGas: uint64(reservoir),
UsedRegularGas: g.UsedRegularGas, UsedRegularGas: g.UsedRegularGas,
UsedStateGas: 0, UsedStateGas: 0,
} }
} }
// ExitHalt produces the leftover for an exceptional halt. // ExitHalt produces the leftover for an exceptional halt. It mirrors the spec's
// // refill_frame_state_gas followed by the gas_left burn: the frame's state gas
// Per the updated EIP-8037, only the regular gas_left is burned (folded into // is rolled back LIFO (spilled portion to regular gas, remainder to the
// UsedRegularGas); the entire state-gas reservoir — including any portion that // reservoir), then ALL remaining regular gas — including the just-refilled
// spilled into the regular pool during execution — is refunded to the caller's // spilled portion — is burned. Only the non-spilled reservoir survives.
// reservoir rather than reclassified as burned regular gas.
func (g GasBudget) ExitHalt() GasBudget { func (g GasBudget) ExitHalt() GasBudget {
reservoir := int64(g.StateGas) + g.UsedStateGas reservoir := int64(g.StateGas) + g.UsedStateGas - int64(g.SpilledStateGas)
if reservoir < 0 { if reservoir < 0 {
// Reservoir should never be negative. By construction it equals // Reservoir should never be negative. By construction it equals the
// the initial state-gas allocation plus any spillover to regular // initial state-gas allocation (spillover is burned with regular gas).
// gas.
reservoir = 0 reservoir = 0
log.Warn("Negative reservoir at halt", "remaining", g.StateGas, "used", g.UsedStateGas) log.Warn("Negative reservoir at halt", "remaining", g.StateGas, "used", g.UsedStateGas, "spilled", g.SpilledStateGas)
} }
return GasBudget{ return GasBudget{
RegularGas: 0, RegularGas: 0,
StateGas: uint64(reservoir), StateGas: uint64(reservoir),
UsedRegularGas: g.UsedRegularGas + g.RegularGas, UsedRegularGas: g.UsedRegularGas + g.RegularGas + g.SpilledStateGas,
UsedStateGas: 0, UsedStateGas: 0,
} }
} }

View file

@ -681,9 +681,9 @@ func opCreate(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
// EIP-8037: no account was created on any failure path, so refund the // EIP-8037: no account was created on any failure path, so refund the
// account-creation state gas charged before the opcode ran (gasCreateEip8037) // account-creation state gas charged before the opcode ran (gasCreateEip8037)
// back to the reservoir. // in LIFO order (regular gas up to the spilled amount, then the reservoir).
if evm.chainRules.IsAmsterdam && suberr != nil { if evm.chainRules.IsAmsterdam && suberr != nil {
scope.Contract.Gas.RefundState(params.AccountCreationSize * evm.Context.CostPerStateByte) scope.Contract.Gas.CreditStateRefund(params.AccountCreationSize * evm.Context.CostPerStateByte)
} }
if suberr == ErrExecutionReverted { if suberr == ErrExecutionReverted {
@ -722,9 +722,9 @@ func opCreate2(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
// EIP-8037: no account was created on any failure path, so refund the // EIP-8037: no account was created on any failure path, so refund the
// account-creation state gas charged before the opcode ran (gasCreate2Eip8037) // account-creation state gas charged before the opcode ran (gasCreate2Eip8037)
// back to the reservoir. // in LIFO order (regular gas up to the spilled amount, then the reservoir).
if evm.chainRules.IsAmsterdam && suberr != nil { if evm.chainRules.IsAmsterdam && suberr != nil {
scope.Contract.Gas.RefundState(params.AccountCreationSize * evm.Context.CostPerStateByte) scope.Contract.Gas.CreditStateRefund(params.AccountCreationSize * evm.Context.CostPerStateByte)
} }
if suberr == ErrExecutionReverted { if suberr == ErrExecutionReverted {
@ -955,12 +955,11 @@ func opSelfdestruct6780(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, erro
evm.StateDB.SubBalance(this, balance, tracing.BalanceDecreaseSelfdestruct) evm.StateDB.SubBalance(this, balance, tracing.BalanceDecreaseSelfdestruct)
evm.StateDB.AddBalance(beneficiary, balance, tracing.BalanceIncreaseSelfdestruct) evm.StateDB.AddBalance(beneficiary, balance, tracing.BalanceIncreaseSelfdestruct)
} }
if evm.chainRules.IsAmsterdam && !balance.IsZero() { // EIP-7708: emit a transfer log when value moves to a distinct beneficiary.
if this != beneficiary { // The current spec emits no burn log for a self-beneficiary selfdestruct
evm.StateDB.AddLog(types.EthTransferLog(this, beneficiary, balance)) // (the balance is preserved and silently cleared at tx end).
} else if newContract { if evm.chainRules.IsAmsterdam && !balance.IsZero() && this != beneficiary {
evm.StateDB.AddLog(types.EthBurnLog(this, balance)) evm.StateDB.AddLog(types.EthTransferLog(this, beneficiary, balance))
}
} }
if tracer := evm.Config.Tracer; tracer != nil { if tracer := evm.Config.Tracer; tracer != nil {

View file

@ -156,7 +156,7 @@ const (
MaxCodeSize = 24576 // Maximum bytecode to permit for a contract MaxCodeSize = 24576 // Maximum bytecode to permit for a contract
MaxInitCodeSize = 2 * MaxCodeSize // Maximum initcode to permit in a creation transaction and create instructions MaxInitCodeSize = 2 * MaxCodeSize // Maximum initcode to permit in a creation transaction and create instructions
MaxCodeSizeAmsterdam = 32768 // Maximum bytecode to permit for a contract post Amsterdam MaxCodeSizeAmsterdam = 65536 // Maximum bytecode to permit for a contract post Amsterdam (EIP-7954: 64 KiB)
MaxInitCodeSizeAmsterdam = 2 * MaxCodeSizeAmsterdam // Maximum initcode to permit in a creation transaction and create instructions post Amsterdam MaxInitCodeSizeAmsterdam = 2 * MaxCodeSizeAmsterdam // Maximum initcode to permit in a creation transaction and create instructions post Amsterdam
// Precompiled contract gas prices // Precompiled contract gas prices