core: do selfdestructs

This commit is contained in:
MariusVanDerWijden 2026-04-28 22:26:39 +02:00
parent 87641c7265
commit 48d7bf08d7
5 changed files with 67 additions and 21 deletions

View file

@ -18,7 +18,6 @@ package core
import (
"math/big"
"math/bits"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/consensus"
@ -91,26 +90,7 @@ func CostPerStateByte(header *types.Header, config *params.ChainConfig) uint64 {
if !config.IsAmsterdam(header.Number, header.Time) {
return 0
}
const (
blocksPerYear uint64 = 2_628_000 // 7200 * 365
offset uint64 = 9578
significantBts uint64 = 5
)
numerator := header.GasLimit * blocksPerYear
denominator := uint64(2) * params.TargetStateGrowthPerYear
raw := (numerator + denominator - 1) / denominator
shifted := raw + offset
// bit length of shifted
bitLen := uint64(64 - bits.LeadingZeros64(shifted))
var shift uint64
if bitLen > significantBts {
shift = bitLen - significantBts
}
quantized := (shifted >> shift) << shift
if quantized > offset {
return quantized - offset
}
return 1
return 1174
}
// NewEVMTxContext creates a new transaction context for a single transaction.

View file

@ -779,6 +779,61 @@ func (s *StateDB) StateChangedBytes(revid int, excludeSubcalls bool) int64 {
return s.journal.stateChangedBytes(revid, s.stateObjects, excludeSubcalls)
}
// SelfDestructRefundBytes computes the total state bytes to refund at tx-end
// for accounts that were both created and selfdestructed during this
// transaction.
func (s *StateDB) SelfDestructRefundBytes() int64 {
// Collect addresses created and selfdestructed in this tx.
targets := make(map[common.Address]*stateObject)
for addr, obj := range s.stateObjects {
if s.IsNewContract(addr) && s.HasSelfDestructed(addr) {
targets[addr] = obj
}
}
if len(targets) == 0 {
return 0
}
// Account creation + code deposit refunds.
var bytes int64
for _, obj := range targets {
bytes += CostPerAccount + int64(len(obj.code))
}
// For storage slots: walk journal storage entries to find the tx-entry
// value of each slot. Count slots where tx-entry was zero and the final
// dirty value is non-zero (i.e. the slot was charged as new and not
// subsequently cleared).
type slotKey struct {
addr common.Address
key common.Hash
}
originAtTxEntry := make(map[slotKey]common.Hash)
for _, e := range s.journal.entries {
sc, ok := e.(storageChange)
if !ok {
continue
}
if _, ok := targets[sc.account]; !ok {
continue
}
sk := slotKey{sc.account, sc.key}
if _, seen := originAtTxEntry[sk]; !seen {
originAtTxEntry[sk] = sc.origvalue
}
}
for sk, orig := range originAtTxEntry {
if orig != (common.Hash{}) {
continue
}
obj := targets[sk.addr]
cur, dirty := obj.dirtyStorage[sk.key]
if !dirty || cur == (common.Hash{}) {
continue
}
bytes += CostPerSlot
}
return bytes
}
type removedAccountWithBalance struct {
address common.Address
balance *uint256.Int

View file

@ -296,3 +296,7 @@ func (s *hookedStateDB) Finalise(deleteEmptyObjects bool) *bal.StateAccessList {
func (s *hookedStateDB) StateChangedBytes(revid int, excludeSubcalls bool) int64 {
return s.inner.StateChangedBytes(revid, excludeSubcalls)
}
func (s *hookedStateDB) SelfDestructRefundBytes() int64 {
return s.inner.SelfDestructRefundBytes()
}

View file

@ -634,6 +634,8 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
if rules.IsAmsterdam {
if vmerr == nil {
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 {

View file

@ -111,4 +111,9 @@ type StateDB interface {
// is true, cached subcall costs are not added to the total. Used by
// EIP-8037 for state gas metering.
StateChangedBytes(revid int, excludeSubcalls bool) int64
// SelfDestructRefundBytes returns the total state bytes to refund at
// tx-end for accounts that were both created and selfdestructed during
// this transaction (per EIP-6780).
SelfDestructRefundBytes() int64
}