add dedicated selfdestruct hook (TODO: this commit is bugged because a prefunded account which is the target of a create2 initcode can become empty without explicitly selfdestructing

This commit is contained in:
Jared Wasinger 2025-10-05 14:59:09 +08:00
parent cb632f0345
commit 12311ee59e
4 changed files with 39 additions and 18 deletions

View file

@ -39,16 +39,17 @@ func NewBlockAccessListTracer(startIdx int) (*BlockAccessListTracer, *tracing.Ho
accessListBuilder: bal.NewAccessListBuilder(), accessListBuilder: bal.NewAccessListBuilder(),
} }
hooks := &tracing.Hooks{ hooks := &tracing.Hooks{
OnTxEnd: balTracer.TxEndHook, OnTxEnd: balTracer.TxEndHook,
OnTxStart: balTracer.TxStartHook, OnTxStart: balTracer.TxStartHook,
OnEnter: balTracer.OnEnter, OnEnter: balTracer.OnEnter,
OnExit: balTracer.OnExit, OnExit: balTracer.OnExit,
OnCodeChangeV2: balTracer.OnCodeChange, OnCodeChangeV2: balTracer.OnCodeChange,
OnBalanceChange: balTracer.OnBalanceChange, OnBalanceChange: balTracer.OnBalanceChange,
OnNonceChangeV2: balTracer.OnNonceChange, OnNonceChangeV2: balTracer.OnNonceChange,
OnStorageChange: balTracer.OnStorageChange, OnStorageChange: balTracer.OnStorageChange,
OnColdAccountRead: balTracer.OnColdAccountRead, OnColdAccountRead: balTracer.OnColdAccountRead,
OnColdStorageRead: balTracer.OnColdStorageRead, OnColdStorageRead: balTracer.OnColdStorageRead,
OnSelfDestructChange: balTracer.OnSelfDestruct,
} }
wrappedHooks, err := tracing.WrapWithJournal(hooks) wrappedHooks, err := tracing.WrapWithJournal(hooks)
if err != nil { if err != nil {
@ -107,6 +108,10 @@ func (a *BlockAccessListTracer) OnCodeChange(addr common.Address, prevCodeHash c
a.accessListBuilder.CodeChange(addr, prevCode, code) a.accessListBuilder.CodeChange(addr, prevCode, code)
} }
func (a *BlockAccessListTracer) OnSelfDestruct(addr common.Address) {
a.accessListBuilder.SelfDestruct(addr)
}
func (a *BlockAccessListTracer) OnBalanceChange(addr common.Address, prevBalance, newBalance *big.Int, _ tracing.BalanceChangeReason) { func (a *BlockAccessListTracer) OnBalanceChange(addr common.Address, prevBalance, newBalance *big.Int, _ tracing.BalanceChangeReason) {
newU256 := new(uint256.Int).SetBytes(newBalance.Bytes()) newU256 := new(uint256.Int).SetBytes(newBalance.Bytes())
prevU256 := new(uint256.Int).SetBytes(prevBalance.Bytes()) prevU256 := new(uint256.Int).SetBytes(prevBalance.Bytes())

View file

@ -254,10 +254,13 @@ func (s *hookedStateDB) AddLog(log *types.Log) {
func (s *hookedStateDB) Finalise(deleteEmptyObjects bool) { func (s *hookedStateDB) Finalise(deleteEmptyObjects bool) {
defer s.inner.Finalise(deleteEmptyObjects) defer s.inner.Finalise(deleteEmptyObjects)
if s.hooks.OnBalanceChange != nil || s.hooks.OnNonceChangeV2 != nil || s.hooks.OnCodeChangeV2 != nil || s.hooks.OnCodeChange != nil { if s.hooks.OnSelfDestructChange != nil || s.hooks.OnBalanceChange != nil || s.hooks.OnNonceChangeV2 != nil || s.hooks.OnCodeChangeV2 != nil || s.hooks.OnCodeChange != nil {
for addr := range s.inner.journal.dirties { for addr := range s.inner.journal.dirties {
obj := s.inner.stateObjects[addr] obj := s.inner.stateObjects[addr]
if obj != nil && obj.selfDestructed { if obj != nil && obj.selfDestructed {
if s.hooks.OnSelfDestructChange != nil {
s.hooks.OnSelfDestructChange(obj.address)
}
// If ether was sent to account post-selfdestruct it is burnt. // If ether was sent to account post-selfdestruct it is burnt.
if s.hooks.OnBalanceChange != nil { if s.hooks.OnBalanceChange != nil {
if bal := obj.Balance(); bal.Sign() != 0 { if bal := obj.Balance(); bal.Sign() != 0 {

View file

@ -189,6 +189,8 @@ type (
// ColdAccountReadHook is called before an previously-unread account is read. // ColdAccountReadHook is called before an previously-unread account is read.
ColdAccountReadHook = func(address common.Address) ColdAccountReadHook = func(address common.Address)
SelfDestructHook = func(address common.Address)
// LogHook is called when a log is emitted. // LogHook is called when a log is emitted.
LogHook = func(log *types.Log) LogHook = func(log *types.Log)
@ -217,13 +219,14 @@ type Hooks struct {
OnSystemCallEnd OnSystemCallEndHook OnSystemCallEnd OnSystemCallEndHook
// State events // State events
OnBalanceChange BalanceChangeHook OnBalanceChange BalanceChangeHook
OnNonceChange NonceChangeHook OnNonceChange NonceChangeHook
OnNonceChangeV2 NonceChangeHookV2 OnNonceChangeV2 NonceChangeHookV2
OnCodeChange CodeChangeHook OnCodeChange CodeChangeHook
OnCodeChangeV2 CodeChangeHookV2 OnCodeChangeV2 CodeChangeHookV2
OnStorageChange StorageChangeHook OnStorageChange StorageChangeHook
OnLog LogHook OnLog LogHook
OnSelfDestructChange SelfDestructHook
//State read events //State read events
OnColdStorageRead ColdStorageReadHook OnColdStorageRead ColdStorageReadHook
OnColdAccountRead ColdAccountReadHook OnColdAccountRead ColdAccountReadHook

View file

@ -133,6 +133,10 @@ func (c *AccessListBuilder) CodeChange(address common.Address, prev, cur []byte)
acctAccesses.CodeChange(cur) acctAccesses.CodeChange(cur)
} }
func (c *AccessListBuilder) SelfDestruct(address common.Address) {
delete(c.accessesStack[len(c.accessesStack)-1], address)
}
func (c *AccessListBuilder) NonceChange(address common.Address, prev, cur uint64) { func (c *AccessListBuilder) NonceChange(address common.Address, prev, cur uint64) {
if _, ok := c.prestates[address]; !ok { if _, ok := c.prestates[address]; !ok {
c.prestates[address] = &AccountState{} c.prestates[address] = &AccountState{}
@ -203,6 +207,12 @@ func (a *AccessListBuilder) FinaliseIdxChanges() (*StateDiff, StateAccesses) {
} }
} }
// two cases of account being removed:
// * initcode runs at address, calls SENDALL (account could be prefunded, but not pre-existing as a contract)
// - if the account makes storage mutations, we have no way to distinguish it from a regular contract that didn't selfdestruct
//
// * contract created in same tx calls SENDALL
// two scenarios where an account can become non-existent: // two scenarios where an account can become non-existent:
// account was created/deleted in the same transaction // account was created/deleted in the same transaction
// account only had balance set (prefunded), was target of create2 initcode which called SENDALL leaving the account empty // account only had balance set (prefunded), was target of create2 initcode which called SENDALL leaving the account empty