From 731ae829445f2052ca9797afae12c6c034741c59 Mon Sep 17 00:00:00 2001 From: lightclient Date: Wed, 26 Jun 2024 17:12:17 -0600 Subject: [PATCH] all: impl eip-7702 Co-authored-by: lightclient Co-authored-by: Mario Vega --- cmd/evm/internal/t8ntool/transaction.go | 2 +- cmd/evm/staterunner.go | 2 +- common/types.go | 3 + core/bench_test.go | 2 +- core/blockchain_test.go | 107 +++++++++++ core/error.go | 7 + core/state/statedb.go | 44 +++++ core/state/statedb_hooked.go | 7 + core/state_processor_test.go | 2 +- core/state_transition.go | 87 ++++++++- core/txpool/legacypool/legacypool.go | 5 +- core/txpool/validation.go | 2 +- core/types/gen_authorization.go | 75 ++++++++ core/types/receipt.go | 4 +- core/types/transaction.go | 12 ++ core/types/transaction_marshalling.go | 124 ++++++++++-- core/types/transaction_signing.go | 77 +++++++- core/types/tx_setcode.go | 238 ++++++++++++++++++++++++ core/types/tx_setcode_test.go | 70 +++++++ core/verkle_witness_test.go | 4 +- core/vm/eips.go | 27 ++- core/vm/evm.go | 12 +- core/vm/instructions.go | 11 +- core/vm/interface.go | 3 + core/vm/jump_table.go | 1 + core/vm/operations_acl.go | 100 ++++++++++ internal/ethapi/api.go | 63 ++++--- params/protocol_params.go | 9 +- tests/gen_stauthorization.go | 74 ++++++++ tests/gen_sttransaction.go | 6 + tests/state_test_util.go | 38 ++++ tests/transaction_test_util.go | 2 +- 32 files changed, 1146 insertions(+), 74 deletions(-) create mode 100644 core/types/gen_authorization.go create mode 100644 core/types/tx_setcode.go create mode 100644 core/types/tx_setcode_test.go create mode 100644 tests/gen_stauthorization.go diff --git a/cmd/evm/internal/t8ntool/transaction.go b/cmd/evm/internal/t8ntool/transaction.go index 7f66ba4d85..64e21b24fd 100644 --- a/cmd/evm/internal/t8ntool/transaction.go +++ b/cmd/evm/internal/t8ntool/transaction.go @@ -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) diff --git a/cmd/evm/staterunner.go b/cmd/evm/staterunner.go index d0a0d3287c..ae4ede08f8 100644 --- a/cmd/evm/staterunner.go +++ b/cmd/evm/staterunner.go @@ -149,7 +149,7 @@ func runStateTest(ctx *cli.Context, fname string, cfg vm.Config, dump bool, benc } 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) } matchingTests := collectMatchedSubtests(ctx, testsByName) diff --git a/common/types.go b/common/types.go index fdb25f1b34..8e8e968ed5 100644 --- a/common/types.go +++ b/common/types.go @@ -45,6 +45,9 @@ var ( hashT = reflect.TypeOf(Hash{}) addressT = reflect.TypeOf(Address{}) + // ZeroAddress represents the zero address value. + ZeroAddress = Address{} + // MaxAddress represents the maximum possible address value. MaxAddress = HexToAddress("0xffffffffffffffffffffffffffffffffffffffff") diff --git a/core/bench_test.go b/core/bench_test.go index 6d518e8d3b..d376830318 100644 --- a/core/bench_test.go +++ b/core/bench_test.go @@ -90,7 +90,7 @@ func genValueTx(nbytes int) func(int, *BlockGen) { data := make([]byte, nbytes) return func(i int, gen *BlockGen) { toaddr := common.Address{} - 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 { diff --git a/core/blockchain_test.go b/core/blockchain_test.go index dc391bb520..5feb023e7c 100644 --- a/core/blockchain_test.go +++ b/core/blockchain_test.go @@ -17,6 +17,7 @@ package core import ( + "bytes" "errors" "fmt" gomath "math" @@ -4232,3 +4233,109 @@ func TestPragueRequests(t *testing.T) { t.Fatalf("block %d: failed to insert into chain: %v", n, err) } } + +// TestEIP7702 deploys two delegation designations and calls them. It writes one +// value to storage which is verified after. +func TestEIP7702(t *testing.T) { + var ( + config = *params.MergedTestChainConfig + signer = types.LatestSigner(&config) + engine = beacon.NewFaker() + key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + key2, _ = crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a") + addr1 = crypto.PubkeyToAddress(key1.PublicKey) + addr2 = crypto.PubkeyToAddress(key2.PublicKey) + aa = common.HexToAddress("0x000000000000000000000000000000000000aaaa") + bb = common.HexToAddress("0x000000000000000000000000000000000000bbbb") + funds = new(big.Int).Mul(common.Big1, big.NewInt(params.Ether)) + ) + 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), + }, + }, + } + + // Sign authorization tuples. + auth1, _ := types.SignAuth(&types.Authorization{ + ChainID: gspec.Config.ChainID.Uint64(), + Address: aa, + Nonce: 1, + }, key1) + + auth2, _ := types.SignAuth(&types.Authorization{ + ChainID: 0, + Address: bb, + Nonce: 0, + }, key2) + + _, blocks, _ := GenerateChainWithGenesis(gspec, engine, 1, func(i int, b *BlockGen) { + b.SetCoinbase(aa) + txdata := &types.SetCodeTx{ + ChainID: gspec.Config.ChainID.Uint64(), + Nonce: 0, + To: addr1, + Gas: 500000, + GasFeeCap: uint256.MustFromBig(newGwei(5)), + GasTipCap: uint256.NewInt(2), + AuthList: []*types.Authorization{auth1, auth2}, + } + tx := types.MustSignNewTx(key1, signer, txdata) + b.AddTx(tx) + }) + chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), nil, gspec, nil, engine, vm.Config{}, 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) + } + + // Verify delegation designations were deployed. + state, _ := chain.State() + code, want := state.GetCode(addr1), types.AddressToDelegation(auth1.Address) + if !bytes.Equal(code, want) { + t.Fatalf("addr1 code incorrect: got %s, want %s", common.Bytes2Hex(code), common.Bytes2Hex(want)) + } + code, want = state.GetCode(addr2), types.AddressToDelegation(auth2.Address) + if !bytes.Equal(code, want) { + t.Fatalf("addr2 code incorrect: got %s, want %s", common.Bytes2Hex(code), common.Bytes2Hex(want)) + } + // Verify delegation executed the correct code. + var ( + fortyTwo = common.BytesToHash([]byte{0x42}) + actual = state.GetState(addr2, fortyTwo) + ) + if actual.Cmp(fortyTwo) != 0 { + t.Fatalf("addr2 storage wrong: expected %d, got %d", fortyTwo, actual) + } +} diff --git a/core/error.go b/core/error.go index 161538fe43..4cccc344cf 100644 --- a/core/error.go +++ b/core/error.go @@ -112,4 +112,11 @@ 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") + + // ErrAuthSignatureVeryHigh is returned if a set code transaction has a + // signature with R or S larger than 2^256-1. + ErrAuthSignatureVeryHigh = errors.New("set code transaction has authorization with R or S value greater than 2^256 - 1") ) diff --git a/core/state/statedb.go b/core/state/statedb.go index d855e5626d..c711b18211 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -368,6 +368,24 @@ func (s *StateDB) GetCodeHash(addr common.Address) common.Hash { return common.Hash{} } +// ResolveCode retrieves the code at addr, resolving any delegation designations +// that may exist. +func (s *StateDB) ResolveCode(addr common.Address) []byte { + if obj := s.resolveCode(addr); obj != nil { + return obj.Code() + } + return nil +} + +// ResolveCodeHash retrieves the code at addr, resolving any delegation +// designations that may exist. +func (s *StateDB) ResolveCodeHash(addr common.Address) common.Hash { + if obj := s.resolveCode(addr); obj != nil { + return common.BytesToHash(obj.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) @@ -603,6 +621,28 @@ func (s *StateDB) getStateObject(addr common.Address) *stateObject { return obj } +// resolveStateObject follows delegation designations to resolve a state object +// given by the address, returning nil if the object is not found or was deleted +// in this execution context. +func (s *StateDB) resolveCode(addr common.Address) *stateObject { + obj := s.getStateObject(addr) + if obj == nil { + return nil + } + if s.witness != nil { + s.witness.AddCode(obj.code) + } + addr, ok := types.ParseDelegation(obj.Code()) + if !ok { + return obj + } + obj = s.getStateObject(addr) + if s.witness != nil { + s.witness.AddCode(obj.code) + } + return obj +} + func (s *StateDB) setStateObject(object *stateObject) { s.stateObjects[object.Address()] = object } @@ -1334,6 +1374,10 @@ func (s *StateDB) Prepare(rules params.Rules, sender, coinbase common.Address, d al.AddAddress(sender) if dst != nil { al.AddAddress(*dst) + // If the dst has a delegation, also warm its target. + 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 { diff --git a/core/state/statedb_hooked.go b/core/state/statedb_hooked.go index 26d0217099..6bf7c0b28e 100644 --- a/core/state/statedb_hooked.go +++ b/core/state/statedb_hooked.go @@ -73,6 +73,13 @@ func (s *hookedStateDB) GetCodeSize(addr common.Address) int { return s.inner.GetCodeSize(addr) } +func (s *hookedStateDB) ResolveCodeHash(addr common.Address) common.Hash { + return s.inner.ResolveCodeHash(addr) +} +func (s *hookedStateDB) ResolveCode(addr common.Address) []byte { + return s.inner.ResolveCode(addr) +} + func (s *hookedStateDB) AddRefund(u uint64) { s.inner.AddRefund(u) } diff --git a/core/state_processor_test.go b/core/state_processor_test.go index f3d2304690..9a4fd1161c 100644 --- a/core/state_processor_test.go +++ b/core/state_processor_test.go @@ -337,7 +337,7 @@ func TestStateProcessorErrors(t *testing.T) { txs: []*types.Transaction{ mkDynamicTx(0, common.Address{}, params.TxGas-1000, big.NewInt(0), big.NewInt(0)), }, - want: "could not apply tx 0 [0x88626ac0d53cb65308f2416103c62bb1f18b805573d4f96a3640bbbfff13c14f]: sender not an eoa: address 0x71562b71999873DB5b286dF957af199Ec94617F7, codehash: 0x9280914443471259d4570a8661015ae4a5b80186dbc619658fb494bebc3da3d1", + want: "could not apply tx 0 [0x88626ac0d53cb65308f2416103c62bb1f18b805573d4f96a3640bbbfff13c14f]: sender not an eoa: address 0x71562b71999873DB5b286dF957af199Ec94617F7, len(code): 4", }, } { block := GenerateBadBlock(gspec.ToBlock(), beacon.New(ethash.NewFaker()), tt.txs, gspec.Config, false) diff --git a/core/state_transition.go b/core/state_transition.go index 4bd3c00167..d01a9bb3e0 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -17,6 +17,7 @@ package core import ( + "bytes" "fmt" "math" "math/big" @@ -67,7 +68,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, 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 { @@ -113,6 +114,9 @@ func IntrinsicGas(data []byte, accessList types.AccessList, isContractCreation, gas += uint64(len(accessList)) * params.TxAccessListAddressGas gas += uint64(accessList.StorageKeys()) * params.TxAccessListStorageKeyGas } + if authList != nil { + gas += uint64(len(authList)) * params.CallNewAccountGas + } return gas, nil } @@ -140,6 +144,7 @@ type Message struct { AccessList types.AccessList BlobGasFeeCap *big.Int BlobHashes []common.Hash + AuthList types.AuthorizationList // When SkipNonceChecks is true, the message nonce is not checked against the // account nonce in state. @@ -162,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(), SkipNonceChecks: false, SkipFromEOACheck: false, BlobHashes: tx.BlobHashes(), @@ -302,10 +308,9 @@ func (st *StateTransition) preCheck() error { } if !msg.SkipFromEOACheck { // Make sure the sender is an EOA - codeHash := st.state.GetCodeHash(msg.From) - if codeHash != (common.Hash{}) && codeHash != types.EmptyCodeHash { - return fmt.Errorf("%w: address %v, codehash: %s", ErrSenderNoEOA, - msg.From.Hex(), codeHash) + code := st.state.GetCode(msg.From) + if len(code) > 0 && !bytes.HasPrefix(code, types.DelegationPrefix) { + return fmt.Errorf("%w: address %v, len(code): %d", ErrSenderNoEOA, msg.From.Hex(), len(code)) } } // Make sure that transaction gasFeeCap is greater than the baseFee (post london) @@ -365,6 +370,15 @@ func (st *StateTransition) preCheck() error { } } } + // Check that EIP-7702 authorization list signatures are well formed. + for i, auth := range msg.AuthList { + switch { + case auth.R.BitLen() > 256: + return fmt.Errorf("%w: address %v, authorization %d", ErrAuthSignatureVeryHigh, msg.From.Hex(), i) + case auth.S.BitLen() > 256: + return fmt.Errorf("%w: address %v, authorization %d", ErrAuthSignatureVeryHigh, msg.From.Hex(), i) + } + } return st.buyGas() } @@ -402,7 +416,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 } @@ -436,11 +450,70 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) { return nil, fmt.Errorf("%w: code size %v limit %v", ErrMaxInitCodeSizeExceeded, len(msg.Data), params.MaxInitCodeSize) } + // If an authorization list exists, verify it is not empty. + if msg.AuthList != nil && len(msg.AuthList) == 0 { + return nil, fmt.Errorf("%w: address %v", ErrEmptyAuthList, msg.From.Hex()) + } + // Execute the preparatory steps for state transition which includes: // - prepare accessList(post-berlin) // - 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 != 0 && st.evm.ChainConfig().ChainID.Uint64() != auth.ChainID { + continue + } + // Limit nonce to 2^64-1 per EIP-2681. + if auth.Nonce+1 < auth.Nonce { + continue + } + // Validate signature values and recover authority. + 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 intrinsic calculation. + if exists := st.state.Exist(authority); exists { + st.state.AddRefund(params.CallNewAccountGas - params.TxAuthTupleGas) + } + st.state.SetNonce(authority, auth.Nonce+1) + delegation := types.AddressToDelegation(auth.Address) + if auth.Address == common.ZeroAddress { + // If the delegation is for the zero address, completely clear all + // delegations from the account. + delegation = []byte{} + } + st.state.SetCode(authority, delegation) + + // Usually the transaction destination and delegation target are added to + // the access list in statedb.Prepare(..), however if the delegation is in + // the same transaction we need add here as Prepare already happened. + if *msg.To == authority { + st.state.AddAddressToAccessList(auth.Address) + } + } + } + var ( ret []byte vmerr error // vm errors do not effect consensus and are therefore not assigned to err @@ -448,8 +521,6 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) { 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) } diff --git a/core/txpool/legacypool/legacypool.go b/core/txpool/legacypool/legacypool.go index 89ff86df02..e95b3496a3 100644 --- a/core/txpool/legacypool/legacypool.go +++ b/core/txpool/legacypool/legacypool.go @@ -279,7 +279,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 @@ -611,7 +611,8 @@ func (pool *LegacyPool) validateTxBasics(tx *types.Transaction, local bool) erro Accept: 0 | 1<. + +package types + +import ( + "bytes" + "crypto/ecdsa" + "errors" + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/rlp" + "github.com/holiman/uint256" +) + +// DelegationPrefix is used by code to denote the account is delegating to +// another account. +var DelegationPrefix = []byte{0xef, 0x01, 0x00} + +// ParseDelegation tries to parse the address from a delegation slice. +func ParseDelegation(b []byte) (common.Address, bool) { + if len(b) != 23 || !bytes.HasPrefix(b, DelegationPrefix) { + return common.Address{}, false + } + return common.BytesToAddress(b[len(DelegationPrefix):]), true +} + +// AddressToDelegation adds the delegation prefix to the specified address. +func AddressToDelegation(addr common.Address) []byte { + return append(DelegationPrefix, addr.Bytes()...) +} + +// SetCodeTx implements the EIP-7702 transaction type which temporarily installs +// the code at the signer's address. +type SetCodeTx struct { + ChainID uint64 + Nonce uint64 + GasTipCap *uint256.Int // a.k.a. maxPriorityFeePerGas + GasFeeCap *uint256.Int // a.k.a. maxFeePerGas + Gas uint64 + To common.Address + Value *uint256.Int + Data []byte + AccessList AccessList + AuthList AuthorizationList + + // Signature values + V *uint256.Int `json:"v" gencodec:"required"` + R *uint256.Int `json:"r" gencodec:"required"` + S *uint256.Int `json:"s" gencodec:"required"` +} + +//go:generate go run github.com/fjl/gencodec -type Authorization -field-override authorizationMarshaling -out gen_authorization.go + +// Authorization is an authorization from an account to deploy code at it's +// address. +type Authorization struct { + ChainID uint64 `json:"chainId" gencodec:"required"` + Address common.Address `json:"address" gencodec:"required"` + Nonce uint64 `json:"nonce" gencodec:"required"` + V uint8 `json:"v" gencodec:"required"` + R *big.Int `json:"r" gencodec:"required"` + S *big.Int `json:"s" gencodec:"required"` +} + +// field type overrides for gencodec +type authorizationMarshaling struct { + ChainID hexutil.Uint64 + Nonce hexutil.Uint64 + V hexutil.Uint64 + R *hexutil.Big + S *hexutil.Big +} + +// SignAuth signs the provided authorization. +func SignAuth(auth *Authorization, prv *ecdsa.PrivateKey) (*Authorization, error) { + h := prefixedRlpHash( + 0x05, + []interface{}{ + auth.ChainID, + auth.Address, + auth.Nonce, + }) + + sig, err := crypto.Sign(h[:], prv) + if err != nil { + return nil, err + } + return auth.withSignature(sig), nil +} + +// withSignature updates the signature of an Authorization to be equal the +// decoded signature provided in sig. +func (a *Authorization) withSignature(sig []byte) *Authorization { + r, s, _ := decodeSignature(sig) + cpy := Authorization{ + ChainID: a.ChainID, + Address: a.Address, + Nonce: a.Nonce, + V: sig[64], + R: r, + S: s, + } + return &cpy +} + +type AuthorizationList []*Authorization + +// Authority recovers the authorizing +func (a Authorization) Authority() (common.Address, error) { + sighash := prefixedRlpHash( + 0x05, + []interface{}{ + a.ChainID, + a.Address, + a.Nonce, + }) + if !crypto.ValidateSignatureValues(a.V, a.R, a.S, true) { + return common.Address{}, ErrInvalidSig + } + // encode the signature in uncompressed format + r, s := a.R.Bytes(), a.S.Bytes() + sig := make([]byte, crypto.SignatureLength) + copy(sig[32-len(r):32], r) + copy(sig[64-len(s):64], s) + sig[64] = a.V + // recover the public key from the signature + pub, err := crypto.Ecrecover(sighash[:], sig) + if err != nil { + return common.Address{}, err + } + if len(pub) == 0 || pub[0] != 4 { + return common.Address{}, errors.New("invalid public key") + } + var addr common.Address + copy(addr[:], crypto.Keccak256(pub[1:])[12:]) + return addr, nil +} + +// copy creates a deep copy of the transaction data and initializes all fields. +func (tx *SetCodeTx) copy() TxData { + cpy := &SetCodeTx{ + Nonce: tx.Nonce, + To: tx.To, + Data: common.CopyBytes(tx.Data), + Gas: tx.Gas, + // These are copied below. + AccessList: make(AccessList, len(tx.AccessList)), + AuthList: make(AuthorizationList, len(tx.AuthList)), + Value: new(uint256.Int), + ChainID: tx.ChainID, + GasTipCap: new(uint256.Int), + GasFeeCap: new(uint256.Int), + V: new(uint256.Int), + R: new(uint256.Int), + S: new(uint256.Int), + } + copy(cpy.AccessList, tx.AccessList) + copy(cpy.AuthList, tx.AuthList) + if tx.Value != nil { + cpy.Value.Set(tx.Value) + } + if tx.GasTipCap != nil { + cpy.GasTipCap.Set(tx.GasTipCap) + } + if tx.GasFeeCap != nil { + cpy.GasFeeCap.Set(tx.GasFeeCap) + } + if tx.V != nil { + cpy.V.Set(tx.V) + } + if tx.R != nil { + cpy.R.Set(tx.R) + } + if tx.S != nil { + cpy.S.Set(tx.S) + } + return cpy +} + +// accessors for innerTx. +func (tx *SetCodeTx) txType() byte { return SetCodeTxType } +func (tx *SetCodeTx) chainID() *big.Int { return big.NewInt(int64(tx.ChainID)) } +func (tx *SetCodeTx) accessList() AccessList { return tx.AccessList } +func (tx *SetCodeTx) data() []byte { return tx.Data } +func (tx *SetCodeTx) gas() uint64 { return tx.Gas } +func (tx *SetCodeTx) gasFeeCap() *big.Int { return tx.GasFeeCap.ToBig() } +func (tx *SetCodeTx) gasTipCap() *big.Int { return tx.GasTipCap.ToBig() } +func (tx *SetCodeTx) gasPrice() *big.Int { return tx.GasFeeCap.ToBig() } +func (tx *SetCodeTx) value() *big.Int { return tx.Value.ToBig() } +func (tx *SetCodeTx) nonce() uint64 { return tx.Nonce } +func (tx *SetCodeTx) to() *common.Address { tmp := tx.To; return &tmp } + +func (tx *SetCodeTx) effectiveGasPrice(dst *big.Int, baseFee *big.Int) *big.Int { + if baseFee == nil { + return dst.Set(tx.GasFeeCap.ToBig()) + } + tip := dst.Sub(tx.GasFeeCap.ToBig(), baseFee) + if tip.Cmp(tx.GasTipCap.ToBig()) > 0 { + tip.Set(tx.GasTipCap.ToBig()) + } + return tip.Add(tip, baseFee) +} + +func (tx *SetCodeTx) rawSignatureValues() (v, r, s *big.Int) { + return tx.V.ToBig(), tx.R.ToBig(), tx.S.ToBig() +} + +func (tx *SetCodeTx) setSignatureValues(chainID, v, r, s *big.Int) { + tx.ChainID = chainID.Uint64() + tx.V.SetFromBig(v) + tx.R.SetFromBig(r) + tx.S.SetFromBig(s) +} + +func (tx *SetCodeTx) encode(b *bytes.Buffer) error { + return rlp.Encode(b, tx) +} + +func (tx *SetCodeTx) decode(input []byte) error { + return rlp.DecodeBytes(input, tx) +} diff --git a/core/types/tx_setcode_test.go b/core/types/tx_setcode_test.go new file mode 100644 index 0000000000..d0544573cf --- /dev/null +++ b/core/types/tx_setcode_test.go @@ -0,0 +1,70 @@ +// Copyright 2024 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package types + +import ( + "testing" + + "github.com/ethereum/go-ethereum/common" +) + +// TestParseDelegation tests a few possible delegation designator values and +// ensures they are parsed correctly. +func TestParseDelegation(t *testing.T) { + addr := common.Address{0x42} + for _, tt := range []struct { + val []byte + want *common.Address + }{ + { // simple correct delegation + val: append(DelegationPrefix, addr.Bytes()...), + want: &addr, + }, + { // wrong address size + val: append(DelegationPrefix, addr.Bytes()[0:19]...), + }, + { // short address + val: append(DelegationPrefix, 0x42), + }, + { // long address + val: append(append(DelegationPrefix, addr.Bytes()...), 0x42), + }, + { // wrong prefix size + val: append(DelegationPrefix[:2], addr.Bytes()...), + }, + { // wrong prefix + val: append([]byte{0xef, 0x01, 0x01}, addr.Bytes()...), + }, + { // wrong prefix + val: append([]byte{0xef, 0x00, 0x00}, addr.Bytes()...), + }, + { // no prefix + val: addr.Bytes(), + }, + { // no address + val: DelegationPrefix, + }, + } { + got, ok := ParseDelegation(tt.val) + if ok && tt.want == nil { + t.Fatalf("expected fail, got %s", got.Hex()) + } + if !ok && tt.want != nil { + t.Fatalf("failed to parse, want %s", tt.want.Hex()) + } + } +} diff --git a/core/verkle_witness_test.go b/core/verkle_witness_test.go index 80996eee33..8a8e42a513 100644 --- a/core/verkle_witness_test.go +++ b/core/verkle_witness_test.go @@ -83,12 +83,12 @@ var ( func TestProcessVerkle(t *testing.T) { var ( code = common.FromHex(`6060604052600a8060106000396000f360606040526008565b00`) - intrinsicContractCreationGas, _ = IntrinsicGas(code, nil, true, true, true, true) + intrinsicContractCreationGas, _ = IntrinsicGas(code, nil, nil, true, true, true, true) // A contract creation that calls EXTCODECOPY in the constructor. Used to ensure that the witness // will not contain that copied data. // Source: https://gist.github.com/gballet/a23db1e1cb4ed105616b5920feb75985 codeWithExtCodeCopy = common.FromHex(`0x60806040526040516100109061017b565b604051809103906000f08015801561002c573d6000803e3d6000fd5b506000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555034801561007857600080fd5b5060008067ffffffffffffffff8111156100955761009461024a565b5b6040519080825280601f01601f1916602001820160405280156100c75781602001600182028036833780820191505090505b50905060008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1690506020600083833c81610101906101e3565b60405161010d90610187565b61011791906101a3565b604051809103906000f080158015610133573d6000803e3d6000fd5b50600160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550505061029b565b60d58061046783390190565b6102068061053c83390190565b61019d816101d9565b82525050565b60006020820190506101b86000830184610194565b92915050565b6000819050602082019050919050565b600081519050919050565b6000819050919050565b60006101ee826101ce565b826101f8846101be565b905061020381610279565b925060208210156102435761023e7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8360200360080261028e565b831692505b5050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b600061028582516101d9565b80915050919050565b600082821b905092915050565b6101bd806102aa6000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c8063f566852414610030575b600080fd5b61003861004e565b6040516100459190610146565b60405180910390f35b6000600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166381ca91d36040518163ffffffff1660e01b815260040160206040518083038186803b1580156100b857600080fd5b505afa1580156100cc573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906100f0919061010a565b905090565b60008151905061010481610170565b92915050565b6000602082840312156101205761011f61016b565b5b600061012e848285016100f5565b91505092915050565b61014081610161565b82525050565b600060208201905061015b6000830184610137565b92915050565b6000819050919050565b600080fd5b61017981610161565b811461018457600080fd5b5056fea2646970667358221220a6a0e11af79f176f9c421b7b12f441356b25f6489b83d38cc828a701720b41f164736f6c63430008070033608060405234801561001057600080fd5b5060b68061001f6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c8063ab5ed15014602d575b600080fd5b60336047565b604051603e9190605d565b60405180910390f35b60006001905090565b6057816076565b82525050565b6000602082019050607060008301846050565b92915050565b600081905091905056fea26469706673582212203a14eb0d5cd07c277d3e24912f110ddda3e553245a99afc4eeefb2fbae5327aa64736f6c63430008070033608060405234801561001057600080fd5b5060405161020638038061020683398181016040528101906100329190610063565b60018160001c6100429190610090565b60008190555050610145565b60008151905061005d8161012e565b92915050565b60006020828403121561007957610078610129565b5b60006100878482850161004e565b91505092915050565b600061009b826100f0565b91506100a6836100f0565b9250827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff038211156100db576100da6100fa565b5b828201905092915050565b6000819050919050565b6000819050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b600080fd5b610137816100e6565b811461014257600080fd5b50565b60b3806101536000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c806381ca91d314602d575b600080fd5b60336047565b604051603e9190605a565b60405180910390f35b60005481565b6054816073565b82525050565b6000602082019050606d6000830184604d565b92915050565b600081905091905056fea26469706673582212209bff7098a2f526de1ad499866f27d6d0d6f17b74a413036d6063ca6a0998ca4264736f6c63430008070033`) - intrinsicCodeWithExtCodeCopyGas, _ = IntrinsicGas(codeWithExtCodeCopy, nil, true, true, true, true) + intrinsicCodeWithExtCodeCopyGas, _ = IntrinsicGas(codeWithExtCodeCopy, nil, nil, true, true, true, true) signer = types.LatestSigner(testVerkleChainConfig) testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") bcdb = rawdb.NewMemoryDatabase() // Database for the blockchain diff --git a/core/vm/eips.go b/core/vm/eips.go index 71d51f81ef..b8024bc590 100644 --- a/core/vm/eips.go +++ b/core/vm/eips.go @@ -40,6 +40,7 @@ var activators = map[int]func(*JumpTable){ 1344: enable1344, 1153: enable1153, 4762: enable4762, + 7702: enable7702, } // EnableEIP enables the given EIP on the config. @@ -336,7 +337,7 @@ func opExtCodeCopyEIP4762(pc *uint64, interpreter *EVMInterpreter, scope *ScopeC uint64CodeOffset = math.MaxUint64 } addr := common.Address(a.Bytes20()) - code := interpreter.evm.StateDB.GetCode(addr) + code := interpreter.evm.StateDB.ResolveCode(addr) contract := &Contract{ Code: code, self: AccountRef(addr), @@ -703,3 +704,27 @@ func enableEOF(jt *JumpTable) { memorySize: memoryExtCall, } } + +// enable7702 the EIP-7702 changes to support delegation designators. +func enable7702(jt *JumpTable) { + jt[EXTCODECOPY].constantGas = params.WarmStorageReadCostEIP2929 + jt[EXTCODECOPY].dynamicGas = gasExtCodeCopyEIP7702 + + jt[EXTCODESIZE].constantGas = params.WarmStorageReadCostEIP2929 + jt[EXTCODESIZE].dynamicGas = gasEip7702CodeCheck + + jt[EXTCODEHASH].constantGas = params.WarmStorageReadCostEIP2929 + jt[EXTCODEHASH].dynamicGas = gasEip7702CodeCheck + + jt[CALL].constantGas = params.WarmStorageReadCostEIP2929 + jt[CALL].dynamicGas = gasCallEIP7702 + + jt[CALLCODE].constantGas = params.WarmStorageReadCostEIP2929 + jt[CALLCODE].dynamicGas = gasCallCodeEIP7702 + + jt[STATICCALL].constantGas = params.WarmStorageReadCostEIP2929 + jt[STATICCALL].dynamicGas = gasStaticCallEIP7702 + + jt[DELEGATECALL].constantGas = params.WarmStorageReadCostEIP2929 + jt[DELEGATECALL].dynamicGas = gasDelegateCallEIP7702 +} diff --git a/core/vm/evm.go b/core/vm/evm.go index 34e5fa766b..9b155ed200 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -217,7 +217,7 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas } else { // Initialise a new contract and set the code that is to be used by the EVM. // The contract is a scoped environment for this execution context only. - code := evm.StateDB.GetCode(addr) + code := evm.StateDB.ResolveCode(addr) if len(code) == 0 { ret, err = nil, nil // gas is unchanged } else { @@ -225,7 +225,7 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas // If the account has no code, we can abort here // The depth-check is already done, and precompiles handled above contract := NewContract(caller, AccountRef(addrCopy), value, gas) - contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), code) + contract.SetCallCode(&addrCopy, evm.StateDB.ResolveCodeHash(addrCopy), code) ret, err = evm.interpreter.Run(contract, input, false) gas = contract.Gas } @@ -285,7 +285,7 @@ func (evm *EVM) CallCode(caller ContractRef, addr common.Address, input []byte, // Initialise a new contract and set the code that is to be used by the EVM. // The contract is a scoped environment for this execution context only. contract := NewContract(caller, AccountRef(caller.Address()), value, gas) - contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), evm.StateDB.GetCode(addrCopy)) + contract.SetCallCode(&addrCopy, evm.StateDB.ResolveCodeHash(addrCopy), evm.StateDB.ResolveCode(addrCopy)) ret, err = evm.interpreter.Run(contract, input, false) gas = contract.Gas } @@ -332,7 +332,7 @@ func (evm *EVM) DelegateCall(caller ContractRef, addr common.Address, input []by addrCopy := addr // Initialise a new contract and make initialise the delegate values contract := NewContract(caller, AccountRef(caller.Address()), nil, gas).AsDelegate() - contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), evm.StateDB.GetCode(addrCopy)) + contract.SetCallCode(&addrCopy, evm.StateDB.ResolveCodeHash(addrCopy), evm.StateDB.ResolveCode(addrCopy)) ret, err = evm.interpreter.Run(contract, input, false) gas = contract.Gas } @@ -387,7 +387,7 @@ func (evm *EVM) StaticCall(caller ContractRef, addr common.Address, input []byte // Initialise a new contract and set the code that is to be used by the EVM. // The contract is a scoped environment for this execution context only. contract := NewContract(caller, AccountRef(addrCopy), new(uint256.Int), gas) - contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), evm.StateDB.GetCode(addrCopy)) + contract.SetCallCode(&addrCopy, evm.StateDB.ResolveCodeHash(addrCopy), evm.StateDB.ResolveCode(addrCopy)) // When an error was returned by the EVM or when setting the creation code // above we revert to the snapshot and consume any gas remaining. Additionally // when we're in Homestead this also counts for code storage gas errors. @@ -463,7 +463,7 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, // - the nonce is non-zero // - the code is non-empty // - the storage is non-empty - contractHash := evm.StateDB.GetCodeHash(address) + contractHash := evm.StateDB.ResolveCodeHash(address) storageRoot := evm.StateDB.GetStorageRoot(address) if evm.StateDB.GetNonce(address) != 0 || (contractHash != (common.Hash{}) && contractHash != types.EmptyCodeHash) || // non-empty code diff --git a/core/vm/instructions.go b/core/vm/instructions.go index 47eb62be08..954d09b4f0 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -340,7 +340,7 @@ func opReturnDataCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeConte func opExtCodeSize(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { slot := scope.Stack.peek() - slot.SetUint64(uint64(interpreter.evm.StateDB.GetCodeSize(slot.Bytes20()))) + slot.SetUint64(uint64(len(interpreter.evm.StateDB.ResolveCode(slot.Bytes20())))) return nil, nil } @@ -378,7 +378,7 @@ func opExtCodeCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) uint64CodeOffset = math.MaxUint64 } addr := common.Address(a.Bytes20()) - code := interpreter.evm.StateDB.GetCode(addr) + code := interpreter.evm.StateDB.ResolveCode(addr) codeCopy := getData(code, uint64CodeOffset, length.Uint64()) scope.Memory.Set(memOffset.Uint64(), length.Uint64(), codeCopy) @@ -387,7 +387,7 @@ 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. +// to `state.ResolveCodeHash` function to ensure the correctness. // // 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. @@ -401,6 +401,9 @@ func opExtCodeCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) // 4. Caller tries to get the code hash of a precompiled account, the result should be // zero or emptyCodeHash. // +// 4. Caller tries to get the code hash of a delegated account, the result should be +// equal the result of calling extcodehash on the account directly. +// // It is worth noting that in order to avoid unnecessary create and clean, all precompile // accounts on mainnet have been transferred 1 wei, so the return here should be // emptyCodeHash. If the precompile account is not transferred any amount on a private or @@ -417,7 +420,7 @@ func opExtCodeHash(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) if interpreter.evm.StateDB.Empty(address) { slot.Clear() } else { - slot.SetBytes(interpreter.evm.StateDB.GetCodeHash(address).Bytes()) + slot.SetBytes(interpreter.evm.StateDB.ResolveCodeHash(address).Bytes()) } return nil, nil } diff --git a/core/vm/interface.go b/core/vm/interface.go index 9229f4d2cd..151768264b 100644 --- a/core/vm/interface.go +++ b/core/vm/interface.go @@ -45,6 +45,9 @@ type StateDB interface { SetCode(common.Address, []byte) GetCodeSize(common.Address) int + ResolveCodeHash(common.Address) common.Hash + ResolveCode(common.Address) []byte + AddRefund(uint64) SubRefund(uint64) GetRefund() uint64 diff --git a/core/vm/jump_table.go b/core/vm/jump_table.go index 658014f24c..7a9119422f 100644 --- a/core/vm/jump_table.go +++ b/core/vm/jump_table.go @@ -108,6 +108,7 @@ func newCancunInstructionSet() JumpTable { enable1153(&instructionSet) // EIP-1153 "Transient Storage" enable5656(&instructionSet) // EIP-5656 (MCOPY opcode) enable6780(&instructionSet) // EIP-6780 SELFDESTRUCT only in same transaction + enable7702(&instructionSet) return validate(instructionSet) } diff --git a/core/vm/operations_acl.go b/core/vm/operations_acl.go index b993b651ff..4245e7ae4a 100644 --- a/core/vm/operations_acl.go +++ b/core/vm/operations_acl.go @@ -22,6 +22,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/core/tracing" + "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/params" ) @@ -242,3 +243,102 @@ func makeSelfdestructGasFn(refundsEnabled bool) gasFunc { } return gasFunc } + +var ( + gasCallEIP7702 = makeCallVariantGasCallEIP7702(gasCall) + gasDelegateCallEIP7702 = makeCallVariantGasCallEIP7702(gasDelegateCall) + gasStaticCallEIP7702 = makeCallVariantGasCallEIP7702(gasStaticCall) + gasCallCodeEIP7702 = makeCallVariantGasCallEIP7702(gasCallCode) +) + +func makeCallVariantGasCallEIP7702(oldCalculator gasFunc) gasFunc { + return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + addr := common.Address(stack.Back(1).Bytes20()) + // Check slot presence in the access list + warmAccess := evm.StateDB.AddressInAccessList(addr) + // The WarmStorageReadCostEIP2929 (100) is already deducted in the form of a constant cost, so + // the cost to charge for cold access, if any, is Cold - Warm + coldCost := params.ColdAccountAccessCostEIP2929 - params.WarmStorageReadCostEIP2929 + if !warmAccess { + evm.StateDB.AddAddressToAccessList(addr) + // Charge the remaining difference here already, to correctly calculate available + // gas for call + if !contract.UseGas(coldCost, evm.Config.Tracer, tracing.GasChangeCallStorageColdAccess) { + return 0, ErrOutOfGas + } + } + + // Check if code is a delegation and if so, charge for resolution. + if addr, ok := types.ParseDelegation(evm.StateDB.GetCode(addr)); ok { + var cost uint64 + if evm.StateDB.AddressInAccessList(addr) { + cost += params.WarmStorageReadCostEIP2929 + } else { + evm.StateDB.AddAddressToAccessList(addr) + cost += params.ColdAccountAccessCostEIP2929 + } + if !contract.UseGas(cost, evm.Config.Tracer, tracing.GasChangeCallStorageColdAccess) { + return 0, ErrOutOfGas + } + coldCost += cost + } + // Now call the old calculator, which takes into account + // - create new account + // - transfer value + // - memory expansion + // - 63/64ths rule + gas, err := oldCalculator(evm, contract, stack, mem, memorySize) + if warmAccess || err != nil { + return gas, err + } + // In case of a cold access, we temporarily add the cold charge back, and also + // add it to the returned gas. By adding it to the return, it will be charged + // outside of this function, as part of the dynamic gas, and that will make it + // also become correctly reported to tracers. + contract.Gas += coldCost + + var overflow bool + if gas, overflow = math.SafeAdd(gas, coldCost); overflow { + return 0, ErrGasUintOverflow + } + return gas, nil + } +} + +func gasEip7702CodeCheck(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + cost, _ := gasEip2929AccountCheck(evm, contract, stack, mem, memorySize) + // Check if code is a delegation and if so, charge for resolution + addr := common.Address(stack.peek().Bytes20()) + if addr, ok := types.ParseDelegation(evm.StateDB.GetCode(addr)); ok { + if evm.StateDB.AddressInAccessList(addr) { + cost += params.WarmStorageReadCostEIP2929 + } else { + evm.StateDB.AddAddressToAccessList(addr) + cost += params.ColdAccountAccessCostEIP2929 + } + } + return cost, nil +} + +func gasExtCodeCopyEIP7702(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + gas, err := gasExtCodeCopyEIP2929(evm, contract, stack, mem, memorySize) + if err != nil { + return 0, err + } + // Check if code is a delegation and if so, charge for resolution + addr := common.Address(stack.peek().Bytes20()) + if addr, ok := types.ParseDelegation(evm.StateDB.GetCode(addr)); ok { + var overflow bool + if evm.StateDB.AddressInAccessList(addr) { + if gas, overflow = math.SafeAdd(gas, params.WarmStorageReadCostEIP2929); overflow { + return 0, ErrGasUintOverflow + } + } else { + evm.StateDB.AddAddressToAccessList(addr) + if gas, overflow = math.SafeAdd(gas, params.ColdAccountAccessCostEIP2929); overflow { + return 0, ErrGasUintOverflow + } + } + } + return gas, nil +} diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 9c08006f1e..25e471c9dc 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -1104,28 +1104,29 @@ func RPCMarshalBlock(block *types.Block, inclTx bool, fullTx bool, config *param // RPCTransaction represents a transaction that will serialize to the RPC representation of a transaction type RPCTransaction struct { - BlockHash *common.Hash `json:"blockHash"` - BlockNumber *hexutil.Big `json:"blockNumber"` - From common.Address `json:"from"` - Gas hexutil.Uint64 `json:"gas"` - GasPrice *hexutil.Big `json:"gasPrice"` - GasFeeCap *hexutil.Big `json:"maxFeePerGas,omitempty"` - GasTipCap *hexutil.Big `json:"maxPriorityFeePerGas,omitempty"` - MaxFeePerBlobGas *hexutil.Big `json:"maxFeePerBlobGas,omitempty"` - Hash common.Hash `json:"hash"` - Input hexutil.Bytes `json:"input"` - Nonce hexutil.Uint64 `json:"nonce"` - To *common.Address `json:"to"` - TransactionIndex *hexutil.Uint64 `json:"transactionIndex"` - Value *hexutil.Big `json:"value"` - Type hexutil.Uint64 `json:"type"` - Accesses *types.AccessList `json:"accessList,omitempty"` - ChainID *hexutil.Big `json:"chainId,omitempty"` - BlobVersionedHashes []common.Hash `json:"blobVersionedHashes,omitempty"` - V *hexutil.Big `json:"v"` - R *hexutil.Big `json:"r"` - S *hexutil.Big `json:"s"` - YParity *hexutil.Uint64 `json:"yParity,omitempty"` + BlockHash *common.Hash `json:"blockHash"` + BlockNumber *hexutil.Big `json:"blockNumber"` + From common.Address `json:"from"` + Gas hexutil.Uint64 `json:"gas"` + GasPrice *hexutil.Big `json:"gasPrice"` + GasFeeCap *hexutil.Big `json:"maxFeePerGas,omitempty"` + GasTipCap *hexutil.Big `json:"maxPriorityFeePerGas,omitempty"` + MaxFeePerBlobGas *hexutil.Big `json:"maxFeePerBlobGas,omitempty"` + Hash common.Hash `json:"hash"` + Input hexutil.Bytes `json:"input"` + Nonce hexutil.Uint64 `json:"nonce"` + To *common.Address `json:"to"` + TransactionIndex *hexutil.Uint64 `json:"transactionIndex"` + Value *hexutil.Big `json:"value"` + Type hexutil.Uint64 `json:"type"` + Accesses *types.AccessList `json:"accessList,omitempty"` + ChainID *hexutil.Big `json:"chainId,omitempty"` + BlobVersionedHashes []common.Hash `json:"blobVersionedHashes,omitempty"` + AuthorizationList types.AuthorizationList `json:"authorizationList,omitempty"` + V *hexutil.Big `json:"v"` + R *hexutil.Big `json:"r"` + S *hexutil.Big `json:"s"` + YParity *hexutil.Uint64 `json:"yParity,omitempty"` } // newRPCTransaction returns a transaction that will serialize to the RPC @@ -1200,6 +1201,24 @@ func newRPCTransaction(tx *types.Transaction, blockHash common.Hash, blockNumber } result.MaxFeePerBlobGas = (*hexutil.Big)(tx.BlobGasFeeCap()) result.BlobVersionedHashes = tx.BlobHashes() + + case types.SetCodeTxType: + al := tx.AccessList() + yparity := hexutil.Uint64(v.Sign()) + result.Accesses = &al + result.ChainID = (*hexutil.Big)(tx.ChainId()) + result.YParity = &yparity + result.GasFeeCap = (*hexutil.Big)(tx.GasFeeCap()) + result.GasTipCap = (*hexutil.Big)(tx.GasTipCap()) + // if the transaction has been mined, compute the effective gas price + if baseFee != nil && blockHash != (common.Hash{}) { + result.GasPrice = (*hexutil.Big)(effectiveGasPrice(tx, baseFee)) + } else { + result.GasPrice = (*hexutil.Big)(tx.GasFeeCap()) + } + result.MaxFeePerBlobGas = (*hexutil.Big)(tx.BlobGasFeeCap()) + result.BlobVersionedHashes = tx.BlobHashes() + result.AuthorizationList = tx.AuthList() } return result } diff --git a/params/protocol_params.go b/params/protocol_params.go index 90e7487cff..4d2baf8054 100644 --- a/params/protocol_params.go +++ b/params/protocol_params.go @@ -90,10 +90,11 @@ const ( SelfdestructRefundGas uint64 = 24000 // Refunded following a selfdestruct operation. MemoryGas uint64 = 3 // Times the address of the (highest referenced byte in memory + 1). NOTE: referencing happens on read, write and in instructions such as RETURN and CALL. - TxDataNonZeroGasFrontier uint64 = 68 // Per byte of data attached to a transaction that is not equal to zero. NOTE: Not payable on data of calls between transactions. - TxDataNonZeroGasEIP2028 uint64 = 16 // Per byte of non zero data attached to a transaction after EIP 2028 (part in Istanbul) - TxAccessListAddressGas uint64 = 2400 // Per address specified in EIP 2930 access list - TxAccessListStorageKeyGas uint64 = 1900 // Per storage key specified in EIP 2930 access list + TxDataNonZeroGasFrontier uint64 = 68 // Per byte of data attached to a transaction that is not equal to zero. NOTE: Not payable on data of calls between transactions. + TxDataNonZeroGasEIP2028 uint64 = 16 // Per byte of non zero data attached to a transaction after EIP 2028 (part in Istanbul) + TxAccessListAddressGas uint64 = 2400 // Per address specified in EIP 2930 access list + TxAccessListStorageKeyGas uint64 = 1900 // Per storage key specified in EIP 2930 access list + TxAuthTupleGas uint64 = 12500 // Per auth tuple code specified in EIP-7702 // These have been changed during the course of the chain CallGasFrontier uint64 = 40 // Once per CALL operation & message call transaction. diff --git a/tests/gen_stauthorization.go b/tests/gen_stauthorization.go new file mode 100644 index 0000000000..fbafd6fdea --- /dev/null +++ b/tests/gen_stauthorization.go @@ -0,0 +1,74 @@ +// Code generated by github.com/fjl/gencodec. DO NOT EDIT. + +package tests + +import ( + "encoding/json" + "errors" + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/math" +) + +var _ = (*stAuthorizationMarshaling)(nil) + +// MarshalJSON marshals as JSON. +func (s stAuthorization) MarshalJSON() ([]byte, error) { + type stAuthorization struct { + ChainID math.HexOrDecimal64 + Address common.Address `json:"address" gencodec:"required"` + Nonce math.HexOrDecimal64 `json:"nonce" gencodec:"required"` + V math.HexOrDecimal64 `json:"v" gencodec:"required"` + R *math.HexOrDecimal256 `json:"r" gencodec:"required"` + S *math.HexOrDecimal256 `json:"s" gencodec:"required"` + } + var enc stAuthorization + enc.ChainID = math.HexOrDecimal64(s.ChainID) + enc.Address = s.Address + enc.Nonce = math.HexOrDecimal64(s.Nonce) + enc.V = math.HexOrDecimal64(s.V) + enc.R = (*math.HexOrDecimal256)(s.R) + enc.S = (*math.HexOrDecimal256)(s.S) + return json.Marshal(&enc) +} + +// UnmarshalJSON unmarshals from JSON. +func (s *stAuthorization) UnmarshalJSON(input []byte) error { + type stAuthorization struct { + ChainID *math.HexOrDecimal64 + Address *common.Address `json:"address" gencodec:"required"` + Nonce *math.HexOrDecimal64 `json:"nonce" gencodec:"required"` + V *math.HexOrDecimal64 `json:"v" gencodec:"required"` + R *math.HexOrDecimal256 `json:"r" gencodec:"required"` + S *math.HexOrDecimal256 `json:"s" gencodec:"required"` + } + var dec stAuthorization + if err := json.Unmarshal(input, &dec); err != nil { + return err + } + if dec.ChainID != nil { + s.ChainID = uint64(*dec.ChainID) + } + if dec.Address == nil { + return errors.New("missing required field 'address' for stAuthorization") + } + s.Address = *dec.Address + if dec.Nonce == nil { + return errors.New("missing required field 'nonce' for stAuthorization") + } + s.Nonce = uint64(*dec.Nonce) + if dec.V == nil { + return errors.New("missing required field 'v' for stAuthorization") + } + s.V = uint8(*dec.V) + if dec.R == nil { + return errors.New("missing required field 'r' for stAuthorization") + } + s.R = (*big.Int)(dec.R) + if dec.S == nil { + return errors.New("missing required field 's' for stAuthorization") + } + s.S = (*big.Int)(dec.S) + return nil +} diff --git a/tests/gen_sttransaction.go b/tests/gen_sttransaction.go index 9b5aecbfe6..b25ce76166 100644 --- a/tests/gen_sttransaction.go +++ b/tests/gen_sttransaction.go @@ -30,6 +30,7 @@ func (s stTransaction) MarshalJSON() ([]byte, error) { Sender *common.Address `json:"sender"` BlobVersionedHashes []common.Hash `json:"blobVersionedHashes,omitempty"` BlobGasFeeCap *math.HexOrDecimal256 `json:"maxFeePerBlobGas,omitempty"` + AuthorizationList []*stAuthorization `json:"authorizationList,omitempty"` } var enc stTransaction enc.GasPrice = (*math.HexOrDecimal256)(s.GasPrice) @@ -50,6 +51,7 @@ func (s stTransaction) MarshalJSON() ([]byte, error) { enc.Sender = s.Sender enc.BlobVersionedHashes = s.BlobVersionedHashes enc.BlobGasFeeCap = (*math.HexOrDecimal256)(s.BlobGasFeeCap) + enc.AuthorizationList = s.AuthorizationList return json.Marshal(&enc) } @@ -69,6 +71,7 @@ func (s *stTransaction) UnmarshalJSON(input []byte) error { Sender *common.Address `json:"sender"` BlobVersionedHashes []common.Hash `json:"blobVersionedHashes,omitempty"` BlobGasFeeCap *math.HexOrDecimal256 `json:"maxFeePerBlobGas,omitempty"` + AuthorizationList []*stAuthorization `json:"authorizationList,omitempty"` } var dec stTransaction if err := json.Unmarshal(input, &dec); err != nil { @@ -116,5 +119,8 @@ func (s *stTransaction) UnmarshalJSON(input []byte) error { if dec.BlobGasFeeCap != nil { s.BlobGasFeeCap = (*big.Int)(dec.BlobGasFeeCap) } + if dec.AuthorizationList != nil { + s.AuthorizationList = dec.AuthorizationList + } return nil } diff --git a/tests/state_test_util.go b/tests/state_test_util.go index 446ffb40d5..25806a1b3c 100644 --- a/tests/state_test_util.go +++ b/tests/state_test_util.go @@ -123,6 +123,7 @@ type stTransaction struct { Sender *common.Address `json:"sender"` BlobVersionedHashes []common.Hash `json:"blobVersionedHashes,omitempty"` BlobGasFeeCap *big.Int `json:"maxFeePerBlobGas,omitempty"` + AuthorizationList []*stAuthorization `json:"authorizationList,omitempty"` } type stTransactionMarshaling struct { @@ -135,6 +136,28 @@ type stTransactionMarshaling struct { BlobGasFeeCap *math.HexOrDecimal256 } +//go:generate go run github.com/fjl/gencodec -type stAuthorization -field-override stAuthorizationMarshaling -out gen_stauthorization.go + +// Authorization is an authorization from an account to deploy code at it's +// address. +type stAuthorization struct { + ChainID uint64 + Address common.Address `json:"address" gencodec:"required"` + Nonce uint64 `json:"nonce" gencodec:"required"` + V uint8 `json:"v" gencodec:"required"` + R *big.Int `json:"r" gencodec:"required"` + S *big.Int `json:"s" gencodec:"required"` +} + +// field type overrides for gencodec +type stAuthorizationMarshaling struct { + ChainID math.HexOrDecimal64 + Nonce math.HexOrDecimal64 + V math.HexOrDecimal64 + R *math.HexOrDecimal256 + S *math.HexOrDecimal256 +} + // GetChainConfig takes a fork definition and returns a chain config. // The fork definition can be // - a plain forkname, e.g. `Byzantium`, @@ -421,6 +444,20 @@ func (tx *stTransaction) toMessage(ps stPostState, baseFee *big.Int) (*core.Mess if gasPrice == nil { return nil, errors.New("no gas price provided") } + var authList types.AuthorizationList + if tx.AuthorizationList != nil { + authList = make(types.AuthorizationList, 0) + for _, auth := range tx.AuthorizationList { + authList = append(authList, &types.Authorization{ + ChainID: auth.ChainID, + Address: auth.Address, + Nonce: auth.Nonce, + V: auth.V, + R: auth.R, + S: auth.S, + }) + } + } msg := &core.Message{ From: from, @@ -435,6 +472,7 @@ func (tx *stTransaction) toMessage(ps stPostState, baseFee *big.Int) (*core.Mess AccessList: accessList, BlobHashes: tx.BlobVersionedHashes, BlobGasFeeCap: tx.BlobGasFeeCap, + AuthList: authList, } return msg, nil } diff --git a/tests/transaction_test_util.go b/tests/transaction_test_util.go index d3dbbd5db2..4da27ff943 100644 --- a/tests/transaction_test_util.go +++ b/tests/transaction_test_util.go @@ -59,7 +59,7 @@ func (tt *TransactionTest) Run(config *params.ChainConfig) error { return nil, nil, err } // Intrinsic gas - requiredGas, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.To() == nil, isHomestead, isIstanbul, false) + requiredGas, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.AuthList(), tx.To() == nil, isHomestead, isIstanbul, false) if err != nil { return nil, nil, err }