Skip to content

Commit

Permalink
all: impl eip-7702
Browse files Browse the repository at this point in the history
Co-authored-by: lightclient <[email protected]>
Co-authored-by: Mario Vega <[email protected]>
  • Loading branch information
2 people authored and buddh0 committed Sep 27, 2024
1 parent 27f67a5 commit f6ca7a0
Show file tree
Hide file tree
Showing 29 changed files with 1,115 additions and 67 deletions.
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)
}
}
34 changes: 33 additions & 1 deletion 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 @@ -1812,6 +1840,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
76 changes: 69 additions & 7 deletions core/state_transition.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package core

import (
"bytes"
"errors"
"fmt"
"math"
Expand All @@ -28,6 +29,7 @@ import (
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/crypto/kzg4844"
"github.com/ethereum/go-ethereum/crypto/secp256k1"
"github.com/ethereum/go-ethereum/params"
"github.com/holiman/uint256"
)
Expand Down Expand Up @@ -69,7 +71,7 @@ func (result *ExecutionResult) Revert() []byte {
}

// IntrinsicGas computes the 'intrinsic gas' for a message with the given data.
func IntrinsicGas(data []byte, accessList types.AccessList, isContractCreation bool, isHomestead, isEIP2028, isEIP3860 bool) (uint64, error) {
func IntrinsicGas(data []byte, accessList types.AccessList, authList types.AuthorizationList, isContractCreation, isHomestead, isEIP2028, isEIP3860 bool) (uint64, error) {
// Set the starting gas for the raw transaction
var gas uint64
if isContractCreation && isHomestead {
Expand Down Expand Up @@ -115,6 +117,9 @@ func IntrinsicGas(data []byte, accessList types.AccessList, isContractCreation b
gas += uint64(len(accessList)) * params.TxAccessListAddressGas
gas += uint64(accessList.StorageKeys()) * params.TxAccessListStorageKeyGas
}
if authList != nil {
gas += uint64(len(authList)) * params.CallNewAccountGas
}
return gas, nil
}

Expand Down Expand Up @@ -142,6 +147,7 @@ type Message struct {
AccessList types.AccessList
BlobGasFeeCap *big.Int
BlobHashes []common.Hash
AuthList types.AuthorizationList

// When SkipAccountChecks is true, the message nonce is not checked against the
// account nonce in state. It also disables checking that the sender is an EOA.
Expand All @@ -161,6 +167,7 @@ func TransactionToMessage(tx *types.Transaction, s types.Signer, baseFee *big.In
Value: tx.Value(),
Data: tx.Data(),
AccessList: tx.AccessList(),
AuthList: tx.AuthList(),
SkipAccountChecks: false,
BlobHashes: tx.BlobHashes(),
BlobGasFeeCap: tx.BlobGasFeeCap(),
Expand Down Expand Up @@ -290,10 +297,10 @@ func (st *StateTransition) preCheck() error {
msg.From.Hex(), stNonce)
}
// Make sure the sender is an EOA
codeHash := st.state.GetCodeHash(msg.From)
if codeHash != (common.Hash{}) && codeHash != types.EmptyCodeHash {
code := st.state.GetCode(msg.From)
if 0 < len(code) && !bytes.HasPrefix(code, []byte{0xef, 0x01, 0x00}) {
return fmt.Errorf("%w: address %v, codehash: %s", ErrSenderNoEOA,
msg.From.Hex(), codeHash)
msg.From.Hex(), st.state.GetCodeHash(msg.From))
}
}
// Make sure that transaction gasFeeCap is greater than the baseFee (post london)
Expand Down Expand Up @@ -353,6 +360,27 @@ func (st *StateTransition) preCheck() error {
}
}
}

// Check that auth list isn't empty.
if msg.AuthList != nil && len(msg.AuthList) == 0 {
return fmt.Errorf("%w: address %v", ErrEmptyAuthList, msg.From.Hex())
}

// TODO: remove after this spec change is merged:
// https://github.com/ethereum/EIPs/pull/8845
if msg.AuthList != nil {
var (
secp256k1N = secp256k1.S256().Params().N
secp256k1halfN = new(big.Int).Div(secp256k1N, big.NewInt(2))
)
for _, auth := range msg.AuthList {
if auth.V.Cmp(common.Big1) > 0 || auth.S.Cmp(secp256k1halfN) > 0 {
w := fmt.Errorf("set code transaction with invalid auth signature")
return fmt.Errorf("%w: address %v", w, msg.From.Hex())
}
}
}

return st.buyGas()
}

