Skip to content

Commit

Permalink
core/state, core/vm: implement EIP 6780 (#27189)
Browse files Browse the repository at this point in the history
commit ethereum/go-ethereum@988d84a.

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 <martin@swende.se>
  • Loading branch information
2 people authored and minh-bq committed Sep 16, 2024
1 parent 61620ea commit 65ce9eb
Show file tree
Hide file tree
Showing 7 changed files with 76 additions and 15 deletions.
24 changes: 15 additions & 9 deletions core/blockchain_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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,
Expand All @@ -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)
Expand Down Expand Up @@ -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
Expand All @@ -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,
Expand All @@ -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)
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down
3 changes: 3 additions & 0 deletions core/state/state_object.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
15 changes: 15 additions & 0 deletions core/state/statedb.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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{}{}

Expand Down
14 changes: 14 additions & 0 deletions core/vm/eips.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (

var activators = map[int]func(*JumpTable){
5656: enable5656,
6780: enable6780,
3855: enable3855,
3860: enable3860,
3529: enable3529,
Expand Down Expand Up @@ -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,
}
}
32 changes: 26 additions & 6 deletions core/vm/instructions.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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()
Expand Down Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions core/vm/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions core/vm/jump_table.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down

0 comments on commit 65ce9eb

Please # to comment.