diff --git a/core/state/statedb.go b/core/state/statedb.go index 1c49d46020..b1046ac4df 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -764,44 +764,6 @@ func (s *StateDB) GetRefund() uint64 { return s.refund } -type removedAccountWithBalance struct { - address common.Address - balance *uint256.Int -} - -// LogsForBurnAccounts returns the eth burn logs for accounts scheduled for -// removal which still have positive balance. The purpose of this function is -// to handle a corner case of EIP-7708 where a self-destructed account might -// still receive funds between sending/burning its previous balance and actual -// removal. In this case the burning of these remaining balances still need to -// be logged. -// Specification EIP-7708: https://eips.ethereum.org/EIPS/eip-7708 -// -// This function should only be invoked at the transaction boundary, specifically -// before the Finalise. -func (s *StateDB) LogsForBurnAccounts() []*types.Log { - var list []removedAccountWithBalance - for addr := range s.journal.mutations { - if obj, exist := s.stateObjects[addr]; exist && obj.selfDestructed && !obj.Balance().IsZero() { - list = append(list, removedAccountWithBalance{ - address: obj.address, - balance: obj.Balance(), - }) - } - } - if list == nil { - return nil - } - sort.Slice(list, func(i, j int) bool { - return list[i].address.Cmp(list[j].address) < 0 - }) - logs := make([]*types.Log, len(list)) - for i, acct := range list { - logs[i] = types.EthBurnLog(acct.address, acct.balance) - } - return logs -} - // Finalise finalises the state by removing the destructed objects and clears // the journal as well as the refunds. Finalise, however, will not push any updates // into the tries just yet. Only IntermediateRoot or Commit will do that. @@ -821,7 +783,40 @@ func (s *StateDB) Finalise(deleteEmptyObjects bool) *bal.ConstructionBlockAccess // finalise or delete, so ignore it here. continue } - if obj.selfDestructed || (deleteEmptyObjects && obj.empty()) { + // EIP-8246: clear code/storage/nonce, preserve balance. + if obj.selfDestructed && s.stateAccessList != nil { + clearSelfdestructAccount(obj) + + if deleteEmptyObjects && obj.empty() { + // Cleanup left account empty; delete per EIP-161. + delete(s.stateObjects, obj.address) + s.markDelete(addr) + if _, ok := s.stateObjectsDestruct[obj.address]; !ok { + s.stateObjectsDestruct[obj.address] = obj + } + balance := uint256.NewInt(0) + if state.balanceSet && balance.Cmp(state.balance) != 0 { + s.stateAccessList.BalanceChange(s.blockAccessIndex, addr, balance) + } + } else { + // Keep as balance-only account. + balance := obj.Balance() + if state.balanceSet && balance.Cmp(state.balance) != 0 { + s.stateAccessList.BalanceChange(s.blockAccessIndex, addr, balance) + } + nonce := obj.Nonce() + if state.nonceSet && nonce != state.nonce { + s.stateAccessList.NonceChange(addr, s.blockAccessIndex, nonce) + } + if state.codeSet { + if code := obj.Code(); !bytes.Equal(code, state.code) { + s.stateAccessList.CodeChange(addr, s.blockAccessIndex, code) + } + } + obj.finalise() + s.markUpdate(addr) + } + } else if obj.selfDestructed || (deleteEmptyObjects && obj.empty()) { delete(s.stateObjects, obj.address) s.markDelete(addr) @@ -887,6 +882,17 @@ func (s *StateDB) Finalise(deleteEmptyObjects bool) *bal.ConstructionBlockAccess return s.stateAccessList } +// clearSelfdestructAccount clears code, storage, and nonce for an EIP-8246 +// selfdestructed account while preserving the balance. +func clearSelfdestructAccount(obj *stateObject) { + obj.data.CodeHash = types.EmptyCodeHash.Bytes() + obj.dirtyCode = true + obj.dirtyStorage = make(Storage) + obj.pendingStorage = make(Storage) + obj.originStorage = make(Storage) + obj.data.Nonce = 0 +} + // IntermediateRoot computes the current root hash of the state trie. // It is called in between transactions to get the root hash that // goes into transaction receipts. diff --git a/core/state/statedb_hooked.go b/core/state/statedb_hooked.go index 98d01343a4..3c79740f54 100644 --- a/core/state/statedb_hooked.go +++ b/core/state/statedb_hooked.go @@ -230,10 +230,6 @@ func (s *hookedStateDB) AddLog(log *types.Log) { } } -func (s *hookedStateDB) LogsForBurnAccounts() []*types.Log { - return s.inner.LogsForBurnAccounts() -} - func (s *hookedStateDB) Finalise(deleteEmptyObjects bool) *bal.ConstructionBlockAccessList { if s.hooks.OnBalanceChange == nil && s.hooks.OnNonceChangeV2 == nil && s.hooks.OnNonceChange == nil && s.hooks.OnCodeChangeV2 == nil && s.hooks.OnCodeChange == nil { // Short circuit if no relevant hooks are set. @@ -261,7 +257,8 @@ func (s *hookedStateDB) Finalise(deleteEmptyObjects bool) *bal.ConstructionBlock // Bingo: state object was self-destructed, call relevant hooks. // If ether was sent to account post-selfdestruct, record as burnt. - if s.hooks.OnBalanceChange != nil { + // EIP-8246: balance is preserved, skip the burn trace. + if s.hooks.OnBalanceChange != nil && s.inner.stateAccessList == nil { if bal := obj.Balance(); bal.Sign() != 0 { s.hooks.OnBalanceChange(addr, bal.ToBig(), new(big.Int), tracing.BalanceDecreaseSelfdestructBurn) } diff --git a/core/state_transition.go b/core/state_transition.go index dac8123530..34f1bdceeb 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -744,12 +744,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{ UsedGas: gasUsed, MaxUsedGas: peakUsed, diff --git a/core/vm/instructions.go b/core/vm/instructions.go index 209457f670..2ea1f8a8f7 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -916,7 +916,11 @@ func opSelfdestruct6780(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, erro if this != beneficiary { // Skip no-op transfer when self-destructing to self. evm.StateDB.AddBalance(beneficiary, balance, tracing.BalanceIncreaseSelfdestruct) } - evm.StateDB.SubBalance(this, balance, tracing.BalanceDecreaseSelfdestruct) + // EIP-8246: if the beneficiary is the executing account itself and the fork + // is active, the balance remains unchanged instead of being burned. + if !evm.chainRules.IsAmsterdam || this != beneficiary { + evm.StateDB.SubBalance(this, balance, tracing.BalanceDecreaseSelfdestruct) + } evm.StateDB.SelfDestruct(this) } @@ -928,8 +932,6 @@ func opSelfdestruct6780(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, erro if evm.chainRules.IsAmsterdam && !balance.IsZero() { if this != beneficiary { evm.StateDB.AddLog(types.EthTransferLog(this, beneficiary, balance)) - } else if newContract { - evm.StateDB.AddLog(types.EthBurnLog(this, balance)) } } diff --git a/core/vm/interface.go b/core/vm/interface.go index a9938c2a28..5bba39069c 100644 --- a/core/vm/interface.go +++ b/core/vm/interface.go @@ -90,7 +90,6 @@ type StateDB interface { Snapshot() int AddLog(*types.Log) - LogsForBurnAccounts() []*types.Log AddPreimage(common.Hash, []byte) Witness() *stateless.Witness diff --git a/p2p/transport_test.go b/p2p/transport_test.go index 777be1bd0d..d92405dd11 100644 --- a/p2p/transport_test.go +++ b/p2p/transport_test.go @@ -147,7 +147,7 @@ func TestProtocolHandshakeErrors(t *testing.T) { p1, p2 := MsgPipe() go Send(p1, test.code, test.msg) _, err := readProtocolHandshake(p2) - if !reflect.DeepEqual(err, test.err) { + if err.Error() != test.err.Error() { t.Errorf("test %d: error mismatch: got %q, want %q", i, err, test.err) } }