Expand Down Expand Up @@ -406,7 +434,7 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) {
}
}
// Check clauses 4-5, subtract intrinsic gas if everything is correct
gas, err := IntrinsicGas(msg.Data, msg.AccessList, contractCreation, rules.IsHomestead, rules.IsIstanbul, rules.IsShanghai)
gas, err := IntrinsicGas(msg.Data, msg.AccessList, msg.AuthList, contractCreation, rules.IsHomestead, rules.IsIstanbul, rules.IsShanghai)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -434,15 +462,49 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) {
// - reset transient storage(eip 1153)
st.state.Prepare(rules, msg.From, st.evm.Context.Coinbase, msg.To, vm.ActivePrecompiles(rules), msg.AccessList)

if !contractCreation {
// Increment the nonce for the next transaction
st.state.SetNonce(msg.From, st.state.GetNonce(sender.Address())+1)
}

// Check authorizations list validity.
if msg.AuthList != nil {
for _, auth := range msg.AuthList {
// Verify chain ID is 0 or equal to current chain ID.
if auth.ChainID.Sign() != 0 && st.evm.ChainConfig().ChainID.Cmp(auth.ChainID) != 0 {
continue
}
authority, err := auth.Authority()
if err != nil {
continue
}
// Check the authority account 1) doesn't have code or has exisiting
// delegation 2) matches the auth's nonce
st.state.AddAddressToAccessList(authority)
code := st.state.GetCode(authority)
if _, ok := types.ParseDelegation(code); len(code) != 0 && !ok {
continue
}
if have := st.state.GetNonce(authority); have != auth.Nonce {
continue
}
// If the account already exists in state, refund the new account cost
// charged in the initrinsic calculation.
if exists := st.state.Exist(authority); exists {
st.state.AddRefund(params.CallNewAccountGas - params.TxAuthTupleGas)
}
st.state.SetNonce(authority, auth.Nonce+1)
st.state.SetCode(authority, types.AddressToDelegation(auth.Address))
}
}

var (
ret []byte
vmerr error // vm errors do not effect consensus and are therefore not assigned to err
)
if contractCreation {
ret, _, st.gasRemaining, vmerr = st.evm.Create(sender, msg.Data, st.gasRemaining, value)
} else {
// Increment the nonce for the next transaction
st.state.SetNonce(msg.From, st.state.GetNonce(sender.Address())+1)
ret, st.gasRemaining, vmerr = st.evm.Call(sender, st.to(), msg.Data, st.gasRemaining, value)
}

Expand Down
5 changes: 3 additions & 2 deletions core/txpool/legacypool/legacypool.go
Original file line number Diff line number Diff line change
Expand Up @@ -290,7 +290,7 @@ func New(config Config, chain BlockChain) *LegacyPool {
// pool, specifically, whether it is a Legacy, AccessList or Dynamic transaction.
func (pool *LegacyPool) Filter(tx *types.Transaction) bool {
switch tx.Type() {
case types.LegacyTxType, types.AccessListTxType, types.DynamicFeeTxType:
case types.SetCodeTxType, types.LegacyTxType, types.AccessListTxType, types.DynamicFeeTxType:
return true
default:
return false
Expand Down Expand Up @@ -668,7 +668,8 @@ func (pool *LegacyPool) validateTxBasics(tx *types.Transaction, local bool) erro
Accept: 0 |
1<<types.LegacyTxType |
1<<types.AccessListTxType |
1<<types.DynamicFeeTxType,
1<<types.DynamicFeeTxType |
1<<types.SetCodeTxType,
MaxSize: txMaxSize,
MinTip: pool.gasTip.Load().ToBig(),
MaxGas: pool.GetMaxGas(),
Expand Down
Loading

0 comments on commit f6ca7a0

Please sign in to comment.