diff --git a/core/state/statedb.go b/core/state/statedb.go index b770698255e..384b1a903f0 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -419,6 +419,13 @@ func (s *StateDB) HasSelfDestructed(addr common.Address) bool { return false } +// ExistBeforeCurTx returns true if a contract exists and was not created +// in the current transaction. +func (s *StateDB) ExistBeforeCurTx(addr common.Address) bool { + obj := s.getStateObject(addr) + return obj != nil && !obj.newContract +} + /* * SETTERS */ diff --git a/core/state/statedb_hooked.go b/core/state/statedb_hooked.go index d2595bcefed..5612ae2bc89 100644 --- a/core/state/statedb_hooked.go +++ b/core/state/statedb_hooked.go @@ -216,55 +216,21 @@ func (s *hookedStateDB) SetState(address common.Address, key common.Hash, value } func (s *hookedStateDB) SelfDestruct(address common.Address) uint256.Int { - var prevCode []byte - var prevCodeHash common.Hash - - if s.hooks.OnCodeChange != nil { - prevCode = s.inner.GetCode(address) - prevCodeHash = s.inner.GetCodeHash(address) - } - prev := s.inner.SelfDestruct(address) if s.hooks.OnBalanceChange != nil && !prev.IsZero() { s.hooks.OnBalanceChange(address, prev.ToBig(), new(big.Int), tracing.BalanceDecreaseSelfdestruct) } - if len(prevCode) > 0 { - if s.hooks.OnCodeChangeV2 != nil { - s.hooks.OnCodeChangeV2(address, prevCodeHash, prevCode, types.EmptyCodeHash, nil, tracing.CodeChangeSelfDestruct) - } else if s.hooks.OnCodeChange != nil { - s.hooks.OnCodeChange(address, prevCodeHash, prevCode, types.EmptyCodeHash, nil) - } - } - return prev } func (s *hookedStateDB) SelfDestruct6780(address common.Address) (uint256.Int, bool) { - var prevCode []byte - var prevCodeHash common.Hash - - if s.hooks.OnCodeChange != nil { - prevCodeHash = s.inner.GetCodeHash(address) - prevCode = s.inner.GetCode(address) - } - - prev, changed := s.inner.SelfDestruct6780(address) - - if s.hooks.OnBalanceChange != nil && !prev.IsZero() { - s.hooks.OnBalanceChange(address, prev.ToBig(), new(big.Int), tracing.BalanceDecreaseSelfdestruct) - } - - if changed && len(prevCode) > 0 { - if s.hooks.OnCodeChangeV2 != nil { - s.hooks.OnCodeChangeV2(address, prevCodeHash, prevCode, types.EmptyCodeHash, nil, tracing.CodeChangeSelfDestruct) - } else if s.hooks.OnCodeChange != nil { - s.hooks.OnCodeChange(address, prevCodeHash, prevCode, types.EmptyCodeHash, nil) - } - } + return s.inner.SelfDestruct6780(address) +} - return prev, changed +func (s *hookedStateDB) ExistBeforeCurTx(address common.Address) bool { + return s.inner.ExistBeforeCurTx(address) } func (s *hookedStateDB) AddLog(log *types.Log) { @@ -277,15 +243,32 @@ func (s *hookedStateDB) AddLog(log *types.Log) { func (s *hookedStateDB) Finalise(deleteEmptyObjects bool) { defer s.inner.Finalise(deleteEmptyObjects) - if s.hooks.OnBalanceChange == nil { - return - } - for addr := range s.inner.journal.dirties { - obj := s.inner.stateObjects[addr] - if obj != nil && obj.selfDestructed { - // If ether was sent to account post-selfdestruct it is burnt. - if bal := obj.Balance(); bal.Sign() != 0 { - s.hooks.OnBalanceChange(addr, bal.ToBig(), new(big.Int), tracing.BalanceDecreaseSelfdestructBurn) + if s.hooks.OnBalanceChange != nil || s.hooks.OnNonceChangeV2 != nil || s.hooks.OnCodeChangeV2 != nil || s.hooks.OnCodeChange != nil { + for addr := range s.inner.journal.dirties { + obj := s.inner.stateObjects[addr] + if obj != nil && obj.selfDestructed { + // If ether was sent to account post-selfdestruct it is burnt. + if s.hooks.OnBalanceChange != nil { + if bal := obj.Balance(); bal.Sign() != 0 { + s.hooks.OnBalanceChange(addr, bal.ToBig(), new(big.Int), tracing.BalanceDecreaseSelfdestructBurn) + } + } + if s.hooks.OnNonceChangeV2 != nil { + prevNonce := obj.Nonce() + s.hooks.OnNonceChangeV2(addr, prevNonce, 0, tracing.NonceChangeSelfdestruct) + } + prevCodeHash := s.inner.GetCodeHash(addr) + prevCode := s.inner.GetCode(addr) + + // if an initcode invokes selfdestruct, do not emit a code change. + if prevCodeHash == types.EmptyCodeHash { + continue + } + if s.hooks.OnCodeChangeV2 != nil { + s.hooks.OnCodeChangeV2(addr, prevCodeHash, prevCode, types.EmptyCodeHash, nil, tracing.CodeChangeSelfDestruct) + } else if s.hooks.OnCodeChange != nil { + s.hooks.OnCodeChange(addr, prevCodeHash, prevCode, types.EmptyCodeHash, nil) + } } } } diff --git a/core/tracing/hooks.go b/core/tracing/hooks.go index 8e50dc3d8f3..c5646755524 100644 --- a/core/tracing/hooks.go +++ b/core/tracing/hooks.go @@ -375,6 +375,9 @@ const ( // NonceChangeRevert is emitted when the nonce is reverted back to a previous value due to call failure. // It is only emitted when the tracer has opted in to use the journaling wrapper (WrapWithJournal). NonceChangeRevert NonceChangeReason = 6 + + // NonceChangeSelfdestruct is emitted when the nonce is reset to zero due to a self-destruct + NonceChangeSelfdestruct NonceChangeReason = 7 ) // CodeChangeReason is used to indicate the reason for a code change. diff --git a/core/vm/instructions.go b/core/vm/instructions.go index 44d3e81a9cf..255cd498f44 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -906,8 +906,21 @@ func opSelfdestruct6780(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, erro } beneficiary := scope.Stack.pop() balance := evm.StateDB.GetBalance(scope.Contract.Address()) - evm.StateDB.SubBalance(scope.Contract.Address(), balance, tracing.BalanceDecreaseSelfdestruct) - evm.StateDB.AddBalance(beneficiary.Bytes20(), balance, tracing.BalanceIncreaseSelfdestruct) + createdInTx := !evm.StateDB.ExistBeforeCurTx(scope.Contract.Address()) + + if createdInTx { + // if the contract is not preexisting, the balance is immediately burned on selfdestruct-to-self + evm.StateDB.SubBalance(scope.Contract.Address(), balance, tracing.BalanceDecreaseSelfdestruct) + if scope.Contract.Address() != common.BytesToAddress(beneficiary.Bytes()) { + evm.StateDB.AddBalance(beneficiary.Bytes20(), balance, tracing.BalanceIncreaseSelfdestruct) + } + } else { + // if the contract is preexisting, the balance isn't burned on selfdestruct-to-self + if scope.Contract.Address() != common.BytesToAddress(beneficiary.Bytes()) { + evm.StateDB.SubBalance(scope.Contract.Address(), balance, tracing.BalanceDecreaseSelfdestruct) + evm.StateDB.AddBalance(beneficiary.Bytes20(), balance, tracing.BalanceIncreaseSelfdestruct) + } + } evm.StateDB.SelfDestruct6780(scope.Contract.Address()) if tracer := evm.Config.Tracer; tracer != nil { if tracer.OnEnter != nil { diff --git a/core/vm/interface.go b/core/vm/interface.go index d7f4c10e1f5..e2555dfb567 100644 --- a/core/vm/interface.go +++ b/core/vm/interface.go @@ -71,6 +71,9 @@ type StateDB interface { // Exist reports whether the given account exists in state. // Notably this also returns true for self-destructed accounts within the current transaction. Exist(common.Address) bool + // ExistBeforeCurTx returns true if a contract exists and was not created + // in the current transaction. + ExistBeforeCurTx(addr common.Address) bool // Empty returns whether the given account is empty. Empty // is defined according to EIP161 (balance = nonce = code = 0). Empty(common.Address) bool