Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

BEP-441: Implement EIP-7702: Set EOA account code #2724

Draft
wants to merge 3 commits into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion cmd/evm/internal/t8ntool/transaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ func Transaction(ctx *cli.Context) error {
r.Address = sender
}
// Check intrinsic gas
if gas, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.To() == nil,
if gas, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.AuthList(), tx.To() == nil,
chainConfig.IsHomestead(new(big.Int)), chainConfig.IsIstanbul(new(big.Int)), chainConfig.IsShanghai(new(big.Int), 0)); err != nil {
r.Error = err
results = append(results, r)
Expand Down
2 changes: 1 addition & 1 deletion cmd/evm/staterunner.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ func runStateTest(fname string, cfg vm.Config, jsonOut, dump bool) error {
}
var testsByName map[string]tests.StateTest
if err := json.Unmarshal(src, &testsByName); err != nil {
return err
return fmt.Errorf("unable to read test file: %w", err)
}

// Iterate over all the tests, run them and aggregate the results
Expand Down
2 changes: 1 addition & 1 deletion core/bench_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ func genValueTx(nbytes int) func(int, *BlockGen) {
return func(i int, gen *BlockGen) {
toaddr := common.Address{}
data := make([]byte, nbytes)
gas, _ := IntrinsicGas(data, nil, false, false, false, false)
gas, _ := IntrinsicGas(data, nil, nil, false, false, false, false)
signer := gen.Signer()
gasPrice := big.NewInt(0)
if gen.header.BaseFee != nil {
Expand Down
3 changes: 3 additions & 0 deletions core/error.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,4 +119,7 @@ var (

// ErrBlobTxCreate is returned if a blob transaction has no explicit to field.
ErrBlobTxCreate = errors.New("blob transaction of type create")

// ErrEmptyAuthList is returned if a set code transaction has an empty auth list.
ErrEmptyAuthList = errors.New("set code transaction with empty auth list")
)
131 changes: 131 additions & 0 deletions core/setcode_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
package core

import (
"bytes"
"math/big"
"os"
"testing"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/consensus/beacon"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/eth/tracers/logger"
"github.com/ethereum/go-ethereum/params"
"github.com/holiman/uint256"
)

func TestEIP7702(t *testing.T) {
var (
aa = common.HexToAddress("0x000000000000000000000000000000000000aaaa")
bb = common.HexToAddress("0x000000000000000000000000000000000000bbbb")
engine = beacon.NewFaker()

// A sender who makes transactions, has some funds
key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
key2, _ = crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a")
addr1 = crypto.PubkeyToAddress(key1.PublicKey)
addr2 = crypto.PubkeyToAddress(key2.PublicKey)
funds = new(big.Int).Mul(common.Big1, big.NewInt(params.Ether))
config = *params.AllEthashProtocolChanges
gspec = &Genesis{
Config: &config,
Alloc: types.GenesisAlloc{
addr1: {Balance: funds},
addr2: {Balance: funds},
// The address 0xAAAA sstores 1 into slot 2.
aa: {
Code: []byte{
byte(vm.PC), // [0]
byte(vm.DUP1), // [0,0]
byte(vm.DUP1), // [0,0,0]
byte(vm.DUP1), // [0,0,0,0]
byte(vm.PUSH1), 0x01, // [0,0,0,0,1] (value)
byte(vm.PUSH20), addr2[0], addr2[1], addr2[2], addr2[3], addr2[4], addr2[5], addr2[6], addr2[7], addr2[8], addr2[9], addr2[10], addr2[11], addr2[12], addr2[13], addr2[14], addr2[15], addr2[16], addr2[17], addr2[18], addr2[19],
byte(vm.GAS),
byte(vm.CALL),
byte(vm.STOP),
},
Nonce: 0,
Balance: big.NewInt(0),
},
// The address 0xBBBB sstores 42 into slot 42.
bb: {
Code: []byte{
byte(vm.PUSH1), 0x42,
byte(vm.DUP1),
byte(vm.SSTORE),
byte(vm.STOP),
},
Nonce: 0,
Balance: big.NewInt(0),
},
},
}
)

gspec.Config.BerlinBlock = common.Big0
gspec.Config.LondonBlock = common.Big0
gspec.Config.TerminalTotalDifficulty = common.Big0
gspec.Config.TerminalTotalDifficultyPassed = true
gspec.Config.ShanghaiTime = u64(0)
gspec.Config.CancunTime = u64(0)
gspec.Config.PragueTime = u64(0)
signer := types.LatestSigner(gspec.Config)

auth1, _ := types.SignAuth(&types.Authorization{
ChainID: new(big.Int).Set(gspec.Config.ChainID),
Address: aa,
Nonce: 1,
}, key1)

auth2, _ := types.SignAuth(&types.Authorization{
ChainID: new(big.Int),
Address: bb,
Nonce: 0,
}, key2)

_, blocks, _ := GenerateChainWithGenesis(gspec, engine, 1, func(i int, b *BlockGen) {
b.SetCoinbase(aa)
txdata := &types.SetCodeTx{
ChainID: uint256.MustFromBig(gspec.Config.ChainID),
Nonce: 0,
To: addr1,
Gas: 500000,
GasFeeCap: uint256.MustFromBig(newGwei(5)),
GasTipCap: uint256.NewInt(2),
AuthList: []*types.Authorization{auth1, auth2},
}
tx := types.NewTx(txdata)
tx, err := types.SignTx(tx, signer, key1)
if err != nil {
t.Fatalf("%s", err)
}
b.AddTx(tx)
})
chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), nil, gspec, nil, engine, vm.Config{Tracer: logger.NewMarkdownLogger(&logger.Config{}, os.Stderr)}, nil, nil)
if err != nil {
t.Fatalf("failed to create tester chain: %v", err)
}
defer chain.Stop()
if n, err := chain.InsertChain(blocks); err != nil {
t.Fatalf("block %d: failed to insert into chain: %v", n, err)
}

var (
state, _ = chain.State()
fortyTwo = common.BytesToHash([]byte{0x42})
actual = state.GetState(addr2, fortyTwo)
)
if code, want := state.GetCode(addr1), types.AddressToDelegation(auth1.Address); !bytes.Equal(code, want) {
t.Fatalf("addr1 code incorrect: got %s, want %s", common.Bytes2Hex(code), common.Bytes2Hex(want))
}
if code, want := state.GetCode(addr2), types.AddressToDelegation(auth2.Address); !bytes.Equal(code, want) {
t.Fatalf("addr2 code incorrect: got %s, want %s", common.Bytes2Hex(code), common.Bytes2Hex(want))
}
if actual.Cmp(fortyTwo) != 0 {
t.Fatalf("addr2 storage wrong: expected %d, got %d", fortyTwo, actual)
}
}
11 changes: 6 additions & 5 deletions core/state/state_object.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,10 +100,7 @@ func (s *stateObject) empty() bool {

// newObject creates a state object.
func newObject(db *StateDB, address common.Address, acct *types.StateAccount) *stateObject {
var (
origin = acct
created = acct == nil // true if the account was not existent
)
origin := acct
if acct == nil {
acct = types.NewEmptyStateAccount()
}
Expand All @@ -123,7 +120,6 @@ func newObject(db *StateDB, address common.Address, acct *types.StateAccount) *s
originStorage: make(Storage),
pendingStorage: make(Storage),
dirtyStorage: make(Storage),
created: created,
}
}

Expand Down Expand Up @@ -300,6 +296,10 @@ func (s *stateObject) finalise(prefetch bool) {
if len(s.dirtyStorage) > 0 {
s.dirtyStorage = make(Storage)
}
// Revoke the flag at the end of the transaction. It finalizes the status
// of the newly-created object as it's no longer eligible for self-destruct
// by EIP-6780. For non-newly-created objects, it's a no-op.
s.created = false
}

// updateTrie is responsible for persisting cached storage changes into the
Expand Down Expand Up @@ -528,6 +528,7 @@ func (s *stateObject) deepCopy(db *StateDB) *stateObject {
obj.selfDestructed = s.selfDestructed
obj.dirtyCode = s.dirtyCode
obj.deleted = s.deleted
obj.created = s.created
return obj
}

Expand Down
47 changes: 45 additions & 2 deletions core/state/statedb.go
Original file line number Diff line number Diff line change
Expand Up @@ -490,7 +490,23 @@ func (s *StateDB) GetCodeHash(addr common.Address) common.Hash {
return common.Hash{}
}

// GetState retrieves a value from the given account's storage trie.
func (s *StateDB) ResolveCode(addr common.Address) []byte {
stateObject := s.resolveStateObject(addr)
if stateObject != nil {
return stateObject.Code()
}
return nil
}

func (s *StateDB) ResolveCodeHash(addr common.Address) common.Hash {
stateObject := s.resolveStateObject(addr)
if stateObject != nil {
return common.BytesToHash(stateObject.CodeHash())
}
return common.Hash{}
}

// GetState retrieves the value associated with the specific key.
func (s *StateDB) GetState(addr common.Address, hash common.Hash) common.Hash {
stateObject := s.getStateObject(addr)
if stateObject != nil {
Expand Down Expand Up @@ -773,6 +789,18 @@ func (s *StateDB) getDeletedStateObject(addr common.Address) *stateObject {
return obj
}

func (s *StateDB) resolveStateObject(addr common.Address) *stateObject {
obj := s.getStateObject(addr)
if obj == nil {
return nil
}
addr, ok := types.ParseDelegation(obj.Code())
if !ok {
return obj
}
return s.getStateObject(addr)
}

func (s *StateDB) setStateObject(object *stateObject) {
s.stateObjects[object.Address()] = object
}
Expand Down Expand Up @@ -845,6 +873,18 @@ func (s *StateDB) CreateAccount(addr common.Address) {
}
}

// CreateContract is used whenever a contract is created. This may be preceded
// by CreateAccount, but that is not required if it already existed in the
// state due to funds sent beforehand.
// This operation sets the 'created'-flag, which is required in order to
// correctly handle EIP-6780 'delete-in-same-transaction' logic.
func (s *StateDB) CreateContract(addr common.Address) {
obj := s.getStateObject(addr)
if !obj.created {
obj.created = true
}
}

// Copy creates a deep, independent copy of the state.
// Snapshots of the copied state cannot be applied to the copy.
func (s *StateDB) Copy() *StateDB {
Expand Down Expand Up @@ -1045,7 +1085,6 @@ 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 Expand Up @@ -1812,6 +1851,10 @@ func (s *StateDB) Prepare(rules params.Rules, sender, coinbase common.Address, d
al.AddAddress(sender)
if dst != nil {
al.AddAddress(*dst)
// TODO: is this right?
if addr, ok := types.ParseDelegation(s.GetCode(*dst)); ok {
al.AddAddress(addr)
}
// If it's a create-tx, the destination will be added inside evm.create
}
for _, addr := range precompiles {
Expand Down
20 changes: 20 additions & 0 deletions core/state/statedb_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,26 @@ func newTestAction(addr common.Address, r *rand.Rand) testAction {
s.CreateAccount(addr)
},
},
{
name: "CreateContract",
fn: func(a testAction, s *StateDB) {
if !s.Exist(addr) {
s.CreateAccount(addr)
}
contractHash := s.GetCodeHash(addr)
emptyCode := contractHash == (common.Hash{}) || contractHash == types.EmptyCodeHash
storageRoot := s.GetStorageRoot(addr)
emptyStorage := storageRoot == (common.Hash{}) || storageRoot == types.EmptyRootHash
if s.GetNonce(addr) == 0 && emptyCode && emptyStorage {
s.CreateContract(addr)
// We also set some code here, to prevent the
// CreateContract action from being performed twice in a row,
// which would cause a difference in state when unrolling
// the journal.
s.SetCode(addr, []byte{1})
}
},
},
{
name: "SelfDestruct",
fn: func(a testAction, s *StateDB) {
Expand Down
Loading
Loading