diff --git a/core/evm.go b/core/evm.go index fe0847dfce..019a8f29ed 100644 --- a/core/evm.go +++ b/core/evm.go @@ -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. diff --git a/core/state/statedb.go b/core/state/statedb.go index 56b083e17c..5920277728 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -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 diff --git a/core/state/statedb_hooked.go b/core/state/statedb_hooked.go index db3d692aee..652dd7d9b8 100644 --- a/core/state/statedb_hooked.go +++ b/core/state/statedb_hooked.go @@ -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() +} diff --git a/core/state_transition.go b/core/state_transition.go index fa2332106f..0662e8c16b 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -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 { diff --git a/core/vm/interface.go b/core/vm/interface.go index 3e2f6d115c..ccd5210d6c 100644 --- a/core/vm/interface.go +++ b/core/vm/interface.go @@ -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 }