From 65ce9ebbc76abece16056d85cf207964165d9498 Mon Sep 17 00:00:00 2001 From: jwasinger Date: Mon, 17 Jul 2023 19:02:18 +0200 Subject: [PATCH] core/state, core/vm: implement EIP 6780 (#27189) commit https://github.com/ethereum/go-ethereum/commit/988d84aa7caf8e71ce441fa65f80d44216d9e00e. EIP-6780: SELFDESTRUCT only in same transaction > SELFDESTRUCT will recover all funds to the caller but not delete the account, except when called in the same transaction as creation --------- Co-authored-by: Martin Holst Swende --- core/blockchain_test.go | 24 +++++++++++++++--------- core/state/state_object.go | 3 +++ core/state/statedb.go | 15 +++++++++++++++ core/vm/eips.go | 14 ++++++++++++++ core/vm/instructions.go | 32 ++++++++++++++++++++++++++------ core/vm/interface.go | 2 ++ core/vm/jump_table.go | 1 + 7 files changed, 76 insertions(+), 15 deletions(-) diff --git a/core/blockchain_test.go b/core/blockchain_test.go index e59442920..d212a5152 100644 --- a/core/blockchain_test.go +++ b/core/blockchain_test.go @@ -2662,8 +2662,10 @@ func TestDeleteRecreateSlots(t *testing.T) { aa := crypto.CreateAddress2(bb, [32]byte{}, initHash[:]) t.Logf("Destination address: %x\n", aa) + chainConfig := *params.TestChainConfig + chainConfig.CancunBlock = nil gspec := &Genesis{ - Config: params.TestChainConfig, + Config: &chainConfig, Alloc: GenesisAlloc{ address: {Balance: funds}, // The address 0xAAAAA selfdestructs if called @@ -2683,7 +2685,7 @@ func TestDeleteRecreateSlots(t *testing.T) { } genesis := gspec.MustCommit(db) - blocks, _ := GenerateChain(params.TestChainConfig, genesis, engine, db, 1, func(i int, b *BlockGen) { + blocks, _ := GenerateChain(&chainConfig, genesis, engine, db, 1, func(i int, b *BlockGen) { b.SetCoinbase(common.Address{1}) // One transaction to AA, to kill it tx, _ := types.SignTx(types.NewTransaction(0, aa, @@ -2697,7 +2699,7 @@ func TestDeleteRecreateSlots(t *testing.T) { // Import the canonical chain diskdb := rawdb.NewMemoryDatabase() gspec.MustCommit(diskdb) - chain, err := NewBlockChain(diskdb, nil, params.TestChainConfig, engine, vm.Config{ + chain, err := NewBlockChain(diskdb, nil, &chainConfig, engine, vm.Config{ Debug: true, Tracer: logger.NewJSONLogger(nil, os.Stdout), }, nil, nil) @@ -2747,8 +2749,10 @@ func TestDeleteRecreateAccount(t *testing.T) { aaStorage[common.HexToHash("01")] = common.HexToHash("01") aaStorage[common.HexToHash("02")] = common.HexToHash("02") + chainConfig := *params.TestChainConfig + chainConfig.CancunBlock = nil gspec := &Genesis{ - Config: params.TestChainConfig, + Config: &chainConfig, Alloc: GenesisAlloc{ address: {Balance: funds}, // The address 0xAAAAA selfdestructs if called @@ -2763,7 +2767,7 @@ func TestDeleteRecreateAccount(t *testing.T) { } genesis := gspec.MustCommit(db) - blocks, _ := GenerateChain(params.TestChainConfig, genesis, engine, db, 1, func(i int, b *BlockGen) { + blocks, _ := GenerateChain(&chainConfig, genesis, engine, db, 1, func(i int, b *BlockGen) { b.SetCoinbase(common.Address{1}) // One transaction to AA, to kill it tx, _ := types.SignTx(types.NewTransaction(0, aa, @@ -2777,7 +2781,7 @@ func TestDeleteRecreateAccount(t *testing.T) { // Import the canonical chain diskdb := rawdb.NewMemoryDatabase() gspec.MustCommit(diskdb) - chain, err := NewBlockChain(diskdb, nil, params.TestChainConfig, engine, vm.Config{ + chain, err := NewBlockChain(diskdb, nil, &chainConfig, engine, vm.Config{ Debug: true, Tracer: logger.NewJSONLogger(nil, os.Stdout), }, nil, nil) @@ -2866,8 +2870,10 @@ func TestDeleteRecreateSlotsAcrossManyBlocks(t *testing.T) { initHash := crypto.Keccak256Hash(initCode) aa := crypto.CreateAddress2(bb, [32]byte{}, initHash[:]) t.Logf("Destination address: %x\n", aa) + chainConfig := *params.TestChainConfig + chainConfig.CancunBlock = nil gspec := &Genesis{ - Config: params.TestChainConfig, + Config: &chainConfig, Alloc: GenesisAlloc{ address: {Balance: funds}, // The address 0xAAAAA selfdestructs if called @@ -2922,7 +2928,7 @@ func TestDeleteRecreateSlotsAcrossManyBlocks(t *testing.T) { return tx } - blocks, _ := GenerateChain(params.TestChainConfig, genesis, engine, db, 150, func(i int, b *BlockGen) { + blocks, _ := GenerateChain(&chainConfig, genesis, engine, db, 150, func(i int, b *BlockGen) { var exp = new(expectation) exp.blocknum = i + 1 exp.values = make(map[int]int) @@ -2950,7 +2956,7 @@ func TestDeleteRecreateSlotsAcrossManyBlocks(t *testing.T) { // Import the canonical chain diskdb := rawdb.NewMemoryDatabase() gspec.MustCommit(diskdb) - chain, err := NewBlockChain(diskdb, nil, params.TestChainConfig, engine, vm.Config{ + chain, err := NewBlockChain(diskdb, nil, &chainConfig, engine, vm.Config{ //Debug: true, //Tracer: vm.NewJSONLogger(nil, os.Stdout), }, nil, nil) diff --git a/core/state/state_object.go b/core/state/state_object.go index 138fcbdec..73e22d68c 100644 --- a/core/state/state_object.go +++ b/core/state/state_object.go @@ -91,6 +91,9 @@ type stateObject struct { dirtyCode bool // true if the code was updated suicided bool deleted bool + + // Flag whether the object was created in the current transaction + created bool } // empty returns whether the account is considered empty. diff --git a/core/state/statedb.go b/core/state/statedb.go index fc252bbcd..7da8f1306 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -453,6 +453,17 @@ func (s *StateDB) Suicide(addr common.Address) bool { return true } +func (s *StateDB) Suicide6780(addr common.Address) { + stateObject := s.getStateObject(addr) + if stateObject == nil { + return + } + + if stateObject.created { + s.Suicide(addr) + } +} + // SetTransientState sets transient storage for a given account. It // adds the change to the journal so that it can be rolled back // to its previous value if there is a revert. @@ -623,6 +634,9 @@ func (s *StateDB) createObject(addr common.Address) (newobj, prev *stateObject) } else { s.journal.append(resetObjectChange{prev: prev, prevdestruct: prevdestruct}) } + + newobj.created = true + s.setStateObject(newobj) if prev != nil && !prev.deleted { return newobj, prev @@ -850,6 +864,7 @@ func (s *StateDB) Finalise(deleteEmptyObjects bool) { } else { obj.finalise(true) // Prefetch slots in the background } + obj.created = false s.stateObjectsPending[addr] = struct{}{} s.stateObjectsDirty[addr] = struct{}{} diff --git a/core/vm/eips.go b/core/vm/eips.go index 88b807bd5..c46301cdc 100644 --- a/core/vm/eips.go +++ b/core/vm/eips.go @@ -27,6 +27,7 @@ import ( var activators = map[int]func(*JumpTable){ 5656: enable5656, + 6780: enable6780, 3855: enable3855, 3860: enable3860, 3529: enable3529, @@ -307,3 +308,16 @@ func opMcopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]by scope.Memory.Copy(dst.Uint64(), src.Uint64(), length.Uint64()) return nil, nil } + +// enable6780 applies EIP-6780 (deactivate SELFDESTRUCT) +func enable6780(jt *JumpTable) { + jt[SELFDESTRUCT] = &operation{ + execute: opSelfdestruct6780, + dynamicGas: gasSelfdestructEIP3529, + constantGas: params.SelfdestructGasEIP150, + minStack: minStack(1, 0), + maxStack: maxStack(1, 0), + writes: true, + halts: true, + } +} diff --git a/core/vm/instructions.go b/core/vm/instructions.go index 00fafe359..c351af2a7 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -390,16 +390,21 @@ func opExtCodeCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) // opExtCodeHash returns the code hash of a specified account. // There are several cases when the function is called, while we can relay everything // to `state.GetCodeHash` function to ensure the correctness. -// (1) Caller tries to get the code hash of a normal contract account, state +// +// (1) Caller tries to get the code hash of a normal contract account, state +// // should return the relative code hash and set it as the result. // -// (2) Caller tries to get the code hash of a non-existent account, state should +// (2) Caller tries to get the code hash of a non-existent account, state should +// // return common.Hash{} and zero will be set as the result. // -// (3) Caller tries to get the code hash for an account without contract code, +// (3) Caller tries to get the code hash for an account without contract code, +// // state should return emptyCodeHash(0xc5d246...) as the result. // -// (4) Caller tries to get the code hash of a precompiled account, the result +// (4) Caller tries to get the code hash of a precompiled account, the result +// // should be zero or emptyCodeHash. // // It is worth noting that in order to avoid unnecessary create and clean, @@ -408,10 +413,12 @@ func opExtCodeCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) // If the precompile account is not transferred any amount on a private or // customized chain, the return value will be zero. // -// (5) Caller tries to get the code hash for an account which is marked as suicided +// (5) Caller tries to get the code hash for an account which is marked as suicided +// // in the current transaction, the code hash of this account should be returned. // -// (6) Caller tries to get the code hash for an account which is marked as deleted, +// (6) Caller tries to get the code hash for an account which is marked as deleted, +// // this account should be regarded as a non-existent account and zero should be returned. func opExtCodeHash(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { slot := scope.Stack.peek() @@ -810,6 +817,19 @@ func opSuicide(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([] return nil, nil } +func opSelfdestruct6780(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + beneficiary := scope.Stack.pop() + balance := interpreter.evm.StateDB.GetBalance(scope.Contract.Address()) + interpreter.evm.StateDB.SubBalance(scope.Contract.Address(), balance) + interpreter.evm.StateDB.AddBalance(beneficiary.Bytes20(), balance) + interpreter.evm.StateDB.Suicide6780(scope.Contract.Address()) + if tracer := interpreter.evm.Config.Tracer; tracer != nil { + tracer.CaptureEnter(SELFDESTRUCT, scope.Contract.Address(), beneficiary.Bytes20(), []byte{}, 0, balance) + tracer.CaptureExit([]byte{}, 0, nil) + } + return nil, nil +} + // following functions are used by the instruction jump table // make log instruction function diff --git a/core/vm/interface.go b/core/vm/interface.go index 639b5d14b..1e56827f2 100644 --- a/core/vm/interface.go +++ b/core/vm/interface.go @@ -54,6 +54,8 @@ type StateDB interface { Suicide(common.Address) bool HasSuicided(common.Address) bool + Suicide6780(common.Address) + // Exist reports whether the given account exists in state. // Notably this should also return true for suicided accounts. Exist(common.Address) bool diff --git a/core/vm/jump_table.go b/core/vm/jump_table.go index 0690a2a77..3600f3865 100644 --- a/core/vm/jump_table.go +++ b/core/vm/jump_table.go @@ -70,6 +70,7 @@ func newCancunInstructionSet() JumpTable { enable4844(&instructionSet) // EIP-4844 (BLOBHASH opcode) enable7516(&instructionSet) // EIP-7516 (BLOBBASEFEE opcode) enable5656(&instructionSet) // EIP-5656 (MCOPY opcode) + enable6780(&instructionSet) // EIP-6780 SELFDESTRUCT only in same transaction return instructionSet }