core: apply fixes for 8037

This commit is contained in:
Marius van der Wijden 2026-04-22 17:31:53 +02:00 committed by MariusVanDerWijden
parent 2d708415d7
commit fa45d1a38f
8 changed files with 122 additions and 10 deletions

View file

@ -687,6 +687,36 @@ func (s *StateDB) IsNewContract(addr common.Address) bool {
return obj.newContract return obj.newContract
} }
// SameTxSelfDestructs returns the addresses that were both created and
// self-destructed in the current transaction (EIP-6780). Used for the
// EIP-8037 same-tx selfdestruct state-gas refund.
func (s *StateDB) SameTxSelfDestructs() []common.Address {
var out []common.Address
for addr, obj := range s.stateObjects {
if obj.newContract && obj.selfDestructed {
out = append(out, addr)
}
}
return out
}
// NewStorageSlotCount returns the number of storage slots that were written
// to a non-zero value in the current transaction on the given account. Used
// for the EIP-8037 same-tx selfdestruct state-gas refund.
func (s *StateDB) NewStorageSlotCount(addr common.Address) int {
obj, ok := s.stateObjects[addr]
if !ok {
return 0
}
var count int
for _, v := range obj.dirtyStorage {
if v != (common.Hash{}) {
count++
}
}
return count
}
// Copy creates a deep, independent copy of the state. // Copy creates a deep, independent copy of the state.
// Snapshots of the copied state cannot be applied to the copy. // Snapshots of the copied state cannot be applied to the copy.
func (s *StateDB) Copy() *StateDB { func (s *StateDB) Copy() *StateDB {

View file

@ -80,6 +80,14 @@ func (s *hookedStateDB) GetCodeSize(addr common.Address) int {
return s.inner.GetCodeSize(addr) return s.inner.GetCodeSize(addr)
} }
func (s *hookedStateDB) SameTxSelfDestructs() []common.Address {
return s.inner.SameTxSelfDestructs()
}
func (s *hookedStateDB) NewStorageSlotCount(addr common.Address) int {
return s.inner.NewStorageSlotCount(addr)
}
func (s *hookedStateDB) AddRefund(u uint64) { func (s *hookedStateDB) AddRefund(u uint64) {
s.inner.AddRefund(u) s.inner.AddRefund(u)
} }

View file

@ -89,7 +89,7 @@ func IntrinsicGas(data []byte, accessList types.AccessList, authList []types.Set
gas.RegularGas += uint64(len(authList)) * params.TxAuthTupleRegularGas gas.RegularGas += uint64(len(authList)) * params.TxAuthTupleRegularGas
gas.StateGas += uint64(len(authList)) * (params.AuthorizationCreationSize + params.AccountCreationSize) * costPerStateByte gas.StateGas += uint64(len(authList)) * (params.AuthorizationCreationSize + params.AccountCreationSize) * costPerStateByte
} else { } else {
gas.RegularGas += uint64(len(authList)) * params.TxAuthTupleGas gas.RegularGas += uint64(len(authList)) * params.CallNewAccountGas
} }
} }
dataLen := uint64(len(data)) dataLen := uint64(len(data))
@ -607,6 +607,42 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
ret, st.gasRemaining, execGasUsed, vmerr = st.evm.Call(msg.From, st.to(), msg.Data, st.gasRemaining, value) ret, st.gasRemaining, execGasUsed, vmerr = st.evm.Call(msg.From, st.to(), msg.Data, st.gasRemaining, value)
} }
// On outer level tx failure, no state is written.
if rules.IsAmsterdam && vmerr != nil {
st.gasRemaining.StateGas += execGasUsed.StateGas
execGasUsed.StateGas = 0
}
// Refund costs for selfdestructed accounts and slots.
if rules.IsAmsterdam && vmerr == nil {
cpsb := st.evm.Context.CostPerGasByte
stateGasUsed := execGasUsed.StateGas + cost.StateGas
var sdRefund uint64
for _, addr := range st.state.SameTxSelfDestructs() {
r := params.AccountCreationSize * cpsb
r += uint64(st.state.NewStorageSlotCount(addr)) * params.StorageCreationSize * cpsb
r += uint64(st.state.GetCodeSize(addr)) * cpsb
sdRefund += r
}
if sdRefund > stateGasUsed {
sdRefund = stateGasUsed
}
if sdRefund > 0 {
st.gasRemaining.StateGas += sdRefund
if execGasUsed.StateGas >= sdRefund {
execGasUsed.StateGas -= sdRefund
} else {
extra := sdRefund - execGasUsed.StateGas
execGasUsed.StateGas = 0
if cost.StateGas >= extra {
cost.StateGas -= extra
} else {
cost.StateGas = 0
}
}
}
}
// Record the gas used excluding gas refunds. This value represents the actual // Record the gas used excluding gas refunds. This value represents the actual
// gas allowance required to complete execution. // gas allowance required to complete execution.
peakGasUsed := st.gasUsed() peakGasUsed := st.gasUsed()

View file

