This commit is contained in:
Vansh Sahay 2026-06-19 09:35:41 +00:00 committed by GitHub
commit 679860866a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 53 additions and 55 deletions

View file

@ -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.

View file

@ -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)
}

View file

@ -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,

View file

@ -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))
}
}

View file

@ -90,7 +90,6 @@ type StateDB interface {
Snapshot() int
AddLog(*types.Log)
LogsForBurnAccounts() []*types.Log
AddPreimage(common.Hash, []byte)
Witness() *stateless.Witness

View file

@ -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)
}
}