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
}