@ -160,6 +160,18 @@ func (c *Contract) RefundGas(err error, initialRegularGasUsed uint64, gas GasBud
c.GasUsed.RegularGas = initialRegularGasUsed + gasUsed.RegularGas c.GasUsed.RegularGas = initialRegularGasUsed + gasUsed.RegularGas
} }
// Refunds the account creation state costs if a CREATE/CREATE2 call fails.
func (c *Contract) RefundCreateStateGas(refund uint64) {
if refund > 0 {
c.Gas.StateGas += refund
if c.GasUsed.StateGas >= refund {
c.GasUsed.StateGas -= refund
} else {
c.GasUsed.StateGas = 0
}
}
}
// Address returns the contracts address // Address returns the contracts address
func (c *Contract) Address() common.Address { func (c *Contract) Address() common.Address {
return c.address return c.address

View file

@ -544,8 +544,10 @@ func (evm *EVM) create(caller common.Address, code []byte, gas GasBudget, 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)
} }
// Record all burned gas
burned := gas.RegularGas
gas.Exhaust() gas.Exhaust()
return nil, common.Address{}, gas, GasCosts{}, ErrContractAddressCollision return nil, common.Address{}, gas, GasUsed{RegularGas: burned}, 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

View file

@ -671,12 +671,14 @@ 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: Return both regular and state gas. The interpreter // EIP-8037: Return both regular and state gas. System calls do not charge state gas.
// charges regular gas before state gas, preventing reservoir var stateGas uint64
// inflation when the regular charge OOGs. if !contract.IsSystemCall {
stateGas = params.StorageCreationSize * evm.Context.CostPerGasByte
}
return GasCosts{ return GasCosts{
RegularGas: cost.RegularGas + params.SstoreResetGasEIP2200 - params.ColdSloadCostEIP2929, RegularGas: cost.RegularGas + params.SstoreResetGasEIP2200 - params.ColdSloadCostEIP2929,
StateGas: params.StorageCreationSize * evm.Context.CostPerGasByte, StateGas: stateGas,
}, nil }, nil
} }
if value == (common.Hash{}) { // delete slot (2.1.2b) if value == (common.Hash{}) { // delete slot (2.1.2b)
@ -695,9 +697,18 @@ func gasSStore8037(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memo
} }
if original == value { if original == value {
if original == (common.Hash{}) { // reset to original inexistent slot (2.2.2.1) if original == (common.Hash{}) { // reset to original inexistent slot (2.2.2.1)
// EIP 2200 Original clause: // EIP-8037 point (2): refund state gas directly to the reservoir
//evm.StateDB.AddRefund(params.SstoreSetGasEIP2200 - params.SloadGasEIP2200) // at the SSTORE restoration point (0→x→0 in same tx); not to the
evm.StateDB.AddRefund(params.StorageCreationSize*evm.Context.CostPerGasByte + params.SstoreResetGasEIP2200 - params.ColdSloadCostEIP2929 - params.WarmStorageReadCostEIP2929) // refund counter, which is capped at gas_used/5.
stateRefund := params.StorageCreationSize * evm.Context.CostPerGasByte
contract.Gas.StateGas += stateRefund
if contract.GasUsed.StateGas >= stateRefund {
contract.GasUsed.StateGas -= stateRefund
} else {
contract.GasUsed.StateGas = 0
}
// Regular portion of the refund still goes through the refund counter.
evm.StateDB.AddRefund(params.SstoreResetGasEIP2200 - params.ColdSloadCostEIP2929 - params.WarmStorageReadCostEIP2929)
} else { // reset to original existing slot (2.2.2.2) } else { // reset to original existing slot (2.2.2.2)
// EIP 2200 Original clause: // EIP 2200 Original clause:
// evm.StateDB.AddRefund(params.SstoreResetGasEIP2200 - params.SloadGasEIP2200) // evm.StateDB.AddRefund(params.SstoreResetGasEIP2200 - params.SloadGasEIP2200)

View file

@ -682,7 +682,9 @@ func opCreate(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
scope.Stack.push(&stackvalue) scope.Stack.push(&stackvalue)
scope.Contract.RefundGas(suberr, regularGasUsed, returnGas, childGasUsed, evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded) scope.Contract.RefundGas(suberr, regularGasUsed, returnGas, childGasUsed, evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded)
if evm.chainRules.IsAmsterdam && suberr != nil {
scope.Contract.RefundCreateStateGas(params.AccountCreationSize * evm.Context.CostPerGasByte)
}
if suberr == ErrExecutionReverted { if suberr == ErrExecutionReverted {
evm.returnData = res // set REVERT data to return data buffer evm.returnData = res // set REVERT data to return data buffer
return res, nil return res, nil
@ -719,6 +721,9 @@ func opCreate2(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
scope.Stack.push(&stackvalue) scope.Stack.push(&stackvalue)
scope.Contract.RefundGas(suberr, regularGasUsed, returnGas, childGasUsed, evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded) scope.Contract.RefundGas(suberr, regularGasUsed, returnGas, childGasUsed, evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded)
if evm.chainRules.IsAmsterdam && suberr != nil {
scope.Contract.RefundCreateStateGas(params.AccountCreationSize * evm.Context.CostPerGasByte)
}
if suberr == ErrExecutionReverted { if suberr == ErrExecutionReverted {
evm.returnData = res // set REVERT data to return data buffer evm.returnData = res // set REVERT data to return data buffer

View file

@ -71,6 +71,14 @@ type StateDB interface {
// during the current transaction. // during the current transaction.
IsNewContract(addr common.Address) bool IsNewContract(addr common.Address) bool
// SameTxSelfDestructs returns addresses that were created and then
// self-destructed in the current transaction.
SameTxSelfDestructs() []common.Address
// NewStorageSlotCount returns the number of storage slots written to a
// non-zero value in the current transaction on the given address.
NewStorageSlotCount(addr common.Address) int
// Empty returns whether the given account is empty. Empty // Empty returns whether the given account is empty. Empty
// is defined according to EIP161 (balance = nonce = code = 0). // is defined according to EIP161 (balance = nonce = code = 0).
Empty(common.Address) bool Empty(common.Address) bool