diff --git a/Makefile b/Makefile
index 8f84d6cfad45..9436af8fc5e8 100644
--- a/Makefile
+++ b/Makefile
@@ -43,6 +43,11 @@ test: all
$(GORUN) build/ci.go test ./consensus ./core ./eth ./miner ./node ./trie ./rollup/...
# RIP-7212 (secp256r1) precompiled contract test
cd ${PWD}/core/vm; go test -v -run=^TestPrecompiledP256 -bench=^BenchmarkPrecompiledP256
+ # EIP-7702 test
+ cd ${PWD}/core/vm/runtime; go test -v -run=^TestDelegatedAccountAccessCost
+ cd ${PWD}/core/types; go test -v -run=^TestParseDelegation
+ cd ${PWD}/internal/ethapi; go test -v -run=^TestEstimateGas
+ cd ${PWD}/cmd/evm; go test -v -run=^TestT8n
lint: ## Run linters.
$(GORUN) build/ci.go lint
diff --git a/accounts/abi/bind/backends/simulated.go b/accounts/abi/bind/backends/simulated.go
index 3a105e2e5669..07216769915c 100644
--- a/accounts/abi/bind/backends/simulated.go
+++ b/accounts/abi/bind/backends/simulated.go
@@ -639,7 +639,7 @@ func (b *SimulatedBackend) callContract(ctx context.Context, call ethereum.CallM
// about the transaction and calling mechanisms.
vmEnv := vm.NewEVM(evmContext, txContext, stateDB, b.config, vm.Config{NoBaseFee: true})
gasPool := new(core.GasPool).AddGas(math.MaxUint64)
- signer := types.MakeSigner(b.blockchain.Config(), head.Number)
+ signer := types.MakeSigner(b.blockchain.Config(), head.Number, head.Time)
l1DataFee, err := fees.EstimateL1DataFeeForMessage(msg, head.BaseFee, b.blockchain.Config(), signer, stateDB, head.Number)
if err != nil {
return nil, err
@@ -660,7 +660,7 @@ func (b *SimulatedBackend) SendTransaction(ctx context.Context, tx *types.Transa
panic("could not fetch parent")
}
// Check transaction validity
- signer := types.MakeSigner(b.blockchain.Config(), block.Number())
+ signer := types.MakeSigner(b.blockchain.Config(), block.Number(), block.Time())
sender, err := types.Sender(signer, tx)
if err != nil {
panic(fmt.Errorf("invalid transaction: %v", err))
@@ -809,19 +809,20 @@ type callMsg struct {
ethereum.CallMsg
}
-func (m callMsg) From() common.Address { return m.CallMsg.From }
-func (m callMsg) Nonce() uint64 { return 0 }
-func (m callMsg) IsFake() bool { return true }
-func (m callMsg) To() *common.Address { return m.CallMsg.To }
-func (m callMsg) GasPrice() *big.Int { return m.CallMsg.GasPrice }
-func (m callMsg) GasFeeCap() *big.Int { return m.CallMsg.GasFeeCap }
-func (m callMsg) GasTipCap() *big.Int { return m.CallMsg.GasTipCap }
-func (m callMsg) Gas() uint64 { return m.CallMsg.Gas }
-func (m callMsg) Value() *big.Int { return m.CallMsg.Value }
-func (m callMsg) Data() []byte { return m.CallMsg.Data }
-func (m callMsg) AccessList() types.AccessList { return m.CallMsg.AccessList }
-func (m callMsg) IsL1MessageTx() bool { return false }
-func (m callMsg) TxSize() common.StorageSize { return 0 }
+func (m callMsg) From() common.Address { return m.CallMsg.From }
+func (m callMsg) Nonce() uint64 { return 0 }
+func (m callMsg) IsFake() bool { return true }
+func (m callMsg) To() *common.Address { return m.CallMsg.To }
+func (m callMsg) GasPrice() *big.Int { return m.CallMsg.GasPrice }
+func (m callMsg) GasFeeCap() *big.Int { return m.CallMsg.GasFeeCap }
+func (m callMsg) GasTipCap() *big.Int { return m.CallMsg.GasTipCap }
+func (m callMsg) Gas() uint64 { return m.CallMsg.Gas }
+func (m callMsg) Value() *big.Int { return m.CallMsg.Value }
+func (m callMsg) Data() []byte { return m.CallMsg.Data }
+func (m callMsg) AccessList() types.AccessList { return m.CallMsg.AccessList }
+func (m callMsg) IsL1MessageTx() bool { return false }
+func (m callMsg) TxSize() common.StorageSize { return 0 }
+func (m callMsg) SetCodeAuthorizations() []types.SetCodeAuthorization { return nil }
// filterBackend implements filters.Backend to support filtering for logs without
// taking bloom-bits acceleration structures into account.
diff --git a/accounts/external/backend.go b/accounts/external/backend.go
index d594ece361af..26893a419b17 100644
--- a/accounts/external/backend.go
+++ b/accounts/external/backend.go
@@ -218,7 +218,7 @@ func (api *ExternalSigner) SignTx(account accounts.Account, tx *types.Transactio
switch tx.Type() {
case types.LegacyTxType, types.AccessListTxType:
args.GasPrice = (*hexutil.Big)(tx.GasPrice())
- case types.DynamicFeeTxType:
+ case types.DynamicFeeTxType, types.SetCodeTxType:
args.MaxFeePerGas = (*hexutil.Big)(tx.GasFeeCap())
args.MaxPriorityFeePerGas = (*hexutil.Big)(tx.GasTipCap())
default:
diff --git a/cmd/evm/internal/t8ntool/execution.go b/cmd/evm/internal/t8ntool/execution.go
index b1f5c13ea0ff..393129a3ac14 100644
--- a/cmd/evm/internal/t8ntool/execution.go
+++ b/cmd/evm/internal/t8ntool/execution.go
@@ -117,7 +117,7 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig,
}
var (
statedb = MakePreState(rawdb.NewMemoryDatabase(), pre.Pre)
- signer = types.MakeSigner(chainConfig, new(big.Int).SetUint64(pre.Env.Number))
+ signer = types.MakeSigner(chainConfig, new(big.Int).SetUint64(pre.Env.Number), pre.Env.Timestamp)
gaspool = new(core.GasPool)
blockHash = common.Hash{0x13, 0x37}
rejectedTxs []*rejectedTx
diff --git a/cmd/evm/internal/t8ntool/transaction.go b/cmd/evm/internal/t8ntool/transaction.go
index 4f0a0d67a449..1a1535a8757e 100644
--- a/cmd/evm/internal/t8ntool/transaction.go
+++ b/cmd/evm/internal/t8ntool/transaction.go
@@ -113,7 +113,7 @@ func Transaction(ctx *cli.Context) error {
return NewError(ErrorIO, errors.New("only rlp supported"))
}
}
- signer := types.MakeSigner(chainConfig, new(big.Int))
+ signer := types.MakeSigner(chainConfig, new(big.Int), 0)
// We now have the transactions in 'body', which is supposed to be an
// rlp list of transactions
it, err := rlp.NewListIterator([]byte(body))
@@ -140,7 +140,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.SetCodeAuthorizations(), tx.To() == nil,
chainConfig.IsHomestead(new(big.Int)), chainConfig.IsIstanbul(new(big.Int)), chainConfig.IsShanghai(new(big.Int))); err != nil {
r.Error = err
results = append(results, r)
diff --git a/cmd/evm/internal/t8ntool/transition.go b/cmd/evm/internal/t8ntool/transition.go
index 02732bfa9663..f8870088f313 100644
--- a/cmd/evm/internal/t8ntool/transition.go
+++ b/cmd/evm/internal/t8ntool/transition.go
@@ -241,7 +241,7 @@ func Transition(ctx *cli.Context) error {
}
}
// We may have to sign the transactions.
- signer := types.MakeSigner(chainConfig, big.NewInt(int64(prestate.Env.Number)))
+ signer := types.MakeSigner(chainConfig, big.NewInt(int64(prestate.Env.Number)), prestate.Env.Timestamp)
if txs, err = signUnsignedTransactions(txsWithKeys, signer); err != nil {
return NewError(ErrorJson, fmt.Errorf("failed signing transactions: %v", err))
diff --git a/cmd/evm/t8n_test.go b/cmd/evm/t8n_test.go
index e8d7774e2072..50602a5c0a8c 100644
--- a/cmd/evm/t8n_test.go
+++ b/cmd/evm/t8n_test.go
@@ -124,14 +124,14 @@ func TestT8n(t *testing.T) {
output: t8nOutput{alloc: true, result: true},
expOut: "exp.json",
},
- { // missing blockhash test
- base: "./testdata/4",
- input: t8nInput{
- "alloc.json", "txs.json", "env.json", "Berlin", "",
- },
- output: t8nOutput{alloc: true, result: true},
- expExitCode: 4,
- },
+ //{ // missing blockhash test
+ // base: "./testdata/4",
+ // input: t8nInput{
+ // "alloc.json", "txs.json", "env.json", "Berlin", "",
+ // },
+ // output: t8nOutput{alloc: true, result: true},
+ // expExitCode: 4,
+ //},
{ // Uncle test
base: "./testdata/5",
input: t8nInput{
@@ -204,6 +204,14 @@ func TestT8n(t *testing.T) {
output: t8nOutput{result: true},
expOut: "exp.json",
},
+ { // EuclidV2 test, EIP-7702 transaction
+ base: "./testdata/33",
+ input: t8nInput{
+ "alloc.json", "txs.json", "env.json", "EuclidV2", "",
+ },
+ output: t8nOutput{alloc: true, result: true},
+ expOut: "exp.json",
+ },
} {
args := []string{"t8n"}
diff --git a/cmd/evm/testdata/33/README.md b/cmd/evm/testdata/33/README.md
new file mode 100644
index 000000000000..6a1ea2473901
--- /dev/null
+++ b/cmd/evm/testdata/33/README.md
@@ -0,0 +1 @@
+This test sets some EIP-7702 delegations and calls them.
\ No newline at end of file
diff --git a/cmd/evm/testdata/33/alloc.json b/cmd/evm/testdata/33/alloc.json
new file mode 100644
index 000000000000..6f2bc78d94c0
--- /dev/null
+++ b/cmd/evm/testdata/33/alloc.json
@@ -0,0 +1,30 @@
+{
+ "0x8a0a19589531694250d570040a0c4b74576919b8": {
+ "nonce": "0x00",
+ "balance": "0x0de0b6b3a7640000",
+ "code": "0x600060006000600060007310000000000000000000000000000000000000015af1600155600060006000600060007310000000000000000000000000000000000000025af16002553d600060003e600051600355",
+ "storage": {
+ "0x01": "0x0100",
+ "0x02": "0x0100",
+ "0x03": "0x0100"
+ }
+ },
+ "0x000000000000000000000000000000000000aaaa": {
+ "nonce": "0x00",
+ "balance": "0x4563918244f40000",
+ "code": "0x58808080600173703c4b2bd70c169f5717101caee543299fc946c75af100",
+ "storage": {}
+ },
+ "0x000000000000000000000000000000000000bbbb": {
+ "nonce": "0x00",
+ "balance": "0x29a2241af62c0000",
+ "code": "0x6042805500",
+ "storage": {}
+ },
+ "0x71562b71999873DB5b286dF957af199Ec94617F7": {
+ "nonce": "0x00",
+ "balance": "0x6124fee993bc0000",
+ "code": "0x",
+ "storage": {}
+ }
+}
diff --git a/cmd/evm/testdata/33/env.json b/cmd/evm/testdata/33/env.json
new file mode 100644
index 000000000000..708691e5abf0
--- /dev/null
+++ b/cmd/evm/testdata/33/env.json
@@ -0,0 +1,14 @@
+{
+ "currentCoinbase": "0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba",
+ "currentGasLimit": "71794957647893862",
+ "currentNumber": "1",
+ "currentTimestamp": "1000",
+ "currentRandom": "0",
+ "currentDifficulty": "0",
+ "blockHashes": {},
+ "ommers": [],
+ "currentBaseFee": "7",
+ "parentUncleHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
+ "withdrawals": [],
+ "parentBeaconBlockRoot": "0x0000000000000000000000000000000000000000000000000000000000000000"
+}
diff --git a/cmd/evm/testdata/33/exp.json b/cmd/evm/testdata/33/exp.json
new file mode 100644
index 000000000000..1b5e40bf10d3
--- /dev/null
+++ b/cmd/evm/testdata/33/exp.json
@@ -0,0 +1,57 @@
+{
+ "alloc": {
+ "0x000000000000000000000000000000000000aaaa": {
+ "code": "0x58808080600173703c4b2bd70c169f5717101caee543299fc946c75af100",
+ "balance": "0x4563918244f40000"
+ },
+ "0x000000000000000000000000000000000000bbbb": {
+ "code": "0x6042805500",
+ "balance": "0x29a2241af62c0000"
+ },
+ "0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba": {
+ "balance": "0x2bf52"
+ },
+ "0x703c4b2bd70c169f5717101caee543299fc946c7": {
+ "code": "0xef0100000000000000000000000000000000000000bbbb",
+ "storage": {
+ "0x0000000000000000000000000000000000000000000000000000000000000042": "0x0000000000000000000000000000000000000000000000000000000000000042"
+ },
+ "balance": "0x1",
+ "nonce": "0x1"
+ },
+ "0x71562b71999873db5b286df957af199ec94617f7": {
+ "code": "0xef0100000000000000000000000000000000000000aaaa",
+ "balance": "0x6124fee993afa30e",
+ "nonce": "0x2"
+ },
+ "0x8a0a19589531694250d570040a0c4b74576919b8": {
+ "code": "0x600060006000600060007310000000000000000000000000000000000000015af1600155600060006000600060007310000000000000000000000000000000000000025af16002553d600060003e600051600355",
+ "storage": {
+ "0x0000000000000000000000000000000000000000000000000000000000000001": "0x0000000000000000000000000000000000000000000000000000000000000100",
+ "0x0000000000000000000000000000000000000000000000000000000000000002": "0x0000000000000000000000000000000000000000000000000000000000000100",
+ "0x0000000000000000000000000000000000000000000000000000000000000003": "0x0000000000000000000000000000000000000000000000000000000000000100"
+ },
+ "balance": "0xde0b6b3a7640000"
+ }
+ },
+ "result": {
+ "stateRoot": "0x9fdcacd4510e93c4488e537dc51578b5c6d505771db64a2610036eeb4be7b26f",
+ "txRoot": "0x5d13a0b074e80388dc754da92b22922313a63417b3e25a10f324935e09697a53",
+ "receiptsRoot": "0x504c5d86c34391f70d210e6c482615b391db4bdb9f43479366399d9c5599850a",
+ "logsHash": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
+ "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","receipts": [{
+ "type": "0x4",
+ "root": "0x",
+ "status": "0x1",
+ "cumulativeGasUsed": "0x15fa9",
+ "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","logs": null,"transactionHash": "0x0417aab7c1d8a3989190c3167c132876ce9b8afd99262c5a0f9d06802de3d7ef",
+ "contractAddress": "0x0000000000000000000000000000000000000000",
+ "gasUsed": "0x15fa9",
+ "blockHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
+ "transactionIndex": "0x0"
+ }
+ ],
+ "currentDifficulty": "0x0",
+ "gasUsed": "0x15fa9"
+}
+}
diff --git a/cmd/evm/testdata/33/txs.json b/cmd/evm/testdata/33/txs.json
new file mode 100644
index 000000000000..e3bff4df6a76
--- /dev/null
+++ b/cmd/evm/testdata/33/txs.json
@@ -0,0 +1,37 @@
+[
+ {
+ "type": "0x4",
+ "chainId": "0x1",
+ "nonce": "0x0",
+ "to": "0x71562b71999873db5b286df957af199ec94617f7",
+ "gas": "0x7a120",
+ "gasPrice": null,
+ "maxPriorityFeePerGas": "0x2",
+ "maxFeePerGas": "0x12a05f200",
+ "value": "0x0",
+ "input": "0x",
+ "accessList": [],
+ "authorizationList": [
+ {
+ "chainId": "0x1",
+ "address": "0x000000000000000000000000000000000000aaaa",
+ "nonce": "0x1",
+ "yParity": "0x1",
+ "r": "0xf7e3e597fc097e71ed6c26b14b25e5395bc8510d58b9136af439e12715f2d721",
+ "s": "0x6cf7c3d7939bfdb784373effc0ebb0bd7549691a513f395e3cdabf8602724987"
+ },
+ {
+ "chainId": "0x0",
+ "address": "0x000000000000000000000000000000000000bbbb",
+ "nonce": "0x0",
+ "yParity": "0x1",
+ "r": "0x5011890f198f0356a887b0779bde5afa1ed04e6acb1e3f37f8f18c7b6f521b98",
+ "s": "0x56c3fa3456b103f3ef4a0acb4b647b9cab9ec4bc68fbcdf1e10b49fb2bcbcf61"
+ }
+ ],
+ "secretKey": "0xb71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291",
+ "v": "0x0",
+ "r": "0x0",
+ "s": "0x0"
+ }
+]
diff --git a/common/hexutil/json.go b/common/hexutil/json.go
index 50db208118ee..e0ac98f52d15 100644
--- a/common/hexutil/json.go
+++ b/common/hexutil/json.go
@@ -23,6 +23,8 @@ import (
"math/big"
"reflect"
"strconv"
+
+ "github.com/holiman/uint256"
)
var (
@@ -30,6 +32,7 @@ var (
bigT = reflect.TypeOf((*Big)(nil))
uintT = reflect.TypeOf(Uint(0))
uint64T = reflect.TypeOf(Uint64(0))
+ u256T = reflect.TypeOf((*uint256.Int)(nil))
)
// Bytes marshals/unmarshals as a JSON string with 0x prefix.
@@ -225,6 +228,48 @@ func (b *Big) UnmarshalGraphQL(input interface{}) error {
return err
}
+// U256 marshals/unmarshals as a JSON string with 0x prefix.
+// The zero value marshals as "0x0".
+type U256 uint256.Int
+
+// MarshalText implements encoding.TextMarshaler
+func (b U256) MarshalText() ([]byte, error) {
+ u256 := (*uint256.Int)(&b)
+ return []byte(u256.Hex()), nil
+}
+
+// UnmarshalJSON implements json.Unmarshaler.
+func (b *U256) UnmarshalJSON(input []byte) error {
+ // The uint256.Int.UnmarshalJSON method accepts "dec", "0xhex"; we must be
+ // more strict, hence we check string and invoke SetFromHex directly.
+ if !isString(input) {
+ return errNonString(u256T)
+ }
+ // The hex decoder needs to accept empty string ("") as '0', which uint256.Int
+ // would reject.
+ if len(input) == 2 {
+ (*uint256.Int)(b).Clear()
+ return nil
+ }
+ err := (*uint256.Int)(b).SetFromHex(string(input[1 : len(input)-1]))
+ if err != nil {
+ return &json.UnmarshalTypeError{Value: err.Error(), Type: u256T}
+ }
+ return nil
+}
+
+// UnmarshalText implements encoding.TextUnmarshaler
+func (b *U256) UnmarshalText(input []byte) error {
+ // The uint256.Int.UnmarshalText method accepts "dec", "0xhex"; we must be
+ // more strict, hence we check string and invoke SetFromHex directly.
+ return (*uint256.Int)(b).SetFromHex(string(input))
+}
+
+// String returns the hex encoding of b.
+func (b *U256) String() string {
+ return (*uint256.Int)(b).Hex()
+}
+
// Uint64 marshals/unmarshals as a JSON string with 0x prefix.
// The zero value marshals as "0x0".
type Uint64 uint64
diff --git a/core/bench_test.go b/core/bench_test.go
index 622b4433ccc2..84cbd8c5d3c4 100644
--- a/core/bench_test.go
+++ b/core/bench_test.go
@@ -85,8 +85,8 @@ func genValueTx(nbytes int) func(int, *BlockGen) {
return func(i int, gen *BlockGen) {
toaddr := common.Address{}
data := make([]byte, nbytes)
- gas, _ := IntrinsicGas(data, nil, false, false, false, false)
- signer := types.MakeSigner(gen.config, big.NewInt(int64(i)))
+ gas, _ := IntrinsicGas(data, nil, nil, false, false, false, false)
+ signer := types.MakeSigner(gen.config, big.NewInt(int64(i)), 0 /* block time */)
gasPrice := big.NewInt(0)
if gen.header.BaseFee != nil {
gasPrice = gen.header.BaseFee
@@ -130,7 +130,7 @@ func genTxRing(naccounts int) func(int, *BlockGen) {
if gen.header.BaseFee != nil {
gasPrice = gen.header.BaseFee
}
- signer := types.MakeSigner(gen.config, big.NewInt(int64(i)))
+ signer := types.MakeSigner(gen.config, big.NewInt(int64(i)), 0 /* block time */)
for {
gas -= params.TxGas
if gas < params.TxGas {
diff --git a/core/blockchain.go b/core/blockchain.go
index 73e2a57fa21a..2bdbf1bf5e25 100644
--- a/core/blockchain.go
+++ b/core/blockchain.go
@@ -1507,7 +1507,7 @@ func (bc *BlockChain) insertChain(chain types.Blocks, verifySeals bool) (int, er
}
// Start a parallel signature recovery (signer will fluke on fork transition, minimal perf loss)
- senderCacher.recoverFromBlocks(types.MakeSigner(bc.chainConfig, chain[0].Number()), chain)
+ senderCacher.recoverFromBlocks(types.MakeSigner(bc.chainConfig, chain[0].Number(), chain[0].Time()), chain)
var (
stats = insertStats{startTime: mclock.Now()}
diff --git a/core/blockchain_test.go b/core/blockchain_test.go
index f3488b02ee0a..febdb032ac13 100644
--- a/core/blockchain_test.go
+++ b/core/blockchain_test.go
@@ -17,6 +17,7 @@
package core
import (
+ "bytes"
"errors"
"fmt"
"io/ioutil"
@@ -27,6 +28,7 @@ import (
"testing"
"time"
+ "github.com/holiman/uint256"
"github.com/stretchr/testify/assert"
"github.com/scroll-tech/go-ethereum/common"
@@ -3789,3 +3791,111 @@ func TestCurieTransition(t *testing.T) {
}
}
}
+
+// 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.TestChainConfig
+ signer = types.LatestSigner(&config)
+ 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: GenesisAlloc{
+ addr1: {Balance: funds},
+ addr2: {Balance: funds},
+ aa: { // The address 0xAAAA calls into addr2
+ Code: append(
+ []byte{
+ byte(vm.PUSH1), 0x00,
+ byte(vm.PUSH1), 0x00,
+ byte(vm.PUSH1), 0x00,
+ byte(vm.PUSH1), 0x00,
+ byte(vm.PUSH1), 0x01,
+ byte(vm.PUSH20),
+ },
+ append(
+ addr2.Bytes(),
+ byte(vm.GAS),
+ byte(vm.CALL),
+ )...),
+ Nonce: 0,
+ Balance: big.NewInt(0),
+ },
+ bb: { // The address 0xBBBB sstores 42 into slot 42.
+ Code: []byte{
+ byte(vm.PUSH1), 0x42,
+ byte(vm.PUSH1), 0x42,
+ byte(vm.SSTORE),
+ },
+ Nonce: 0,
+ Balance: big.NewInt(0),
+ },
+ },
+ }
+
+ // Sign authorization tuples.
+ // The way the auths are combined, it becomes
+ // 1. tx -> addr1 which is delegated to 0xaaaa
+ // 2. addr1:0xaaaa calls into addr2:0xbbbb
+ // 3. addr2:0xbbbb writes to storage
+ auth1, _ := types.SignSetCode(key1, types.SetCodeAuthorization{
+ ChainID: *uint256.MustFromBig(gspec.Config.ChainID),
+ Address: aa,
+ Nonce: 1,
+ })
+ auth2, _ := types.SignSetCode(key2, types.SetCodeAuthorization{
+ Address: bb,
+ Nonce: 0,
+ })
+
+ db := rawdb.NewMemoryDatabase()
+ genesis := gspec.MustCommit(db)
+ blocks, _ := GenerateChain(gspec.Config, genesis, ethash.NewFaker(), db, 1, func(i int, b *BlockGen) {
+ b.SetCoinbase(aa)
+ txdata := &types.SetCodeTx{
+ ChainID: uint256.MustFromBig(gspec.Config.ChainID),
+ Nonce: 0,
+ To: addr1,
+ Gas: 500000,
+ GasFeeCap: uint256.MustFromBig(newGwei(5)),
+ GasTipCap: uint256.NewInt(2),
+ AuthList: []types.SetCodeAuthorization{auth1, auth2},
+ }
+ tx := types.MustSignNewTx(key1, signer, txdata)
+ b.AddTx(tx)
+ })
+ chain, _ := NewBlockChain(db, nil, gspec.Config, ethash.NewFaker(), vm.Config{}, nil, nil)
+ 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 !bytes.Equal(actual[:], fortyTwo[:]) {
+ t.Fatalf("addr2 storage wrong: expected %d, got %d", fortyTwo, actual)
+ }
+}
diff --git a/core/chain_makers.go b/core/chain_makers.go
index 9d167f7ca0b9..47a7d9fab847 100644
--- a/core/chain_makers.go
+++ b/core/chain_makers.go
@@ -150,6 +150,11 @@ func (b *BlockGen) Number() *big.Int {
return new(big.Int).Set(b.header.Number)
}
+// Time returns the timestamp of the block being generated.
+func (b *BlockGen) Time() uint64 {
+ return b.header.Time
+}
+
// BaseFee returns the EIP-1559 base fee of the block being generated.
func (b *BlockGen) BaseFee() *big.Int {
if b.header.BaseFee != nil {
diff --git a/core/error.go b/core/error.go
index a29f56ebf3dc..54ec1b188dba 100644
--- a/core/error.go
+++ b/core/error.go
@@ -103,4 +103,20 @@ var (
// ErrSenderNoEOA is returned if the sender of a transaction is a contract.
ErrSenderNoEOA = errors.New("sender not an eoa")
+
+ // -- EIP-7702 errors --
+
+ // Message validation errors:
+ ErrEmptyAuthList = errors.New("EIP-7702 transaction with empty auth list")
+ ErrSetCodeTxCreate = errors.New("EIP-7702 transaction cannot be used to create contract")
+)
+
+// EIP-7702 state transition errors.
+// Note these are just informational, and do not cause tx execution abort.
+var (
+ ErrAuthorizationWrongChainID = errors.New("EIP-7702 authorization chain ID mismatch")
+ ErrAuthorizationNonceOverflow = errors.New("EIP-7702 authorization nonce > 64 bit")
+ ErrAuthorizationInvalidSignature = errors.New("EIP-7702 authorization has invalid signature")
+ ErrAuthorizationDestinationHasCode = errors.New("EIP-7702 authorization destination is a contract")
+ ErrAuthorizationNonceMismatch = errors.New("EIP-7702 authorization nonce does not match current account nonce")
)
diff --git a/core/rawdb/accessors_chain.go b/core/rawdb/accessors_chain.go
index a79037e32573..d89a7d2e2183 100644
--- a/core/rawdb/accessors_chain.go
+++ b/core/rawdb/accessors_chain.go
@@ -586,7 +586,8 @@ func ReadReceipts(db ethdb.Reader, hash common.Hash, number uint64, config *para
log.Error("Missing body but have receipt", "hash", hash, "number", number)
return nil
}
- if err := receipts.DeriveFields(config, hash, number, body.Transactions); err != nil {
+ header := ReadHeader(db, hash, number)
+ if err := receipts.DeriveFields(config, hash, number, header.Time, body.Transactions); err != nil {
log.Error("Failed to derive block receipts fields", "hash", hash, "number", number, "err", err)
return nil
}
diff --git a/core/rawdb/accessors_chain_test.go b/core/rawdb/accessors_chain_test.go
index 9f5fffc5d8c3..3d7729e4c728 100644
--- a/core/rawdb/accessors_chain_test.go
+++ b/core/rawdb/accessors_chain_test.go
@@ -760,7 +760,7 @@ func TestReadLogs(t *testing.T) {
}
// Fill in log fields so we can compare their rlp encoding
- if err := types.Receipts(receipts).DeriveFields(params.TestChainConfig, hash, 0, body.Transactions); err != nil {
+ if err := types.Receipts(receipts).DeriveFields(params.TestChainConfig, hash, 0, 0, body.Transactions); err != nil {
t.Fatal(err)
}
for i, pr := range receipts {
diff --git a/core/state_prefetcher.go b/core/state_prefetcher.go
index 962f43a3ec91..7cdbf1dde696 100644
--- a/core/state_prefetcher.go
+++ b/core/state_prefetcher.go
@@ -55,7 +55,7 @@ func (p *statePrefetcher) Prefetch(block *types.Block, statedb *state.StateDB, c
gaspool = new(GasPool).AddGas(block.GasLimit())
blockContext = NewEVMBlockContext(header, p.bc, p.config, nil)
evm = vm.NewEVM(blockContext, vm.TxContext{}, statedb, p.config, cfg)
- signer = types.MakeSigner(p.config, header.Number)
+ signer = types.MakeSigner(p.config, header.Number, header.Time)
)
// Iterate over and process the individual transactions
byzantium := p.config.IsByzantium(block.Number())
diff --git a/core/state_processor.go b/core/state_processor.go
index 58acf8747642..7a6c5d0d9783 100644
--- a/core/state_processor.go
+++ b/core/state_processor.go
@@ -96,7 +96,7 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg
processorBlockTransactionGauge.Update(int64(block.Transactions().Len()))
// Iterate over and process the individual transactions
for i, tx := range block.Transactions() {
- msg, err := tx.AsMessage(types.MakeSigner(p.config, header.Number), header.BaseFee)
+ msg, err := tx.AsMessage(types.MakeSigner(p.config, header.Number, header.Time), header.BaseFee)
if err != nil {
return nil, nil, 0, fmt.Errorf("could not apply tx %d [%v]: %w", i, tx.Hash().Hex(), err)
}
@@ -190,7 +190,7 @@ func applyTransaction(msg types.Message, config *params.ChainConfig, bc ChainCon
// for the transaction, gas used and an error if the transaction failed,
// indicating the block was invalid.
func ApplyTransaction(config *params.ChainConfig, bc ChainContext, author *common.Address, gp *GasPool, statedb *state.StateDB, header *types.Header, tx *types.Transaction, usedGas *uint64, cfg vm.Config) (*types.Receipt, error) {
- msg, err := tx.AsMessage(types.MakeSigner(config, header.Number), header.BaseFee)
+ msg, err := tx.AsMessage(types.MakeSigner(config, header.Number, header.Time), header.BaseFee)
if err != nil {
return nil, err
}
diff --git a/core/state_processor_test.go b/core/state_processor_test.go
index e565b7062b69..a2585941ce8f 100644
--- a/core/state_processor_test.go
+++ b/core/state_processor_test.go
@@ -23,6 +23,8 @@ import (
"golang.org/x/crypto/sha3"
+ "github.com/holiman/uint256"
+
"github.com/scroll-tech/go-ethereum/common"
"github.com/scroll-tech/go-ethereum/common/math"
"github.com/scroll-tech/go-ethereum/consensus"
@@ -60,6 +62,8 @@ func TestStateProcessorErrors(t *testing.T) {
CurieBlock: big.NewInt(0),
DarwinTime: new(uint64),
DarwinV2Time: new(uint64),
+ EuclidTime: new(uint64),
+ EuclidV2Time: new(uint64),
Ethash: new(params.EthashConfig),
}
signer = types.LatestSigner(config)
@@ -92,6 +96,22 @@ func TestStateProcessorErrors(t *testing.T) {
}), signer, key1)
return tx
}
+ var mkSetCodeTx = func(nonce uint64, to common.Address, gasLimit uint64, gasTipCap, gasFeeCap *big.Int, setCodeAuthorizations []types.SetCodeAuthorization) *types.Transaction {
+ tx, err := types.SignTx(types.NewTx(&types.SetCodeTx{
+ ChainID: uint256.MustFromBig(config.ChainID),
+ Nonce: nonce,
+ GasTipCap: uint256.MustFromBig(gasTipCap),
+ GasFeeCap: uint256.MustFromBig(gasFeeCap),
+ Gas: gasLimit,
+ To: to,
+ Value: new(uint256.Int),
+ AuthList: setCodeAuthorizations,
+ }), signer, key1)
+ if err != nil {
+ t.Fatal(err)
+ }
+ return tx
+ }
{ // Tests against a 'recent' chain definition
var (
db = rawdb.NewMemoryDatabase()
@@ -213,6 +233,14 @@ func TestStateProcessorErrors(t *testing.T) {
},
want: "could not apply tx 0 [0xd82a0c2519acfeac9a948258c47e784acd20651d9d80f9a1c67b4137651c3a24]: insufficient funds for gas * price + value: address 0x71562b71999873DB5b286dF957af199Ec94617F7 have 1000000000000000000 want 2431633873983640103894990685182446064918669677978451844828609264166175722438635000",
},
+ { // ErrEmptyAuthList
+ txs: []*types.Transaction{
+ mkSetCodeTx(0, common.Address{}, params.TxGas, big.NewInt(params.InitialBaseFee), big.NewInt(params.InitialBaseFee), nil),
+ },
+ want: "could not apply tx 0 [0xc18d10f4c809dbdfa1a074c3300de9bc4b7f16a20f0ec667f6f67312b71b956a]: EIP-7702 transaction with empty auth list (sender 0x71562b71999873DB5b286dF957af199Ec94617F7)",
+ },
+ // ErrSetCodeTxCreate cannot be tested here: it is impossible to create a SetCode-tx with nil `to`.
+ // The EstimateGas API tests test this case.
} {
block := GenerateBadBlock(genesis, ethash.NewFaker(), tt.txs, gspec.Config)
_, err := blockchain.InsertChain(types.Blocks{block})
@@ -301,7 +329,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(genesis, ethash.NewFaker(), tt.txs, gspec.Config)
diff --git a/core/state_transition.go b/core/state_transition.go
index 5aeb7ca86898..5c7eb5fce0f3 100644
--- a/core/state_transition.go
+++ b/core/state_transition.go
@@ -26,14 +26,11 @@ import (
cmath "github.com/scroll-tech/go-ethereum/common/math"
"github.com/scroll-tech/go-ethereum/core/types"
"github.com/scroll-tech/go-ethereum/core/vm"
- "github.com/scroll-tech/go-ethereum/crypto/codehash"
"github.com/scroll-tech/go-ethereum/log"
"github.com/scroll-tech/go-ethereum/metrics"
"github.com/scroll-tech/go-ethereum/params"
)
-var emptyKeccakCodeHash = codehash.EmptyKeccakCodeHash
-
var (
stateTransitionEvmCallExecutionTimer = metrics.NewRegisteredTimer("state/transition/call_execution", nil)
stateTransitionApplyMessageTimer = metrics.NewRegisteredTimer("state/transition/apply_message", nil)
@@ -91,6 +88,8 @@ type Message interface {
AccessList() types.AccessList
IsL1MessageTx() bool
TxSize() common.StorageSize
+
+ SetCodeAuthorizations() []types.SetCodeAuthorization
}
// ExecutionResult includes all output after executing given evm
@@ -130,7 +129,7 @@ func (result *ExecutionResult) Revert() []byte {
}
// IntrinsicGas computes the 'intrinsic gas' for a message with the given data.
-func IntrinsicGas(data []byte, accessList types.AccessList, isContractCreation bool, isHomestead, isEIP2028 bool, isEIP3860 bool) (uint64, error) {
+func IntrinsicGas(data []byte, accessList types.AccessList, authList []types.SetCodeAuthorization, isContractCreation bool, isHomestead, isEIP2028 bool, isEIP3860 bool) (uint64, error) {
// Set the starting gas for the raw transaction
var gas uint64
if isContractCreation && isHomestead {
@@ -176,6 +175,9 @@ func IntrinsicGas(data []byte, accessList types.AccessList, isContractCreation b
gas += uint64(len(accessList)) * params.TxAccessListAddressGas
gas += uint64(accessList.StorageKeys()) * params.TxAccessListStorageKeyGas
}
+ if authList != nil {
+ gas += uint64(len(authList)) * params.CallNewAccountGas
+ }
return gas, nil
}
@@ -290,9 +292,10 @@ func (st *StateTransition) preCheck() error {
st.msg.From().Hex(), stNonce)
}
// Make sure the sender is an EOA
- if codeHash := st.state.GetKeccakCodeHash(st.msg.From()); codeHash != emptyKeccakCodeHash && codeHash != (common.Hash{}) {
- return fmt.Errorf("%w: address %v, codehash: %s", ErrSenderNoEOA,
- st.msg.From().Hex(), codeHash)
+ code := st.state.GetCode(st.msg.From())
+ _, delegated := types.ParseDelegation(code)
+ if len(code) > 0 && !delegated {
+ return fmt.Errorf("%w: address %v, len(code): %d", ErrSenderNoEOA, st.msg.From().Hex(), len(code))
}
}
// Make sure that transaction gasFeeCap is greater than the baseFee (post london)
@@ -324,6 +327,15 @@ func (st *StateTransition) preCheck() error {
}
}
}
+ // Check that EIP-7702 authorization list signatures are well formed.
+ if st.msg.SetCodeAuthorizations() != nil {
+ if st.msg.To() == nil {
+ return fmt.Errorf("%w (sender %v)", ErrSetCodeTxCreate, st.msg.From())
+ }
+ if len(st.msg.SetCodeAuthorizations()) == 0 {
+ return fmt.Errorf("%w (sender %v)", ErrEmptyAuthList, st.msg.From())
+ }
+ }
return st.buyGas()
}
@@ -364,7 +376,7 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) {
)
// Check clauses 4-5, subtract intrinsic gas if everything is correct
- gas, err := IntrinsicGas(st.data, st.msg.AccessList(), contractCreation, rules.IsHomestead, rules.IsIstanbul, rules.IsShanghai)
+ gas, err := IntrinsicGas(st.data, st.msg.AccessList(), st.msg.SetCodeAuthorizations(), contractCreation, rules.IsHomestead, rules.IsIstanbul, rules.IsShanghai)
if err != nil {
return nil, err
}
@@ -399,8 +411,27 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) {
} else {
// Increment the nonce for the next transaction
st.state.SetNonce(msg.From(), st.state.GetNonce(sender.Address())+1)
+
+ // Apply EIP-7702 authorizations.
+ var authorizationResults []types.AuthorizationResult
+ if msg.SetCodeAuthorizations() != nil {
+ for _, auth := range st.msg.SetCodeAuthorizations() {
+ // Note errors are ignored, we simply skip invalid authorizations here.
+ authorizationResults = append(authorizationResults, st.applyAuthorization(&auth))
+ }
+ }
+
+ // Perform convenience warming of sender's delegation target. Although the
+ // sender is already warmed in Prepare(..), it's possible a delegation to
+ // the account was deployed during this transaction. To handle correctly,
+ // simply wait until the final state of delegations is determined before
+ // performing the resolution and warming.
+ if addr, ok := types.ParseDelegation(st.state.GetCode(*st.msg.To())); ok {
+ st.state.AddAddressToAccessList(addr)
+ }
+
evmCallStart := time.Now()
- ret, st.gas, vmerr = st.evm.Call(sender, st.to(), st.data, st.gas, st.value)
+ ret, st.gas, vmerr = st.evm.Call(sender, st.to(), st.data, st.gas, st.value, authorizationResults)
stateTransitionEvmCallExecutionTimer.Update(time.Since(evmCallStart))
}
@@ -443,6 +474,67 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) {
}, nil
}
+// validateAuthorization validates an EIP-7702 authorization against the state.
+func (st *StateTransition) validateAuthorization(auth *types.SetCodeAuthorization) (authority common.Address, preCode []byte, err error) {
+ // Verify chain ID is 0 or equal to current chain ID.
+ if !auth.ChainID.IsZero() && auth.ChainID.CmpBig(st.evm.ChainConfig().ChainID) != 0 {
+ return authority, nil, ErrAuthorizationWrongChainID
+ }
+ // Limit nonce to 2^64-1 per EIP-2681.
+ if auth.Nonce+1 < auth.Nonce {
+ return authority, nil, ErrAuthorizationNonceOverflow
+ }
+ // Validate signature values and recover authority.
+ authority, err = auth.Authority()
+ if err != nil {
+ return authority, nil, fmt.Errorf("%w: %v", ErrAuthorizationInvalidSignature, err)
+ }
+ // Check the authority account
+ // 1) doesn't have code or has exisiting delegation
+ // 2) matches the auth's nonce
+ //
+ // Note it is added to the access list even if the authorization is invalid.
+ st.state.AddAddressToAccessList(authority)
+ code := st.state.GetCode(authority)
+ if _, ok := types.ParseDelegation(code); len(code) != 0 && !ok {
+ return authority, nil, ErrAuthorizationDestinationHasCode
+ }
+ if have := st.state.GetNonce(authority); have != auth.Nonce {
+ return authority, nil, ErrAuthorizationNonceMismatch
+ }
+
+ preCodeCopy := make([]byte, len(code))
+ copy(preCodeCopy, code)
+ return authority, preCodeCopy, nil
+}
+
+// applyAuthorization applies an EIP-7702 code delegation to the state.
+func (st *StateTransition) applyAuthorization(auth *types.SetCodeAuthorization) types.AuthorizationResult {
+ authority, preCode, err := st.validateAuthorization(auth)
+ if err != nil {
+ return types.AuthorizationResult{Authority: common.Address{}, PreCode: nil, Success: false}
+ }
+
+ // If the account already exists in state, refund the new account cost
+ // charged in the intrinsic calculation.
+ if st.state.Exist(authority) {
+ st.state.AddRefund(params.CallNewAccountGas - params.TxAuthTupleGas)
+ }
+
+ // Update nonce and account code.
+ st.state.SetNonce(authority, auth.Nonce+1)
+ if auth.Address == (common.Address{}) {
+ // Delegation to zero address means clear.
+ st.state.SetCode(authority, nil)
+ return types.AuthorizationResult{Authority: authority, PreCode: preCode, Success: true}
+ }
+
+ // Otherwise install delegation to auth.Address.
+ st.state.SetCode(authority, types.AddressToDelegation(auth.Address))
+
+ return types.AuthorizationResult{Authority: authority, PreCode: preCode, Success: true}
+}
+
func (st *StateTransition) refundGas(refundQuotient uint64) {
// Apply refund counter, capped to a refund quotient
refund := st.gasUsed() / refundQuotient
diff --git a/core/state_transition_test.go b/core/state_transition_test.go
new file mode 100644
index 000000000000..db07fd5a4385
--- /dev/null
+++ b/core/state_transition_test.go
@@ -0,0 +1,153 @@
+// Copyright 2014 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 core
+
+import (
+ "errors"
+ "math/big"
+ "reflect"
+ "testing"
+
+ "github.com/agiledragon/gomonkey/v2"
+ "github.com/holiman/uint256"
+ "github.com/stretchr/testify/assert"
+
+ "github.com/scroll-tech/go-ethereum/common"
+ "github.com/scroll-tech/go-ethereum/core/rawdb"
+ "github.com/scroll-tech/go-ethereum/core/state"
+ "github.com/scroll-tech/go-ethereum/core/types"
+ "github.com/scroll-tech/go-ethereum/core/vm"
+ "github.com/scroll-tech/go-ethereum/params"
+)
+
+func TestValidateAuthorizations(t *testing.T) {
+ stateDb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil)
+ evm := vm.NewEVM(vm.BlockContext{BlockNumber: new(big.Int), Time: new(big.Int)}, vm.TxContext{}, stateDb, params.TestChainConfig, vm.Config{})
+ st := &StateTransition{evm: evm, state: stateDb}
+
+ t.Run("Chain ID mismatch", func(t *testing.T) {
+ auth := &types.SetCodeAuthorization{
+ ChainID: *uint256.MustFromBig(big.NewInt(2)),
+ Nonce: 0,
+ }
+ _, _, err := st.validateAuthorization(auth)
+ assert.Equal(t, ErrAuthorizationWrongChainID, err)
+ })
+
+ t.Run("Nonce overflow", func(t *testing.T) {
+ auth := &types.SetCodeAuthorization{
+ ChainID: *uint256.MustFromBig(big.NewInt(1)),
+ Nonce: ^uint64(0),
+ }
+ _, _, err := st.validateAuthorization(auth)
+ assert.Equal(t, ErrAuthorizationNonceOverflow, err)
+ })
+
+ t.Run("Invalid signature", func(t *testing.T) {
+ patches := gomonkey.NewPatches()
+ defer patches.Reset()
+ patches.ApplyFunc((*types.SetCodeAuthorization).Authority, func(_ *types.SetCodeAuthorization) (common.Address, error) {
+ return common.Address{}, errors.New("invalid signature")
+ })
+ auth := &types.SetCodeAuthorization{
+ ChainID: *uint256.MustFromBig(big.NewInt(1)),
+ Nonce: 0,
+ }
+ _, _, err := st.validateAuthorization(auth)
+ assert.ErrorIs(t, err, ErrAuthorizationInvalidSignature)
+ })
+
+ t.Run("Destination has code", func(t *testing.T) {
+ patches := gomonkey.NewPatches()
+ defer patches.Reset()
+ patches.ApplyFunc((*types.SetCodeAuthorization).Authority, func(_ *types.SetCodeAuthorization) (common.Address, error) {
+ return common.Address{}, nil
+ })
+ patches.ApplyMethod(reflect.TypeOf(st.state),
+ "GetCode",
+ func(_ interface{}, addr common.Address) []byte {
+ return []byte{byte(vm.PUSH1), 0x00, byte(vm.PUSH1), 0x00, byte(vm.RETURN)}
+ },
+ )
+ auth := &types.SetCodeAuthorization{
+ ChainID: *uint256.MustFromBig(big.NewInt(1)),
+ Nonce: 0,
+ }
+ _, _, err := st.validateAuthorization(auth)
+ assert.Equal(t, ErrAuthorizationDestinationHasCode, err)
+ })
+
+ t.Run("Nonce mismatch", func(t *testing.T) {
+ patches := gomonkey.NewPatches()
+ defer patches.Reset()
+ patches.ApplyFunc((*types.SetCodeAuthorization).Authority, func(_ *types.SetCodeAuthorization) (common.Address, error) {
+ return common.Address{}, nil
+ })
+ patches.ApplyMethod(reflect.TypeOf(st.state),
+ "GetCode",
+ func(_ interface{}, addr common.Address) []byte {
+ return nil
+ },
+ )
+ patches.ApplyMethod(reflect.TypeOf(st.state),
+ "GetNonce",
+ func(_ interface{}, addr common.Address) uint64 {
+ return 1
+ },
+ )
+ auth := &types.SetCodeAuthorization{
+ ChainID: *uint256.MustFromBig(big.NewInt(1)),
+ Nonce: 0,
+ }
+ _, _, err := st.validateAuthorization(auth)
+ assert.Equal(t, ErrAuthorizationNonceMismatch, err)
+ })
+
+ t.Run("Valid authorization", func(t *testing.T) {
+ patches := gomonkey.NewPatches()
+ defer patches.Reset()
+ patches.ApplyFunc((*types.SetCodeAuthorization).Authority, func(_ *types.SetCodeAuthorization) (common.Address, error) {
+ return common.Address{}, nil
+ })
+ patches.ApplyMethod(reflect.TypeOf(st.state),
+ "GetCode",
+ func(_ interface{}, addr common.Address) []byte {
+ return nil
+ },
+ )
+ patches.ApplyMethod(reflect.TypeOf(st.state),
+ "GetNonce",
+ func(_ interface{}, addr common.Address) uint64 {
+ return 0
+ },
+ )
+ auth := &types.SetCodeAuthorization{
+ ChainID: *uint256.MustFromBig(big.NewInt(1)),
+ Nonce: 0,
+ }
+ authority, preCode, err := st.validateAuthorization(auth)
+ assert.NoError(t, err)
+ assert.Equal(t, common.Address{}, authority)
+ assert.Equal(t, []byte{}, preCode)
+
+ auth.ChainID = *uint256.MustFromBig(big.NewInt(0))
+ authority, preCode, err = st.validateAuthorization(auth)
+ assert.NoError(t, err)
+ assert.Equal(t, common.Address{}, authority)
+ assert.Equal(t, []byte{}, preCode)
+ })
+}
diff --git a/core/tx_pool.go b/core/tx_pool.go
index 812a5ccd2227..9e059815840a 100644
--- a/core/tx_pool.go
+++ b/core/tx_pool.go
@@ -21,6 +21,7 @@ import (
"fmt"
"math"
"math/big"
+ "slices"
"sort"
"sync"
"sync/atomic"
@@ -32,6 +33,7 @@ import (
"github.com/scroll-tech/go-ethereum/core/rawdb"
"github.com/scroll-tech/go-ethereum/core/state"
"github.com/scroll-tech/go-ethereum/core/types"
+ "github.com/scroll-tech/go-ethereum/crypto/codehash"
"github.com/scroll-tech/go-ethereum/ethdb"
"github.com/scroll-tech/go-ethereum/event"
"github.com/scroll-tech/go-ethereum/log"
@@ -77,6 +79,10 @@ var (
// with a different one without the required price bump.
ErrReplaceUnderpriced = errors.New("replacement transaction underpriced")
+ // ErrAccountLimitExceeded is returned if a transaction would exceed the number
+ // allowed by a pool for a single account.
+ ErrAccountLimitExceeded = errors.New("account limit exceeded")
+
// ErrGasLimit is returned if a transaction's requested gas limit exceeds the
// maximum allowance of the current block.
ErrGasLimit = errors.New("exceeds block gas limit")
@@ -89,6 +95,11 @@ var (
// than some meaningful limit a user might use. This is not a consensus error
// making the transaction invalid, rather a DOS protection.
ErrOversizedData = errors.New("oversized data")
+
+ // ErrAuthorityReserved is returned if a transaction has an authorization
+ // signed by an address which already has in-flight transactions known to the
+ // pool.
+ ErrAuthorityReserved = errors.New("authority already reserved")
)
var (
@@ -253,6 +264,20 @@ func (config *TxPoolConfig) sanitize() TxPoolConfig {
// The pool separates processable transactions (which can be applied to the
// current state) and future transactions. Transactions move between those
// two states over time as they are received and processed.
+//
+// In addition to tracking transactions, the pool also tracks a set of pending SetCode
+// authorizations (EIP7702). This helps minimize number of transactions that can be
+// trivially churned in the pool. As a standard rule, any account with a deployed
+// delegation or an in-flight authorization to deploy a delegation will only be allowed a
+// single transaction slot instead of the standard number. This is due to the possibility
+// of the account being sweeped by an unrelated account.
+//
+// Because SetCode transactions can have many authorizations included, we avoid explicitly
+// checking their validity to save the state lookup. So long as the encompassing
+// transaction is valid, the authorization will be accepted and tracked by the pool. In
+// case the pool is tracking a pending / queued transaction from a specific account, it
+// will reject new transactions with delegations from that account with standard in-flight
+// transactions.
type TxPool struct {
config TxPoolConfig
chainconfig *params.ChainConfig
@@ -267,6 +292,7 @@ type TxPool struct {
eip2718 bool // Fork indicator whether we are using EIP-2718 type transactions.
eip1559 bool // Fork indicator whether we are using EIP-1559 type transactions.
shanghai bool // Fork indicator whether we are in the Shanghai stage.
+ eip7702 bool // Fork indicator whether we are using EIP-7702 type transactions.
currentState *state.StateDB // Current state in the blockchain head
currentHead *big.Int // Current blockchain head
@@ -728,6 +754,10 @@ func (pool *TxPool) validateTx(tx *types.Transaction, local bool) error {
if !pool.eip1559 && tx.Type() == types.DynamicFeeTxType {
return ErrTxTypeNotSupported
}
+ // Reject set code transactions until EIP-7702 activates.
+ if !pool.eip7702 && tx.Type() == types.SetCodeTxType {
+ return ErrTxTypeNotSupported
+ }
// Reject transactions over defined size to prevent DOS attacks
if uint64(tx.Size()) > txMaxSize {
return ErrOversizedData
@@ -779,6 +809,47 @@ func (pool *TxPool) validateTx(tx *types.Transaction, local bool) error {
if pool.currentState.GetBalance(from).Cmp(tx.Cost()) < 0 {
return ErrInsufficientFunds
}
+ list := pool.pending[from]
+ if list == nil || !list.Overlaps(tx) {
+ usedAndLeftSlots := func(addr common.Address) (int, int) {
+ var have int
+ if list := pool.pending[addr]; list != nil {
+ have += list.Len()
+ }
+ if list := pool.queue[addr]; list != nil {
+ have += list.Len()
+ }
+ if pool.currentState.GetKeccakCodeHash(addr) != codehash.EmptyKeccakCodeHash || len(pool.all.auths[addr]) != 0 {
+ // Allow at most one in-flight tx for delegated accounts or those with
+ // a pending authorization.
+ return have, max(0, 1-have)
+ }
+ return have, math.MaxInt
+ }
+ if used, left := usedAndLeftSlots(from); left <= 0 {
+ return fmt.Errorf("%w: pooled %d txs", ErrAccountLimitExceeded, used)
+ }
+ knownConflicts := func(auths []common.Address) []common.Address {
+ var conflicts []common.Address
+ // Authorities cannot conflict with any pending or queued transactions.
+ for _, addr := range auths {
+ if list := pool.pending[addr]; list != nil {
+ conflicts = append(conflicts, addr)
+ } else if list := pool.queue[addr]; list != nil {
+ conflicts = append(conflicts, addr)
+ }
+ }
+ return conflicts
+ }
+ if conflicts := knownConflicts(tx.SetCodeAuthorities()); len(conflicts) > 0 {
+ return fmt.Errorf("%w: authorization conflicts with other known tx", ErrAuthorityReserved)
+ }
+ }
+ if tx.Type() == types.SetCodeTxType {
+ if len(tx.SetCodeAuthorizations()) == 0 {
+ return fmt.Errorf("set code tx must have at least one authorization tuple")
+ }
+ }
// 2. If FeeVault is enabled, perform an additional check for L1 data fees.
if pool.chainconfig.Scroll.FeeVaultEnabled() {
// Get L1 data fee in current state
@@ -793,7 +864,7 @@ func (pool *TxPool) validateTx(tx *types.Transaction, local bool) error {
}
}
// Ensure the transaction has more gas than the basic tx fee.
- intrGas, err := IntrinsicGas(tx.Data(), tx.AccessList(), tx.To() == nil, true, pool.istanbul, pool.shanghai)
+ intrGas, err := IntrinsicGas(tx.Data(), tx.AccessList(), tx.SetCodeAuthorizations(), tx.To() == nil, true, pool.istanbul, pool.shanghai)
if err != nil {
return err
}
@@ -1514,6 +1585,7 @@ func (pool *TxPool) reset(oldHead, newHead *types.Header) {
pool.eip2718 = pool.chainconfig.IsCurie(next)
pool.eip1559 = pool.chainconfig.IsCurie(next)
pool.shanghai = pool.chainconfig.IsShanghai(next)
+ pool.eip7702 = pool.chainconfig.IsEuclidV2(newHead.Time)
// Update current head
pool.currentHead = next
@@ -1966,6 +2038,8 @@ type txLookup struct {
lock sync.RWMutex
locals map[common.Hash]*types.Transaction
remotes map[common.Hash]*types.Transaction
+
+ auths map[common.Address][]common.Hash // All accounts with a pooled authorization
}
// newTxLookup returns a new txLookup structure.
@@ -1973,6 +2047,7 @@ func newTxLookup() *txLookup {
return &txLookup{
locals: make(map[common.Hash]*types.Transaction),
remotes: make(map[common.Hash]*types.Transaction),
+ auths: make(map[common.Address][]common.Hash),
}
}
@@ -2071,6 +2146,7 @@ func (t *txLookup) Add(tx *types.Transaction, local bool) {
} else {
t.remotes[tx.Hash()] = tx
}
+ t.addAuthorities(tx)
}
// Remove removes a transaction from the lookup.
@@ -2078,6 +2154,7 @@ func (t *txLookup) Remove(hash common.Hash) {
t.lock.Lock()
defer t.lock.Unlock()
+ t.removeAuthorities(hash)
tx, ok := t.locals[hash]
if !ok {
tx, ok = t.remotes[hash]
@@ -2122,6 +2199,43 @@ func (t *txLookup) RemotesBelowTip(threshold *big.Int) types.Transactions {
return found
}
+// addAuthorities tracks the supplied tx in relation to each authority it
+// specifies.
+func (t *txLookup) addAuthorities(tx *types.Transaction) {
+ for _, addr := range tx.SetCodeAuthorities() {
+ list, ok := t.auths[addr]
+ if !ok {
+ list = []common.Hash{}
+ }
+ if slices.Contains(list, tx.Hash()) {
+ // Don't add duplicates.
+ continue
+ }
+ list = append(list, tx.Hash())
+ t.auths[addr] = list
+ }
+}
+
+// removeAuthorities stops tracking the supplied tx in relation to its
+// authorities.
+func (t *txLookup) removeAuthorities(hash common.Hash) {
+ for addr := range t.auths {
+ list := t.auths[addr]
+ // Remove tx from tracker.
+ if i := slices.Index(list, hash); i >= 0 {
+ list = append(list[:i], list[i+1:]...)
+ } else {
+ log.Error("Authority with untracked tx", "addr", addr, "hash", hash)
+ }
+ if len(list) == 0 {
+ // If list is newly empty, delete it entirely.
+ delete(t.auths, addr)
+ continue
+ }
+ t.auths[addr] = list
+ }
+}
+
// numSlots calculates the number of slots needed for a single transaction.
func numSlots(tx *types.Transaction) int {
return int((tx.Size() + txSlotSize - 1) / txSlotSize)
diff --git a/core/tx_pool_test.go b/core/tx_pool_test.go
index 0fdade058cc0..f02bb61ac592 100644
--- a/core/tx_pool_test.go
+++ b/core/tx_pool_test.go
@@ -28,6 +28,7 @@ import (
"testing"
"time"
+ "github.com/holiman/uint256"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@@ -35,6 +36,7 @@ import (
"github.com/scroll-tech/go-ethereum/core/rawdb"
"github.com/scroll-tech/go-ethereum/core/state"
"github.com/scroll-tech/go-ethereum/core/types"
+ "github.com/scroll-tech/go-ethereum/core/vm"
"github.com/scroll-tech/go-ethereum/crypto"
"github.com/scroll-tech/go-ethereum/ethdb"
"github.com/scroll-tech/go-ethereum/event"
@@ -135,6 +137,39 @@ func dynamicFeeTx(nonce uint64, gaslimit uint64, gasFee *big.Int, tip *big.Int,
return tx
}
+type unsignedAuth struct {
+ nonce uint64
+ key *ecdsa.PrivateKey
+}
+
+func setCodeTx(nonce uint64, key *ecdsa.PrivateKey, unsigned []unsignedAuth) *types.Transaction {
+ return pricedSetCodeTx(nonce, 250000, uint256.NewInt(1000), uint256.NewInt(1), key, unsigned)
+}
+
+func pricedSetCodeTx(nonce uint64, gaslimit uint64, gasFee, tip *uint256.Int, key *ecdsa.PrivateKey, unsigned []unsignedAuth) *types.Transaction {
+ var authList []types.SetCodeAuthorization
+ for _, u := range unsigned {
+ auth, _ := types.SignSetCode(u.key, types.SetCodeAuthorization{
+ ChainID: *uint256.MustFromBig(params.TestChainConfig.ChainID),
+ Address: common.Address{0x42},
+ Nonce: u.nonce,
+ })
+ authList = append(authList, auth)
+ }
+ return types.MustSignNewTx(key, types.LatestSignerForChainID(params.TestChainConfig.ChainID), &types.SetCodeTx{
+ ChainID: uint256.MustFromBig(params.TestChainConfig.ChainID),
+ Nonce: nonce,
+ GasTipCap: tip,
+ GasFeeCap: gasFee,
+ Gas: gaslimit,
+ To: common.Address{},
+ Value: uint256.NewInt(100),
+ Data: nil,
+ AccessList: nil,
+ AuthList: authList,
+ })
+}
+
func setupTxPool() (*TxPool, *ecdsa.PrivateKey) {
return setupTxPoolWithConfig(params.TestChainConfig)
}
@@ -2631,6 +2666,198 @@ func TestTransactionSlotCount(t *testing.T) {
}
}
+// TestSetCodeTransactions tests a few scenarios regarding the EIP-7702
+// SetCodeTx.
+func TestSetCodeTransactions(t *testing.T) {
+ t.Parallel()
+
+ // Create the test accounts
+ var (
+ keyA, _ = crypto.GenerateKey()
+ keyB, _ = crypto.GenerateKey()
+ keyC, _ = crypto.GenerateKey()
+ addrA = crypto.PubkeyToAddress(keyA.PublicKey)
+ addrB = crypto.PubkeyToAddress(keyB.PublicKey)
+ addrC = crypto.PubkeyToAddress(keyC.PublicKey)
+ )
+
+ for _, tt := range []struct {
+ name string
+ pending int
+ queued int
+ run func(string, *TxPool, *state.StateDB)
+ }{
+ {
+ // Check that only one in-flight transaction is allowed for accounts
+ // with delegation set. Also verify the accepted transaction can be
+ // replaced by fee.
+ name: "only-one-in-flight",
+ pending: 1,
+ run: func(name string, pool *TxPool, statedb *state.StateDB) {
+ aa := common.Address{0xaa, 0xaa}
+ statedb.SetCode(addrA, append(types.DelegationPrefix, aa.Bytes()...))
+ statedb.SetCode(aa, []byte{byte(vm.ADDRESS), byte(vm.PUSH0), byte(vm.SSTORE)})
+ // Send transactions. First is accepted, second is rejected.
+ if err := pool.addRemoteSync(pricedTransaction(0, 100000, big.NewInt(1), keyA)); err != nil {
+ t.Fatalf("%s: failed to add remote transaction: %v", name, err)
+ }
+ if err := pool.addRemoteSync(pricedTransaction(1, 100000, big.NewInt(1), keyA)); !errors.Is(err, ErrAccountLimitExceeded) {
+ t.Fatalf("%s: error mismatch: want %v, have %v", name, ErrAccountLimitExceeded, err)
+ }
+ // Also check gapped transaction.
+ if err := pool.addRemoteSync(pricedTransaction(2, 100000, big.NewInt(1), keyA)); !errors.Is(err, ErrAccountLimitExceeded) {
+ t.Fatalf("%s: error mismatch: want %v, have %v", name, ErrAccountLimitExceeded, err)
+ }
+ // Replace by fee.
+ if err := pool.addRemoteSync(pricedTransaction(0, 100000, big.NewInt(10), keyA)); err != nil {
+ t.Fatalf("%s: failed to replace with remote transaction: %v", name, err)
+ }
+ },
+ },
+ {
+ name: "allow-setcode-tx-with-pending-authority-tx",
+ pending: 2,
+ run: func(name string, pool *TxPool, statedb *state.StateDB) {
+ // Send two transactions where the first has no conflicting delegations and
+ // the second should be allowed despite conflicting with the authorities in 1).
+ if err := pool.addRemoteSync(setCodeTx(0, keyA, []unsignedAuth{{1, keyC}})); err != nil {
+ t.Fatalf("%s: failed to add with remote setcode transaction: %v", name, err)
+ }
+ if err := pool.addRemoteSync(setCodeTx(0, keyB, []unsignedAuth{{1, keyC}})); err != nil {
+ t.Fatalf("%s: failed to add conflicting delegation: %v", name, err)
+ }
+ },
+ },
+ {
+ name: "allow-one-tx-from-pooled-delegation",
+ pending: 2,
+ run: func(name string, pool *TxPool, statedb *state.StateDB) {
+ // Verify C cannot originate another transaction when it has a pooled delegation.
+ if err := pool.addRemoteSync(setCodeTx(0, keyA, []unsignedAuth{{0, keyC}})); err != nil {
+ t.Fatalf("%s: failed to add with remote setcode transaction: %v", name, err)
+ }
+ if err := pool.addRemoteSync(pricedTransaction(0, 100000, big.NewInt(1), keyC)); err != nil {
+ t.Fatalf("%s: failed to add with pending delegation: %v", name, err)
+ }
+ // Also check gapped transaction is rejected.
+ if err := pool.addRemoteSync(pricedTransaction(1, 100000, big.NewInt(1), keyC)); !errors.Is(err, ErrAccountLimitExceeded) {
+ t.Fatalf("%s: error mismatch: want %v, have %v", name, ErrAccountLimitExceeded, err)
+ }
+ },
+ },
+ {
+ name: "replace-by-fee-setcode-tx",
+ pending: 1,
+ run: func(name string, pool *TxPool, statedb *state.StateDB) {
+ // 4. Fee bump the setcode tx send.
+ if err := pool.addRemoteSync(setCodeTx(0, keyB, []unsignedAuth{{1, keyC}})); err != nil {
+ t.Fatalf("%s: failed to add with remote setcode transaction: %v", name, err)
+ }
+ if err := pool.addRemoteSync(pricedSetCodeTx(0, 250000, uint256.NewInt(2000), uint256.NewInt(2), keyB, []unsignedAuth{{0, keyC}})); err != nil {
+ t.Fatalf("%s: failed to add with remote setcode transaction: %v", name, err)
+ }
+ },
+ },
+ {
+ name: "allow-tx-from-replaced-authority",
+ pending: 2,
+ run: func(name string, pool *TxPool, statedb *state.StateDB) {
+ // Fee bump with a different auth list. Make sure that unlocks the authorities.
+ if err := pool.addRemoteSync(pricedSetCodeTx(0, 250000, uint256.NewInt(10), uint256.NewInt(3), keyA, []unsignedAuth{{0, keyB}})); err != nil {
+ t.Fatalf("%s: failed to add with remote setcode transaction: %v", name, err)
+ }
+ if err := pool.addRemoteSync(pricedSetCodeTx(0, 250000, uint256.NewInt(3000), uint256.NewInt(300), keyA, []unsignedAuth{{0, keyC}})); err != nil {
+ t.Fatalf("%s: failed to add with remote setcode transaction: %v", name, err)
+ }
+ // Now send a regular tx from B.
+ if err := pool.addRemoteSync(pricedTransaction(0, 100000, big.NewInt(10), keyB)); err != nil {
+ t.Fatalf("%s: failed to replace with remote transaction: %v", name, err)
+ }
+ },
+ },
+ {
+ name: "allow-tx-from-replaced-self-sponsor-authority",
+ pending: 2,
+ run: func(name string, pool *TxPool, statedb *state.StateDB) {
+ if err := pool.addRemoteSync(pricedSetCodeTx(0, 250000, uint256.NewInt(10), uint256.NewInt(3), keyA, []unsignedAuth{{0, keyA}})); err != nil {
+ t.Fatalf("%s: failed to add with remote setcode transaction: %v", name, err)
+ }
+ if err := pool.addRemoteSync(pricedSetCodeTx(0, 250000, uint256.NewInt(30), uint256.NewInt(30), keyA, []unsignedAuth{{0, keyB}})); err != nil {
+ t.Fatalf("%s: failed to add with remote setcode transaction: %v", name, err)
+ }
+ // Now send a regular tx from keyA.
+ if err := pool.addRemoteSync(pricedTransaction(0, 100000, big.NewInt(1000), keyA)); err != nil {
+ t.Fatalf("%s: failed to replace with remote transaction: %v", name, err)
+ }
+ // Make sure we can still send from keyB.
+ if err := pool.addRemoteSync(pricedTransaction(0, 100000, big.NewInt(1000), keyB)); err != nil {
+ t.Fatalf("%s: failed to replace with remote transaction: %v", name, err)
+ }
+ },
+ },
+ {
+ name: "track-multiple-conflicting-delegations",
+ pending: 3,
+ run: func(name string, pool *TxPool, statedb *state.StateDB) {
+ // Send two setcode txs both with C as an authority.
+ if err := pool.addRemoteSync(pricedSetCodeTx(0, 250000, uint256.NewInt(10), uint256.NewInt(3), keyA, []unsignedAuth{{0, keyC}})); err != nil {
+ t.Fatalf("%s: failed to add with remote setcode transaction: %v", name, err)
+ }
+ if err := pool.addRemoteSync(pricedSetCodeTx(0, 250000, uint256.NewInt(30), uint256.NewInt(30), keyB, []unsignedAuth{{0, keyC}})); err != nil {
+ t.Fatalf("%s: failed to add with remote setcode transaction: %v", name, err)
+ }
+ // Replace the tx from A with a non-setcode tx.
+ if err := pool.addRemoteSync(pricedTransaction(0, 100000, big.NewInt(1000), keyA)); err != nil {
+ t.Fatalf("%s: failed to replace with remote transaction: %v", name, err)
+ }
+ // Make sure we can only pool one tx from keyC since it is still a
+ // pending authority.
+ if err := pool.addRemoteSync(pricedTransaction(0, 100000, big.NewInt(1000), keyC)); err != nil {
+ t.Fatalf("%s: failed to added single pooled for account with pending delegation: %v", name, err)
+ }
+ if err, want := pool.addRemoteSync(pricedTransaction(1, 100000, big.NewInt(1000), keyC)), ErrAccountLimitExceeded; !errors.Is(err, want) {
+ t.Fatalf("%s: error mismatch: want %v, have %v", name, want, err)
+ }
+ },
+ },
+ {
+ name: "reject-delegation-from-pending-account",
+ pending: 1,
+ run: func(name string, pool *TxPool, statedb *state.StateDB) {
+ // Attempt to submit a delegation from an account with a pending tx.
+ if err := pool.addRemoteSync(pricedTransaction(0, 100000, big.NewInt(1000), keyC)); err != nil {
+ t.Fatalf("%s: failed to add with remote setcode transaction: %v", name, err)
+ }
+ if err, want := pool.addRemoteSync(setCodeTx(0, keyA, []unsignedAuth{{1, keyC}})), ErrAuthorityReserved; !errors.Is(err, want) {
+ t.Fatalf("%s: error mismatch: want %v, have %v", name, want, err)
+ }
+ },
+ },
+ } {
+ // Create the pool to test the status retrievals with
+ statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil)
+ blockchain := &testBlockChain{1000000, statedb, new(event.Feed)}
+ pool := NewTxPool(testTxPoolConfig, params.TestChainConfig, blockchain)
+ defer pool.Stop()
+
+ testAddBalance(pool, addrA, big.NewInt(params.Ether))
+ testAddBalance(pool, addrB, big.NewInt(params.Ether))
+ testAddBalance(pool, addrC, big.NewInt(params.Ether))
+
+ tt.run(tt.name, pool, statedb)
+ pending, queued := pool.Stats()
+ if pending != tt.pending {
+ t.Fatalf("%s: pending transactions mismatched: have %d, want %d", tt.name, pending, tt.pending)
+ }
+ if queued != tt.queued {
+ t.Fatalf("%s: queued transactions mismatched: have %d, want %d", tt.name, queued, tt.queued)
+ }
+ if err := validateTxPoolInternals(pool); err != nil {
+ t.Fatalf("%s: pool internal state corrupted: %v", tt.name, err)
+ }
+ }
+}
+
// Benchmarks the speed of validating the contents of the pending queue of the
// transaction pool.
func BenchmarkPendingDemotion100(b *testing.B) { benchmarkPendingDemotion(b, 100) }
diff --git a/core/types/gen_authorization.go b/core/types/gen_authorization.go
new file mode 100644
index 000000000000..6dcb31af4d83
--- /dev/null
+++ b/core/types/gen_authorization.go
@@ -0,0 +1,76 @@
+// Code generated by github.com/fjl/gencodec. DO NOT EDIT.
+
+package types
+
+import (
+ "encoding/json"
+ "errors"
+
+ "github.com/holiman/uint256"
+
+ "github.com/scroll-tech/go-ethereum/common"
+ "github.com/scroll-tech/go-ethereum/common/hexutil"
+)
+
+var _ = (*authorizationMarshaling)(nil)
+
+// MarshalJSON marshals as JSON.
+func (s SetCodeAuthorization) MarshalJSON() ([]byte, error) {
+ type SetCodeAuthorization struct {
+ ChainID hexutil.U256 `json:"chainId" gencodec:"required"`
+ Address common.Address `json:"address" gencodec:"required"`
+ Nonce hexutil.Uint64 `json:"nonce" gencodec:"required"`
+ V hexutil.Uint64 `json:"yParity" gencodec:"required"`
+ R hexutil.U256 `json:"r" gencodec:"required"`
+ S hexutil.U256 `json:"s" gencodec:"required"`
+ }
+ var enc SetCodeAuthorization
+ enc.ChainID = hexutil.U256(s.ChainID)
+ enc.Address = s.Address
+ enc.Nonce = hexutil.Uint64(s.Nonce)
+ enc.V = hexutil.Uint64(s.V)
+ enc.R = hexutil.U256(s.R)
+ enc.S = hexutil.U256(s.S)
+ return json.Marshal(&enc)
+}
+
+// UnmarshalJSON unmarshals from JSON.
+func (s *SetCodeAuthorization) UnmarshalJSON(input []byte) error {
+ type SetCodeAuthorization struct {
+ ChainID *hexutil.U256 `json:"chainId" gencodec:"required"`
+ Address *common.Address `json:"address" gencodec:"required"`
+ Nonce *hexutil.Uint64 `json:"nonce" gencodec:"required"`
+ V *hexutil.Uint64 `json:"yParity" gencodec:"required"`
+ R *hexutil.U256 `json:"r" gencodec:"required"`
+ S *hexutil.U256 `json:"s" gencodec:"required"`
+ }
+ var dec SetCodeAuthorization
+ if err := json.Unmarshal(input, &dec); err != nil {
+ return err
+ }
+ if dec.ChainID == nil {
+ return errors.New("missing required field 'chainId' for SetCodeAuthorization")
+ }
+ s.ChainID = uint256.Int(*dec.ChainID)
+ if dec.Address == nil {
+ return errors.New("missing required field 'address' for SetCodeAuthorization")
+ }
+ s.Address = *dec.Address
+ if dec.Nonce == nil {
+ return errors.New("missing required field 'nonce' for SetCodeAuthorization")
+ }
+ s.Nonce = uint64(*dec.Nonce)
+ if dec.V == nil {
+ return errors.New("missing required field 'yParity' for SetCodeAuthorization")
+ }
+ s.V = uint8(*dec.V)
+ if dec.R == nil {
+ return errors.New("missing required field 'r' for SetCodeAuthorization")
+ }
+ s.R = uint256.Int(*dec.R)
+ if dec.S == nil {
+ return errors.New("missing required field 's' for SetCodeAuthorization")
+ }
+ s.S = uint256.Int(*dec.S)
+ return nil
+}
diff --git a/core/types/l2trace.go b/core/types/l2trace.go
index b5a1ebd8d053..a04f572fbe5d 100644
--- a/core/types/l2trace.go
+++ b/core/types/l2trace.go
@@ -124,29 +124,30 @@ type StorageWrapper struct {
}
type TransactionData struct {
- Type uint8 `json:"type"`
- Nonce uint64 `json:"nonce"`
- TxHash string `json:"txHash"`
- Gas uint64 `json:"gas"`
- GasPrice *hexutil.Big `json:"gasPrice"`
- GasTipCap *hexutil.Big `json:"gasTipCap"`
- GasFeeCap *hexutil.Big `json:"gasFeeCap"`
- From common.Address `json:"from"`
- To *common.Address `json:"to"`
- ChainId *hexutil.Big `json:"chainId"`
- Value *hexutil.Big `json:"value"`
- Data string `json:"data"`
- IsCreate bool `json:"isCreate"`
- AccessList AccessList `json:"accessList"`
- V *hexutil.Big `json:"v"`
- R *hexutil.Big `json:"r"`
- S *hexutil.Big `json:"s"`
+ Type uint8 `json:"type"`
+ Nonce uint64 `json:"nonce"`
+ TxHash string `json:"txHash"`
+ Gas uint64 `json:"gas"`
+ GasPrice *hexutil.Big `json:"gasPrice"`
+ GasTipCap *hexutil.Big `json:"gasTipCap"`
+ GasFeeCap *hexutil.Big `json:"gasFeeCap"`
+ From common.Address `json:"from"`
+ To *common.Address `json:"to"`
+ ChainId *hexutil.Big `json:"chainId"`
+ Value *hexutil.Big `json:"value"`
+ Data string `json:"data"`
+ IsCreate bool `json:"isCreate"`
+ AccessList AccessList `json:"accessList"`
+ AuthorizationList []SetCodeAuthorization `json:"authorizationList"`
+ V *hexutil.Big `json:"v"`
+ R *hexutil.Big `json:"r"`
+ S *hexutil.Big `json:"s"`
}
// NewTransactionData returns a transaction that will serialize to the trace
// representation, with the given location metadata set (if available).
-func NewTransactionData(tx *Transaction, blockNumber uint64, config *params.ChainConfig) *TransactionData {
- signer := MakeSigner(config, big.NewInt(0).SetUint64(blockNumber))
+func NewTransactionData(tx *Transaction, blockNumber, blockTime uint64, config *params.ChainConfig) *TransactionData {
+ signer := MakeSigner(config, big.NewInt(0).SetUint64(blockNumber), blockTime)
from, _ := Sender(signer, tx)
v, r, s := tx.RawSignatureValues()
@@ -156,23 +157,24 @@ func NewTransactionData(tx *Transaction, blockNumber uint64, config *params.Chai
}
result := &TransactionData{
- Type: tx.Type(),
- TxHash: tx.Hash().String(),
- Nonce: nonce,
- ChainId: (*hexutil.Big)(tx.ChainId()),
- From: from,
- Gas: tx.Gas(),
- GasPrice: (*hexutil.Big)(tx.GasPrice()),
- GasTipCap: (*hexutil.Big)(tx.GasTipCap()),
- GasFeeCap: (*hexutil.Big)(tx.GasFeeCap()),
- To: tx.To(),
- Value: (*hexutil.Big)(tx.Value()),
- Data: hexutil.Encode(tx.Data()),
- IsCreate: tx.To() == nil,
- AccessList: tx.AccessList(),
- V: (*hexutil.Big)(v),
- R: (*hexutil.Big)(r),
- S: (*hexutil.Big)(s),
+ Type: tx.Type(),
+ TxHash: tx.Hash().String(),
+ Nonce: nonce,
+ ChainId: (*hexutil.Big)(tx.ChainId()),
+ From: from,
+ Gas: tx.Gas(),
+ GasPrice: (*hexutil.Big)(tx.GasPrice()),
+ GasTipCap: (*hexutil.Big)(tx.GasTipCap()),
+ GasFeeCap: (*hexutil.Big)(tx.GasFeeCap()),
+ To: tx.To(),
+ Value: (*hexutil.Big)(tx.Value()),
+ Data: hexutil.Encode(tx.Data()),
+ IsCreate: tx.To() == nil,
+ AccessList: tx.AccessList(),
+ AuthorizationList: tx.SetCodeAuthorizations(),
+ V: (*hexutil.Big)(v),
+ R: (*hexutil.Big)(r),
+ S: (*hexutil.Big)(s),
}
return result
}
diff --git a/core/types/receipt.go b/core/types/receipt.go
index 4377ca7e74cb..fbfdb72036aa 100644
--- a/core/types/receipt.go
+++ b/core/types/receipt.go
@@ -236,7 +236,7 @@ func (r *Receipt) decodeTyped(b []byte) error {
return errEmptyTypedReceipt
}
switch b[0] {
- case DynamicFeeTxType, AccessListTxType, BlobTxType, L1MessageTxType:
+ case DynamicFeeTxType, AccessListTxType, BlobTxType, SetCodeTxType, L1MessageTxType:
var data receiptRLP
err := rlp.DecodeBytes(b[1:], &data)
if err != nil {
@@ -419,20 +419,13 @@ func (rs Receipts) Len() int { return len(rs) }
func (rs Receipts) EncodeIndex(i int, w *bytes.Buffer) {
r := rs[i]
data := &receiptRLP{r.statusEncoding(), r.CumulativeGasUsed, r.Bloom, r.Logs}
- switch r.Type {
- case LegacyTxType:
- rlp.Encode(w, data)
- case AccessListTxType:
- w.WriteByte(AccessListTxType)
- rlp.Encode(w, data)
- case DynamicFeeTxType:
- w.WriteByte(DynamicFeeTxType)
- rlp.Encode(w, data)
- case BlobTxType:
- w.WriteByte(BlobTxType)
+ if r.Type == LegacyTxType {
rlp.Encode(w, data)
- case L1MessageTxType:
- w.WriteByte(L1MessageTxType)
+ return
+ }
+ w.WriteByte(r.Type)
+ switch r.Type {
+ case AccessListTxType, DynamicFeeTxType, BlobTxType, SetCodeTxType, L1MessageTxType:
rlp.Encode(w, data)
default:
// For unsupported types, write nothing. Since this is for
@@ -443,8 +436,8 @@ func (rs Receipts) EncodeIndex(i int, w *bytes.Buffer) {
// DeriveFields fills the receipts with their computed fields based on consensus
// data and contextual infos like containing block and transactions.
-func (rs Receipts) DeriveFields(config *params.ChainConfig, hash common.Hash, number uint64, txs Transactions) error {
- signer := MakeSigner(config, new(big.Int).SetUint64(number))
+func (rs Receipts) DeriveFields(config *params.ChainConfig, hash common.Hash, number, time uint64, txs Transactions) error {
+ signer := MakeSigner(config, new(big.Int).SetUint64(number), time)
logIndex := uint(0)
if len(txs) != len(rs) {
diff --git a/core/types/receipt_test.go b/core/types/receipt_test.go
index 3d9759ee3ff2..edc1f84812b3 100644
--- a/core/types/receipt_test.go
+++ b/core/types/receipt_test.go
@@ -297,13 +297,14 @@ func TestDeriveFields(t *testing.T) {
// Clear all the computed fields and re-derive them
number := big.NewInt(1)
hash := common.BytesToHash([]byte{0x03, 0x14})
+ time := uint64(0)
clearComputedFieldsOnReceipts(t, receipts)
- if err := receipts.DeriveFields(params.TestChainConfig, hash, number.Uint64(), txs); err != nil {
+ if err := receipts.DeriveFields(params.TestChainConfig, hash, number.Uint64(), time, txs); err != nil {
t.Fatalf("DeriveFields(...) = %v, want ", err)
}
// Iterate over all the computed fields and check that they're correct
- signer := MakeSigner(params.TestChainConfig, number)
+ signer := MakeSigner(params.TestChainConfig, number, time)
logIndex := uint(0)
for i := range receipts {
diff --git a/core/types/setcode_tx.go b/core/types/setcode_tx.go
new file mode 100644
index 000000000000..eb25e1004361
--- /dev/null
+++ b/core/types/setcode_tx.go
@@ -0,0 +1,222 @@
+// 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 (
+ "bytes"
+ "crypto/ecdsa"
+ "errors"
+ "math/big"
+
+ "github.com/holiman/uint256"
+
+ "github.com/scroll-tech/go-ethereum/common"
+ "github.com/scroll-tech/go-ethereum/common/hexutil"
+ "github.com/scroll-tech/go-ethereum/crypto"
+ "github.com/scroll-tech/go-ethereum/rlp"
+)
+
+// 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()...)
+}
+
+// AuthorizationResult is the result of SetCode transaction's delegations.
+type AuthorizationResult struct {
+ Authority common.Address
+ PreCode []byte
+ Success bool
+}
+
+// SetCodeTx implements the EIP-7702 transaction type which temporarily installs
+// the code at the signer's address.
+type SetCodeTx struct {
+ ChainID *uint256.Int
+ 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 []SetCodeAuthorization
+
+ // 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 SetCodeAuthorization -field-override authorizationMarshaling -out gen_authorization.go
+
+// SetCodeAuthorization is an authorization from an account to deploy code at its address.
+type SetCodeAuthorization struct {
+ ChainID uint256.Int `json:"chainId" gencodec:"required"`
+ Address common.Address `json:"address" gencodec:"required"`
+ Nonce uint64 `json:"nonce" gencodec:"required"`
+ V uint8 `json:"yParity" gencodec:"required"`
+ R uint256.Int `json:"r" gencodec:"required"`
+ S uint256.Int `json:"s" gencodec:"required"`
+}
+
+// field type overrides for gencodec
+type authorizationMarshaling struct {
+ ChainID hexutil.U256
+ Nonce hexutil.Uint64
+ V hexutil.Uint64
+ R hexutil.U256
+ S hexutil.U256
+}
+
+// SignSetCode creates a signed the SetCode authorization.
+func SignSetCode(prv *ecdsa.PrivateKey, auth SetCodeAuthorization) (SetCodeAuthorization, error) {
+ sighash := auth.sigHash()
+ sig, err := crypto.Sign(sighash[:], prv)
+ if err != nil {
+ return SetCodeAuthorization{}, err
+ }
+ r, s, _ := decodeSignature(sig)
+ return SetCodeAuthorization{
+ ChainID: auth.ChainID,
+ Address: auth.Address,
+ Nonce: auth.Nonce,
+ V: sig[64],
+ R: *uint256.MustFromBig(r),
+ S: *uint256.MustFromBig(s),
+ }, nil
+}
+
+func (a *SetCodeAuthorization) sigHash() common.Hash {
+ return prefixedRlpHash(0x05, []any{
+ a.ChainID,
+ a.Address,
+ a.Nonce,
+ })
+}
+
+// Authority recovers the the authorizing account of an authorization.
+func (a *SetCodeAuthorization) Authority() (common.Address, error) {
+ sighash := a.sigHash()
+ if !crypto.ValidateSignatureValues(a.V, a.R.ToBig(), a.S.ToBig(), true) {
+ return common.Address{}, ErrInvalidSig
+ }
+ // encode the signature in uncompressed format
+ var sig [crypto.SignatureLength]byte
+ a.R.WriteToSlice(sig[:32])
+ a.S.WriteToSlice(sig[32:64])
+ 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([]SetCodeAuthorization, len(tx.AuthList)),
+ Value: new(uint256.Int),
+ ChainID: new(uint256.Int),
+ 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.ChainID != nil {
+ cpy.ChainID.Set(tx.ChainID)
+ }
+ 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 tx.ChainID.ToBig() }
+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) 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 = uint256.MustFromBig(chainID)
+ 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/setcode_tx_test.go b/core/types/setcode_tx_test.go
new file mode 100644
index 000000000000..70e20d58c79f
--- /dev/null
+++ b/core/types/setcode_tx_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/scroll-tech/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/types/transaction.go b/core/types/transaction.go
index f7fbcd31c28e..6a5a50f8b286 100644
--- a/core/types/transaction.go
+++ b/core/types/transaction.go
@@ -52,6 +52,7 @@ const (
AccessListTxType = 0x01
DynamicFeeTxType = 0x02
BlobTxType = 0x03
+ SetCodeTxType = 0x04
L1MessageTxType = 0x7E
)
@@ -202,6 +203,8 @@ func (tx *Transaction) decodeTyped(b []byte) (TxData, error) {
inner = new(DynamicFeeTx)
case BlobTxType:
inner = new(BlobTx)
+ case SetCodeTxType:
+ inner = new(SetCodeTx)
case L1MessageTxType:
inner = new(L1MessageTx)
default:
@@ -465,6 +468,30 @@ func (tx *Transaction) EffectiveGasTipIntCmp(other *big.Int, baseFee *big.Int) i
return tx.EffectiveGasTipValue(baseFee).Cmp(other)
}
+// SetCodeAuthorizations returns the authorizations list of the transaction.
+func (tx *Transaction) SetCodeAuthorizations() []SetCodeAuthorization {
+ setcodetx, ok := tx.inner.(*SetCodeTx)
+ if !ok {
+ return nil
+ }
+ return setcodetx.AuthList
+}
+
+// SetCodeAuthorities returns a list of each authorization's corresponding authority.
+func (tx *Transaction) SetCodeAuthorities() []common.Address {
+ setcodetx, ok := tx.inner.(*SetCodeTx)
+ if !ok {
+ return nil
+ }
+ auths := make([]common.Address, 0, len(setcodetx.AuthList))
+ for _, auth := range setcodetx.AuthList {
+ if addr, err := auth.Authority(); err == nil {
+ auths = append(auths, addr)
+ }
+ }
+ return auths
+}
+
// Hash returns the transaction hash.
func (tx *Transaction) Hash() common.Hash {
if hash := tx.hash.Load(); hash != nil {
@@ -740,53 +767,56 @@ func (t *L1MessagesByQueueIndex) Pop() {
//
// NOTE: In a future PR this will be removed.
type Message struct {
- to *common.Address
- from common.Address
- nonce uint64
- amount *big.Int
- gasLimit uint64
- gasPrice *big.Int
- gasFeeCap *big.Int
- gasTipCap *big.Int
- data []byte
- accessList AccessList
- isFake bool
- isL1MessageTx bool
- txSize common.StorageSize
-}
-
-func NewMessage(from common.Address, to *common.Address, nonce uint64, amount *big.Int, gasLimit uint64, gasPrice, gasFeeCap, gasTipCap *big.Int, data []byte, accessList AccessList, isFake bool) Message {
+ to *common.Address
+ from common.Address
+ nonce uint64
+ amount *big.Int
+ gasLimit uint64
+ gasPrice *big.Int
+ gasFeeCap *big.Int
+ gasTipCap *big.Int
+ data []byte
+ accessList AccessList
+ isFake bool
+ isL1MessageTx bool
+ txSize common.StorageSize
+ setCodeAuthorizations []SetCodeAuthorization
+}
+
+func NewMessage(from common.Address, to *common.Address, nonce uint64, amount *big.Int, gasLimit uint64, gasPrice, gasFeeCap, gasTipCap *big.Int, data []byte, accessList AccessList, isFake bool, setCodeAuthorizations []SetCodeAuthorization) Message {
return Message{
- from: from,
- to: to,
- nonce: nonce,
- amount: amount,
- gasLimit: gasLimit,
- gasPrice: gasPrice,
- gasFeeCap: gasFeeCap,
- gasTipCap: gasTipCap,
- data: data,
- accessList: accessList,
- isFake: isFake,
- isL1MessageTx: false,
+ from: from,
+ to: to,
+ nonce: nonce,
+ amount: amount,
+ gasLimit: gasLimit,
+ gasPrice: gasPrice,
+ gasFeeCap: gasFeeCap,
+ gasTipCap: gasTipCap,
+ data: data,
+ accessList: accessList,
+ isFake: isFake,
+ isL1MessageTx: false,
+ setCodeAuthorizations: setCodeAuthorizations,
}
}
// AsMessage returns the transaction as a core.Message.
func (tx *Transaction) AsMessage(s Signer, baseFee *big.Int) (Message, error) {
msg := Message{
- nonce: tx.Nonce(),
- gasLimit: tx.Gas(),
- gasPrice: new(big.Int).Set(tx.GasPrice()),
- gasFeeCap: new(big.Int).Set(tx.GasFeeCap()),
- gasTipCap: new(big.Int).Set(tx.GasTipCap()),
- to: tx.To(),
- amount: tx.Value(),
- data: tx.Data(),
- accessList: tx.AccessList(),
- isFake: false,
- isL1MessageTx: tx.IsL1MessageTx(),
- txSize: tx.Size(),
+ nonce: tx.Nonce(),
+ gasLimit: tx.Gas(),
+ gasPrice: new(big.Int).Set(tx.GasPrice()),
+ gasFeeCap: new(big.Int).Set(tx.GasFeeCap()),
+ gasTipCap: new(big.Int).Set(tx.GasTipCap()),
+ to: tx.To(),
+ amount: tx.Value(),
+ data: tx.Data(),
+ accessList: tx.AccessList(),
+ isFake: false,
+ isL1MessageTx: tx.IsL1MessageTx(),
+ txSize: tx.Size(),
+ setCodeAuthorizations: tx.SetCodeAuthorizations(),
}
// If baseFee provided, set gasPrice to effectiveGasPrice.
if baseFee != nil {
@@ -797,19 +827,20 @@ func (tx *Transaction) AsMessage(s Signer, baseFee *big.Int) (Message, error) {
return msg, err
}
-func (m Message) From() common.Address { return m.from }
-func (m Message) To() *common.Address { return m.to }
-func (m Message) GasPrice() *big.Int { return m.gasPrice }
-func (m Message) GasFeeCap() *big.Int { return m.gasFeeCap }
-func (m Message) GasTipCap() *big.Int { return m.gasTipCap }
-func (m Message) Value() *big.Int { return m.amount }
-func (m Message) Gas() uint64 { return m.gasLimit }
-func (m Message) Nonce() uint64 { return m.nonce }
-func (m Message) Data() []byte { return m.data }
-func (m Message) AccessList() AccessList { return m.accessList }
-func (m Message) IsFake() bool { return m.isFake }
-func (m Message) IsL1MessageTx() bool { return m.isL1MessageTx }
-func (m Message) TxSize() common.StorageSize { return m.txSize }
+func (m Message) From() common.Address { return m.from }
+func (m Message) To() *common.Address { return m.to }
+func (m Message) GasPrice() *big.Int { return m.gasPrice }
+func (m Message) GasFeeCap() *big.Int { return m.gasFeeCap }
+func (m Message) GasTipCap() *big.Int { return m.gasTipCap }
+func (m Message) Value() *big.Int { return m.amount }
+func (m Message) Gas() uint64 { return m.gasLimit }
+func (m Message) Nonce() uint64 { return m.nonce }
+func (m Message) Data() []byte { return m.data }
+func (m Message) AccessList() AccessList { return m.accessList }
+func (m Message) IsFake() bool { return m.isFake }
+func (m Message) IsL1MessageTx() bool { return m.isL1MessageTx }
+func (m Message) TxSize() common.StorageSize { return m.txSize }
+func (m Message) SetCodeAuthorizations() []SetCodeAuthorization { return m.setCodeAuthorizations }
// copyAddressPtr copies an address.
func copyAddressPtr(a *common.Address) *common.Address {
diff --git a/core/types/transaction_marshalling.go b/core/types/transaction_marshalling.go
index af7707650ef2..6d2c0bf268ae 100644
--- a/core/types/transaction_marshalling.go
+++ b/core/types/transaction_marshalling.go
@@ -32,22 +32,23 @@ import (
type txJSON struct {
Type hexutil.Uint64 `json:"type"`
- ChainID *hexutil.Big `json:"chainId,omitempty"`
- Nonce *hexutil.Uint64 `json:"nonce"`
- To *common.Address `json:"to"`
- Gas *hexutil.Uint64 `json:"gas"`
- GasPrice *hexutil.Big `json:"gasPrice"`
- MaxPriorityFeePerGas *hexutil.Big `json:"maxPriorityFeePerGas"`
- MaxFeePerGas *hexutil.Big `json:"maxFeePerGas"`
- MaxFeePerBlobGas *hexutil.Big `json:"maxFeePerBlobGas,omitempty"`
- Value *hexutil.Big `json:"value"`
- Input *hexutil.Bytes `json:"input"`
- AccessList *AccessList `json:"accessList,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"`
+ ChainID *hexutil.Big `json:"chainId,omitempty"`
+ Nonce *hexutil.Uint64 `json:"nonce"`
+ To *common.Address `json:"to"`
+ Gas *hexutil.Uint64 `json:"gas"`
+ GasPrice *hexutil.Big `json:"gasPrice"`
+ MaxPriorityFeePerGas *hexutil.Big `json:"maxPriorityFeePerGas"`
+ MaxFeePerGas *hexutil.Big `json:"maxFeePerGas"`
+ MaxFeePerBlobGas *hexutil.Big `json:"maxFeePerBlobGas,omitempty"`
+ Value *hexutil.Big `json:"value"`
+ Input *hexutil.Bytes `json:"input"`
+ AccessList *AccessList `json:"accessList,omitempty"`
+ BlobVersionedHashes []common.Hash `json:"blobVersionedHashes,omitempty"`
+ AuthorizationList []SetCodeAuthorization `json:"authorizationList,omitempty"`
+ V *hexutil.Big `json:"v"`
+ R *hexutil.Big `json:"r"`
+ S *hexutil.Big `json:"s"`
+ YParity *hexutil.Uint64 `json:"yParity,omitempty"`
// Blob transaction sidecar encoding:
Blobs []kzg4844.Blob `json:"blobs,omitempty"`
@@ -166,6 +167,23 @@ func (tx *Transaction) MarshalJSON() ([]byte, error) {
enc.Commitments = itx.Sidecar.Commitments
enc.Proofs = itx.Sidecar.Proofs
}
+
+ case *SetCodeTx:
+ enc.ChainID = (*hexutil.Big)(itx.ChainID.ToBig())
+ enc.Nonce = (*hexutil.Uint64)(&itx.Nonce)
+ enc.To = tx.To()
+ enc.Gas = (*hexutil.Uint64)(&itx.Gas)
+ enc.MaxFeePerGas = (*hexutil.Big)(itx.GasFeeCap.ToBig())
+ enc.MaxPriorityFeePerGas = (*hexutil.Big)(itx.GasTipCap.ToBig())
+ enc.Value = (*hexutil.Big)(itx.Value.ToBig())
+ enc.Input = (*hexutil.Bytes)(&itx.Data)
+ enc.AccessList = &itx.AccessList
+ enc.AuthorizationList = itx.AuthList
+ enc.V = (*hexutil.Big)(itx.V.ToBig())
+ enc.R = (*hexutil.Big)(itx.R.ToBig())
+ enc.S = (*hexutil.Big)(itx.S.ToBig())
+ yparity := itx.V.Uint64()
+ enc.YParity = (*hexutil.Uint64)(&yparity)
}
return json.Marshal(&enc)
}
@@ -346,8 +364,10 @@ func (tx *Transaction) UnmarshalJSON(input []byte) error {
case BlobTxType:
var itx BlobTx
inner = &itx
- if dec.ChainID == nil {
- return errors.New("missing required field 'chainId' in transaction")
+ var overflow bool
+ itx.ChainID, overflow = uint256.FromBig(dec.ChainID.ToInt())
+ if overflow {
+ return errors.New("'chainId' value overflows uint256")
}
itx.ChainID = uint256.MustFromBig((*big.Int)(dec.ChainID))
if dec.Nonce == nil {
@@ -391,7 +411,81 @@ func (tx *Transaction) UnmarshalJSON(input []byte) error {
itx.BlobHashes = dec.BlobVersionedHashes
// signature R
+ if dec.R == nil {
+ return errors.New("missing required field 'r' in transaction")
+ }
+ itx.R, overflow = uint256.FromBig((*big.Int)(dec.R))
+ if overflow {
+ return errors.New("'r' value overflows uint256")
+ }
+ // signature S
+ if dec.S == nil {
+ return errors.New("missing required field 's' in transaction")
+ }
+ itx.S, overflow = uint256.FromBig((*big.Int)(dec.S))
+ if overflow {
+ return errors.New("'s' value overflows uint256")
+ }
+ // signature V
+ vbig, err := dec.yParityValue()
+ if err != nil {
+ return err
+ }
+ itx.V, overflow = uint256.FromBig(vbig)
+ if overflow {
+ return errors.New("'v' value overflows uint256")
+ }
+ if itx.V.Sign() != 0 || itx.R.Sign() != 0 || itx.S.Sign() != 0 {
+ if err := sanityCheckSignature(vbig, itx.R.ToBig(), itx.S.ToBig(), false); err != nil {
+ return err
+ }
+ }
+
+ case SetCodeTxType:
+ var itx SetCodeTx
+ inner = &itx
+ if dec.ChainID == nil {
+ return errors.New("missing required field 'chainId' in transaction")
+ }
var overflow bool
+ itx.ChainID, overflow = uint256.FromBig(dec.ChainID.ToInt())
+ if overflow {
+ return errors.New("'chainId' value overflows uint256")
+ }
+ itx.Nonce = uint64(*dec.Nonce)
+ if dec.To == nil {
+ return errors.New("missing required field 'to' in transaction")
+ }
+ itx.To = *dec.To
+ if dec.Gas == nil {
+ return errors.New("missing required field 'gas' for txdata")
+ }
+ itx.Gas = uint64(*dec.Gas)
+ if dec.MaxPriorityFeePerGas == nil {
+ return errors.New("missing required field 'maxPriorityFeePerGas' for txdata")
+ }
+ itx.GasTipCap = uint256.MustFromBig((*big.Int)(dec.MaxPriorityFeePerGas))
+ if dec.MaxFeePerGas == nil {
+ return errors.New("missing required field 'maxFeePerGas' for txdata")
+ }
+ itx.GasFeeCap = uint256.MustFromBig((*big.Int)(dec.MaxFeePerGas))
+ if dec.Value == nil {
+ return errors.New("missing required field 'value' in transaction")
+ }
+ itx.Value = uint256.MustFromBig((*big.Int)(dec.Value))
+ if dec.Input == nil {
+ return errors.New("missing required field 'input' in transaction")
+ }
+ itx.Data = *dec.Input
+ if dec.AccessList != nil {
+ itx.AccessList = *dec.AccessList
+ }
+ if dec.AuthorizationList == nil {
+ return errors.New("missing required field 'authorizationList' in transaction")
+ }
+ itx.AuthList = dec.AuthorizationList
+
+ // signature R
if dec.R == nil {
return errors.New("missing required field 'r' in transaction")
}
diff --git a/core/types/transaction_signing.go b/core/types/transaction_signing.go
index 98df72f1184f..3e0bba8cf6d5 100644
--- a/core/types/transaction_signing.go
+++ b/core/types/transaction_signing.go
@@ -37,9 +37,11 @@ type sigCache struct {
}
// MakeSigner returns a Signer based on the given chain config and block number.
-func MakeSigner(config *params.ChainConfig, blockNumber *big.Int) Signer {
+func MakeSigner(config *params.ChainConfig, blockNumber *big.Int, blockTime uint64) Signer {
var signer Signer
switch {
+ case config.IsEuclidV2(blockTime):
+ signer = NewEuclidV2Signer(config.ChainID)
case config.IsCurie(blockNumber):
signer = NewLondonSignerWithEIP4844(config.ChainID)
case config.IsLondon(blockNumber):
@@ -65,6 +67,9 @@ func MakeSigner(config *params.ChainConfig, blockNumber *big.Int) Signer {
// have the current block number available, use MakeSigner instead.
func LatestSigner(config *params.ChainConfig) Signer {
if config.ChainID != nil {
+ if config.EuclidV2Time != nil {
+ return NewEuclidV2Signer(config.ChainID)
+ }
if config.CurieBlock != nil {
return NewLondonSignerWithEIP4844(config.ChainID)
}
@@ -92,7 +97,7 @@ func LatestSignerForChainID(chainID *big.Int) Signer {
if chainID == nil {
return HomesteadSigner{}
}
- return NewLondonSignerWithEIP4844(chainID)
+ return NewEuclidV2Signer(chainID)
}
// SignTx signs the transaction using the given signer and private key.
@@ -175,6 +180,77 @@ type Signer interface {
Equal(Signer) bool
}
+type euclidV2Signer struct{ londonSignerWithEIP4844 }
+
+// NewEuclidV2Signer returns a signer that accepts
+// - EIP-7702 set code transactions
+// - EIP-4844 blob transactions
+// - EIP-1559 dynamic fee transactions
+// - EIP-2930 access list transactions,
+// - EIP-155 replay protected transactions, and
+// - legacy Homestead transactions.
+func NewEuclidV2Signer(chainId *big.Int) Signer {
+ signer, _ := NewLondonSignerWithEIP4844(chainId).(londonSignerWithEIP4844)
+ return euclidV2Signer{signer}
+}
+
+func (s euclidV2Signer) Sender(tx *Transaction) (common.Address, error) {
+ if tx.Type() != SetCodeTxType {
+ return s.londonSignerWithEIP4844.Sender(tx)
+ }
+ V, R, S := tx.RawSignatureValues()
+
+ // Set code txs are defined to use 0 and 1 as their recovery
+ // id, add 27 to become equivalent to unprotected Homestead signatures.
+ V = new(big.Int).Add(V, big.NewInt(27))
+ if tx.ChainId().Cmp(s.chainId) != 0 {
+ return common.Address{}, fmt.Errorf("%w: have %d want %d", ErrInvalidChainId, tx.ChainId(), s.chainId)
+ }
+ return recoverPlain(s.Hash(tx), R, S, V, true)
+}
+
+func (s euclidV2Signer) Equal(s2 Signer) bool {
+ x, ok := s2.(euclidV2Signer)
+ return ok && x.chainId.Cmp(s.chainId) == 0
+}
+
+func (s euclidV2Signer) SignatureValues(tx *Transaction, sig []byte) (R, S, V *big.Int, err error) {
+ txdata, ok := tx.inner.(*SetCodeTx)
+ if !ok {
+ return s.londonSignerWithEIP4844.SignatureValues(tx, sig)
+ }
+ // Check that chain ID of tx matches the signer. We also accept ID zero here,
+ // because it indicates that the chain ID was not specified in the tx.
+ if txdata.ChainID != nil && txdata.ChainID.CmpBig(s.chainId) != 0 {
+ return nil, nil, nil, fmt.Errorf("%w: have %d want %d", ErrInvalidChainId, txdata.ChainID, s.chainId)
+ }
+ R, S, _ = decodeSignature(sig)
+ V = big.NewInt(int64(sig[64]))
+ return R, S, V, nil
+}
+
+// Hash returns the hash to be signed by the sender.
+// It does not uniquely identify the transaction.
+func (s euclidV2Signer) Hash(tx *Transaction) common.Hash {
+ if tx.Type() != SetCodeTxType {
+ return s.londonSignerWithEIP4844.Hash(tx)
+ }
+ return prefixedRlpHash(
+ tx.Type(),
+ []interface{}{
+ s.chainId,
+ tx.Nonce(),
+ tx.GasTipCap(),
+ tx.GasFeeCap(),
+ tx.Gas(),
+ tx.To(),
+ tx.Value(),
+ tx.Data(),
+ tx.AccessList(),
+ tx.SetCodeAuthorizations(),
+ })
+}
+
type londonSignerWithEIP4844 struct{ londonSigner }
// NewLondonSignerWithEIP4844 returns a signer that accepts
diff --git a/core/vm/access_list_tracer.go b/core/vm/access_list_tracer.go
index edae720f0559..d4edde39f1c8 100644
--- a/core/vm/access_list_tracer.go
+++ b/core/vm/access_list_tracer.go
@@ -137,7 +137,7 @@ func NewAccessListTracer(acl types.AccessList, from, to common.Address, precompi
}
}
-func (a *AccessListTracer) CaptureStart(env *EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) {
+func (a *AccessListTracer) CaptureStart(env *EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int, authorizationResults []types.AuthorizationResult) {
}
// CaptureState captures all opcodes that touch storage or addresses and adds them to the accesslist.
diff --git a/core/vm/eips.go b/core/vm/eips.go
index fe2c8fa5aaf5..829c3206a320 100644
--- a/core/vm/eips.go
+++ b/core/vm/eips.go
@@ -38,6 +38,7 @@ var activators = map[int]func(*JumpTable){
1884: enable1884,
1344: enable1344,
1153: enable1153,
+ 7702: enable7702,
}
// EnableEIP enables the given EIP on the config.
@@ -271,3 +272,11 @@ func opMcopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]by
scope.Memory.Copy(dst.Uint64(), src.Uint64(), length.Uint64())
return nil, nil
}
+
+// enable7702 the EIP-7702 changes to support delegation designators.
+func enable7702(jt *JumpTable) {
+ jt[CALL].dynamicGas = gasCallEIP7702
+ jt[CALLCODE].dynamicGas = gasCallCodeEIP7702
+ jt[STATICCALL].dynamicGas = gasStaticCallEIP7702
+ jt[DELEGATECALL].dynamicGas = gasDelegateCallEIP7702
+}
diff --git a/core/vm/errors.go b/core/vm/errors.go
index 500acb71a8bb..a798ed2ecfaa 100644
--- a/core/vm/errors.go
+++ b/core/vm/errors.go
@@ -67,3 +67,8 @@ type ErrInvalidOpCode struct {
}
func (e *ErrInvalidOpCode) Error() string { return fmt.Sprintf("invalid opcode: %s", e.opcode) }
+
+// NewErrInvalidOpCode is only used in tests
+func NewErrInvalidOpCode(opcode OpCode) *ErrInvalidOpCode {
+ return &ErrInvalidOpCode{opcode: opcode}
+}
diff --git a/core/vm/evm.go b/core/vm/evm.go
index b3685c934358..afb5c7cb39be 100644
--- a/core/vm/evm.go
+++ b/core/vm/evm.go
@@ -24,6 +24,7 @@ import (
"github.com/holiman/uint256"
"github.com/scroll-tech/go-ethereum/common"
+ "github.com/scroll-tech/go-ethereum/core/types"
"github.com/scroll-tech/go-ethereum/crypto"
"github.com/scroll-tech/go-ethereum/crypto/codehash"
"github.com/scroll-tech/go-ethereum/params"
@@ -175,7 +176,7 @@ func (evm *EVM) Interpreter() *EVMInterpreter {
// parameters. It also handles any necessary value transfer required and takes
// the necessary steps to create accounts and reverses the state in case of an
// execution error or failed value transfer.
-func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas uint64, value *big.Int) (ret []byte, leftOverGas uint64, err error) {
+func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas uint64, value *big.Int, authorizationResults []types.AuthorizationResult) (ret []byte, leftOverGas uint64, err error) {
if evm.Config.NoRecursion && evm.depth > 0 {
return nil, gas, nil
}
@@ -195,7 +196,7 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas
// Calling a non existing account, don't do anything, but ping the tracer
if evm.Config.Debug {
if evm.depth == 0 {
- evm.Config.Tracer.CaptureStart(evm, caller.Address(), addr, false, input, gas, value)
+ evm.Config.Tracer.CaptureStart(evm, caller.Address(), addr, false, input, gas, value, authorizationResults)
evm.Config.Tracer.CaptureEnd(ret, 0, 0, nil)
} else {
evm.Config.Tracer.CaptureEnter(CALL, caller.Address(), addr, input, gas, value)
@@ -211,7 +212,7 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas
// Capture the tracer start/end events in debug mode
if evm.Config.Debug {
if evm.depth == 0 {
- evm.Config.Tracer.CaptureStart(evm, caller.Address(), addr, false, input, gas, value)
+ evm.Config.Tracer.CaptureStart(evm, caller.Address(), addr, false, input, gas, value, authorizationResults)
defer func(startGas uint64, startTime time.Time) { // Lazy evaluation of the parameters
evm.Config.Tracer.CaptureEnd(ret, startGas-gas, time.Since(startTime), err)
}(gas, time.Now())
@@ -229,7 +230,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.resolveCode(addr)
if len(code) == 0 {
ret, err = nil, nil // gas is unchanged
} else {
@@ -237,7 +238,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.GetKeccakCodeHash(addrCopy), code)
+ contract.SetCallCode(&addrCopy, evm.resolveKeccakCodeHash(addrCopy), code)
ret, err = evm.interpreter.Run(contract, input, false)
gas = contract.Gas
}
@@ -297,7 +298,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.GetKeccakCodeHash(addrCopy), evm.StateDB.GetCode(addrCopy))
+ contract.SetCallCode(&addrCopy, evm.resolveKeccakCodeHash(addrCopy), evm.resolveCode(addrCopy))
ret, err = evm.interpreter.Run(contract, input, false)
gas = contract.Gas
}
@@ -340,7 +341,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.GetKeccakCodeHash(addrCopy), evm.StateDB.GetCode(addrCopy))
+ contract.SetCallCode(&addrCopy, evm.resolveKeccakCodeHash(addrCopy), evm.resolveCode(addrCopy))
ret, err = evm.interpreter.Run(contract, input, false)
gas = contract.Gas
}
@@ -396,7 +397,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(big.Int), gas)
- contract.SetCallCode(&addrCopy, evm.StateDB.GetKeccakCodeHash(addrCopy), evm.StateDB.GetCode(addrCopy))
+ contract.SetCallCode(&addrCopy, evm.resolveKeccakCodeHash(addrCopy), evm.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.
@@ -469,7 +470,7 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64,
if evm.Config.Debug {
if evm.depth == 0 {
- evm.Config.Tracer.CaptureStart(evm, caller.Address(), address, true, codeAndHash.code, gas, value)
+ evm.Config.Tracer.CaptureStart(evm, caller.Address(), address, true, codeAndHash.code, gas, value, nil)
} else {
evm.Config.Tracer.CaptureEnter(typ, caller.Address(), address, codeAndHash.code, gas, value)
}
@@ -538,6 +539,35 @@ func (evm *EVM) Create2(caller ContractRef, code []byte, gas uint64, endowment *
return evm.create(caller, codeAndHash, gas, endowment, contractAddr, CREATE2)
}
+// resolveCode returns the code associated with the provided account. After
+// EuclidV2, it can also resolve code pointed to by a delegation designator.
+func (evm *EVM) resolveCode(addr common.Address) []byte {
+ code := evm.StateDB.GetCode(addr)
+ if !evm.chainRules.IsEuclidV2 {
+ return code
+ }
+ if target, ok := types.ParseDelegation(code); ok {
+ // Note we only follow one level of delegation.
+ return evm.StateDB.GetCode(target)
+ }
+ return code
+}
+
+// resolveKeccakCodeHash returns the code hash associated with the provided address.
+// After EuclidV2, it can also resolve code hash of the account pointed to by a
+// delegation designator. Although this is not accessible in the EVM it is used
+// internally to associate jumpdest analysis to code.
+func (evm *EVM) resolveKeccakCodeHash(addr common.Address) common.Hash {
+ if evm.chainRules.IsEuclidV2 {
+ code := evm.StateDB.GetCode(addr)
+ if target, ok := types.ParseDelegation(code); ok {
+ // Note we only follow one level of delegation.
+ return evm.StateDB.GetKeccakCodeHash(target)
+ }
+ }
+ return evm.StateDB.GetKeccakCodeHash(addr)
+}
+
// ChainConfig returns the environment's chain configuration
func (evm *EVM) ChainConfig() *params.ChainConfig { return evm.chainConfig }
diff --git a/core/vm/gas_table_test.go b/core/vm/gas_table_test.go
index 9df9ed2049fd..e5c0f2674798 100644
--- a/core/vm/gas_table_test.go
+++ b/core/vm/gas_table_test.go
@@ -95,7 +95,7 @@ func TestEIP2200(t *testing.T) {
}
vmenv := NewEVM(vmctx, TxContext{}, statedb, params.AllEthashProtocolChanges, Config{ExtraEips: []int{2200}})
- _, gas, err := vmenv.Call(AccountRef(common.Address{}), address, nil, tt.gaspool, new(big.Int))
+ _, gas, err := vmenv.Call(AccountRef(common.Address{}), address, nil, tt.gaspool, new(big.Int), nil)
if err != tt.failure {
t.Errorf("test %d: failure mismatch: have %v, want %v", i, err, tt.failure)
}
@@ -151,7 +151,7 @@ func TestCreateGas(t *testing.T) {
vmenv := NewEVM(vmctx, TxContext{}, statedb, params.AllEthashProtocolChanges, config)
var startGas = uint64(testGas)
- ret, gas, err := vmenv.Call(AccountRef(common.Address{}), address, nil, startGas, new(big.Int))
+ ret, gas, err := vmenv.Call(AccountRef(common.Address{}), address, nil, startGas, new(big.Int), nil)
if err != nil {
return false
}
diff --git a/core/vm/instructions.go b/core/vm/instructions.go
index aba8fdb0a379..36a467a8f3ff 100644
--- a/core/vm/instructions.go
+++ b/core/vm/instructions.go
@@ -676,7 +676,7 @@ func opCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byt
bigVal = value.ToBig()
}
- ret, returnGas, err := interpreter.evm.Call(scope.Contract, toAddr, args, gas, bigVal)
+ ret, returnGas, err := interpreter.evm.Call(scope.Contract, toAddr, args, gas, bigVal, nil)
if err != nil {
temp.Clear()
diff --git a/core/vm/interpreter.go b/core/vm/interpreter.go
index 8e3e6e30f859..81de5f3c0577 100644
--- a/core/vm/interpreter.go
+++ b/core/vm/interpreter.go
@@ -74,6 +74,8 @@ func NewEVMInterpreter(evm *EVM, cfg Config) *EVMInterpreter {
if cfg.JumpTable[STOP] == nil {
var jt JumpTable
switch {
+ case evm.chainRules.IsEuclidV2:
+ jt = euclidV2InstructionSet
case evm.chainRules.IsDarwin:
jt = darwinInstructionSet
case evm.chainRules.IsCurie:
diff --git a/core/vm/jump_table.go b/core/vm/jump_table.go
index 2ed9c6487530..5c46758dbf7d 100644
--- a/core/vm/jump_table.go
+++ b/core/vm/jump_table.go
@@ -61,11 +61,20 @@ var (
shanghaiInstructionSet = newShanghaiInstructionSet()
curieInstructionSet = newCurieInstructionSet()
darwinInstructionSet = newDarwinInstructionSet()
+ euclidV2InstructionSet = newEuclidV2InstructionSet()
)
// JumpTable contains the EVM opcodes supported at a given fork.
type JumpTable [256]*operation
+// newEuclidV2InstructionSet returns the frontier, homestead, byzantium,
+// contantinople, istanbul, petersburg, berlin, london, shanghai, curie, darwin and euclidV2 instructions.
+func newEuclidV2InstructionSet() JumpTable {
+ instructionSet := newDarwinInstructionSet()
+ enable7702(&instructionSet) // EIP-7702 Setcode transaction type
+ return instructionSet
+}
+
// newDarwinInstructionSet returns the frontier, homestead, byzantium,
// contantinople, istanbul, petersburg, berlin, london, shanghai, curie, and darwin instructions.
func newDarwinInstructionSet() JumpTable {
diff --git a/core/vm/logger.go b/core/vm/logger.go
index 740c7b93cc05..22eba83a548e 100644
--- a/core/vm/logger.go
+++ b/core/vm/logger.go
@@ -127,7 +127,7 @@ func (s *StructLog) ErrorString() string {
// Note that reference types are actual VM data structures; make copies
// if you need to retain them beyond the current call.
type EVMLogger interface {
- CaptureStart(env *EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int)
+ CaptureStart(env *EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int, authorizationResults []types.AuthorizationResult)
CaptureState(pc uint64, op OpCode, gas, cost uint64, scope *ScopeContext, rData []byte, depth int, err error)
CaptureStateAfter(pc uint64, op OpCode, gas, cost uint64, scope *ScopeContext, rData []byte, depth int, err error)
CaptureEnter(typ OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int)
@@ -191,7 +191,7 @@ func (l *StructLogger) Reset() {
}
// CaptureStart implements the EVMLogger interface to initialize the tracing operation.
-func (l *StructLogger) CaptureStart(env *EVM, from common.Address, to common.Address, isCreate bool, input []byte, gas uint64, value *big.Int) {
+func (l *StructLogger) CaptureStart(env *EVM, from common.Address, to common.Address, isCreate bool, input []byte, gas uint64, value *big.Int, authorizationResults []types.AuthorizationResult) {
l.env = env
if isCreate {
@@ -208,6 +208,9 @@ func (l *StructLogger) CaptureStart(env *EVM, from common.Address, to common.Add
l.statesAffected[from] = struct{}{}
l.statesAffected[to] = struct{}{}
+ for _, auth := range authorizationResults {
+ l.statesAffected[auth.Authority] = struct{}{}
+ }
}
// CaptureState logs a new structured log message and pushes it out to the environment
@@ -430,7 +433,7 @@ func NewMarkdownLogger(cfg *LogConfig, writer io.Writer) *mdLogger {
return l
}
-func (t *mdLogger) CaptureStart(env *EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) {
+func (t *mdLogger) CaptureStart(env *EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int, authorizationResults []types.AuthorizationResult) {
t.env = env
if !create {
fmt.Fprintf(t.out, "From: `%v`\nTo: `%v`\nData: `0x%x`\nGas: `%d`\nValue `%v` wei\n",
diff --git a/core/vm/logger_json.go b/core/vm/logger_json.go
index d2abd22b9d07..2dfab98fea6d 100644
--- a/core/vm/logger_json.go
+++ b/core/vm/logger_json.go
@@ -24,6 +24,7 @@ import (
"github.com/scroll-tech/go-ethereum/common"
"github.com/scroll-tech/go-ethereum/common/math"
+ "github.com/scroll-tech/go-ethereum/core/types"
)
type JSONLogger struct {
@@ -42,7 +43,7 @@ func NewJSONLogger(cfg *LogConfig, writer io.Writer) *JSONLogger {
return l
}
-func (l *JSONLogger) CaptureStart(env *EVM, from, to common.Address, create bool, input []byte, gas uint64, value *big.Int) {
+func (l *JSONLogger) CaptureStart(env *EVM, from, to common.Address, create bool, input []byte, gas uint64, value *big.Int, authorizationResults []types.AuthorizationResult) {
l.env = env
}
diff --git a/core/vm/logger_test.go b/core/vm/logger_test.go
index 6bc37ef50202..772da3df3612 100644
--- a/core/vm/logger_test.go
+++ b/core/vm/logger_test.go
@@ -72,7 +72,7 @@ func TestStoreCapture(t *testing.T) {
scope.Stack.push(uint256.NewInt(1))
scope.Stack.push(new(uint256.Int))
var index common.Hash
- logger.CaptureStart(env, common.Address{}, contract.Address(), false, nil, 0, nil)
+ logger.CaptureStart(env, common.Address{}, contract.Address(), false, nil, 0, nil, nil)
logger.CaptureState(0, SSTORE, 0, 0, scope, nil, 0, nil)
if len(logger.storage[contract.Address()]) == 0 {
t.Fatalf("expected exactly 1 changed value on address %x, got %d", contract.Address(),
diff --git a/core/vm/logger_trace.go b/core/vm/logger_trace.go
index 39f89286c035..4f8f8f6fd802 100644
--- a/core/vm/logger_trace.go
+++ b/core/vm/logger_trace.go
@@ -47,10 +47,17 @@ func traceLastNAddressCode(n int) traceFunc {
func traceCodeWithAddress(l *StructLogger, address common.Address) {
code := l.env.StateDB.GetCode(address)
+
keccakCodeHash := l.env.StateDB.GetKeccakCodeHash(address)
- poseidonCodeHash := l.env.StateDB.GetPoseidonCodeHash(address)
+ codeHash := keccakCodeHash
+ poseidonCodeHash := common.Hash{}
+ if !l.env.chainRules.IsEuclid && l.env.chainConfig.Scroll.UseZktrie {
+ poseidonCodeHash = l.env.StateDB.GetPoseidonCodeHash(address)
+ codeHash = poseidonCodeHash
+ }
+
codeSize := l.env.StateDB.GetCodeSize(address)
- l.bytecodes[poseidonCodeHash] = CodeInfo{
+ l.bytecodes[codeHash] = CodeInfo{
codeSize,
keccakCodeHash,
poseidonCodeHash,
diff --git a/core/vm/operations_acl.go b/core/vm/operations_acl.go
index 3dbccbd97cf7..d9a8b4410202 100644
--- a/core/vm/operations_acl.go
+++ b/core/vm/operations_acl.go
@@ -21,6 +21,7 @@ import (
"github.com/scroll-tech/go-ethereum/common"
"github.com/scroll-tech/go-ethereum/common/math"
+ "github.com/scroll-tech/go-ethereum/core/types"
"github.com/scroll-tech/go-ethereum/params"
)
@@ -215,3 +216,70 @@ var (
// Replace `SSTORE_CLEARS_SCHEDULE` with `SSTORE_RESET_GAS + ACCESS_LIST_STORAGE_KEY_COST` (4,800)
gasSStoreEIP3529 = makeGasSStoreFunc(params.SstoreClearsScheduleRefundEIP3529)
)
+
+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) {
+ var (
+ total uint64 // total dynamic gas used
+ addr = common.Address(stack.Back(1).Bytes20())
+ )
+
+ // Check slot presence in the access list
+ if !evm.StateDB.AddressInAccessList(addr) {
+ evm.StateDB.AddAddressToAccessList(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
+ // Charge the remaining difference here already, to correctly calculate available
+ // gas for call
+ if !contract.UseGas(coldCost) {
+ return 0, ErrOutOfGas
+ }
+ total += coldCost
+ }
+
+ // Check if code is a delegation and if so, charge for resolution.
+ if target, ok := types.ParseDelegation(evm.StateDB.GetCode(addr)); ok {
+ var cost uint64
+ if evm.StateDB.AddressInAccessList(target) {
+ cost = params.WarmStorageReadCostEIP2929
+ } else {
+ evm.StateDB.AddAddressToAccessList(target)
+ cost = params.ColdAccountAccessCostEIP2929
+ }
+ if !contract.UseGas(cost) {
+ return 0, ErrOutOfGas
+ }
+ total += cost
+ }
+
+ // Now call the old calculator, which takes into account
+ // - create new account
+ // - transfer value
+ // - memory expansion
+ // - 63/64ths rule
+ old, err := oldCalculator(evm, contract, stack, mem, memorySize)
+ if err != nil {
+ return old, err
+ }
+
+ // Temporarily add the gas charge back to the contract and return value. By
+ // adding it to the return, it will be charged outside of this function, as
+ // part of the dynamic gas. This will ensure it is correctly reported to
+ // tracers.
+ contract.Gas += total
+
+ var overflow bool
+ if total, overflow = math.SafeAdd(old, total); overflow {
+ return 0, ErrGasUintOverflow
+ }
+ return total, nil
+ }
+}
diff --git a/core/vm/runtime/runtime.go b/core/vm/runtime/runtime.go
index 3808d0b4d329..7e4029aec419 100644
--- a/core/vm/runtime/runtime.go
+++ b/core/vm/runtime/runtime.go
@@ -140,6 +140,7 @@ func Execute(code, input []byte, cfg *Config) ([]byte, *state.StateDB, error) {
input,
cfg.GasLimit,
cfg.Value,
+ nil,
)
return ret, cfg.State, err
}
@@ -200,6 +201,7 @@ func Call(address common.Address, input []byte, cfg *Config) ([]byte, uint64, er
input,
cfg.GasLimit,
cfg.Value,
+ nil,
)
return ret, leftOverGas, err
}
diff --git a/core/vm/runtime/runtime_test.go b/core/vm/runtime/runtime_test.go
index 46f94a2195a5..fa1556fa59cb 100644
--- a/core/vm/runtime/runtime_test.go
+++ b/core/vm/runtime/runtime_test.go
@@ -20,10 +20,13 @@ import (
"fmt"
"math/big"
"os"
+ "reflect"
"strings"
"testing"
"time"
+ "github.com/agiledragon/gomonkey/v2"
+
"github.com/scroll-tech/go-ethereum/accounts/abi"
"github.com/scroll-tech/go-ethereum/common"
"github.com/scroll-tech/go-ethereum/consensus"
@@ -354,7 +357,7 @@ type stepCounter struct {
steps int
}
-func (s *stepCounter) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) {
+func (s *stepCounter) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int, authorizationResults []types.AuthorizationResult) {
}
func (s *stepCounter) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, depth int, err error) {
@@ -409,12 +412,12 @@ func benchmarkNonModifyingCode(gas uint64, code []byte, name string, tracerCode
//cfg.State.CreateAccount(cfg.Origin)
// set the receiver's (the executing contract) code for execution.
cfg.State.SetCode(destination, code)
- vmenv.Call(sender, destination, nil, gas, cfg.Value)
+ vmenv.Call(sender, destination, nil, gas, cfg.Value, nil)
b.Run(name, func(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
- vmenv.Call(sender, destination, nil, gas, cfg.Value)
+ vmenv.Call(sender, destination, nil, gas, cfg.Value, nil)
}
})
}
@@ -964,3 +967,87 @@ func BenchmarkTracerStepVsCallFrame(b *testing.B) {
benchmarkNonModifyingCode(10000000, code, "tracer-step-10M", stepTracer, b)
benchmarkNonModifyingCode(10000000, code, "tracer-call-frame-10M", callFrameTracer, b)
}
+
+// TestDelegatedAccountAccessCost tests that calling an account with an EIP-7702
+// delegation designator incurs the correct amount of gas based on the tracer.
+func TestDelegatedAccountAccessCost(t *testing.T) {
+ statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil)
+ statedb.SetCode(common.HexToAddress("0xff"), types.AddressToDelegation(common.HexToAddress("0xaa")))
+ statedb.SetCode(common.HexToAddress("0xaa"), []byte{
+ byte(vm.PUSH1), 0x00,
+ byte(vm.PUSH1), 0x00,
+ byte(vm.RETURN),
+ })
+
+ patches := gomonkey.ApplyMethod(reflect.TypeOf(statedb), "GetPoseidonCodeHash", func(_ *state.StateDB, addr common.Address) common.Hash {
+ return common.HexToHash("0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef")
+ })
+ defer patches.Reset()
+
+ for i, tc := range []struct {
+ code []byte
+ step int
+ want uint64
+ }{
+ { // CALL(0xff)
+ code: []byte{
+ byte(vm.PUSH1), 0x0,
+ byte(vm.DUP1), byte(vm.DUP1), byte(vm.DUP1), byte(vm.DUP1),
+ byte(vm.PUSH1), 0xff, byte(vm.DUP1), byte(vm.CALL), byte(vm.POP),
+ },
+ step: 7,
+ want: 5455,
+ },
+ { // CALLCODE(0xff)
+ code: []byte{
+ byte(vm.PUSH1), 0x0,
+ byte(vm.DUP1), byte(vm.DUP1), byte(vm.DUP1), byte(vm.DUP1),
+ byte(vm.PUSH1), 0xff, byte(vm.DUP1), byte(vm.CALLCODE), byte(vm.POP),
+ },
+ step: 7,
+ want: 5455,
+ },
+ { // DELEGATECALL(0xff)
+ code: []byte{
+ byte(vm.PUSH1), 0x0,
+ byte(vm.DUP1), byte(vm.DUP1), byte(vm.DUP1),
+ byte(vm.PUSH1), 0xff, byte(vm.DUP1), byte(vm.DELEGATECALL), byte(vm.POP),
+ },
+ step: 6,
+ want: 5455,
+ },
+ { // STATICCALL(0xff)
+ code: []byte{
+ byte(vm.PUSH1), 0x0,
+ byte(vm.DUP1), byte(vm.DUP1), byte(vm.DUP1),
+ byte(vm.PUSH1), 0xff, byte(vm.DUP1), byte(vm.STATICCALL), byte(vm.POP),
+ },
+ step: 6,
+ want: 5455,
+ },
+ { // SELFDESTRUCT(0xff): should not be affected by resolution
+ code: []byte{
+ byte(vm.PUSH1), 0xff, byte(vm.SELFDESTRUCT),
+ },
+ step: 1,
+ want: 3,
+ },
+ } {
+ tracer := vm.NewStructLogger(nil)
+ Execute(tc.code, nil, &Config{
+ ChainConfig: params.TestChainConfig,
+ State: statedb,
+ EVMConfig: vm.Config{
+ Debug: true,
+ Tracer: tracer,
+ },
+ })
+ have := tracer.StructLogs()[tc.step].GasCost
+ if want := tc.want; have != want {
+ for ii, op := range tracer.StructLogs() {
+ t.Logf("%d: %v %d", ii, op.OpName(), op.GasCost)
+ }
+ t.Fatalf("tescase %d, gas report wrong, step %d, have %d want %d", i, tc.step, have, want)
+ }
+ }
+}
diff --git a/eth/api.go b/eth/api.go
index 8082a56650b7..8a0f094eef27 100644
--- a/eth/api.go
+++ b/eth/api.go
@@ -920,7 +920,7 @@ func (api *ScrollAPI) GetSkippedTransaction(ctx context.Context, hash common.Has
return nil, nil
}
var rpcTx RPCTransaction
- rpcTx.RPCTransaction = *ethapi.NewRPCTransaction(stx.Tx, common.Hash{}, 0, 0, nil, api.eth.blockchain.Config())
+ rpcTx.RPCTransaction = *ethapi.NewRPCTransaction(stx.Tx, common.Hash{}, 0, 0, 0, nil, api.eth.blockchain.Config())
rpcTx.SkipReason = stx.Reason
rpcTx.SkipBlockNumber = (*hexutil.Big)(new(big.Int).SetUint64(stx.BlockNumber))
rpcTx.SkipBlockHash = stx.BlockHash
diff --git a/eth/catalyst/api.go b/eth/catalyst/api.go
index c4bd3feb8a07..ea2989d8ad5a 100644
--- a/eth/catalyst/api.go
+++ b/eth/catalyst/api.go
@@ -161,7 +161,7 @@ func (api *consensusAPI) AssembleBlock(params assembleBlockParams) (*executableD
}
var (
- signer = types.MakeSigner(bc.Config(), header.Number)
+ signer = types.MakeSigner(bc.Config(), header.Number, header.Time)
txHeap = types.NewTransactionsByPriceAndNonce(signer, pending, nil)
transactions []*types.Transaction
)
diff --git a/eth/downloader/queue_test.go b/eth/downloader/queue_test.go
index 07a05dc20eda..5899f8f43742 100644
--- a/eth/downloader/queue_test.go
+++ b/eth/downloader/queue_test.go
@@ -47,7 +47,7 @@ func makeChain(n int, seed byte, parent *types.Block, empty bool) ([]*types.Bloc
block.SetCoinbase(common.Address{seed})
// Add one tx to every secondblock
if !empty && i%2 == 0 {
- signer := types.MakeSigner(params.TestChainConfig, block.Number())
+ signer := types.MakeSigner(params.TestChainConfig, block.Number(), block.Time())
tx, err := types.SignTx(types.NewTransaction(block.TxNonce(testAddress), common.Address{seed}, big.NewInt(1000), params.TxGas, block.BaseFee(), nil), signer, testKey)
if err != nil {
panic(err)
diff --git a/eth/downloader/testchain_test.go b/eth/downloader/testchain_test.go
index 8757755bab01..a3962dde1769 100644
--- a/eth/downloader/testchain_test.go
+++ b/eth/downloader/testchain_test.go
@@ -126,7 +126,7 @@ func (tc *testChain) generate(n int, seed byte, parent *types.Block, heavy bool)
}
// Include transactions to the miner to make blocks more interesting.
if parent == tc.genesis && i%22 == 0 {
- signer := types.MakeSigner(params.TestChainConfig, block.Number())
+ signer := types.MakeSigner(params.TestChainConfig, block.Number(), block.Time())
tx, err := types.SignTx(types.NewTransaction(block.TxNonce(testAddress), common.Address{seed}, big.NewInt(1000), params.TxGas, block.BaseFee(), nil), signer, testKey)
if err != nil {
panic(err)
diff --git a/eth/fetcher/block_fetcher_test.go b/eth/fetcher/block_fetcher_test.go
index 6553df3c2d28..6712c1e115a1 100644
--- a/eth/fetcher/block_fetcher_test.go
+++ b/eth/fetcher/block_fetcher_test.go
@@ -52,7 +52,7 @@ func makeChain(n int, seed byte, parent *types.Block) ([]common.Hash, map[common
// If the block number is multiple of 3, send a bonus transaction to the miner
if parent == genesis && i%3 == 0 {
- signer := types.MakeSigner(params.TestChainConfig, block.Number())
+ signer := types.MakeSigner(params.TestChainConfig, block.Number(), block.Time())
tx, err := types.SignTx(types.NewTransaction(block.TxNonce(testAddress), common.Address{seed}, big.NewInt(1000), params.TxGas, block.BaseFee(), nil), signer, testKey)
if err != nil {
panic(err)
diff --git a/eth/gasprice/gasprice.go b/eth/gasprice/gasprice.go
index a5897433e512..b0af967ca49e 100644
--- a/eth/gasprice/gasprice.go
+++ b/eth/gasprice/gasprice.go
@@ -232,7 +232,7 @@ func (oracle *Oracle) SuggestTipCap(ctx context.Context) (*big.Int, error) {
results []*big.Int
)
for sent < oracle.checkBlocks && number > 0 {
- go oracle.getBlockValues(ctx, types.MakeSigner(oracle.backend.ChainConfig(), big.NewInt(int64(number))), number, sampleNumber, oracle.ignorePrice, result, quit)
+ go oracle.getBlockValues(ctx, number, sampleNumber, oracle.ignorePrice, result, quit)
sent++
exp++
number--
@@ -255,7 +255,7 @@ func (oracle *Oracle) SuggestTipCap(ctx context.Context) (*big.Int, error) {
// meaningful returned, try to query more blocks. But the maximum
// is 2*checkBlocks.
if len(res.values) == 1 && len(results)+1+exp < oracle.checkBlocks*2 && number > 0 {
- go oracle.getBlockValues(ctx, types.MakeSigner(oracle.backend.ChainConfig(), big.NewInt(int64(number))), number, sampleNumber, oracle.ignorePrice, result, quit)
+ go oracle.getBlockValues(ctx, number, sampleNumber, oracle.ignorePrice, result, quit)
sent++
exp++
number--
@@ -311,7 +311,7 @@ func (s *txSorter) Less(i, j int) bool {
// and sends it to the result channel. If the block is empty or all transactions
// are sent by the miner itself(it doesn't make any sense to include this kind of
// transaction prices for sampling), nil gasprice is returned.
-func (oracle *Oracle) getBlockValues(ctx context.Context, signer types.Signer, blockNum uint64, limit int, ignoreUnder *big.Int, result chan results, quit chan struct{}) {
+func (oracle *Oracle) getBlockValues(ctx context.Context, blockNum uint64, limit int, ignoreUnder *big.Int, result chan results, quit chan struct{}) {
block, err := oracle.backend.BlockByNumber(ctx, rpc.BlockNumber(blockNum))
if block == nil {
select {
@@ -320,6 +320,8 @@ func (oracle *Oracle) getBlockValues(ctx context.Context, signer types.Signer, b
}
return
}
+ signer := types.MakeSigner(oracle.backend.ChainConfig(), block.Number(), block.Time())
+
// Sort the transaction by effective tip in ascending sort.
txs := make([]*types.Transaction, len(block.Transactions()))
copy(txs, block.Transactions())
diff --git a/eth/state_accessor.go b/eth/state_accessor.go
index 48e31bb6a72c..c610edd85d79 100644
--- a/eth/state_accessor.go
+++ b/eth/state_accessor.go
@@ -180,7 +180,7 @@ func (eth *Ethereum) stateAtTransaction(block *types.Block, txIndex int, reexec
return nil, vm.BlockContext{}, statedb, nil
}
// Recompute transactions up to the target index.
- signer := types.MakeSigner(eth.blockchain.Config(), block.Number())
+ signer := types.MakeSigner(eth.blockchain.Config(), block.Number(), block.Time())
for idx, tx := range block.Transactions() {
// Assemble the transaction call message and return if the requested offset
msg, _ := tx.AsMessage(signer, block.BaseFee())
diff --git a/eth/tracers/api.go b/eth/tracers/api.go
index 53570fcddb15..271ddf963201 100644
--- a/eth/tracers/api.go
+++ b/eth/tracers/api.go
@@ -275,7 +275,7 @@ func (api *API) traceChain(ctx context.Context, start, end *types.Block, config
// Fetch and execute the next block trace tasks
for task := range tasks {
- signer := types.MakeSigner(api.backend.ChainConfig(), task.block.Number())
+ signer := types.MakeSigner(api.backend.ChainConfig(), task.block.Number(), task.block.Time())
blockCtx := core.NewEVMBlockContext(task.block.Header(), api.chainContext(localctx), api.backend.ChainConfig(), nil)
// Trace all the transactions contained within
for i, tx := range task.block.Transactions() {
@@ -534,7 +534,7 @@ func (api *API) IntermediateRoots(ctx context.Context, hash common.Hash, config
}
var (
roots []common.Hash
- signer = types.MakeSigner(api.backend.ChainConfig(), block.Number())
+ signer = types.MakeSigner(api.backend.ChainConfig(), block.Number(), block.Time())
chainConfig = api.backend.ChainConfig()
vmctx = core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), api.backend.ChainConfig(), nil)
deleteEmptyObjects = chainConfig.IsEIP158(block.Number())
@@ -602,7 +602,7 @@ func (api *API) traceBlock(ctx context.Context, block *types.Block, config *Trac
}
// Execute all the transaction contained within the block concurrently
var (
- signer = types.MakeSigner(api.backend.ChainConfig(), block.Number())
+ signer = types.MakeSigner(api.backend.ChainConfig(), block.Number(), block.Time())
txs = block.Transactions()
results = make([]*txTraceResult, len(txs))
@@ -726,7 +726,7 @@ func (api *API) standardTraceBlockToFile(ctx context.Context, block *types.Block
// Execute transaction, either tracing all or just the requested one
var (
dumps []string
- signer = types.MakeSigner(api.backend.ChainConfig(), block.Number())
+ signer = types.MakeSigner(api.backend.ChainConfig(), block.Number(), block.Time())
chainConfig = api.backend.ChainConfig()
vmctx = core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), api.backend.ChainConfig(), nil)
canon = true
@@ -906,7 +906,7 @@ func (api *API) TraceCall(ctx context.Context, args ethapi.TransactionArgs, bloc
}
}
- signer := types.MakeSigner(api.backend.ChainConfig(), block.Number())
+ signer := types.MakeSigner(api.backend.ChainConfig(), block.Number(), block.Time())
l1DataFee, err := fees.EstimateL1DataFeeForMessage(msg, block.BaseFee(), api.backend.ChainConfig(), signer, statedb, block.Number())
if err != nil {
return nil, err
diff --git a/eth/tracers/api_test.go b/eth/tracers/api_test.go
index e9928434a981..d49e7df80a4c 100644
--- a/eth/tracers/api_test.go
+++ b/eth/tracers/api_test.go
@@ -164,7 +164,7 @@ func (b *testBackend) StateAtTransaction(ctx context.Context, block *types.Block
return nil, vm.BlockContext{}, statedb, nil
}
// Recompute transactions up to the target index.
- signer := types.MakeSigner(b.chainConfig, block.Number())
+ signer := types.MakeSigner(b.chainConfig, block.Number(), block.Time())
for idx, tx := range block.Transactions() {
msg, _ := tx.AsMessage(signer, block.BaseFee())
txContext := core.NewEVMTxContext(msg)
diff --git a/eth/tracers/internal/tracetest/calltrace_test.go b/eth/tracers/internal/tracetest/calltrace_test.go
index 8ef0b653017e..c3d8bfff39ea 100644
--- a/eth/tracers/internal/tracetest/calltrace_test.go
+++ b/eth/tracers/internal/tracetest/calltrace_test.go
@@ -167,7 +167,7 @@ func testCallTracer(tracerName string, dirPath string, t *testing.T) {
}
// Configure a blockchain with the given prestate
var (
- signer = types.MakeSigner(test.Genesis.Config, new(big.Int).SetUint64(uint64(test.Context.Number)))
+ signer = types.MakeSigner(test.Genesis.Config, new(big.Int).SetUint64(uint64(test.Context.Number)), uint64(test.Context.Time))
origin, _ = signer.Sender(tx)
txContext = vm.TxContext{
Origin: origin,
@@ -278,7 +278,7 @@ func benchTracer(tracerName string, test *callTracerTest, b *testing.B) {
if err := rlp.DecodeBytes(common.FromHex(test.Input), tx); err != nil {
b.Fatalf("failed to parse testcase input: %v", err)
}
- signer := types.MakeSigner(test.Genesis.Config, new(big.Int).SetUint64(uint64(test.Context.Number)))
+ signer := types.MakeSigner(test.Genesis.Config, new(big.Int).SetUint64(uint64(test.Context.Number)), uint64(test.Context.Time))
msg, err := tx.AsMessage(signer, nil)
if err != nil {
b.Fatalf("failed to prepare transaction for tracing: %v", err)
diff --git a/eth/tracers/js/tracer.go b/eth/tracers/js/tracer.go
index de375d0a3b05..e4e5001b1957 100644
--- a/eth/tracers/js/tracer.go
+++ b/eth/tracers/js/tracer.go
@@ -33,6 +33,7 @@ import (
"github.com/scroll-tech/go-ethereum/common"
"github.com/scroll-tech/go-ethereum/common/hexutil"
"github.com/scroll-tech/go-ethereum/core"
+ "github.com/scroll-tech/go-ethereum/core/types"
"github.com/scroll-tech/go-ethereum/core/vm"
"github.com/scroll-tech/go-ethereum/crypto"
tracers2 "github.com/scroll-tech/go-ethereum/eth/tracers"
@@ -681,7 +682,7 @@ func wrapError(context string, err error) error {
}
// CaptureStart implements the Tracer interface to initialize the tracing operation.
-func (jst *jsTracer) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) {
+func (jst *jsTracer) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int, authorizationResults []types.AuthorizationResult) {
jst.env = env
jst.ctx["type"] = "CALL"
if create {
@@ -705,7 +706,7 @@ func (jst *jsTracer) CaptureStart(env *vm.EVM, from common.Address, to common.Ad
isHomestead := env.ChainConfig().IsHomestead(env.Context.BlockNumber)
isIstanbul := env.ChainConfig().IsIstanbul(env.Context.BlockNumber)
isShanghai := env.ChainConfig().IsShanghai(env.Context.BlockNumber)
- intrinsicGas, err := core.IntrinsicGas(input, nil, jst.ctx["type"] == "CREATE", isHomestead, isIstanbul, isShanghai)
+ intrinsicGas, err := core.IntrinsicGas(input, nil, nil, jst.ctx["type"] == "CREATE", isHomestead, isIstanbul, isShanghai)
if err != nil {
return
}
diff --git a/eth/tracers/js/tracer_test.go b/eth/tracers/js/tracer_test.go
index 44d3bcb270a0..2f37b332d87f 100644
--- a/eth/tracers/js/tracer_test.go
+++ b/eth/tracers/js/tracer_test.go
@@ -68,7 +68,7 @@ func runTrace(tracer tracers.Tracer, vmctx *vmContext, chaincfg *params.ChainCon
)
contract.Code = []byte{byte(vm.PUSH1), 0x1, byte(vm.PUSH1), 0x1, 0x0}
- tracer.CaptureStart(env, contract.Caller(), contract.Address(), false, []byte{}, startGas, value)
+ tracer.CaptureStart(env, contract.Caller(), contract.Address(), false, []byte{}, startGas, value, nil)
ret, err := env.Interpreter().Run(contract, []byte{}, false)
tracer.CaptureEnd(ret, startGas-contract.Gas, 1, err)
if err != nil {
@@ -152,7 +152,7 @@ func TestHaltBetweenSteps(t *testing.T) {
scope := &vm.ScopeContext{
Contract: vm.NewContract(&account{}, &account{}, big.NewInt(0), 0),
}
- tracer.CaptureStart(env, common.Address{}, common.Address{}, false, []byte{}, 0, big.NewInt(0))
+ tracer.CaptureStart(env, common.Address{}, common.Address{}, false, []byte{}, 0, big.NewInt(0), nil)
tracer.CaptureState(0, 0, 0, 0, scope, nil, 0, nil)
timeout := errors.New("stahp")
tracer.Stop(timeout)
@@ -173,7 +173,7 @@ func TestNoStepExec(t *testing.T) {
t.Fatal(err)
}
env := vm.NewEVM(vm.BlockContext{BlockNumber: big.NewInt(1)}, vm.TxContext{GasPrice: big.NewInt(100)}, &dummyStatedb{}, params.TestChainConfig, vm.Config{Debug: true, Tracer: tracer})
- tracer.CaptureStart(env, common.Address{}, common.Address{}, false, []byte{}, 1000, big.NewInt(0))
+ tracer.CaptureStart(env, common.Address{}, common.Address{}, false, []byte{}, 1000, big.NewInt(0), nil)
tracer.CaptureEnd(nil, 0, 1, nil)
ret, err := tracer.GetResult()
if err != nil {
diff --git a/eth/tracers/native/4byte.go b/eth/tracers/native/4byte.go
index 355c2ded9d9b..a62da6313766 100644
--- a/eth/tracers/native/4byte.go
+++ b/eth/tracers/native/4byte.go
@@ -24,6 +24,7 @@ import (
"time"
"github.com/scroll-tech/go-ethereum/common"
+ "github.com/scroll-tech/go-ethereum/core/types"
"github.com/scroll-tech/go-ethereum/core/vm"
"github.com/scroll-tech/go-ethereum/eth/tracers"
)
@@ -79,7 +80,7 @@ func (t *fourByteTracer) store(id []byte, size int) {
}
// CaptureStart implements the EVMLogger interface to initialize the tracing operation.
-func (t *fourByteTracer) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) {
+func (t *fourByteTracer) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int, authorizationResults []types.AuthorizationResult) {
t.env = env
// Update list of precompiles based on current block
diff --git a/eth/tracers/native/call.go b/eth/tracers/native/call.go
index 9fad7e1e0b16..b2bebb15ce02 100644
--- a/eth/tracers/native/call.go
+++ b/eth/tracers/native/call.go
@@ -26,6 +26,7 @@ import (
"time"
"github.com/scroll-tech/go-ethereum/common"
+ "github.com/scroll-tech/go-ethereum/core/types"
"github.com/scroll-tech/go-ethereum/core/vm"
"github.com/scroll-tech/go-ethereum/eth/tracers"
)
@@ -64,7 +65,7 @@ func newCallTracer(ctx *tracers.Context) tracers.Tracer {
}
// CaptureStart implements the EVMLogger interface to initialize the tracing operation.
-func (t *callTracer) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) {
+func (t *callTracer) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int, authorizationResults []types.AuthorizationResult) {
t.env = env
t.callstack[0] = callFrame{
Type: "CALL",
diff --git a/eth/tracers/native/noop.go b/eth/tracers/native/noop.go
index 61f0f715098c..9df6584a538c 100644
--- a/eth/tracers/native/noop.go
+++ b/eth/tracers/native/noop.go
@@ -22,6 +22,7 @@ import (
"time"
"github.com/scroll-tech/go-ethereum/common"
+ "github.com/scroll-tech/go-ethereum/core/types"
"github.com/scroll-tech/go-ethereum/core/vm"
"github.com/scroll-tech/go-ethereum/eth/tracers"
)
@@ -40,7 +41,7 @@ func newNoopTracer(ctx *tracers.Context) tracers.Tracer {
}
// CaptureStart implements the EVMLogger interface to initialize the tracing operation.
-func (t *noopTracer) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) {
+func (t *noopTracer) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int, authorizationResults []types.AuthorizationResult) {
}
// CaptureEnd is called after the call finishes to finalize the tracing.
diff --git a/eth/tracers/native/prestate.go b/eth/tracers/native/prestate.go
index 5b59b6af856b..117fa22ab7a1 100644
--- a/eth/tracers/native/prestate.go
+++ b/eth/tracers/native/prestate.go
@@ -8,6 +8,7 @@ import (
"time"
"github.com/scroll-tech/go-ethereum/common"
+ "github.com/scroll-tech/go-ethereum/core/types"
"github.com/scroll-tech/go-ethereum/core/vm"
"github.com/scroll-tech/go-ethereum/crypto"
"github.com/scroll-tech/go-ethereum/eth/tracers"
@@ -71,7 +72,7 @@ func newPrestateTracer(ctx *tracers.Context) tracers.Tracer {
}
// CaptureStart implements the EVMLogger interface to initialize the tracing operation.
-func (t *prestateTracer) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) {
+func (t *prestateTracer) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int, authorizationResults []types.AuthorizationResult) {
t.env = env
t.create = create
t.to = to
@@ -80,6 +81,10 @@ func (t *prestateTracer) CaptureStart(env *vm.EVM, from common.Address, to commo
t.lookupAccount(to)
t.lookupAccount(env.Context.Coinbase)
+ for _, auth := range authorizationResults {
+ t.lookupAccount(auth.Authority)
+ }
+
// The recipient balance includes the value transferred.
toBal := new(big.Int).Sub(t.pre[to].Balance, value)
t.pre[to].Balance = toBal
@@ -92,6 +97,14 @@ func (t *prestateTracer) CaptureStart(env *vm.EVM, from common.Address, to commo
fromBal.Add(fromBal, new(big.Int).Add(value, consumedGas))
t.pre[from].Balance = fromBal
t.pre[from].Nonce--
+
+ for i := len(authorizationResults) - 1; i >= 0; i-- {
+ auth := authorizationResults[i]
+ if auth.Success {
+ t.pre[auth.Authority].Nonce--
+ t.pre[auth.Authority].Code = auth.PreCode
+ }
+ }
}
// CaptureEnd is called after the call finishes to finalize the tracing.
diff --git a/ethclient/ethclient.go b/ethclient/ethclient.go
index 4428a866fa2b..66b3490631a4 100644
--- a/ethclient/ethclient.go
+++ b/ethclient/ethclient.go
@@ -751,5 +751,8 @@ func toCallArg(msg ethereum.CallMsg) interface{} {
if msg.BlobHashes != nil {
arg["blobVersionedHashes"] = msg.BlobHashes
}
+ if msg.AuthList != nil {
+ arg["authorizationList"] = msg.AuthList
+ }
return arg
}
diff --git a/ethclient/gethclient/gethclient.go b/ethclient/gethclient/gethclient.go
index 172c2681e20d..916007751137 100644
--- a/ethclient/gethclient/gethclient.go
+++ b/ethclient/gethclient/gethclient.go
@@ -228,6 +228,9 @@ func toCallArg(msg ethereum.CallMsg) interface{} {
if msg.BlobHashes != nil {
arg["blobVersionedHashes"] = msg.BlobHashes
}
+ if msg.AuthList != nil {
+ arg["authorizationList"] = msg.AuthList
+ }
return arg
}
diff --git a/graphql/graphql.go b/graphql/graphql.go
index ecc704af2140..546a993c1085 100644
--- a/graphql/graphql.go
+++ b/graphql/graphql.go
@@ -227,7 +227,7 @@ func (t *Transaction) GasPrice(ctx context.Context) (hexutil.Big, error) {
switch tx.Type() {
case types.AccessListTxType:
return hexutil.Big(*tx.GasPrice()), nil
- case types.DynamicFeeTxType:
+ case types.DynamicFeeTxType, types.SetCodeTxType:
if t.block != nil {
if baseFee, _ := t.block.BaseFeePerGas(ctx); baseFee != nil {
// price = min(tip, gasFeeCap - baseFee) + baseFee
@@ -263,7 +263,7 @@ func (t *Transaction) MaxFeePerGas(ctx context.Context) (*hexutil.Big, error) {
switch tx.Type() {
case types.AccessListTxType:
return nil, nil
- case types.DynamicFeeTxType:
+ case types.DynamicFeeTxType, types.SetCodeTxType:
return (*hexutil.Big)(tx.GasFeeCap()), nil
default:
return nil, nil
@@ -278,7 +278,7 @@ func (t *Transaction) MaxPriorityFeePerGas(ctx context.Context) (*hexutil.Big, e
switch tx.Type() {
case types.AccessListTxType:
return nil, nil
- case types.DynamicFeeTxType:
+ case types.DynamicFeeTxType, types.SetCodeTxType:
return (*hexutil.Big)(tx.GasTipCap()), nil
default:
return nil, nil
diff --git a/interfaces.go b/interfaces.go
index 3069b736b47f..f7ce7e7c4952 100644
--- a/interfaces.go
+++ b/interfaces.go
@@ -127,6 +127,9 @@ type CallMsg struct {
// For BlobTxType
BlobGasFeeCap *big.Int
BlobHashes []common.Hash
+
+ // For SetCodeTxType
+ AuthList []types.SetCodeAuthorization
}
// A ContractCaller provides contract calls, essentially transactions that are executed by
diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go
index 2d9c034066b7..83b04f4a6190 100644
--- a/internal/ethapi/api.go
+++ b/internal/ethapi/api.go
@@ -888,7 +888,7 @@ func (s *PublicBlockChainAPI) GetBlockReceipts(ctx context.Context, blockNrOrHas
for i, receipt := range receipts {
blockNumber := block.NumberU64()
bigblock := new(big.Int).SetUint64(blockNumber)
- signer := types.MakeSigner(s.b.ChainConfig(), bigblock)
+ signer := types.MakeSigner(s.b.ChainConfig(), bigblock, block.Time())
res, err := marshalReceipt(ctx, s.b, receipt, bigblock, block.Hash(), blockNumber, signer, txs[i], i)
if err != nil {
return nil, fmt.Errorf("failed to marshal receipt %d: %w", i, err)
@@ -991,7 +991,7 @@ func EstimateL1MsgFee(ctx context.Context, b Backend, args TransactionArgs, bloc
evm.Cancel()
}()
- signer := types.MakeSigner(config, header.Number)
+ signer := types.MakeSigner(config, header.Number, header.Time)
return fees.EstimateL1DataFeeForMessage(msg, header.BaseFee, config, signer, evm.StateDB, header.Number)
}
@@ -1334,25 +1334,27 @@ func (s *PublicBlockChainAPI) rpcMarshalBlock(ctx context.Context, b *types.Bloc
// 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"`
- 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"`
- V *hexutil.Big `json:"v"`
- R *hexutil.Big `json:"r"`
- S *hexutil.Big `json:"s"`
+ 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"`
+ 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"`
+ AuthorizationList []types.SetCodeAuthorization `json:"authorizationList,omitempty"`
+ V *hexutil.Big `json:"v"`
+ R *hexutil.Big `json:"r"`
+ S *hexutil.Big `json:"s"`
+ YParity *hexutil.Uint64 `json:"yParity,omitempty"`
// L1 message transaction fields:
Sender *common.Address `json:"sender,omitempty"`
@@ -1361,8 +1363,8 @@ type RPCTransaction struct {
// NewRPCTransaction returns a transaction that will serialize to the RPC
// representation, with the given location metadata set (if available).
-func NewRPCTransaction(tx *types.Transaction, blockHash common.Hash, blockNumber uint64, index uint64, baseFee *big.Int, config *params.ChainConfig) *RPCTransaction {
- signer := types.MakeSigner(config, big.NewInt(0).SetUint64(blockNumber))
+func NewRPCTransaction(tx *types.Transaction, blockHash common.Hash, blockNumber, blockTime uint64, index uint64, baseFee *big.Int, config *params.ChainConfig) *RPCTransaction {
+ signer := types.MakeSigner(config, big.NewInt(0).SetUint64(blockNumber), blockTime)
from, _ := types.Sender(signer, tx)
v, r, s := tx.RawSignatureValues()
result := &RPCTransaction{
@@ -1387,12 +1389,16 @@ func NewRPCTransaction(tx *types.Transaction, blockHash common.Hash, blockNumber
switch tx.Type() {
case types.AccessListTxType:
al := tx.AccessList()
+ yparity := hexutil.Uint64(v.Sign())
result.Accesses = &al
result.ChainID = (*hexutil.Big)(tx.ChainId())
+ result.YParity = &yparity
case types.DynamicFeeTxType:
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
@@ -1403,6 +1409,23 @@ func NewRPCTransaction(tx *types.Transaction, blockHash common.Hash, blockNumber
} else {
result.GasPrice = (*hexutil.Big)(tx.GasFeeCap())
}
+ 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{}) {
+ // price = min(tip, gasFeeCap - baseFee) + baseFee
+ price := math.BigMin(new(big.Int).Add(tx.GasTipCap(), baseFee), tx.GasFeeCap())
+ result.GasPrice = (*hexutil.Big)(price)
+ } else {
+ result.GasPrice = (*hexutil.Big)(tx.GasFeeCap())
+ }
+ result.AuthorizationList = tx.SetCodeAuthorizations()
case types.L1MessageTxType:
msg := tx.AsL1MessageTx()
result.Sender = &msg.Sender
@@ -1415,11 +1438,13 @@ func NewRPCTransaction(tx *types.Transaction, blockHash common.Hash, blockNumber
func newRPCPendingTransaction(tx *types.Transaction, current *types.Header, config *params.ChainConfig, l1BaseFee *big.Int) *RPCTransaction {
var baseFee *big.Int
blockNumber := uint64(0)
+ blockTime := uint64(0)
if current != nil {
baseFee = misc.CalcBaseFee(config, current, l1BaseFee)
blockNumber = current.Number.Uint64()
+ blockTime = current.Time
}
- return NewRPCTransaction(tx, common.Hash{}, blockNumber, 0, baseFee, config)
+ return NewRPCTransaction(tx, common.Hash{}, blockNumber, blockTime, 0, baseFee, config)
}
// newRPCTransactionFromBlockIndex returns a transaction that will serialize to the RPC representation.
@@ -1428,7 +1453,7 @@ func newRPCTransactionFromBlockIndex(b *types.Block, index uint64, config *param
if index >= uint64(len(txs)) {
return nil
}
- return NewRPCTransaction(txs[index], b.Hash(), b.NumberU64(), index, b.BaseFee(), config)
+ return NewRPCTransaction(txs[index], b.Hash(), b.NumberU64(), b.Time(), index, b.BaseFee(), config)
}
// newRPCRawTransactionFromBlockIndex returns the bytes of a transaction given a block and a transaction index.
@@ -1539,7 +1564,7 @@ func AccessList(ctx context.Context, b Backend, blockNrOrHash rpc.BlockNumberOrH
if err != nil {
return nil, 0, nil, err
}
- signer := types.MakeSigner(b.ChainConfig(), header.Number)
+ signer := types.MakeSigner(b.ChainConfig(), header.Number, header.Time)
l1DataFee, err := fees.EstimateL1DataFeeForMessage(msg, header.BaseFee, b.ChainConfig(), signer, statedb, header.Number)
if err != nil {
return nil, 0, nil, fmt.Errorf("failed to apply transaction: %v err: %v", args.toTransaction().Hash(), err)
@@ -1651,7 +1676,7 @@ func (s *PublicTransactionPoolAPI) GetTransactionByHash(ctx context.Context, has
if err != nil {
return nil, err
}
- return NewRPCTransaction(tx, blockHash, blockNumber, index, header.BaseFee, s.b.ChainConfig()), nil
+ return NewRPCTransaction(tx, blockHash, blockNumber, header.Time, index, header.BaseFee, s.b.ChainConfig()), nil
}
// No finalized transaction, try to retrieve it from the pool
if tx := s.b.GetPoolTransaction(hash); tx != nil {
@@ -1695,6 +1720,10 @@ func (s *PublicTransactionPoolAPI) GetTransactionReceipt(ctx context.Context, ha
if err != nil {
return nil, nil
}
+ header, err := s.b.HeaderByHash(ctx, blockHash)
+ if err != nil {
+ return nil, err
+ }
receipts, err := s.b.GetReceipts(ctx, blockHash)
if err != nil {
return nil, err
@@ -1705,9 +1734,8 @@ func (s *PublicTransactionPoolAPI) GetTransactionReceipt(ctx context.Context, ha
receipt := receipts[index]
// Derive the sender.
- bigblock := new(big.Int).SetUint64(blockNumber)
- signer := types.MakeSigner(s.b.ChainConfig(), bigblock)
- return marshalReceipt(ctx, s.b, receipt, bigblock, blockHash, blockNumber, signer, tx, int(index))
+ signer := types.MakeSigner(s.b.ChainConfig(), header.Number, header.Time)
+ return marshalReceipt(ctx, s.b, receipt, header.Number, blockHash, blockNumber, signer, tx, int(index))
}
// marshalReceipt marshals a transaction receipt into a JSON object.
@@ -1790,7 +1818,7 @@ func SubmitTransaction(ctx context.Context, b Backend, tx *types.Transaction) (c
return common.Hash{}, err
}
// Print a log with full tx details for manual investigations and interventions
- signer := types.MakeSigner(b.ChainConfig(), b.CurrentBlock().Number())
+ signer := types.MakeSigner(b.ChainConfig(), b.CurrentBlock().Number(), b.CurrentBlock().Time())
from, err := types.Sender(signer, tx)
if err != nil {
return common.Hash{}, err
diff --git a/internal/ethapi/api_test.go b/internal/ethapi/api_test.go
new file mode 100644
index 000000000000..db37b071212f
--- /dev/null
+++ b/internal/ethapi/api_test.go
@@ -0,0 +1,415 @@
+// Copyright 2023 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 ethapi
+
+import (
+ "bytes"
+ "context"
+ "crypto/ecdsa"
+ "errors"
+ "math/big"
+ "reflect"
+ "slices"
+ "testing"
+ "time"
+
+ "github.com/scroll-tech/go-ethereum"
+ "github.com/scroll-tech/go-ethereum/accounts"
+ "github.com/scroll-tech/go-ethereum/accounts/keystore"
+ "github.com/scroll-tech/go-ethereum/common"
+ "github.com/scroll-tech/go-ethereum/common/hexutil"
+ "github.com/scroll-tech/go-ethereum/consensus"
+ "github.com/scroll-tech/go-ethereum/consensus/ethash"
+ "github.com/scroll-tech/go-ethereum/core"
+ "github.com/scroll-tech/go-ethereum/core/bloombits"
+ "github.com/scroll-tech/go-ethereum/core/rawdb"
+ "github.com/scroll-tech/go-ethereum/core/state"
+ "github.com/scroll-tech/go-ethereum/core/types"
+ "github.com/scroll-tech/go-ethereum/core/vm"
+ "github.com/scroll-tech/go-ethereum/crypto"
+ "github.com/scroll-tech/go-ethereum/ethdb"
+ "github.com/scroll-tech/go-ethereum/event"
+ "github.com/scroll-tech/go-ethereum/params"
+ "github.com/scroll-tech/go-ethereum/rpc"
+)
+
+func newTestAccountManager(t *testing.T) (*accounts.Manager, accounts.Account) {
+ var (
+ dir = t.TempDir()
+ am = accounts.NewManager(nil)
+ b = keystore.NewKeyStore(dir, 2, 1)
+ testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
+ )
+ acc, err := b.ImportECDSA(testKey, "")
+ if err != nil {
+ t.Fatalf("failed to create test account: %v", err)
+ }
+ if err := b.Unlock(acc, ""); err != nil {
+ t.Fatalf("failed to unlock account: %v\n", err)
+ }
+ am.AddBackend(b)
+ return am, acc
+}
+
+type account struct {
+ key *ecdsa.PrivateKey
+ addr common.Address
+}
+
+func newAccounts(n int) (accounts []account) {
+ for i := 0; i < n; i++ {
+ key, _ := crypto.GenerateKey()
+ addr := crypto.PubkeyToAddress(key.PublicKey)
+ accounts = append(accounts, account{key: key, addr: addr})
+ }
+ slices.SortFunc(accounts, func(a, b account) int { return bytes.Compare(a.addr[:], b.addr[:]) })
+ return accounts
+}
+
+type testBackend struct {
+ db ethdb.Database
+ chain *core.BlockChain
+ pending *types.Block
+ accman *accounts.Manager
+ acc accounts.Account
+}
+
+func newTestBackend(t *testing.T, n int, gspec *core.Genesis, engine consensus.Engine, generator func(i int, b *core.BlockGen)) *testBackend {
+ var (
+ cacheConfig = &core.CacheConfig{
+ TrieCleanLimit: 256,
+ TrieDirtyLimit: 256,
+ TrieTimeLimit: 5 * time.Minute,
+ SnapshotLimit: 0,
+ TrieDirtyDisabled: true, // Archive mode
+ }
+ )
+ accman, acc := newTestAccountManager(t)
+ gspec.Alloc[acc.Address] = core.GenesisAccount{Balance: big.NewInt(params.Ether)}
+ // Generate blocks for testing
+ db := rawdb.NewMemoryDatabase()
+ genesis := gspec.MustCommit(db)
+ blocks, _ := core.GenerateChain(gspec.Config, genesis, engine, db, n, generator)
+ txlookupLimit := uint64(0)
+ chain, err := core.NewBlockChain(db, cacheConfig, gspec.Config, engine, vm.Config{}, nil, &txlookupLimit)
+ if err != nil {
+ t.Fatalf("failed to create tester chain: %v", err)
+ }
+ if n, err := chain.InsertChain(blocks); err != nil {
+ t.Fatalf("block %d: failed to insert into chain: %v", n, err)
+ }
+
+ backend := &testBackend{db: db, chain: chain, accman: accman, acc: acc}
+ return backend
+}
+
+func (b *testBackend) setPendingBlock(block *types.Block) {
+ b.pending = block
+}
+
+func (b testBackend) SyncProgress() ethereum.SyncProgress { return ethereum.SyncProgress{} }
+func (b testBackend) SuggestGasTipCap(ctx context.Context) (*big.Int, error) {
+ return big.NewInt(0), nil
+}
+func (b testBackend) FeeHistory(ctx context.Context, blockCount int, lastBlock rpc.BlockNumber, rewardPercentiles []float64) (*big.Int, [][]*big.Int, []*big.Int, []float64, error) {
+ return nil, nil, nil, nil, nil
+}
+func (b testBackend) BlobBaseFee(ctx context.Context) *big.Int { return new(big.Int) }
+func (b testBackend) ChainDb() ethdb.Database { return b.db }
+func (b testBackend) AccountManager() *accounts.Manager { return b.accman }
+func (b testBackend) ExtRPCEnabled() bool { return false }
+func (b testBackend) RPCGasCap() uint64 { return 10000000 }
+func (b testBackend) RPCEVMTimeout() time.Duration { return time.Second }
+func (b testBackend) RPCTxFeeCap() float64 { return 0 }
+func (b testBackend) UnprotectedAllowed() bool { return false }
+func (b testBackend) SetHead(number uint64) {}
+func (b testBackend) HeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Header, error) {
+ if number == rpc.LatestBlockNumber {
+ return b.chain.CurrentHeader(), nil
+ }
+ if number == rpc.PendingBlockNumber && b.pending != nil {
+ return b.pending.Header(), nil
+ }
+ return b.chain.GetHeaderByNumber(uint64(number)), nil
+}
+func (b testBackend) HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error) {
+ return b.chain.GetHeaderByHash(hash), nil
+}
+func (b testBackend) HeaderByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*types.Header, error) {
+ if blockNr, ok := blockNrOrHash.Number(); ok {
+ return b.HeaderByNumber(ctx, blockNr)
+ }
+ if blockHash, ok := blockNrOrHash.Hash(); ok {
+ return b.HeaderByHash(ctx, blockHash)
+ }
+ panic("unknown type rpc.BlockNumberOrHash")
+}
+
+func (b testBackend) CurrentHeader() *types.Header { return b.chain.CurrentHeader() }
+func (b testBackend) CurrentBlock() *types.Block { return b.chain.CurrentBlock() }
+func (b testBackend) BlockByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Block, error) {
+ if number == rpc.LatestBlockNumber {
+ head := b.chain.CurrentBlock()
+ return b.chain.GetBlock(head.Hash(), head.Number().Uint64()), nil
+ }
+ if number == rpc.PendingBlockNumber {
+ return b.pending, nil
+ }
+ return b.chain.GetBlockByNumber(uint64(number)), nil
+}
+func (b testBackend) BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error) {
+ return b.chain.GetBlockByHash(hash), nil
+}
+func (b testBackend) BlockByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*types.Block, error) {
+ if blockNr, ok := blockNrOrHash.Number(); ok {
+ return b.BlockByNumber(ctx, blockNr)
+ }
+ if blockHash, ok := blockNrOrHash.Hash(); ok {
+ return b.BlockByHash(ctx, blockHash)
+ }
+ panic("unknown type rpc.BlockNumberOrHash")
+}
+func (b testBackend) GetBody(ctx context.Context, hash common.Hash, number rpc.BlockNumber) (*types.Body, error) {
+ return b.chain.GetBlock(hash, uint64(number.Int64())).Body(), nil
+}
+func (b testBackend) StateAndHeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*state.StateDB, *types.Header, error) {
+ if number == rpc.PendingBlockNumber {
+ panic("pending state not implemented")
+ }
+ header, err := b.HeaderByNumber(ctx, number)
+ if err != nil {
+ return nil, nil, err
+ }
+ if header == nil {
+ return nil, nil, errors.New("header not found")
+ }
+ stateDb, err := b.chain.StateAt(header.Root)
+ return stateDb, header, err
+}
+func (b testBackend) StateAndHeaderByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*state.StateDB, *types.Header, error) {
+ if blockNr, ok := blockNrOrHash.Number(); ok {
+ return b.StateAndHeaderByNumber(ctx, blockNr)
+ }
+ panic("only implemented for number")
+}
+func (b testBackend) Pending() (*types.Block, types.Receipts, *state.StateDB) { panic("implement me") }
+func (b testBackend) GetReceipts(ctx context.Context, hash common.Hash) (types.Receipts, error) {
+ header, err := b.HeaderByHash(ctx, hash)
+ if header == nil || err != nil {
+ return nil, err
+ }
+ receipts := rawdb.ReadReceipts(b.db, hash, header.Number.Uint64(), b.chain.Config())
+ return receipts, nil
+}
+func (b testBackend) GetTd(ctx context.Context, hash common.Hash) *big.Int {
+ if b.pending != nil && hash == b.pending.Hash() {
+ return nil
+ }
+ return big.NewInt(1)
+}
+
+func (b testBackend) GetEVM(ctx context.Context, msg core.Message, state *state.StateDB, header *types.Header, vmConfig *vm.Config) (*vm.EVM, func() error, error) {
+ vmError := func() error { return nil }
+ if vmConfig == nil {
+ vmConfig = b.chain.GetVMConfig()
+ }
+ txContext := core.NewEVMTxContext(msg)
+ context := core.NewEVMBlockContext(header, b.chain, b.ChainConfig(), nil)
+ return vm.NewEVM(context, txContext, state, b.chain.Config(), *vmConfig), vmError, nil
+}
+func (b testBackend) SubscribeChainEvent(ch chan<- core.ChainEvent) event.Subscription {
+ panic("implement me")
+}
+func (b testBackend) SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription {
+ panic("implement me")
+}
+func (b testBackend) SendTx(ctx context.Context, signedTx *types.Transaction) error {
+ panic("implement me")
+}
+func (b testBackend) PendingBlockAndReceipts() (*types.Block, types.Receipts) {
+ panic("implement me")
+}
+func (b testBackend) RemoveTx(txHash common.Hash) {
+ panic("implement me")
+}
+func (b testBackend) StateAt(root common.Hash) (*state.StateDB, error) {
+ panic("implement me")
+}
+func (b testBackend) SubscribeChainSideEvent(ch chan<- core.ChainSideEvent) event.Subscription {
+ panic("implement me")
+}
+func (b testBackend) SubscribePendingLogsEvent(ch chan<- []*types.Log) event.Subscription {
+ panic("implement me")
+}
+func (b testBackend) GetTransaction(ctx context.Context, txHash common.Hash) (*types.Transaction, common.Hash, uint64, uint64, error) {
+ tx, blockHash, blockNumber, index := rawdb.ReadTransaction(b.db, txHash)
+ return tx, blockHash, blockNumber, index, nil
+}
+func (b testBackend) GetPoolTransactions() (types.Transactions, error) { panic("implement me") }
+func (b testBackend) GetPoolTransaction(txHash common.Hash) *types.Transaction { panic("implement me") }
+func (b testBackend) GetPoolNonce(ctx context.Context, addr common.Address) (uint64, error) {
+ return 0, nil
+}
+func (b testBackend) Stats() (pending int, queued int) { panic("implement me") }
+func (b testBackend) TxPoolContent() (map[common.Address]types.Transactions, map[common.Address]types.Transactions) {
+ panic("implement me")
+}
+func (b testBackend) TxPoolContentFrom(addr common.Address) (types.Transactions, types.Transactions) {
+ panic("implement me")
+}
+func (b testBackend) SubscribeNewTxsEvent(events chan<- core.NewTxsEvent) event.Subscription {
+ panic("implement me")
+}
+func (b testBackend) ChainConfig() *params.ChainConfig { return b.chain.Config() }
+func (b testBackend) Engine() consensus.Engine { return b.chain.Engine() }
+func (b testBackend) GetLogs(ctx context.Context, blockHash common.Hash) ([][]*types.Log, error) {
+ panic("implement me")
+}
+func (b testBackend) SubscribeRemovedLogsEvent(ch chan<- core.RemovedLogsEvent) event.Subscription {
+ panic("implement me")
+}
+func (b testBackend) SubscribeLogsEvent(ch chan<- []*types.Log) event.Subscription {
+ panic("implement me")
+}
+func (b testBackend) BloomStatus() (uint64, uint64) { panic("implement me") }
+func (b testBackend) ServiceFilter(ctx context.Context, session *bloombits.MatcherSession) {
+ panic("implement me")
+}
+
+func TestEstimateGas(t *testing.T) {
+ t.Parallel()
+ // Initialize test accounts
+ var (
+ accounts = newAccounts(3)
+ genesis = &core.Genesis{
+ Config: params.TestChainConfig,
+ Alloc: core.GenesisAlloc{
+ accounts[0].addr: {Balance: big.NewInt(params.Ether)},
+ accounts[1].addr: {Balance: big.NewInt(params.Ether), Code: append(types.DelegationPrefix, accounts[2].addr.Bytes()...)},
+ },
+ }
+ genBlocks = 10
+ signer = types.HomesteadSigner{}
+ )
+
+ api := NewPublicBlockChainAPI(newTestBackend(t, genBlocks, genesis, ethash.NewFaker(), func(i int, b *core.BlockGen) {
+ // Transfer from account[0] to account[1]
+ // value: 1000 wei
+ // fee: 0 wei
+ tx, _ := types.SignTx(types.NewTx(&types.LegacyTx{Nonce: uint64(i), To: &accounts[1].addr, Value: big.NewInt(1000), Gas: params.TxGas, GasPrice: b.BaseFee(), Data: nil}), signer, accounts[0].key)
+ b.AddTx(tx)
+ }))
+
+ setCodeAuthorization, _ := types.SignSetCode(accounts[0].key, types.SetCodeAuthorization{
+ Address: accounts[0].addr,
+ Nonce: uint64(genBlocks + 1),
+ })
+
+ var testSuite = []struct {
+ blockNumber rpc.BlockNumber
+ call TransactionArgs
+ want uint64
+ expectErr error
+ }{
+ // Should be able to send to an EIP-7702 delegated account.
+ {
+ blockNumber: rpc.LatestBlockNumber,
+ call: TransactionArgs{
+ From: &accounts[0].addr,
+ To: &accounts[1].addr,
+ Value: (*hexutil.Big)(big.NewInt(1)),
+ },
+ want: 21000,
+ },
+ // Should be able to send as EIP-7702 delegated account.
+ {
+ blockNumber: rpc.LatestBlockNumber,
+ call: TransactionArgs{
+ From: &accounts[1].addr,
+ To: &accounts[0].addr,
+ Value: (*hexutil.Big)(big.NewInt(1)),
+ },
+ want: 21000,
+ },
+ // Should be able to estimate SetCodeTx.
+ {
+ blockNumber: rpc.LatestBlockNumber,
+ call: TransactionArgs{
+ From: &accounts[0].addr,
+ To: &accounts[1].addr,
+ Value: (*hexutil.Big)(big.NewInt(0)),
+ AuthorizationList: []types.SetCodeAuthorization{setCodeAuthorization},
+ },
+ want: 46000,
+ },
+ // Should retrieve the code of 0xef0001 || accounts[0].addr and return an invalid opcode error.
+ {
+ blockNumber: rpc.LatestBlockNumber,
+ call: TransactionArgs{
+ From: &accounts[0].addr,
+ To: &accounts[0].addr,
+ Value: (*hexutil.Big)(big.NewInt(0)),
+ AuthorizationList: []types.SetCodeAuthorization{setCodeAuthorization},
+ },
+ expectErr: vm.NewErrInvalidOpCode(0xef),
+ },
+ // SetCodeTx with empty authorization list should fail.
+ {
+ blockNumber: rpc.LatestBlockNumber,
+ call: TransactionArgs{
+ From: &accounts[0].addr,
+ To: &common.Address{},
+ Value: (*hexutil.Big)(big.NewInt(0)),
+ AuthorizationList: []types.SetCodeAuthorization{},
+ },
+ expectErr: core.ErrEmptyAuthList,
+ },
+ // SetCodeTx with nil `to` should fail.
+ {
+ blockNumber: rpc.LatestBlockNumber,
+ call: TransactionArgs{
+ From: &accounts[0].addr,
+ To: nil,
+ Value: (*hexutil.Big)(big.NewInt(0)),
+ AuthorizationList: []types.SetCodeAuthorization{setCodeAuthorization},
+ },
+ expectErr: core.ErrSetCodeTxCreate,
+ },
+ }
+ for i, tc := range testSuite {
+ result, err := api.EstimateGas(context.Background(), tc.call, &rpc.BlockNumberOrHash{BlockNumber: &tc.blockNumber})
+ if tc.expectErr != nil {
+ if err == nil {
+ t.Errorf("test %d: want error %v, have nothing", i, tc.expectErr)
+ continue
+ }
+ if !errors.Is(err, tc.expectErr) {
+ if !reflect.DeepEqual(err, tc.expectErr) {
+ t.Errorf("test %d: error mismatch, want %v, have %v", i, tc.expectErr, err)
+ }
+ }
+ continue
+ }
+ if err != nil {
+ t.Errorf("test %d: want no error, have %v", i, err)
+ continue
+ }
+ if float64(result) > float64(tc.want) {
+ t.Errorf("test %d, result mismatch, have\n%v\n, want\n%v\n", i, uint64(result), tc.want)
+ }
+ }
+}
diff --git a/internal/ethapi/transaction_args.go b/internal/ethapi/transaction_args.go
index deac42152514..21a7ce782f47 100644
--- a/internal/ethapi/transaction_args.go
+++ b/internal/ethapi/transaction_args.go
@@ -23,6 +23,8 @@ import (
"fmt"
"math/big"
+ "github.com/holiman/uint256"
+
"github.com/scroll-tech/go-ethereum/common"
"github.com/scroll-tech/go-ethereum/common/hexutil"
"github.com/scroll-tech/go-ethereum/common/math"
@@ -52,6 +54,9 @@ type TransactionArgs struct {
// Introduced by AccessListTxType transaction.
AccessList *types.AccessList `json:"accessList,omitempty"`
ChainID *hexutil.Big `json:"chainId,omitempty"`
+
+ // For SetCodeTxType
+ AuthorizationList []types.SetCodeAuthorization `json:"authorizationList"`
}
// from retrieves the transaction sender address.
@@ -247,7 +252,7 @@ func (args *TransactionArgs) ToMessage(globalGasCap uint64, baseFee *big.Int) (t
if args.AccessList != nil {
accessList = *args.AccessList
}
- msg := types.NewMessage(addr, args.To, 0, value, gas, gasPrice, gasFeeCap, gasTipCap, data, accessList, true)
+ msg := types.NewMessage(addr, args.To, 0, value, gas, gasPrice, gasFeeCap, gasTipCap, data, accessList, true, args.AuthorizationList)
return msg, nil
}
@@ -256,6 +261,27 @@ func (args *TransactionArgs) ToMessage(globalGasCap uint64, baseFee *big.Int) (t
func (args *TransactionArgs) toTransaction() *types.Transaction {
var data types.TxData
switch {
+ case args.AuthorizationList != nil:
+ al := types.AccessList{}
+ if args.AccessList != nil {
+ al = *args.AccessList
+ }
+ setCodeAuthorizations := []types.SetCodeAuthorization{}
+ if args.AuthorizationList != nil {
+ setCodeAuthorizations = args.AuthorizationList
+ }
+ data = &types.SetCodeTx{
+ To: *args.To,
+ ChainID: uint256.MustFromBig(args.ChainID.ToInt()),
+ Nonce: uint64(*args.Nonce),
+ Gas: uint64(*args.Gas),
+ GasFeeCap: uint256.MustFromBig((*big.Int)(args.MaxFeePerGas)),
+ GasTipCap: uint256.MustFromBig((*big.Int)(args.MaxPriorityFeePerGas)),
+ Value: uint256.MustFromBig((*big.Int)(args.Value)),
+ Data: args.data(),
+ AccessList: al,
+ AuthList: setCodeAuthorizations,
+ }
case args.MaxFeePerGas != nil:
al := types.AccessList{}
if args.AccessList != nil {
diff --git a/les/downloader/queue_test.go b/les/downloader/queue_test.go
index 07a05dc20eda..5899f8f43742 100644
--- a/les/downloader/queue_test.go
+++ b/les/downloader/queue_test.go
@@ -47,7 +47,7 @@ func makeChain(n int, seed byte, parent *types.Block, empty bool) ([]*types.Bloc
block.SetCoinbase(common.Address{seed})
// Add one tx to every secondblock
if !empty && i%2 == 0 {
- signer := types.MakeSigner(params.TestChainConfig, block.Number())
+ signer := types.MakeSigner(params.TestChainConfig, block.Number(), block.Time())
tx, err := types.SignTx(types.NewTransaction(block.TxNonce(testAddress), common.Address{seed}, big.NewInt(1000), params.TxGas, block.BaseFee(), nil), signer, testKey)
if err != nil {
panic(err)
diff --git a/les/downloader/testchain_test.go b/les/downloader/testchain_test.go
index 8757755bab01..a3962dde1769 100644
--- a/les/downloader/testchain_test.go
+++ b/les/downloader/testchain_test.go
@@ -126,7 +126,7 @@ func (tc *testChain) generate(n int, seed byte, parent *types.Block, heavy bool)
}
// Include transactions to the miner to make blocks more interesting.
if parent == tc.genesis && i%22 == 0 {
- signer := types.MakeSigner(params.TestChainConfig, block.Number())
+ signer := types.MakeSigner(params.TestChainConfig, block.Number(), block.Time())
tx, err := types.SignTx(types.NewTransaction(block.TxNonce(testAddress), common.Address{seed}, big.NewInt(1000), params.TxGas, block.BaseFee(), nil), signer, testKey)
if err != nil {
panic(err)
diff --git a/les/fetcher/block_fetcher_test.go b/les/fetcher/block_fetcher_test.go
index 6553df3c2d28..6712c1e115a1 100644
--- a/les/fetcher/block_fetcher_test.go
+++ b/les/fetcher/block_fetcher_test.go
@@ -52,7 +52,7 @@ func makeChain(n int, seed byte, parent *types.Block) ([]common.Hash, map[common
// If the block number is multiple of 3, send a bonus transaction to the miner
if parent == genesis && i%3 == 0 {
- signer := types.MakeSigner(params.TestChainConfig, block.Number())
+ signer := types.MakeSigner(params.TestChainConfig, block.Number(), block.Time())
tx, err := types.SignTx(types.NewTransaction(block.TxNonce(testAddress), common.Address{seed}, big.NewInt(1000), params.TxGas, block.BaseFee(), nil), signer, testKey)
if err != nil {
panic(err)
diff --git a/les/odr_test.go b/les/odr_test.go
index 11f254ffd535..e5ab5410f305 100644
--- a/les/odr_test.go
+++ b/les/odr_test.go
@@ -136,7 +136,7 @@ func odrContractCall(ctx context.Context, db ethdb.Database, config *params.Chai
from := statedb.GetOrNewStateObject(bankAddr)
from.SetBalance(math.MaxBig256)
- msg := callmsg{types.NewMessage(from.Address(), &testContractAddr, 0, new(big.Int), 100000, big.NewInt(params.InitialBaseFee), big.NewInt(params.InitialBaseFee), new(big.Int), data, nil, true)}
+ msg := callmsg{types.NewMessage(from.Address(), &testContractAddr, 0, new(big.Int), 100000, big.NewInt(params.InitialBaseFee), big.NewInt(params.InitialBaseFee), new(big.Int), data, nil, true, nil)}
context := core.NewEVMBlockContext(header, bc, config, nil)
txContext := core.NewEVMTxContext(msg)
@@ -144,7 +144,7 @@ func odrContractCall(ctx context.Context, db ethdb.Database, config *params.Chai
//vmenv := core.NewEnv(statedb, config, bc, msg, header, vm.Config{})
gp := new(core.GasPool).AddGas(math.MaxUint64)
- signer := types.MakeSigner(config, header.Number)
+ signer := types.MakeSigner(config, header.Number, header.Time)
l1DataFee, _ := fees.EstimateL1DataFeeForMessage(msg, header.BaseFee, config, signer, statedb, header.Number)
result, _ := core.ApplyMessage(vmenv, msg, gp, l1DataFee)
res = append(res, result.Return()...)
@@ -153,12 +153,12 @@ func odrContractCall(ctx context.Context, db ethdb.Database, config *params.Chai
header := lc.GetHeaderByHash(bhash)
state := light.NewState(ctx, header, lc.Odr())
state.SetBalance(bankAddr, math.MaxBig256)
- msg := callmsg{types.NewMessage(bankAddr, &testContractAddr, 0, new(big.Int), 100000, big.NewInt(params.InitialBaseFee), big.NewInt(params.InitialBaseFee), new(big.Int), data, nil, true)}
+ msg := callmsg{types.NewMessage(bankAddr, &testContractAddr, 0, new(big.Int), 100000, big.NewInt(params.InitialBaseFee), big.NewInt(params.InitialBaseFee), new(big.Int), data, nil, true, nil)}
context := core.NewEVMBlockContext(header, lc, config, nil)
txContext := core.NewEVMTxContext(msg)
vmenv := vm.NewEVM(context, txContext, state, config, vm.Config{NoBaseFee: true})
gp := new(core.GasPool).AddGas(math.MaxUint64)
- signer := types.MakeSigner(config, header.Number)
+ signer := types.MakeSigner(config, header.Number, header.Time)
l1DataFee, _ := fees.EstimateL1DataFeeForMessage(msg, header.BaseFee, config, signer, state, header.Number)
result, _ := core.ApplyMessage(vmenv, msg, gp, l1DataFee)
if state.Error() == nil {
diff --git a/les/state_accessor.go b/les/state_accessor.go
index 9378aefff110..c0db2715f439 100644
--- a/les/state_accessor.go
+++ b/les/state_accessor.go
@@ -53,7 +53,7 @@ func (leth *LightEthereum) stateAtTransaction(ctx context.Context, block *types.
return nil, vm.BlockContext{}, statedb, nil
}
// Recompute transactions up to the target index.
- signer := types.MakeSigner(leth.blockchain.Config(), block.Number())
+ signer := types.MakeSigner(leth.blockchain.Config(), block.Number(), block.Time())
for idx, tx := range block.Transactions() {
// Assemble the transaction call message and return if the requested offset
msg, _ := tx.AsMessage(signer, block.BaseFee())
diff --git a/light/odr_test.go b/light/odr_test.go
index 7dd448535a97..a122207d22bc 100644
--- a/light/odr_test.go
+++ b/light/odr_test.go
@@ -195,12 +195,12 @@ func odrContractCall(ctx context.Context, db ethdb.Database, bc *core.BlockChain
// Perform read-only call.
st.SetBalance(testBankAddress, math.MaxBig256)
- msg := callmsg{types.NewMessage(testBankAddress, &testContractAddr, 0, new(big.Int), 1000000, big.NewInt(params.InitialBaseFee), big.NewInt(params.InitialBaseFee), new(big.Int), data, nil, true)}
+ msg := callmsg{types.NewMessage(testBankAddress, &testContractAddr, 0, new(big.Int), 1000000, big.NewInt(params.InitialBaseFee), big.NewInt(params.InitialBaseFee), new(big.Int), data, nil, true, nil)}
txContext := core.NewEVMTxContext(msg)
context := core.NewEVMBlockContext(header, chain, config, nil)
vmenv := vm.NewEVM(context, txContext, st, config, vm.Config{NoBaseFee: true})
gp := new(core.GasPool).AddGas(math.MaxUint64)
- signer := types.MakeSigner(config, header.Number)
+ signer := types.MakeSigner(config, header.Number, header.Time)
l1DataFee, _ := fees.EstimateL1DataFeeForMessage(msg, header.BaseFee, config, signer, st, header.Number)
result, _ := core.ApplyMessage(vmenv, msg, gp, l1DataFee)
res = append(res, result.Return()...)
diff --git a/light/odr_util.go b/light/odr_util.go
index 38af817b19b3..d843446f8fc2 100644
--- a/light/odr_util.go
+++ b/light/odr_util.go
@@ -175,7 +175,7 @@ func GetBlockReceipts(ctx context.Context, odr OdrBackend, hash common.Hash, num
genesis := rawdb.ReadCanonicalHash(odr.Database(), 0)
config := rawdb.ReadChainConfig(odr.Database(), genesis)
- if err := receipts.DeriveFields(config, block.Hash(), block.NumberU64(), block.Transactions()); err != nil {
+ if err := receipts.DeriveFields(config, block.Hash(), block.NumberU64(), block.Time(), block.Transactions()); err != nil {
return nil, err
}
rawdb.WriteReceipts(odr.Database(), hash, number, receipts)
diff --git a/light/txpool.go b/light/txpool.go
index 2858178ddfe5..ec311d927e2c 100644
--- a/light/txpool.go
+++ b/light/txpool.go
@@ -420,7 +420,7 @@ func (pool *TxPool) validateTx(ctx context.Context, tx *types.Transaction) error
}
// Should supply enough intrinsic gas
- gas, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.To() == nil, true, pool.istanbul, pool.shanghai)
+ gas, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.SetCodeAuthorizations(), tx.To() == nil, true, pool.istanbul, pool.shanghai)
if err != nil {
return err
}
diff --git a/miner/scroll_worker.go b/miner/scroll_worker.go
index db2b355738a9..bad27de84c6d 100644
--- a/miner/scroll_worker.go
+++ b/miner/scroll_worker.go
@@ -648,7 +648,7 @@ func (w *worker) processTxPool() (bool, error) {
}
}
- signer := types.MakeSigner(w.chainConfig, w.current.header.Number)
+ signer := types.MakeSigner(w.chainConfig, w.current.header.Number, w.current.header.Time)
if w.prioritizedTx != nil && w.current.header.Number.Uint64() > w.prioritizedTx.blockNumber {
w.prioritizedTx = nil
}
@@ -688,7 +688,7 @@ func (w *worker) processTxPool() (bool, error) {
// processTxnSlice
func (w *worker) processTxnSlice(txns types.Transactions) (bool, error) {
txsMap := make(map[common.Address]types.Transactions)
- signer := types.MakeSigner(w.chainConfig, w.current.header.Number)
+ signer := types.MakeSigner(w.chainConfig, w.current.header.Number, w.current.header.Time)
for _, tx := range txns {
acc, _ := types.Sender(signer, tx)
txsMap[acc] = append(txsMap[acc], tx)
diff --git a/params/config.go b/params/config.go
index befc38771401..d47286107d51 100644
--- a/params/config.go
+++ b/params/config.go
@@ -499,6 +499,8 @@ var (
CurieBlock: big.NewInt(0),
DarwinTime: new(uint64),
DarwinV2Time: new(uint64),
+ EuclidTime: new(uint64),
+ EuclidV2Time: new(uint64),
TerminalTotalDifficulty: nil,
Ethash: new(EthashConfig),
Clique: nil,
diff --git a/params/protocol_params.go b/params/protocol_params.go
index 6891b28142a5..a541c86a0f43 100644
--- a/params/protocol_params.go
+++ b/params/protocol_params.go
@@ -85,10 +85,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/rollup/ccc/async_checker_test.go b/rollup/ccc/async_checker_test.go
index 97fcda030107..1925823196ab 100644
--- a/rollup/ccc/async_checker_test.go
+++ b/rollup/ccc/async_checker_test.go
@@ -26,6 +26,8 @@ func TestAsyncChecker(t *testing.T) {
// Create a database pre-initialize with a genesis block
db := rawdb.NewMemoryDatabase()
chainConfig := params.TestChainConfig.Clone()
+ chainConfig.EuclidTime = nil
+ chainConfig.EuclidV2Time = nil
chainConfig.Scroll.UseZktrie = true
(&core.Genesis{
Config: chainConfig,
@@ -38,7 +40,7 @@ func TestAsyncChecker(t *testing.T) {
bs, _ := core.GenerateChain(chainConfig, chain.Genesis(), ethash.NewFaker(), db, 100, func(i int, block *core.BlockGen) {
for i := 0; i < 10; i++ {
- signer := types.MakeSigner(chainConfig, block.Number())
+ signer := types.MakeSigner(chainConfig, block.Number(), block.Time())
tx, err := types.SignTx(types.NewTransaction(block.TxNonce(testAddr), testAddr, big.NewInt(1000), params.TxGas, block.BaseFee(), nil), signer, testKey)
if err != nil {
panic(err)
diff --git a/rollup/ccc/logger.go b/rollup/ccc/logger.go
index e1db42b26a28..715d2381293a 100644
--- a/rollup/ccc/logger.go
+++ b/rollup/ccc/logger.go
@@ -163,7 +163,7 @@ func (l *Logger) logSha256(inputLen uint64) {
l.sha256Usage += numBlocks * blockRows
}
-func (l *Logger) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) {
+func (l *Logger) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int, authorizationResults []types.AuthorizationResult) {
l.currentEnv = env
l.isCreate = create
if !l.isCreate {
diff --git a/rollup/tracing/mux_tracer.go b/rollup/tracing/mux_tracer.go
index d71637ccc619..918d4dad13f1 100644
--- a/rollup/tracing/mux_tracer.go
+++ b/rollup/tracing/mux_tracer.go
@@ -5,6 +5,7 @@ import (
"time"
"github.com/scroll-tech/go-ethereum/common"
+ "github.com/scroll-tech/go-ethereum/core/types"
"github.com/scroll-tech/go-ethereum/core/vm"
_ "github.com/scroll-tech/go-ethereum/eth/tracers/native"
)
@@ -20,9 +21,9 @@ func NewMuxTracer(tracers ...vm.EVMLogger) *MuxTracer {
}
// CaptureStart runs CaptureStart for each tracer in the MuxTracer
-func (t *MuxTracer) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) {
+func (t *MuxTracer) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int, authorizationResults []types.AuthorizationResult) {
for _, tracer := range t.tracers {
- tracer.CaptureStart(env, from, to, create, input, gas, value)
+ tracer.CaptureStart(env, from, to, create, input, gas, value, authorizationResults)
}
}
diff --git a/rollup/tracing/tracing.go b/rollup/tracing/tracing.go
index 3cd78149241d..a7285c0f5942 100644
--- a/rollup/tracing/tracing.go
+++ b/rollup/tracing/tracing.go
@@ -105,7 +105,7 @@ func CreateTraceEnvHelper(chainConfig *params.ChainConfig, logConfig *vm.LogConf
commitAfterApply: commitAfterApply,
chainConfig: chainConfig,
coinbase: coinbase,
- signer: types.MakeSigner(chainConfig, block.Number()),
+ signer: types.MakeSigner(chainConfig, block.Number(), block.Time()),
state: statedb,
blockCtx: blockCtx,
StorageTrace: &types.StorageTrace{
@@ -525,7 +525,7 @@ func (env *TraceEnv) fillBlockTrace(block *types.Block) (*types.BlockTrace, erro
txs := make([]*types.TransactionData, block.Transactions().Len())
for i, tx := range block.Transactions() {
- txs[i] = types.NewTransactionData(tx, block.NumberU64(), env.chainConfig)
+ txs[i] = types.NewTransactionData(tx, block.NumberU64(), block.Time(), env.chainConfig)
}
intrinsicStorageProofs := map[common.Address][]common.Hash{
diff --git a/tests/gen_stauthorization.go b/tests/gen_stauthorization.go
new file mode 100644
index 000000000000..d4db7e2b1c3e
--- /dev/null
+++ b/tests/gen_stauthorization.go
@@ -0,0 +1,75 @@
+// Code generated by github.com/fjl/gencodec. DO NOT EDIT.
+
+package tests
+
+import (
+ "encoding/json"
+ "errors"
+ "math/big"
+
+ "github.com/scroll-tech/go-ethereum/common"
+ "github.com/scroll-tech/go-ethereum/common/math"
+)
+
+var _ = (*stAuthorizationMarshaling)(nil)
+
+// MarshalJSON marshals as JSON.
+func (s stAuthorization) MarshalJSON() ([]byte, error) {
+ type stAuthorization struct {
+ ChainID *math.HexOrDecimal256 `json:"chainId" gencodec:"required"`
+ 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.HexOrDecimal256)(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.HexOrDecimal256 `json:"chainId" gencodec:"required"`
+ 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 {
+ return errors.New("missing required field 'chainId' for stAuthorization")
+ }
+ s.ChainID = (*big.Int)(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 64434d89bd16..a708854fdb40 100644
--- a/tests/gen_sttransaction.go
+++ b/tests/gen_sttransaction.go
@@ -26,6 +26,7 @@ func (s stTransaction) MarshalJSON() ([]byte, error) {
GasLimit []math.HexOrDecimal64 `json:"gasLimit"`
Value []string `json:"value"`
PrivateKey hexutil.Bytes `json:"secretKey"`
+ AuthorizationList []*stAuthorization `json:"authorizationList,omitempty"`
}
var enc stTransaction
enc.GasPrice = (*math.HexOrDecimal256)(s.GasPrice)
@@ -43,6 +44,7 @@ func (s stTransaction) MarshalJSON() ([]byte, error) {
}
enc.Value = s.Value
enc.PrivateKey = s.PrivateKey
+ enc.AuthorizationList = s.AuthorizationList
return json.Marshal(&enc)
}
@@ -59,6 +61,7 @@ func (s *stTransaction) UnmarshalJSON(input []byte) error {
GasLimit []math.HexOrDecimal64 `json:"gasLimit"`
Value []string `json:"value"`
PrivateKey *hexutil.Bytes `json:"secretKey"`
+ AuthorizationList []*stAuthorization `json:"authorizationList,omitempty"`
}
var dec stTransaction
if err := json.Unmarshal(input, &dec); err != nil {
@@ -97,5 +100,8 @@ func (s *stTransaction) UnmarshalJSON(input []byte) error {
if dec.PrivateKey != nil {
s.PrivateKey = *dec.PrivateKey
}
+ if dec.AuthorizationList != nil {
+ s.AuthorizationList = dec.AuthorizationList
+ }
return nil
}
diff --git a/tests/init.go b/tests/init.go
index e373bd989b6d..f1d9fb105ebf 100644
--- a/tests/init.go
+++ b/tests/init.go
@@ -181,6 +181,7 @@ var Forks = map[string]*params.ChainConfig{
MuirGlacierBlock: big.NewInt(0),
BerlinBlock: big.NewInt(0),
LondonBlock: big.NewInt(0),
+ CurieBlock: big.NewInt(0), // EIP-1559 is enabled in Curie fork
},
"ArrowGlacier": {
ChainID: big.NewInt(1),
@@ -232,6 +233,29 @@ var Forks = map[string]*params.ChainConfig{
BernoulliBlock: big.NewInt(0),
CurieBlock: big.NewInt(0),
},
+ "EuclidV2": {
+ ChainID: big.NewInt(1),
+ HomesteadBlock: big.NewInt(0),
+ EIP150Block: big.NewInt(0),
+ EIP155Block: big.NewInt(0),
+ EIP158Block: big.NewInt(0),
+ ByzantiumBlock: big.NewInt(0),
+ ConstantinopleBlock: big.NewInt(0),
+ PetersburgBlock: big.NewInt(0),
+ IstanbulBlock: big.NewInt(0),
+ MuirGlacierBlock: big.NewInt(0),
+ BerlinBlock: big.NewInt(0),
+ LondonBlock: big.NewInt(0),
+ ArrowGlacierBlock: big.NewInt(0),
+ ArchimedesBlock: big.NewInt(0),
+ ShanghaiBlock: big.NewInt(0),
+ BernoulliBlock: big.NewInt(0),
+ CurieBlock: big.NewInt(0),
+ DarwinTime: new(uint64),
+ DarwinV2Time: new(uint64),
+ EuclidTime: new(uint64),
+ EuclidV2Time: new(uint64),
+ },
}
// Returns the set of defined fork names
diff --git a/tests/state_test_util.go b/tests/state_test_util.go
index 959c513d05c7..6cb06777e546 100644
--- a/tests/state_test_util.go
+++ b/tests/state_test_util.go
@@ -26,6 +26,8 @@ import (
"golang.org/x/crypto/sha3"
+ "github.com/holiman/uint256"
+
"github.com/scroll-tech/go-ethereum/common"
"github.com/scroll-tech/go-ethereum/common/hexutil"
"github.com/scroll-tech/go-ethereum/common/math"
@@ -111,6 +113,7 @@ type stTransaction struct {
GasLimit []uint64 `json:"gasLimit"`
Value []string `json:"value"`
PrivateKey []byte `json:"secretKey"`
+ AuthorizationList []*stAuthorization `json:"authorizationList,omitempty"`
}
type stTransactionMarshaling struct {
@@ -122,6 +125,27 @@ type stTransactionMarshaling struct {
PrivateKey hexutil.Bytes
}
+//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 *big.Int `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 stAuthorizationMarshaling struct {
+ ChainID *math.HexOrDecimal256
+ 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`,
@@ -355,8 +379,23 @@ func (tx *stTransaction) toMessage(ps stPostState, baseFee *big.Int) (core.Messa
return nil, fmt.Errorf("no gas price provided")
}
+ var authList []types.SetCodeAuthorization
+ if tx.AuthorizationList != nil {
+ authList = make([]types.SetCodeAuthorization, len(tx.AuthorizationList))
+ for i, auth := range tx.AuthorizationList {
+ authList[i] = types.SetCodeAuthorization{
+ ChainID: *uint256.MustFromBig(auth.ChainID),
+ Address: auth.Address,
+ Nonce: auth.Nonce,
+ V: auth.V,
+ R: *uint256.MustFromBig(auth.R),
+ S: *uint256.MustFromBig(auth.S),
+ }
+ }
+ }
+
msg := types.NewMessage(from, to, tx.Nonce, value, gasLimit, gasPrice,
- tx.MaxFeePerGas, tx.MaxPriorityFeePerGas, data, accessList, false)
+ tx.MaxFeePerGas, tx.MaxPriorityFeePerGas, data, accessList, false, authList)
return msg, nil
}
diff --git a/tests/transaction_test_util.go b/tests/transaction_test_util.go
index 789ba6a0be49..142f3d2b0ed7 100644
--- a/tests/transaction_test_util.go
+++ b/tests/transaction_test_util.go
@@ -55,7 +55,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.SetCodeAuthorizations(), tx.To() == nil, isHomestead, isIstanbul, false)
if err != nil {
return nil, nil, err
}