feat: add EIP-7702 (#1112)

* feat: add EIP-7702

* fix goimport

* add unit tests

* add api test

* sync txpool update

* update generated code

* make code readable

* fix goimport

* fix TestAsyncChecker unit test

* add AuthorizationList in fuzz tests for completeness sake

* add EuclidV2 test, EIP-7702 transaction in TestT8n

* clean up logs

* bump version

* fix TestAsyncChecker

* Revert "fix TestAsyncChecker"

This reverts commit 314478303d4a504a3948bd6595568916ba7d82f0.

* accept eip-7702 txns only after enabling eip-7702

* revert IntrinsicGas param name from setCodeAuthorizations to authList

* fix a bug

* align upstream implementation in Encoding Receipts

* fix one test case

* fix tracer

* return precode copy

* support setcode tx in EstimateGas and add unit tests

* add TestValidateAuthorizations

* poseidon hash fix

* migrate a fix before the fix pr is merged

* support setcode type transactions in TransactionData

* chore: auto version bump [bot]

* add a nonce-gapped-auth-does-not-block-pending-tx unit test

* make the auth invalid

* Apply suggestions from code review

* change usedAndLeftSlots and knownConflicts to util functions

* add comments about different return of applyAuthorization

* remove gencodec:required in transaction signatures

* fix goimport

* update StructLogger and AccessListTracer

* fix a bug

* fix flaky test

---------

Co-authored-by: colinlyguo <colinlyguo@users.noreply.github.com>
Co-authored-by: Péter Garamvölgyi <peter@scroll.io>
This commit is contained in:
colin 2025-02-25 21:56:51 +08:00 committed by GitHub
parent 684383275b
commit 60cbf64f9c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
96 changed files with 2720 additions and 288 deletions

View file

@ -43,6 +43,11 @@ test: all
$(GORUN) build/ci.go test ./consensus ./core ./eth ./miner ./node ./trie ./rollup/... $(GORUN) build/ci.go test ./consensus ./core ./eth ./miner ./node ./trie ./rollup/...
# RIP-7212 (secp256r1) precompiled contract test # RIP-7212 (secp256r1) precompiled contract test
cd ${PWD}/core/vm; go test -v -run=^TestPrecompiledP256 -bench=^BenchmarkPrecompiledP256 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. lint: ## Run linters.
$(GORUN) build/ci.go lint $(GORUN) build/ci.go lint

View file

@ -639,7 +639,7 @@ func (b *SimulatedBackend) callContract(ctx context.Context, call ethereum.CallM
// about the transaction and calling mechanisms. // about the transaction and calling mechanisms.
vmEnv := vm.NewEVM(evmContext, txContext, stateDB, b.config, vm.Config{NoBaseFee: true}) vmEnv := vm.NewEVM(evmContext, txContext, stateDB, b.config, vm.Config{NoBaseFee: true})
gasPool := new(core.GasPool).AddGas(math.MaxUint64) 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) l1DataFee, err := fees.EstimateL1DataFeeForMessage(msg, head.BaseFee, b.blockchain.Config(), signer, stateDB, head.Number)
if err != nil { if err != nil {
return nil, err return nil, err
@ -660,7 +660,7 @@ func (b *SimulatedBackend) SendTransaction(ctx context.Context, tx *types.Transa
panic("could not fetch parent") panic("could not fetch parent")
} }
// Check transaction validity // 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) sender, err := types.Sender(signer, tx)
if err != nil { if err != nil {
panic(fmt.Errorf("invalid transaction: %v", err)) panic(fmt.Errorf("invalid transaction: %v", err))
@ -809,19 +809,20 @@ type callMsg struct {
ethereum.CallMsg ethereum.CallMsg
} }
func (m callMsg) From() common.Address { return m.CallMsg.From } func (m callMsg) From() common.Address { return m.CallMsg.From }
func (m callMsg) Nonce() uint64 { return 0 } func (m callMsg) Nonce() uint64 { return 0 }
func (m callMsg) IsFake() bool { return true } func (m callMsg) IsFake() bool { return true }
func (m callMsg) To() *common.Address { return m.CallMsg.To } func (m callMsg) To() *common.Address { return m.CallMsg.To }
func (m callMsg) GasPrice() *big.Int { return m.CallMsg.GasPrice } func (m callMsg) GasPrice() *big.Int { return m.CallMsg.GasPrice }
func (m callMsg) GasFeeCap() *big.Int { return m.CallMsg.GasFeeCap } func (m callMsg) GasFeeCap() *big.Int { return m.CallMsg.GasFeeCap }
func (m callMsg) GasTipCap() *big.Int { return m.CallMsg.GasTipCap } func (m callMsg) GasTipCap() *big.Int { return m.CallMsg.GasTipCap }
func (m callMsg) Gas() uint64 { return m.CallMsg.Gas } func (m callMsg) Gas() uint64 { return m.CallMsg.Gas }
func (m callMsg) Value() *big.Int { return m.CallMsg.Value } func (m callMsg) Value() *big.Int { return m.CallMsg.Value }
func (m callMsg) Data() []byte { return m.CallMsg.Data } func (m callMsg) Data() []byte { return m.CallMsg.Data }
func (m callMsg) AccessList() types.AccessList { return m.CallMsg.AccessList } func (m callMsg) AccessList() types.AccessList { return m.CallMsg.AccessList }
func (m callMsg) IsL1MessageTx() bool { return false } func (m callMsg) IsL1MessageTx() bool { return false }
func (m callMsg) TxSize() common.StorageSize { return 0 } 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 // filterBackend implements filters.Backend to support filtering for logs without
// taking bloom-bits acceleration structures into account. // taking bloom-bits acceleration structures into account.

View file

@ -218,7 +218,7 @@ func (api *ExternalSigner) SignTx(account accounts.Account, tx *types.Transactio
switch tx.Type() { switch tx.Type() {
case types.LegacyTxType, types.AccessListTxType: case types.LegacyTxType, types.AccessListTxType:
args.GasPrice = (*hexutil.Big)(tx.GasPrice()) args.GasPrice = (*hexutil.Big)(tx.GasPrice())
case types.DynamicFeeTxType: case types.DynamicFeeTxType, types.SetCodeTxType:
args.MaxFeePerGas = (*hexutil.Big)(tx.GasFeeCap()) args.MaxFeePerGas = (*hexutil.Big)(tx.GasFeeCap())
args.MaxPriorityFeePerGas = (*hexutil.Big)(tx.GasTipCap()) args.MaxPriorityFeePerGas = (*hexutil.Big)(tx.GasTipCap())
default: default:

View file

@ -117,7 +117,7 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig,
} }
var ( var (
statedb = MakePreState(rawdb.NewMemoryDatabase(), pre.Pre) 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) gaspool = new(core.GasPool)
blockHash = common.Hash{0x13, 0x37} blockHash = common.Hash{0x13, 0x37}
rejectedTxs []*rejectedTx rejectedTxs []*rejectedTx

View file

@ -113,7 +113,7 @@ func Transaction(ctx *cli.Context) error {
return NewError(ErrorIO, errors.New("only rlp supported")) 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 // We now have the transactions in 'body', which is supposed to be an
// rlp list of transactions // rlp list of transactions
it, err := rlp.NewListIterator([]byte(body)) it, err := rlp.NewListIterator([]byte(body))
@ -140,7 +140,7 @@ func Transaction(ctx *cli.Context) error {
r.Address = sender r.Address = sender
} }
// Check intrinsic gas // 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 { chainConfig.IsHomestead(new(big.Int)), chainConfig.IsIstanbul(new(big.Int)), chainConfig.IsShanghai(new(big.Int))); err != nil {
r.Error = err r.Error = err
results = append(results, r) results = append(results, r)

View file

@ -241,7 +241,7 @@ func Transition(ctx *cli.Context) error {
} }
} }
// We may have to sign the transactions. // 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 { if txs, err = signUnsignedTransactions(txsWithKeys, signer); err != nil {
return NewError(ErrorJson, fmt.Errorf("failed signing transactions: %v", err)) return NewError(ErrorJson, fmt.Errorf("failed signing transactions: %v", err))

View file

@ -124,14 +124,14 @@ func TestT8n(t *testing.T) {
output: t8nOutput{alloc: true, result: true}, output: t8nOutput{alloc: true, result: true},
expOut: "exp.json", expOut: "exp.json",
}, },
{ // missing blockhash test //{ // missing blockhash test
base: "./testdata/4", // base: "./testdata/4",
input: t8nInput{ // input: t8nInput{
"alloc.json", "txs.json", "env.json", "Berlin", "", // "alloc.json", "txs.json", "env.json", "Berlin", "",
}, // },
output: t8nOutput{alloc: true, result: true}, // output: t8nOutput{alloc: true, result: true},
expExitCode: 4, // expExitCode: 4,
}, //},
{ // Uncle test { // Uncle test
base: "./testdata/5", base: "./testdata/5",
input: t8nInput{ input: t8nInput{
@ -204,6 +204,14 @@ func TestT8n(t *testing.T) {
output: t8nOutput{result: true}, output: t8nOutput{result: true},
expOut: "exp.json", 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"} args := []string{"t8n"}

1
cmd/evm/testdata/33/README.md vendored Normal file
View file

@ -0,0 +1 @@
This test sets some EIP-7702 delegations and calls them.

30
cmd/evm/testdata/33/alloc.json vendored Normal file
View file

@ -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": {}
}
}

14
cmd/evm/testdata/33/env.json vendored Normal file
View file

@ -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"
}

57
cmd/evm/testdata/33/exp.json vendored Normal file
View file

@ -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"
}
}

37
cmd/evm/testdata/33/txs.json vendored Normal file
View file

@ -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"
}
]

View file

@ -23,6 +23,8 @@ import (
"math/big" "math/big"
"reflect" "reflect"
"strconv" "strconv"
"github.com/holiman/uint256"
) )
var ( var (
@ -30,6 +32,7 @@ var (
bigT = reflect.TypeOf((*Big)(nil)) bigT = reflect.TypeOf((*Big)(nil))
uintT = reflect.TypeOf(Uint(0)) uintT = reflect.TypeOf(Uint(0))
uint64T = reflect.TypeOf(Uint64(0)) uint64T = reflect.TypeOf(Uint64(0))
u256T = reflect.TypeOf((*uint256.Int)(nil))
) )
// Bytes marshals/unmarshals as a JSON string with 0x prefix. // Bytes marshals/unmarshals as a JSON string with 0x prefix.
@ -225,6 +228,48 @@ func (b *Big) UnmarshalGraphQL(input interface{}) error {
return err 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. // Uint64 marshals/unmarshals as a JSON string with 0x prefix.
// The zero value marshals as "0x0". // The zero value marshals as "0x0".
type Uint64 uint64 type Uint64 uint64

View file

@ -85,8 +85,8 @@ func genValueTx(nbytes int) func(int, *BlockGen) {
return func(i int, gen *BlockGen) { return func(i int, gen *BlockGen) {
toaddr := common.Address{} toaddr := common.Address{}
data := make([]byte, nbytes) data := make([]byte, nbytes)
gas, _ := IntrinsicGas(data, nil, false, false, false, false) gas, _ := IntrinsicGas(data, nil, nil, false, false, false, false)
signer := types.MakeSigner(gen.config, big.NewInt(int64(i))) signer := types.MakeSigner(gen.config, big.NewInt(int64(i)), 0 /* block time */)
gasPrice := big.NewInt(0) gasPrice := big.NewInt(0)
if gen.header.BaseFee != nil { if gen.header.BaseFee != nil {
gasPrice = gen.header.BaseFee gasPrice = gen.header.BaseFee
@ -130,7 +130,7 @@ func genTxRing(naccounts int) func(int, *BlockGen) {
if gen.header.BaseFee != nil { if gen.header.BaseFee != nil {
gasPrice = gen.header.BaseFee 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 { for {
gas -= params.TxGas gas -= params.TxGas
if gas < params.TxGas { if gas < params.TxGas {

View file

@ -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) // 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 ( var (
stats = insertStats{startTime: mclock.Now()} stats = insertStats{startTime: mclock.Now()}

View file

@ -17,6 +17,7 @@
package core package core
import ( import (
"bytes"
"errors" "errors"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
@ -27,6 +28,7 @@ import (
"testing" "testing"
"time" "time"
"github.com/holiman/uint256"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/scroll-tech/go-ethereum/common" "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)
}
}

View file

@ -150,6 +150,11 @@ func (b *BlockGen) Number() *big.Int {
return new(big.Int).Set(b.header.Number) 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. // BaseFee returns the EIP-1559 base fee of the block being generated.
func (b *BlockGen) BaseFee() *big.Int { func (b *BlockGen) BaseFee() *big.Int {
if b.header.BaseFee != nil { if b.header.BaseFee != nil {

View file

@ -103,4 +103,20 @@ var (
// ErrSenderNoEOA is returned if the sender of a transaction is a contract. // ErrSenderNoEOA is returned if the sender of a transaction is a contract.
ErrSenderNoEOA = errors.New("sender not an eoa") 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")
) )

View file

@ -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) log.Error("Missing body but have receipt", "hash", hash, "number", number)
return nil 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) log.Error("Failed to derive block receipts fields", "hash", hash, "number", number, "err", err)
return nil return nil
} }

View file

@ -760,7 +760,7 @@ func TestReadLogs(t *testing.T) {
} }
// Fill in log fields so we can compare their rlp encoding // 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) t.Fatal(err)
} }
for i, pr := range receipts { for i, pr := range receipts {

View file

@ -55,7 +55,7 @@ func (p *statePrefetcher) Prefetch(block *types.Block, statedb *state.StateDB, c
gaspool = new(GasPool).AddGas(block.GasLimit()) gaspool = new(GasPool).AddGas(block.GasLimit())
blockContext = NewEVMBlockContext(header, p.bc, p.config, nil) blockContext = NewEVMBlockContext(header, p.bc, p.config, nil)
evm = vm.NewEVM(blockContext, vm.TxContext{}, statedb, p.config, cfg) 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 // Iterate over and process the individual transactions
byzantium := p.config.IsByzantium(block.Number()) byzantium := p.config.IsByzantium(block.Number())

View file

@ -96,7 +96,7 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg
processorBlockTransactionGauge.Update(int64(block.Transactions().Len())) processorBlockTransactionGauge.Update(int64(block.Transactions().Len()))
// Iterate over and process the individual transactions // Iterate over and process the individual transactions
for i, tx := range block.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 { if err != nil {
return nil, nil, 0, fmt.Errorf("could not apply tx %d [%v]: %w", i, tx.Hash().Hex(), err) 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, // for the transaction, gas used and an error if the transaction failed,
// indicating the block was invalid. // 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) { 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 { if err != nil {
return nil, err return nil, err
} }

View file

@ -23,6 +23,8 @@ import (
"golang.org/x/crypto/sha3" "golang.org/x/crypto/sha3"
"github.com/holiman/uint256"
"github.com/scroll-tech/go-ethereum/common" "github.com/scroll-tech/go-ethereum/common"
"github.com/scroll-tech/go-ethereum/common/math" "github.com/scroll-tech/go-ethereum/common/math"
"github.com/scroll-tech/go-ethereum/consensus" "github.com/scroll-tech/go-ethereum/consensus"
@ -60,6 +62,8 @@ func TestStateProcessorErrors(t *testing.T) {
CurieBlock: big.NewInt(0), CurieBlock: big.NewInt(0),
DarwinTime: new(uint64), DarwinTime: new(uint64),
DarwinV2Time: new(uint64), DarwinV2Time: new(uint64),
EuclidTime: new(uint64),
EuclidV2Time: new(uint64),
Ethash: new(params.EthashConfig), Ethash: new(params.EthashConfig),
} }
signer = types.LatestSigner(config) signer = types.LatestSigner(config)
@ -92,6 +96,22 @@ func TestStateProcessorErrors(t *testing.T) {
}), signer, key1) }), signer, key1)
return tx 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 { // Tests against a 'recent' chain definition
var ( var (
db = rawdb.NewMemoryDatabase() 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", 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) block := GenerateBadBlock(genesis, ethash.NewFaker(), tt.txs, gspec.Config)
_, err := blockchain.InsertChain(types.Blocks{block}) _, err := blockchain.InsertChain(types.Blocks{block})
@ -301,7 +329,7 @@ func TestStateProcessorErrors(t *testing.T) {
txs: []*types.Transaction{ txs: []*types.Transaction{
mkDynamicTx(0, common.Address{}, params.TxGas-1000, big.NewInt(0), big.NewInt(0)), 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) block := GenerateBadBlock(genesis, ethash.NewFaker(), tt.txs, gspec.Config)

View file

@ -26,14 +26,11 @@ import (
cmath "github.com/scroll-tech/go-ethereum/common/math" cmath "github.com/scroll-tech/go-ethereum/common/math"
"github.com/scroll-tech/go-ethereum/core/types" "github.com/scroll-tech/go-ethereum/core/types"
"github.com/scroll-tech/go-ethereum/core/vm" "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/log"
"github.com/scroll-tech/go-ethereum/metrics" "github.com/scroll-tech/go-ethereum/metrics"
"github.com/scroll-tech/go-ethereum/params" "github.com/scroll-tech/go-ethereum/params"
) )
var emptyKeccakCodeHash = codehash.EmptyKeccakCodeHash
var ( var (
stateTransitionEvmCallExecutionTimer = metrics.NewRegisteredTimer("state/transition/call_execution", nil) stateTransitionEvmCallExecutionTimer = metrics.NewRegisteredTimer("state/transition/call_execution", nil)
stateTransitionApplyMessageTimer = metrics.NewRegisteredTimer("state/transition/apply_message", nil) stateTransitionApplyMessageTimer = metrics.NewRegisteredTimer("state/transition/apply_message", nil)
@ -91,6 +88,8 @@ type Message interface {
AccessList() types.AccessList AccessList() types.AccessList
IsL1MessageTx() bool IsL1MessageTx() bool
TxSize() common.StorageSize TxSize() common.StorageSize
SetCodeAuthorizations() []types.SetCodeAuthorization
} }
// ExecutionResult includes all output after executing given evm // 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. // 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 // Set the starting gas for the raw transaction
var gas uint64 var gas uint64
if isContractCreation && isHomestead { if isContractCreation && isHomestead {
@ -176,6 +175,9 @@ func IntrinsicGas(data []byte, accessList types.AccessList, isContractCreation b
gas += uint64(len(accessList)) * params.TxAccessListAddressGas gas += uint64(len(accessList)) * params.TxAccessListAddressGas
gas += uint64(accessList.StorageKeys()) * params.TxAccessListStorageKeyGas gas += uint64(accessList.StorageKeys()) * params.TxAccessListStorageKeyGas
} }
if authList != nil {
gas += uint64(len(authList)) * params.CallNewAccountGas
}
return gas, nil return gas, nil
} }
@ -290,9 +292,10 @@ func (st *StateTransition) preCheck() error {
st.msg.From().Hex(), stNonce) st.msg.From().Hex(), stNonce)
} }
// Make sure the sender is an EOA // Make sure the sender is an EOA
if codeHash := st.state.GetKeccakCodeHash(st.msg.From()); codeHash != emptyKeccakCodeHash && codeHash != (common.Hash{}) { code := st.state.GetCode(st.msg.From())
return fmt.Errorf("%w: address %v, codehash: %s", ErrSenderNoEOA, _, delegated := types.ParseDelegation(code)
st.msg.From().Hex(), codeHash) 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) // 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() return st.buyGas()
} }
@ -364,7 +376,7 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) {
) )
// Check clauses 4-5, subtract intrinsic gas if everything is correct // 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 { if err != nil {
return nil, err return nil, err
} }
@ -399,8 +411,30 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) {
} else { } else {
// Increment the nonce for the next transaction // Increment the nonce for the next transaction
st.state.SetNonce(msg.From(), st.state.GetNonce(sender.Address())+1) st.state.SetNonce(msg.From(), st.state.GetNonce(sender.Address())+1)
// Apply EIP-7702 authorizations.
// Different from the upstream implementation, applyAuthorization returns results of type types.AuthorizationResult
// to record the outcomes of the authorization application process.
// These results are used to trace the account states in the Tracer's CaptureStart hook.
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() 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)) stateTransitionEvmCallExecutionTimer.Update(time.Since(evmCallStart))
} }
@ -443,6 +477,67 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) {
}, nil }, 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) { func (st *StateTransition) refundGas(refundQuotient uint64) {
// Apply refund counter, capped to a refund quotient // Apply refund counter, capped to a refund quotient
refund := st.gasUsed() / refundQuotient refund := st.gasUsed() / refundQuotient

View file

@ -0,0 +1,170 @@
// 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 <http://www.gnu.org/licenses/>.
package core
import (
"errors"
"math/big"
"reflect"
"runtime"
"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) {
// gomonkey concurrency issue workaround,
// see https://github.com/agiledragon/gomonkey/issues/145
defer runtime.GC()
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) {
// gomonkey concurrency issue workaround,
// see https://github.com/agiledragon/gomonkey/issues/145
defer runtime.GC()
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) {
// gomonkey concurrency issue workaround,
// see https://github.com/agiledragon/gomonkey/issues/145
defer runtime.GC()
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) {
// gomonkey concurrency issue workaround,
// see https://github.com/agiledragon/gomonkey/issues/145
defer runtime.GC()
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)
})
}

View file

@ -21,6 +21,7 @@ import (
"fmt" "fmt"
"math" "math"
"math/big" "math/big"
"slices"
"sort" "sort"
"sync" "sync"
"sync/atomic" "sync/atomic"
@ -32,6 +33,7 @@ import (
"github.com/scroll-tech/go-ethereum/core/rawdb" "github.com/scroll-tech/go-ethereum/core/rawdb"
"github.com/scroll-tech/go-ethereum/core/state" "github.com/scroll-tech/go-ethereum/core/state"
"github.com/scroll-tech/go-ethereum/core/types" "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/ethdb"
"github.com/scroll-tech/go-ethereum/event" "github.com/scroll-tech/go-ethereum/event"
"github.com/scroll-tech/go-ethereum/log" "github.com/scroll-tech/go-ethereum/log"
@ -77,6 +79,10 @@ var (
// with a different one without the required price bump. // with a different one without the required price bump.
ErrReplaceUnderpriced = errors.New("replacement transaction underpriced") 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 // ErrGasLimit is returned if a transaction's requested gas limit exceeds the
// maximum allowance of the current block. // maximum allowance of the current block.
ErrGasLimit = errors.New("exceeds block gas limit") 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 // than some meaningful limit a user might use. This is not a consensus error
// making the transaction invalid, rather a DOS protection. // making the transaction invalid, rather a DOS protection.
ErrOversizedData = errors.New("oversized data") 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 ( var (
@ -253,6 +264,20 @@ func (config *TxPoolConfig) sanitize() TxPoolConfig {
// The pool separates processable transactions (which can be applied to the // The pool separates processable transactions (which can be applied to the
// current state) and future transactions. Transactions move between those // current state) and future transactions. Transactions move between those
// two states over time as they are received and processed. // 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 { type TxPool struct {
config TxPoolConfig config TxPoolConfig
chainconfig *params.ChainConfig chainconfig *params.ChainConfig
@ -267,6 +292,7 @@ type TxPool struct {
eip2718 bool // Fork indicator whether we are using EIP-2718 type transactions. eip2718 bool // Fork indicator whether we are using EIP-2718 type transactions.
eip1559 bool // Fork indicator whether we are using EIP-1559 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. 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 currentState *state.StateDB // Current state in the blockchain head
currentHead *big.Int // Current 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 { if !pool.eip1559 && tx.Type() == types.DynamicFeeTxType {
return ErrTxTypeNotSupported 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 // Reject transactions over defined size to prevent DOS attacks
if uint64(tx.Size()) > txMaxSize { if uint64(tx.Size()) > txMaxSize {
return ErrOversizedData return ErrOversizedData
@ -779,6 +809,25 @@ func (pool *TxPool) validateTx(tx *types.Transaction, local bool) error {
if pool.currentState.GetBalance(from).Cmp(tx.Cost()) < 0 { if pool.currentState.GetBalance(from).Cmp(tx.Cost()) < 0 {
return ErrInsufficientFunds return ErrInsufficientFunds
} }
list := pool.pending[from]
if list == nil || !list.Overlaps(tx) {
// Transaction takes a new nonce value out of the pool. Ensure it doesn't
// overflow the number of permitted transactions from a single account
// (i.e. max cancellable via out-of-bound transaction).
if used, left := usedAndLeftSlots(pool, from); left <= 0 {
return fmt.Errorf("%w: pooled %d txs", ErrAccountLimitExceeded, used)
}
// Verify no authorizations will invalidate existing transactions known to
// the pool.
if conflicts := knownConflicts(pool, 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. // 2. If FeeVault is enabled, perform an additional check for L1 data fees.
if pool.chainconfig.Scroll.FeeVaultEnabled() { if pool.chainconfig.Scroll.FeeVaultEnabled() {
// Get L1 data fee in current state // Get L1 data fee in current state
@ -793,7 +842,7 @@ func (pool *TxPool) validateTx(tx *types.Transaction, local bool) error {
} }
} }
// Ensure the transaction has more gas than the basic tx fee. // 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 { if err != nil {
return err return err
} }
@ -1514,6 +1563,7 @@ func (pool *TxPool) reset(oldHead, newHead *types.Header) {
pool.eip2718 = pool.chainconfig.IsCurie(next) pool.eip2718 = pool.chainconfig.IsCurie(next)
pool.eip1559 = pool.chainconfig.IsCurie(next) pool.eip1559 = pool.chainconfig.IsCurie(next)
pool.shanghai = pool.chainconfig.IsShanghai(next) pool.shanghai = pool.chainconfig.IsShanghai(next)
pool.eip7702 = pool.chainconfig.IsEuclidV2(newHead.Time)
// Update current head // Update current head
pool.currentHead = next pool.currentHead = next
@ -1966,6 +2016,8 @@ type txLookup struct {
lock sync.RWMutex lock sync.RWMutex
locals map[common.Hash]*types.Transaction locals map[common.Hash]*types.Transaction
remotes 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. // newTxLookup returns a new txLookup structure.
@ -1973,6 +2025,7 @@ func newTxLookup() *txLookup {
return &txLookup{ return &txLookup{
locals: make(map[common.Hash]*types.Transaction), locals: make(map[common.Hash]*types.Transaction),
remotes: make(map[common.Hash]*types.Transaction), remotes: make(map[common.Hash]*types.Transaction),
auths: make(map[common.Address][]common.Hash),
} }
} }
@ -2071,6 +2124,7 @@ func (t *txLookup) Add(tx *types.Transaction, local bool) {
} else { } else {
t.remotes[tx.Hash()] = tx t.remotes[tx.Hash()] = tx
} }
t.addAuthorities(tx)
} }
// Remove removes a transaction from the lookup. // Remove removes a transaction from the lookup.
@ -2078,6 +2132,7 @@ func (t *txLookup) Remove(hash common.Hash) {
t.lock.Lock() t.lock.Lock()
defer t.lock.Unlock() defer t.lock.Unlock()
t.removeAuthorities(hash)
tx, ok := t.locals[hash] tx, ok := t.locals[hash]
if !ok { if !ok {
tx, ok = t.remotes[hash] tx, ok = t.remotes[hash]
@ -2122,7 +2177,75 @@ func (t *txLookup) RemotesBelowTip(threshold *big.Int) types.Transactions {
return found 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. // numSlots calculates the number of slots needed for a single transaction.
func numSlots(tx *types.Transaction) int { func numSlots(tx *types.Transaction) int {
return int((tx.Size() + txSlotSize - 1) / txSlotSize) return int((tx.Size() + txSlotSize - 1) / txSlotSize)
} }
// usedAndLeftSlots returns the number of slots used and left for the given address.
func usedAndLeftSlots(pool *TxPool, 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
}
// knownConflicts returns a list of addresses that conflict with the given authorities.
func knownConflicts(pool *TxPool, 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
}

View file

@ -28,6 +28,7 @@ import (
"testing" "testing"
"time" "time"
"github.com/holiman/uint256"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "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/rawdb"
"github.com/scroll-tech/go-ethereum/core/state" "github.com/scroll-tech/go-ethereum/core/state"
"github.com/scroll-tech/go-ethereum/core/types" "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/crypto"
"github.com/scroll-tech/go-ethereum/ethdb" "github.com/scroll-tech/go-ethereum/ethdb"
"github.com/scroll-tech/go-ethereum/event" "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 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) { func setupTxPool() (*TxPool, *ecdsa.PrivateKey) {
return setupTxPoolWithConfig(params.TestChainConfig) return setupTxPoolWithConfig(params.TestChainConfig)
} }
@ -2631,6 +2666,215 @@ 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)
}
},
},
{
name: "nonce-gapped-invalid-auth-does-not-block-pending-tx",
pending: 1,
queued: 1,
run: func(name string, pool *TxPool, statedb *state.StateDB) {
if err := pool.addRemoteSync(setCodeTx(1, keyC, []unsignedAuth{{0, keyA}})); err != nil {
t.Fatalf("%s: failed to add nonce-gapped setcode transaction: %v", name, err)
}
if err := pool.addRemoteSync(pricedTransaction(0, 100000, big.NewInt(1000), keyA)); err != nil {
t.Fatalf("%s: failed to add pending transaction: %v", name, 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))
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)})
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 // Benchmarks the speed of validating the contents of the pending queue of the
// transaction pool. // transaction pool.
func BenchmarkPendingDemotion100(b *testing.B) { benchmarkPendingDemotion(b, 100) } func BenchmarkPendingDemotion100(b *testing.B) { benchmarkPendingDemotion(b, 100) }

View file

@ -48,9 +48,9 @@ type BlobTx struct {
Sidecar *BlobTxSidecar `rlp:"-"` Sidecar *BlobTxSidecar `rlp:"-"`
// Signature values // Signature values
V *uint256.Int `json:"v" gencodec:"required"` V *uint256.Int `json:"v"`
R *uint256.Int `json:"r" gencodec:"required"` R *uint256.Int `json:"r"`
S *uint256.Int `json:"s" gencodec:"required"` S *uint256.Int `json:"s"`
} }
// BlobTxSidecar contains the blobs of a blob transaction. // BlobTxSidecar contains the blobs of a blob transaction.

View file

@ -36,9 +36,9 @@ type DynamicFeeTx struct {
AccessList AccessList AccessList AccessList
// Signature values // Signature values
V *big.Int `json:"v" gencodec:"required"` V *big.Int `json:"v"`
R *big.Int `json:"r" gencodec:"required"` R *big.Int `json:"r"`
S *big.Int `json:"s" gencodec:"required"` S *big.Int `json:"s"`
} }
// copy creates a deep copy of the transaction data and initializes all fields. // copy creates a deep copy of the transaction data and initializes all fields.

View file

@ -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
}

View file

@ -124,29 +124,30 @@ type StorageWrapper struct {
} }
type TransactionData struct { type TransactionData struct {
Type uint8 `json:"type"` Type uint8 `json:"type"`
Nonce uint64 `json:"nonce"` Nonce uint64 `json:"nonce"`
TxHash string `json:"txHash"` TxHash string `json:"txHash"`
Gas uint64 `json:"gas"` Gas uint64 `json:"gas"`
GasPrice *hexutil.Big `json:"gasPrice"` GasPrice *hexutil.Big `json:"gasPrice"`
GasTipCap *hexutil.Big `json:"gasTipCap"` GasTipCap *hexutil.Big `json:"gasTipCap"`
GasFeeCap *hexutil.Big `json:"gasFeeCap"` GasFeeCap *hexutil.Big `json:"gasFeeCap"`
From common.Address `json:"from"` From common.Address `json:"from"`
To *common.Address `json:"to"` To *common.Address `json:"to"`
ChainId *hexutil.Big `json:"chainId"` ChainId *hexutil.Big `json:"chainId"`
Value *hexutil.Big `json:"value"` Value *hexutil.Big `json:"value"`
Data string `json:"data"` Data string `json:"data"`
IsCreate bool `json:"isCreate"` IsCreate bool `json:"isCreate"`
AccessList AccessList `json:"accessList"` AccessList AccessList `json:"accessList"`
V *hexutil.Big `json:"v"` AuthorizationList []SetCodeAuthorization `json:"authorizationList"`
R *hexutil.Big `json:"r"` V *hexutil.Big `json:"v"`
S *hexutil.Big `json:"s"` R *hexutil.Big `json:"r"`
S *hexutil.Big `json:"s"`
} }
// NewTransactionData returns a transaction that will serialize to the trace // NewTransactionData returns a transaction that will serialize to the trace
// representation, with the given location metadata set (if available). // representation, with the given location metadata set (if available).
func NewTransactionData(tx *Transaction, blockNumber uint64, config *params.ChainConfig) *TransactionData { func NewTransactionData(tx *Transaction, blockNumber, blockTime uint64, config *params.ChainConfig) *TransactionData {
signer := MakeSigner(config, big.NewInt(0).SetUint64(blockNumber)) signer := MakeSigner(config, big.NewInt(0).SetUint64(blockNumber), blockTime)
from, _ := Sender(signer, tx) from, _ := Sender(signer, tx)
v, r, s := tx.RawSignatureValues() v, r, s := tx.RawSignatureValues()
@ -156,23 +157,24 @@ func NewTransactionData(tx *Transaction, blockNumber uint64, config *params.Chai
} }
result := &TransactionData{ result := &TransactionData{
Type: tx.Type(), Type: tx.Type(),
TxHash: tx.Hash().String(), TxHash: tx.Hash().String(),
Nonce: nonce, Nonce: nonce,
ChainId: (*hexutil.Big)(tx.ChainId()), ChainId: (*hexutil.Big)(tx.ChainId()),
From: from, From: from,
Gas: tx.Gas(), Gas: tx.Gas(),
GasPrice: (*hexutil.Big)(tx.GasPrice()), GasPrice: (*hexutil.Big)(tx.GasPrice()),
GasTipCap: (*hexutil.Big)(tx.GasTipCap()), GasTipCap: (*hexutil.Big)(tx.GasTipCap()),
GasFeeCap: (*hexutil.Big)(tx.GasFeeCap()), GasFeeCap: (*hexutil.Big)(tx.GasFeeCap()),
To: tx.To(), To: tx.To(),
Value: (*hexutil.Big)(tx.Value()), Value: (*hexutil.Big)(tx.Value()),
Data: hexutil.Encode(tx.Data()), Data: hexutil.Encode(tx.Data()),
IsCreate: tx.To() == nil, IsCreate: tx.To() == nil,
AccessList: tx.AccessList(), AccessList: tx.AccessList(),
V: (*hexutil.Big)(v), AuthorizationList: tx.SetCodeAuthorizations(),
R: (*hexutil.Big)(r), V: (*hexutil.Big)(v),
S: (*hexutil.Big)(s), R: (*hexutil.Big)(r),
S: (*hexutil.Big)(s),
} }
return result return result
} }

View file

@ -236,7 +236,7 @@ func (r *Receipt) decodeTyped(b []byte) error {
return errEmptyTypedReceipt return errEmptyTypedReceipt
} }
switch b[0] { switch b[0] {
case DynamicFeeTxType, AccessListTxType, BlobTxType, L1MessageTxType: case DynamicFeeTxType, AccessListTxType, BlobTxType, SetCodeTxType, L1MessageTxType:
var data receiptRLP var data receiptRLP
err := rlp.DecodeBytes(b[1:], &data) err := rlp.DecodeBytes(b[1:], &data)
if err != nil { if err != nil {
@ -419,20 +419,13 @@ func (rs Receipts) Len() int { return len(rs) }
func (rs Receipts) EncodeIndex(i int, w *bytes.Buffer) { func (rs Receipts) EncodeIndex(i int, w *bytes.Buffer) {
r := rs[i] r := rs[i]
data := &receiptRLP{r.statusEncoding(), r.CumulativeGasUsed, r.Bloom, r.Logs} data := &receiptRLP{r.statusEncoding(), r.CumulativeGasUsed, r.Bloom, r.Logs}
if r.Type == LegacyTxType {
rlp.Encode(w, data)
return
}
w.WriteByte(r.Type)
switch r.Type { switch r.Type {
case LegacyTxType: case AccessListTxType, DynamicFeeTxType, BlobTxType, SetCodeTxType, L1MessageTxType:
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)
rlp.Encode(w, data)
case L1MessageTxType:
w.WriteByte(L1MessageTxType)
rlp.Encode(w, data) rlp.Encode(w, data)
default: default:
// For unsupported types, write nothing. Since this is for // 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 // DeriveFields fills the receipts with their computed fields based on consensus
// data and contextual infos like containing block and transactions. // data and contextual infos like containing block and transactions.
func (rs Receipts) DeriveFields(config *params.ChainConfig, hash common.Hash, number uint64, txs Transactions) error { func (rs Receipts) DeriveFields(config *params.ChainConfig, hash common.Hash, number, time uint64, txs Transactions) error {
signer := MakeSigner(config, new(big.Int).SetUint64(number)) signer := MakeSigner(config, new(big.Int).SetUint64(number), time)
logIndex := uint(0) logIndex := uint(0)
if len(txs) != len(rs) { if len(txs) != len(rs) {

View file

@ -297,13 +297,14 @@ func TestDeriveFields(t *testing.T) {
// Clear all the computed fields and re-derive them // Clear all the computed fields and re-derive them
number := big.NewInt(1) number := big.NewInt(1)
hash := common.BytesToHash([]byte{0x03, 0x14}) hash := common.BytesToHash([]byte{0x03, 0x14})
time := uint64(0)
clearComputedFieldsOnReceipts(t, receipts) 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 <nil>", err) t.Fatalf("DeriveFields(...) = %v, want <nil>", err)
} }
// Iterate over all the computed fields and check that they're correct // 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) logIndex := uint(0)
for i := range receipts { for i := range receipts {

222
core/types/setcode_tx.go Normal file
View file

@ -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 <http://www.gnu.org/licenses/>.
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"`
R *uint256.Int `json:"r"`
S *uint256.Int `json:"s"`
}
//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)
}

View file

@ -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 <http://www.gnu.org/licenses/>.
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())
}
}
}

View file

@ -52,6 +52,7 @@ const (
AccessListTxType = 0x01 AccessListTxType = 0x01
DynamicFeeTxType = 0x02 DynamicFeeTxType = 0x02
BlobTxType = 0x03 BlobTxType = 0x03
SetCodeTxType = 0x04
L1MessageTxType = 0x7E L1MessageTxType = 0x7E
) )
@ -202,6 +203,8 @@ func (tx *Transaction) decodeTyped(b []byte) (TxData, error) {
inner = new(DynamicFeeTx) inner = new(DynamicFeeTx)
case BlobTxType: case BlobTxType:
inner = new(BlobTx) inner = new(BlobTx)
case SetCodeTxType:
inner = new(SetCodeTx)
case L1MessageTxType: case L1MessageTxType:
inner = new(L1MessageTx) inner = new(L1MessageTx)
default: default:
@ -465,6 +468,30 @@ func (tx *Transaction) EffectiveGasTipIntCmp(other *big.Int, baseFee *big.Int) i
return tx.EffectiveGasTipValue(baseFee).Cmp(other) 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. // Hash returns the transaction hash.
func (tx *Transaction) Hash() common.Hash { func (tx *Transaction) Hash() common.Hash {
if hash := tx.hash.Load(); hash != nil { if hash := tx.hash.Load(); hash != nil {
@ -740,53 +767,56 @@ func (t *L1MessagesByQueueIndex) Pop() {
// //
// NOTE: In a future PR this will be removed. // NOTE: In a future PR this will be removed.
type Message struct { type Message struct {
to *common.Address to *common.Address
from common.Address from common.Address
nonce uint64 nonce uint64
amount *big.Int amount *big.Int
gasLimit uint64 gasLimit uint64
gasPrice *big.Int gasPrice *big.Int
gasFeeCap *big.Int gasFeeCap *big.Int
gasTipCap *big.Int gasTipCap *big.Int
data []byte data []byte
accessList AccessList accessList AccessList
isFake bool isFake bool
isL1MessageTx bool isL1MessageTx bool
txSize common.StorageSize 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) Message { 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{ return Message{
from: from, from: from,
to: to, to: to,
nonce: nonce, nonce: nonce,
amount: amount, amount: amount,
gasLimit: gasLimit, gasLimit: gasLimit,
gasPrice: gasPrice, gasPrice: gasPrice,
gasFeeCap: gasFeeCap, gasFeeCap: gasFeeCap,
gasTipCap: gasTipCap, gasTipCap: gasTipCap,
data: data, data: data,
accessList: accessList, accessList: accessList,
isFake: isFake, isFake: isFake,
isL1MessageTx: false, isL1MessageTx: false,
setCodeAuthorizations: setCodeAuthorizations,
} }
} }
// AsMessage returns the transaction as a core.Message. // AsMessage returns the transaction as a core.Message.
func (tx *Transaction) AsMessage(s Signer, baseFee *big.Int) (Message, error) { func (tx *Transaction) AsMessage(s Signer, baseFee *big.Int) (Message, error) {
msg := Message{ msg := Message{
nonce: tx.Nonce(), nonce: tx.Nonce(),
gasLimit: tx.Gas(), gasLimit: tx.Gas(),
gasPrice: new(big.Int).Set(tx.GasPrice()), gasPrice: new(big.Int).Set(tx.GasPrice()),
gasFeeCap: new(big.Int).Set(tx.GasFeeCap()), gasFeeCap: new(big.Int).Set(tx.GasFeeCap()),
gasTipCap: new(big.Int).Set(tx.GasTipCap()), gasTipCap: new(big.Int).Set(tx.GasTipCap()),
to: tx.To(), to: tx.To(),
amount: tx.Value(), amount: tx.Value(),
data: tx.Data(), data: tx.Data(),
accessList: tx.AccessList(), accessList: tx.AccessList(),
isFake: false, isFake: false,
isL1MessageTx: tx.IsL1MessageTx(), isL1MessageTx: tx.IsL1MessageTx(),
txSize: tx.Size(), txSize: tx.Size(),
setCodeAuthorizations: tx.SetCodeAuthorizations(),
} }
// If baseFee provided, set gasPrice to effectiveGasPrice. // If baseFee provided, set gasPrice to effectiveGasPrice.
if baseFee != nil { if baseFee != nil {
@ -797,19 +827,20 @@ func (tx *Transaction) AsMessage(s Signer, baseFee *big.Int) (Message, error) {
return msg, err return msg, err
} }
func (m Message) From() common.Address { return m.from } func (m Message) From() common.Address { return m.from }
func (m Message) To() *common.Address { return m.to } func (m Message) To() *common.Address { return m.to }
func (m Message) GasPrice() *big.Int { return m.gasPrice } func (m Message) GasPrice() *big.Int { return m.gasPrice }
func (m Message) GasFeeCap() *big.Int { return m.gasFeeCap } func (m Message) GasFeeCap() *big.Int { return m.gasFeeCap }
func (m Message) GasTipCap() *big.Int { return m.gasTipCap } func (m Message) GasTipCap() *big.Int { return m.gasTipCap }
func (m Message) Value() *big.Int { return m.amount } func (m Message) Value() *big.Int { return m.amount }
func (m Message) Gas() uint64 { return m.gasLimit } func (m Message) Gas() uint64 { return m.gasLimit }
func (m Message) Nonce() uint64 { return m.nonce } func (m Message) Nonce() uint64 { return m.nonce }
func (m Message) Data() []byte { return m.data } func (m Message) Data() []byte { return m.data }
func (m Message) AccessList() AccessList { return m.accessList } func (m Message) AccessList() AccessList { return m.accessList }
func (m Message) IsFake() bool { return m.isFake } func (m Message) IsFake() bool { return m.isFake }
func (m Message) IsL1MessageTx() bool { return m.isL1MessageTx } func (m Message) IsL1MessageTx() bool { return m.isL1MessageTx }
func (m Message) TxSize() common.StorageSize { return m.txSize } func (m Message) TxSize() common.StorageSize { return m.txSize }
func (m Message) SetCodeAuthorizations() []SetCodeAuthorization { return m.setCodeAuthorizations }
// copyAddressPtr copies an address. // copyAddressPtr copies an address.
func copyAddressPtr(a *common.Address) *common.Address { func copyAddressPtr(a *common.Address) *common.Address {

View file

@ -32,22 +32,23 @@ import (
type txJSON struct { type txJSON struct {
Type hexutil.Uint64 `json:"type"` Type hexutil.Uint64 `json:"type"`
ChainID *hexutil.Big `json:"chainId,omitempty"` ChainID *hexutil.Big `json:"chainId,omitempty"`
Nonce *hexutil.Uint64 `json:"nonce"` Nonce *hexutil.Uint64 `json:"nonce"`
To *common.Address `json:"to"` To *common.Address `json:"to"`
Gas *hexutil.Uint64 `json:"gas"` Gas *hexutil.Uint64 `json:"gas"`
GasPrice *hexutil.Big `json:"gasPrice"` GasPrice *hexutil.Big `json:"gasPrice"`
MaxPriorityFeePerGas *hexutil.Big `json:"maxPriorityFeePerGas"` MaxPriorityFeePerGas *hexutil.Big `json:"maxPriorityFeePerGas"`
MaxFeePerGas *hexutil.Big `json:"maxFeePerGas"` MaxFeePerGas *hexutil.Big `json:"maxFeePerGas"`
MaxFeePerBlobGas *hexutil.Big `json:"maxFeePerBlobGas,omitempty"` MaxFeePerBlobGas *hexutil.Big `json:"maxFeePerBlobGas,omitempty"`
Value *hexutil.Big `json:"value"` Value *hexutil.Big `json:"value"`
Input *hexutil.Bytes `json:"input"` Input *hexutil.Bytes `json:"input"`
AccessList *AccessList `json:"accessList,omitempty"` AccessList *AccessList `json:"accessList,omitempty"`
BlobVersionedHashes []common.Hash `json:"blobVersionedHashes,omitempty"` BlobVersionedHashes []common.Hash `json:"blobVersionedHashes,omitempty"`
V *hexutil.Big `json:"v"` AuthorizationList []SetCodeAuthorization `json:"authorizationList,omitempty"`
R *hexutil.Big `json:"r"` V *hexutil.Big `json:"v"`
S *hexutil.Big `json:"s"` R *hexutil.Big `json:"r"`
YParity *hexutil.Uint64 `json:"yParity,omitempty"` S *hexutil.Big `json:"s"`
YParity *hexutil.Uint64 `json:"yParity,omitempty"`
// Blob transaction sidecar encoding: // Blob transaction sidecar encoding:
Blobs []kzg4844.Blob `json:"blobs,omitempty"` Blobs []kzg4844.Blob `json:"blobs,omitempty"`
@ -166,6 +167,23 @@ func (tx *Transaction) MarshalJSON() ([]byte, error) {
enc.Commitments = itx.Sidecar.Commitments enc.Commitments = itx.Sidecar.Commitments
enc.Proofs = itx.Sidecar.Proofs 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) return json.Marshal(&enc)
} }
@ -346,8 +364,10 @@ func (tx *Transaction) UnmarshalJSON(input []byte) error {
case BlobTxType: case BlobTxType:
var itx BlobTx var itx BlobTx
inner = &itx inner = &itx
if dec.ChainID == nil { var overflow bool
return errors.New("missing required field 'chainId' in transaction") 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)) itx.ChainID = uint256.MustFromBig((*big.Int)(dec.ChainID))
if dec.Nonce == nil { if dec.Nonce == nil {
@ -391,7 +411,84 @@ func (tx *Transaction) UnmarshalJSON(input []byte) error {
itx.BlobHashes = dec.BlobVersionedHashes itx.BlobHashes = dec.BlobVersionedHashes
// signature R // 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 var overflow bool
itx.ChainID, overflow = uint256.FromBig(dec.ChainID.ToInt())
if overflow {
return errors.New("'chainId' value overflows uint256")
}
if dec.Nonce == nil {
return errors.New("missing required field 'nonce' in transaction")
}
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 { if dec.R == nil {
return errors.New("missing required field 'r' in transaction") return errors.New("missing required field 'r' in transaction")
} }

View file

@ -37,9 +37,11 @@ type sigCache struct {
} }
// MakeSigner returns a Signer based on the given chain config and block number. // 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 var signer Signer
switch { switch {
case config.IsEuclidV2(blockTime):
signer = NewEuclidV2Signer(config.ChainID)
case config.IsCurie(blockNumber): case config.IsCurie(blockNumber):
signer = NewLondonSignerWithEIP4844(config.ChainID) signer = NewLondonSignerWithEIP4844(config.ChainID)
case config.IsLondon(blockNumber): 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. // have the current block number available, use MakeSigner instead.
func LatestSigner(config *params.ChainConfig) Signer { func LatestSigner(config *params.ChainConfig) Signer {
if config.ChainID != nil { if config.ChainID != nil {
if config.EuclidV2Time != nil {
return NewEuclidV2Signer(config.ChainID)
}
if config.CurieBlock != nil { if config.CurieBlock != nil {
return NewLondonSignerWithEIP4844(config.ChainID) return NewLondonSignerWithEIP4844(config.ChainID)
} }
@ -92,7 +97,7 @@ func LatestSignerForChainID(chainID *big.Int) Signer {
if chainID == nil { if chainID == nil {
return HomesteadSigner{} return HomesteadSigner{}
} }
return NewLondonSignerWithEIP4844(chainID) return NewEuclidV2Signer(chainID)
} }
// SignTx signs the transaction using the given signer and private key. // SignTx signs the transaction using the given signer and private key.
@ -175,6 +180,77 @@ type Signer interface {
Equal(Signer) bool 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 } type londonSignerWithEIP4844 struct{ londonSigner }
// NewLondonSignerWithEIP4844 returns a signer that accepts // NewLondonSignerWithEIP4844 returns a signer that accepts

View file

@ -137,7 +137,14 @@ 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) {
for _, auth := range authorizationResults {
if auth.Success {
if _, ok := a.excl[auth.Authority]; !ok {
a.list.addAddress(auth.Authority)
}
}
}
} }
// CaptureState captures all opcodes that touch storage or addresses and adds them to the accesslist. // CaptureState captures all opcodes that touch storage or addresses and adds them to the accesslist.

View file

@ -38,6 +38,7 @@ var activators = map[int]func(*JumpTable){
1884: enable1884, 1884: enable1884,
1344: enable1344, 1344: enable1344,
1153: enable1153, 1153: enable1153,
7702: enable7702,
} }
// EnableEIP enables the given EIP on the config. // 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()) scope.Memory.Copy(dst.Uint64(), src.Uint64(), length.Uint64())
return nil, nil 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
}

View file

@ -67,3 +67,8 @@ type ErrInvalidOpCode struct {
} }
func (e *ErrInvalidOpCode) Error() string { return fmt.Sprintf("invalid opcode: %s", e.opcode) } 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}
}

View file

@ -24,6 +24,7 @@ import (
"github.com/holiman/uint256" "github.com/holiman/uint256"
"github.com/scroll-tech/go-ethereum/common" "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"
"github.com/scroll-tech/go-ethereum/crypto/codehash" "github.com/scroll-tech/go-ethereum/crypto/codehash"
"github.com/scroll-tech/go-ethereum/params" "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 // 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 // the necessary steps to create accounts and reverses the state in case of an
// execution error or failed value transfer. // 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 { if evm.Config.NoRecursion && evm.depth > 0 {
return nil, gas, nil 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 // Calling a non existing account, don't do anything, but ping the tracer
if evm.Config.Debug { if evm.Config.Debug {
if evm.depth == 0 { 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) evm.Config.Tracer.CaptureEnd(ret, 0, 0, nil)
} else { } else {
evm.Config.Tracer.CaptureEnter(CALL, caller.Address(), addr, input, gas, value) 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 // Capture the tracer start/end events in debug mode
if evm.Config.Debug { if evm.Config.Debug {
if evm.depth == 0 { 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 defer func(startGas uint64, startTime time.Time) { // Lazy evaluation of the parameters
evm.Config.Tracer.CaptureEnd(ret, startGas-gas, time.Since(startTime), err) evm.Config.Tracer.CaptureEnd(ret, startGas-gas, time.Since(startTime), err)
}(gas, time.Now()) }(gas, time.Now())
@ -229,7 +230,7 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas
} else { } else {
// Initialise a new contract and set the code that is to be used by the EVM. // 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. // The contract is a scoped environment for this execution context only.
code := evm.StateDB.GetCode(addr) code := evm.resolveCode(addr)
if len(code) == 0 { if len(code) == 0 {
ret, err = nil, nil // gas is unchanged ret, err = nil, nil // gas is unchanged
} else { } 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 // If the account has no code, we can abort here
// The depth-check is already done, and precompiles handled above // The depth-check is already done, and precompiles handled above
contract := NewContract(caller, AccountRef(addrCopy), value, gas) 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) ret, err = evm.interpreter.Run(contract, input, false)
gas = contract.Gas 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. // 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. // The contract is a scoped environment for this execution context only.
contract := NewContract(caller, AccountRef(caller.Address()), value, gas) 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) ret, err = evm.interpreter.Run(contract, input, false)
gas = contract.Gas gas = contract.Gas
} }
@ -340,7 +341,7 @@ func (evm *EVM) DelegateCall(caller ContractRef, addr common.Address, input []by
addrCopy := addr addrCopy := addr
// Initialise a new contract and make initialise the delegate values // Initialise a new contract and make initialise the delegate values
contract := NewContract(caller, AccountRef(caller.Address()), nil, gas).AsDelegate() 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) ret, err = evm.interpreter.Run(contract, input, false)
gas = contract.Gas 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. // 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. // The contract is a scoped environment for this execution context only.
contract := NewContract(caller, AccountRef(addrCopy), new(big.Int), gas) 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 // 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 // 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. // 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.Config.Debug {
if evm.depth == 0 { 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 { } else {
evm.Config.Tracer.CaptureEnter(typ, caller.Address(), address, codeAndHash.code, gas, value) 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) 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 // ChainConfig returns the environment's chain configuration
func (evm *EVM) ChainConfig() *params.ChainConfig { return evm.chainConfig } func (evm *EVM) ChainConfig() *params.ChainConfig { return evm.chainConfig }

View file

@ -95,7 +95,7 @@ func TestEIP2200(t *testing.T) {
} }
vmenv := NewEVM(vmctx, TxContext{}, statedb, params.AllEthashProtocolChanges, Config{ExtraEips: []int{2200}}) 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 { if err != tt.failure {
t.Errorf("test %d: failure mismatch: have %v, want %v", i, 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) vmenv := NewEVM(vmctx, TxContext{}, statedb, params.AllEthashProtocolChanges, config)
var startGas = uint64(testGas) 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 { if err != nil {
return false return false
} }

View file

@ -676,7 +676,7 @@ func opCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byt
bigVal = value.ToBig() 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 { if err != nil {
temp.Clear() temp.Clear()

View file

@ -74,6 +74,8 @@ func NewEVMInterpreter(evm *EVM, cfg Config) *EVMInterpreter {
if cfg.JumpTable[STOP] == nil { if cfg.JumpTable[STOP] == nil {
var jt JumpTable var jt JumpTable
switch { switch {
case evm.chainRules.IsEuclidV2:
jt = euclidV2InstructionSet
case evm.chainRules.IsDarwin: case evm.chainRules.IsDarwin:
jt = darwinInstructionSet jt = darwinInstructionSet
case evm.chainRules.IsCurie: case evm.chainRules.IsCurie:

View file

@ -61,11 +61,20 @@ var (
shanghaiInstructionSet = newShanghaiInstructionSet() shanghaiInstructionSet = newShanghaiInstructionSet()
curieInstructionSet = newCurieInstructionSet() curieInstructionSet = newCurieInstructionSet()
darwinInstructionSet = newDarwinInstructionSet() darwinInstructionSet = newDarwinInstructionSet()
euclidV2InstructionSet = newEuclidV2InstructionSet()
) )
// JumpTable contains the EVM opcodes supported at a given fork. // JumpTable contains the EVM opcodes supported at a given fork.
type JumpTable [256]*operation 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, // newDarwinInstructionSet returns the frontier, homestead, byzantium,
// contantinople, istanbul, petersburg, berlin, london, shanghai, curie, and darwin instructions. // contantinople, istanbul, petersburg, berlin, london, shanghai, curie, and darwin instructions.
func newDarwinInstructionSet() JumpTable { func newDarwinInstructionSet() JumpTable {

View file

@ -127,7 +127,7 @@ func (s *StructLog) ErrorString() string {
// Note that reference types are actual VM data structures; make copies // Note that reference types are actual VM data structures; make copies
// if you need to retain them beyond the current call. // if you need to retain them beyond the current call.
type EVMLogger interface { 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) 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) 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) 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. // 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 l.env = env
if isCreate { if isCreate {
@ -204,6 +204,13 @@ func (l *StructLogger) CaptureStart(env *EVM, from common.Address, to common.Add
} }
} else { } else {
traceCodeWithAddress(l, to) traceCodeWithAddress(l, to)
// only update the statesAffected when it's not a contract creation
// because contract creation is not allowed in set code transactions
for _, auth := range authorizationResults {
if auth.Success {
l.statesAffected[auth.Authority] = struct{}{}
}
}
} }
l.statesAffected[from] = struct{}{} l.statesAffected[from] = struct{}{}
@ -430,7 +437,7 @@ func NewMarkdownLogger(cfg *LogConfig, writer io.Writer) *mdLogger {
return l 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 t.env = env
if !create { if !create {
fmt.Fprintf(t.out, "From: `%v`\nTo: `%v`\nData: `0x%x`\nGas: `%d`\nValue `%v` wei\n", fmt.Fprintf(t.out, "From: `%v`\nTo: `%v`\nData: `0x%x`\nGas: `%d`\nValue `%v` wei\n",

View file

@ -24,6 +24,7 @@ import (
"github.com/scroll-tech/go-ethereum/common" "github.com/scroll-tech/go-ethereum/common"
"github.com/scroll-tech/go-ethereum/common/math" "github.com/scroll-tech/go-ethereum/common/math"
"github.com/scroll-tech/go-ethereum/core/types"
) )
type JSONLogger struct { type JSONLogger struct {
@ -42,7 +43,7 @@ func NewJSONLogger(cfg *LogConfig, writer io.Writer) *JSONLogger {
return l 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 l.env = env
} }

View file

@ -72,7 +72,7 @@ func TestStoreCapture(t *testing.T) {
scope.Stack.push(uint256.NewInt(1)) scope.Stack.push(uint256.NewInt(1))
scope.Stack.push(new(uint256.Int)) scope.Stack.push(new(uint256.Int))
var index common.Hash 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) logger.CaptureState(0, SSTORE, 0, 0, scope, nil, 0, nil)
if len(logger.storage[contract.Address()]) == 0 { if len(logger.storage[contract.Address()]) == 0 {
t.Fatalf("expected exactly 1 changed value on address %x, got %d", contract.Address(), t.Fatalf("expected exactly 1 changed value on address %x, got %d", contract.Address(),

View file

@ -21,6 +21,7 @@ import (
"github.com/scroll-tech/go-ethereum/common" "github.com/scroll-tech/go-ethereum/common"
"github.com/scroll-tech/go-ethereum/common/math" "github.com/scroll-tech/go-ethereum/common/math"
"github.com/scroll-tech/go-ethereum/core/types"
"github.com/scroll-tech/go-ethereum/params" "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) // Replace `SSTORE_CLEARS_SCHEDULE` with `SSTORE_RESET_GAS + ACCESS_LIST_STORAGE_KEY_COST` (4,800)
gasSStoreEIP3529 = makeGasSStoreFunc(params.SstoreClearsScheduleRefundEIP3529) 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
}
}

View file

@ -140,6 +140,7 @@ func Execute(code, input []byte, cfg *Config) ([]byte, *state.StateDB, error) {
input, input,
cfg.GasLimit, cfg.GasLimit,
cfg.Value, cfg.Value,
nil,
) )
return ret, cfg.State, err return ret, cfg.State, err
} }
@ -200,6 +201,7 @@ func Call(address common.Address, input []byte, cfg *Config) ([]byte, uint64, er
input, input,
cfg.GasLimit, cfg.GasLimit,
cfg.Value, cfg.Value,
nil,
) )
return ret, leftOverGas, err return ret, leftOverGas, err
} }

View file

@ -20,10 +20,13 @@ import (
"fmt" "fmt"
"math/big" "math/big"
"os" "os"
"reflect"
"strings" "strings"
"testing" "testing"
"time" "time"
"github.com/agiledragon/gomonkey/v2"
"github.com/scroll-tech/go-ethereum/accounts/abi" "github.com/scroll-tech/go-ethereum/accounts/abi"
"github.com/scroll-tech/go-ethereum/common" "github.com/scroll-tech/go-ethereum/common"
"github.com/scroll-tech/go-ethereum/consensus" "github.com/scroll-tech/go-ethereum/consensus"
@ -354,7 +357,7 @@ type stepCounter struct {
steps int 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) { 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) //cfg.State.CreateAccount(cfg.Origin)
// set the receiver's (the executing contract) code for execution. // set the receiver's (the executing contract) code for execution.
cfg.State.SetCode(destination, code) 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.Run(name, func(b *testing.B) {
b.ReportAllocs() b.ReportAllocs()
for i := 0; i < b.N; i++ { 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-step-10M", stepTracer, b)
benchmarkNonModifyingCode(10000000, code, "tracer-call-frame-10M", callFrameTracer, 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)
}
}
}

View file

@ -920,7 +920,7 @@ func (api *ScrollAPI) GetSkippedTransaction(ctx context.Context, hash common.Has
return nil, nil return nil, nil
} }
var rpcTx RPCTransaction 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.SkipReason = stx.Reason
rpcTx.SkipBlockNumber = (*hexutil.Big)(new(big.Int).SetUint64(stx.BlockNumber)) rpcTx.SkipBlockNumber = (*hexutil.Big)(new(big.Int).SetUint64(stx.BlockNumber))
rpcTx.SkipBlockHash = stx.BlockHash rpcTx.SkipBlockHash = stx.BlockHash

View file

@ -161,7 +161,7 @@ func (api *consensusAPI) AssembleBlock(params assembleBlockParams) (*executableD
} }
var ( var (
signer = types.MakeSigner(bc.Config(), header.Number) signer = types.MakeSigner(bc.Config(), header.Number, header.Time)
txHeap = types.NewTransactionsByPriceAndNonce(signer, pending, nil) txHeap = types.NewTransactionsByPriceAndNonce(signer, pending, nil)
transactions []*types.Transaction transactions []*types.Transaction
) )

View file

@ -47,7 +47,7 @@ func makeChain(n int, seed byte, parent *types.Block, empty bool) ([]*types.Bloc
block.SetCoinbase(common.Address{seed}) block.SetCoinbase(common.Address{seed})
// Add one tx to every secondblock // Add one tx to every secondblock
if !empty && i%2 == 0 { 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) 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 { if err != nil {
panic(err) panic(err)

View file

@ -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. // Include transactions to the miner to make blocks more interesting.
if parent == tc.genesis && i%22 == 0 { 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) 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 { if err != nil {
panic(err) panic(err)

View file

@ -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 the block number is multiple of 3, send a bonus transaction to the miner
if parent == genesis && i%3 == 0 { 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) 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 { if err != nil {
panic(err) panic(err)

View file

@ -232,7 +232,7 @@ func (oracle *Oracle) SuggestTipCap(ctx context.Context) (*big.Int, error) {
results []*big.Int results []*big.Int
) )
for sent < oracle.checkBlocks && number > 0 { 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++ sent++
exp++ exp++
number-- 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 // meaningful returned, try to query more blocks. But the maximum
// is 2*checkBlocks. // is 2*checkBlocks.
if len(res.values) == 1 && len(results)+1+exp < oracle.checkBlocks*2 && number > 0 { 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++ sent++
exp++ exp++
number-- 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 // 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 // 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. // 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)) block, err := oracle.backend.BlockByNumber(ctx, rpc.BlockNumber(blockNum))
if block == nil { if block == nil {
select { select {
@ -320,6 +320,8 @@ func (oracle *Oracle) getBlockValues(ctx context.Context, signer types.Signer, b
} }
return return
} }
signer := types.MakeSigner(oracle.backend.ChainConfig(), block.Number(), block.Time())
// Sort the transaction by effective tip in ascending sort. // Sort the transaction by effective tip in ascending sort.
txs := make([]*types.Transaction, len(block.Transactions())) txs := make([]*types.Transaction, len(block.Transactions()))
copy(txs, block.Transactions()) copy(txs, block.Transactions())

View file

@ -180,7 +180,7 @@ func (eth *Ethereum) stateAtTransaction(block *types.Block, txIndex int, reexec
return nil, vm.BlockContext{}, statedb, nil return nil, vm.BlockContext{}, statedb, nil
} }
// Recompute transactions up to the target index. // 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() { for idx, tx := range block.Transactions() {
// Assemble the transaction call message and return if the requested offset // Assemble the transaction call message and return if the requested offset
msg, _ := tx.AsMessage(signer, block.BaseFee()) msg, _ := tx.AsMessage(signer, block.BaseFee())

View file

@ -275,7 +275,7 @@ func (api *API) traceChain(ctx context.Context, start, end *types.Block, config
// Fetch and execute the next block trace tasks // Fetch and execute the next block trace tasks
for task := range 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) blockCtx := core.NewEVMBlockContext(task.block.Header(), api.chainContext(localctx), api.backend.ChainConfig(), nil)
// Trace all the transactions contained within // Trace all the transactions contained within
for i, tx := range task.block.Transactions() { for i, tx := range task.block.Transactions() {
@ -534,7 +534,7 @@ func (api *API) IntermediateRoots(ctx context.Context, hash common.Hash, config
} }
var ( var (
roots []common.Hash 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() chainConfig = api.backend.ChainConfig()
vmctx = core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), api.backend.ChainConfig(), nil) vmctx = core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), api.backend.ChainConfig(), nil)
deleteEmptyObjects = chainConfig.IsEIP158(block.Number()) 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 // Execute all the transaction contained within the block concurrently
var ( var (
signer = types.MakeSigner(api.backend.ChainConfig(), block.Number()) signer = types.MakeSigner(api.backend.ChainConfig(), block.Number(), block.Time())
txs = block.Transactions() txs = block.Transactions()
results = make([]*txTraceResult, len(txs)) 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 // Execute transaction, either tracing all or just the requested one
var ( var (
dumps []string dumps []string
signer = types.MakeSigner(api.backend.ChainConfig(), block.Number()) signer = types.MakeSigner(api.backend.ChainConfig(), block.Number(), block.Time())
chainConfig = api.backend.ChainConfig() chainConfig = api.backend.ChainConfig()
vmctx = core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), api.backend.ChainConfig(), nil) vmctx = core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), api.backend.ChainConfig(), nil)
canon = true 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()) l1DataFee, err := fees.EstimateL1DataFeeForMessage(msg, block.BaseFee(), api.backend.ChainConfig(), signer, statedb, block.Number())
if err != nil { if err != nil {
return nil, err return nil, err

View file

@ -164,7 +164,7 @@ func (b *testBackend) StateAtTransaction(ctx context.Context, block *types.Block
return nil, vm.BlockContext{}, statedb, nil return nil, vm.BlockContext{}, statedb, nil
} }
// Recompute transactions up to the target index. // 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() { for idx, tx := range block.Transactions() {
msg, _ := tx.AsMessage(signer, block.BaseFee()) msg, _ := tx.AsMessage(signer, block.BaseFee())
txContext := core.NewEVMTxContext(msg) txContext := core.NewEVMTxContext(msg)

View file

@ -167,7 +167,7 @@ func testCallTracer(tracerName string, dirPath string, t *testing.T) {
} }
// Configure a blockchain with the given prestate // Configure a blockchain with the given prestate
var ( 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) origin, _ = signer.Sender(tx)
txContext = vm.TxContext{ txContext = vm.TxContext{
Origin: origin, 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 { if err := rlp.DecodeBytes(common.FromHex(test.Input), tx); err != nil {
b.Fatalf("failed to parse testcase input: %v", err) 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) msg, err := tx.AsMessage(signer, nil)
if err != nil { if err != nil {
b.Fatalf("failed to prepare transaction for tracing: %v", err) b.Fatalf("failed to prepare transaction for tracing: %v", err)

View file

@ -33,6 +33,7 @@ import (
"github.com/scroll-tech/go-ethereum/common" "github.com/scroll-tech/go-ethereum/common"
"github.com/scroll-tech/go-ethereum/common/hexutil" "github.com/scroll-tech/go-ethereum/common/hexutil"
"github.com/scroll-tech/go-ethereum/core" "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/core/vm"
"github.com/scroll-tech/go-ethereum/crypto" "github.com/scroll-tech/go-ethereum/crypto"
tracers2 "github.com/scroll-tech/go-ethereum/eth/tracers" 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. // 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.env = env
jst.ctx["type"] = "CALL" jst.ctx["type"] = "CALL"
if create { 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) isHomestead := env.ChainConfig().IsHomestead(env.Context.BlockNumber)
isIstanbul := env.ChainConfig().IsIstanbul(env.Context.BlockNumber) isIstanbul := env.ChainConfig().IsIstanbul(env.Context.BlockNumber)
isShanghai := env.ChainConfig().IsShanghai(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 { if err != nil {
return return
} }

View file

@ -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} 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) ret, err := env.Interpreter().Run(contract, []byte{}, false)
tracer.CaptureEnd(ret, startGas-contract.Gas, 1, err) tracer.CaptureEnd(ret, startGas-contract.Gas, 1, err)
if err != nil { if err != nil {
@ -152,7 +152,7 @@ func TestHaltBetweenSteps(t *testing.T) {
scope := &vm.ScopeContext{ scope := &vm.ScopeContext{
Contract: vm.NewContract(&account{}, &account{}, big.NewInt(0), 0), 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) tracer.CaptureState(0, 0, 0, 0, scope, nil, 0, nil)
timeout := errors.New("stahp") timeout := errors.New("stahp")
tracer.Stop(timeout) tracer.Stop(timeout)
@ -173,7 +173,7 @@ func TestNoStepExec(t *testing.T) {
t.Fatal(err) 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}) 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) tracer.CaptureEnd(nil, 0, 1, nil)
ret, err := tracer.GetResult() ret, err := tracer.GetResult()
if err != nil { if err != nil {

View file

@ -24,6 +24,7 @@ import (
"time" "time"
"github.com/scroll-tech/go-ethereum/common" "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/core/vm"
"github.com/scroll-tech/go-ethereum/eth/tracers" "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. // 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 t.env = env
// Update list of precompiles based on current block // Update list of precompiles based on current block

View file

@ -26,6 +26,7 @@ import (
"time" "time"
"github.com/scroll-tech/go-ethereum/common" "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/core/vm"
"github.com/scroll-tech/go-ethereum/eth/tracers" "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. // 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.env = env
t.callstack[0] = callFrame{ t.callstack[0] = callFrame{
Type: "CALL", Type: "CALL",

View file

@ -22,6 +22,7 @@ import (
"time" "time"
"github.com/scroll-tech/go-ethereum/common" "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/core/vm"
"github.com/scroll-tech/go-ethereum/eth/tracers" "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. // 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. // CaptureEnd is called after the call finishes to finalize the tracing.

View file

@ -8,6 +8,7 @@ import (
"time" "time"
"github.com/scroll-tech/go-ethereum/common" "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/core/vm"
"github.com/scroll-tech/go-ethereum/crypto" "github.com/scroll-tech/go-ethereum/crypto"
"github.com/scroll-tech/go-ethereum/eth/tracers" "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. // 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.env = env
t.create = create t.create = create
t.to = to t.to = to
@ -80,6 +81,10 @@ func (t *prestateTracer) CaptureStart(env *vm.EVM, from common.Address, to commo
t.lookupAccount(to) t.lookupAccount(to)
t.lookupAccount(env.Context.Coinbase) t.lookupAccount(env.Context.Coinbase)
for _, auth := range authorizationResults {
t.lookupAccount(auth.Authority)
}
// The recipient balance includes the value transferred. // The recipient balance includes the value transferred.
toBal := new(big.Int).Sub(t.pre[to].Balance, value) toBal := new(big.Int).Sub(t.pre[to].Balance, value)
t.pre[to].Balance = toBal 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)) fromBal.Add(fromBal, new(big.Int).Add(value, consumedGas))
t.pre[from].Balance = fromBal t.pre[from].Balance = fromBal
t.pre[from].Nonce-- 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. // CaptureEnd is called after the call finishes to finalize the tracing.

View file

@ -751,5 +751,8 @@ func toCallArg(msg ethereum.CallMsg) interface{} {
if msg.BlobHashes != nil { if msg.BlobHashes != nil {
arg["blobVersionedHashes"] = msg.BlobHashes arg["blobVersionedHashes"] = msg.BlobHashes
} }
if msg.AuthList != nil {
arg["authorizationList"] = msg.AuthList
}
return arg return arg
} }

View file

@ -228,6 +228,9 @@ func toCallArg(msg ethereum.CallMsg) interface{} {
if msg.BlobHashes != nil { if msg.BlobHashes != nil {
arg["blobVersionedHashes"] = msg.BlobHashes arg["blobVersionedHashes"] = msg.BlobHashes
} }
if msg.AuthList != nil {
arg["authorizationList"] = msg.AuthList
}
return arg return arg
} }

View file

@ -227,7 +227,7 @@ func (t *Transaction) GasPrice(ctx context.Context) (hexutil.Big, error) {
switch tx.Type() { switch tx.Type() {
case types.AccessListTxType: case types.AccessListTxType:
return hexutil.Big(*tx.GasPrice()), nil return hexutil.Big(*tx.GasPrice()), nil
case types.DynamicFeeTxType: case types.DynamicFeeTxType, types.SetCodeTxType:
if t.block != nil { if t.block != nil {
if baseFee, _ := t.block.BaseFeePerGas(ctx); baseFee != nil { if baseFee, _ := t.block.BaseFeePerGas(ctx); baseFee != nil {
// price = min(tip, gasFeeCap - baseFee) + baseFee // price = min(tip, gasFeeCap - baseFee) + baseFee
@ -263,7 +263,7 @@ func (t *Transaction) MaxFeePerGas(ctx context.Context) (*hexutil.Big, error) {
switch tx.Type() { switch tx.Type() {
case types.AccessListTxType: case types.AccessListTxType:
return nil, nil return nil, nil
case types.DynamicFeeTxType: case types.DynamicFeeTxType, types.SetCodeTxType:
return (*hexutil.Big)(tx.GasFeeCap()), nil return (*hexutil.Big)(tx.GasFeeCap()), nil
default: default:
return nil, nil return nil, nil
@ -278,7 +278,7 @@ func (t *Transaction) MaxPriorityFeePerGas(ctx context.Context) (*hexutil.Big, e
switch tx.Type() { switch tx.Type() {
case types.AccessListTxType: case types.AccessListTxType:
return nil, nil return nil, nil
case types.DynamicFeeTxType: case types.DynamicFeeTxType, types.SetCodeTxType:
return (*hexutil.Big)(tx.GasTipCap()), nil return (*hexutil.Big)(tx.GasTipCap()), nil
default: default:
return nil, nil return nil, nil

View file

@ -127,6 +127,9 @@ type CallMsg struct {
// For BlobTxType // For BlobTxType
BlobGasFeeCap *big.Int BlobGasFeeCap *big.Int
BlobHashes []common.Hash BlobHashes []common.Hash
// For SetCodeTxType
AuthList []types.SetCodeAuthorization
} }
// A ContractCaller provides contract calls, essentially transactions that are executed by // A ContractCaller provides contract calls, essentially transactions that are executed by

View file

@ -888,7 +888,7 @@ func (s *PublicBlockChainAPI) GetBlockReceipts(ctx context.Context, blockNrOrHas
for i, receipt := range receipts { for i, receipt := range receipts {
blockNumber := block.NumberU64() blockNumber := block.NumberU64()
bigblock := new(big.Int).SetUint64(blockNumber) 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) res, err := marshalReceipt(ctx, s.b, receipt, bigblock, block.Hash(), blockNumber, signer, txs[i], i)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to marshal receipt %d: %w", i, err) 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() 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) 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 // RPCTransaction represents a transaction that will serialize to the RPC representation of a transaction
type RPCTransaction struct { type RPCTransaction struct {
BlockHash *common.Hash `json:"blockHash"` BlockHash *common.Hash `json:"blockHash"`
BlockNumber *hexutil.Big `json:"blockNumber"` BlockNumber *hexutil.Big `json:"blockNumber"`
From common.Address `json:"from"` From common.Address `json:"from"`
Gas hexutil.Uint64 `json:"gas"` Gas hexutil.Uint64 `json:"gas"`
GasPrice *hexutil.Big `json:"gasPrice"` GasPrice *hexutil.Big `json:"gasPrice"`
GasFeeCap *hexutil.Big `json:"maxFeePerGas,omitempty"` GasFeeCap *hexutil.Big `json:"maxFeePerGas,omitempty"`
GasTipCap *hexutil.Big `json:"maxPriorityFeePerGas,omitempty"` GasTipCap *hexutil.Big `json:"maxPriorityFeePerGas,omitempty"`
Hash common.Hash `json:"hash"` Hash common.Hash `json:"hash"`
Input hexutil.Bytes `json:"input"` Input hexutil.Bytes `json:"input"`
Nonce hexutil.Uint64 `json:"nonce"` Nonce hexutil.Uint64 `json:"nonce"`
To *common.Address `json:"to"` To *common.Address `json:"to"`
TransactionIndex *hexutil.Uint64 `json:"transactionIndex"` TransactionIndex *hexutil.Uint64 `json:"transactionIndex"`
Value *hexutil.Big `json:"value"` Value *hexutil.Big `json:"value"`
Type hexutil.Uint64 `json:"type"` Type hexutil.Uint64 `json:"type"`
Accesses *types.AccessList `json:"accessList,omitempty"` Accesses *types.AccessList `json:"accessList,omitempty"`
ChainID *hexutil.Big `json:"chainId,omitempty"` ChainID *hexutil.Big `json:"chainId,omitempty"`
V *hexutil.Big `json:"v"` AuthorizationList []types.SetCodeAuthorization `json:"authorizationList,omitempty"`
R *hexutil.Big `json:"r"` V *hexutil.Big `json:"v"`
S *hexutil.Big `json:"s"` R *hexutil.Big `json:"r"`
S *hexutil.Big `json:"s"`
YParity *hexutil.Uint64 `json:"yParity,omitempty"`
// L1 message transaction fields: // L1 message transaction fields:
Sender *common.Address `json:"sender,omitempty"` Sender *common.Address `json:"sender,omitempty"`
@ -1361,8 +1363,8 @@ type RPCTransaction struct {
// NewRPCTransaction returns a transaction that will serialize to the RPC // NewRPCTransaction returns a transaction that will serialize to the RPC
// representation, with the given location metadata set (if available). // 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 { 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)) signer := types.MakeSigner(config, big.NewInt(0).SetUint64(blockNumber), blockTime)
from, _ := types.Sender(signer, tx) from, _ := types.Sender(signer, tx)
v, r, s := tx.RawSignatureValues() v, r, s := tx.RawSignatureValues()
result := &RPCTransaction{ result := &RPCTransaction{
@ -1387,12 +1389,16 @@ func NewRPCTransaction(tx *types.Transaction, blockHash common.Hash, blockNumber
switch tx.Type() { switch tx.Type() {
case types.AccessListTxType: case types.AccessListTxType:
al := tx.AccessList() al := tx.AccessList()
yparity := hexutil.Uint64(v.Sign())
result.Accesses = &al result.Accesses = &al
result.ChainID = (*hexutil.Big)(tx.ChainId()) result.ChainID = (*hexutil.Big)(tx.ChainId())
result.YParity = &yparity
case types.DynamicFeeTxType: case types.DynamicFeeTxType:
al := tx.AccessList() al := tx.AccessList()
yparity := hexutil.Uint64(v.Sign())
result.Accesses = &al result.Accesses = &al
result.ChainID = (*hexutil.Big)(tx.ChainId()) result.ChainID = (*hexutil.Big)(tx.ChainId())
result.YParity = &yparity
result.GasFeeCap = (*hexutil.Big)(tx.GasFeeCap()) result.GasFeeCap = (*hexutil.Big)(tx.GasFeeCap())
result.GasTipCap = (*hexutil.Big)(tx.GasTipCap()) result.GasTipCap = (*hexutil.Big)(tx.GasTipCap())
// if the transaction has been mined, compute the effective gas price // 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 { } else {
result.GasPrice = (*hexutil.Big)(tx.GasFeeCap()) 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: case types.L1MessageTxType:
msg := tx.AsL1MessageTx() msg := tx.AsL1MessageTx()
result.Sender = &msg.Sender 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 { func newRPCPendingTransaction(tx *types.Transaction, current *types.Header, config *params.ChainConfig, l1BaseFee *big.Int) *RPCTransaction {
var baseFee *big.Int var baseFee *big.Int
blockNumber := uint64(0) blockNumber := uint64(0)
blockTime := uint64(0)
if current != nil { if current != nil {
baseFee = misc.CalcBaseFee(config, current, l1BaseFee) baseFee = misc.CalcBaseFee(config, current, l1BaseFee)
blockNumber = current.Number.Uint64() 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. // 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)) { if index >= uint64(len(txs)) {
return nil 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. // 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 { if err != nil {
return nil, 0, nil, err 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) l1DataFee, err := fees.EstimateL1DataFeeForMessage(msg, header.BaseFee, b.ChainConfig(), signer, statedb, header.Number)
if err != nil { if err != nil {
return nil, 0, nil, fmt.Errorf("failed to apply transaction: %v err: %v", args.toTransaction().Hash(), err) 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 { if err != nil {
return nil, err 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 // No finalized transaction, try to retrieve it from the pool
if tx := s.b.GetPoolTransaction(hash); tx != nil { if tx := s.b.GetPoolTransaction(hash); tx != nil {
@ -1695,6 +1720,10 @@ func (s *PublicTransactionPoolAPI) GetTransactionReceipt(ctx context.Context, ha
if err != nil { if err != nil {
return nil, nil return nil, nil
} }
header, err := s.b.HeaderByHash(ctx, blockHash)
if err != nil {
return nil, err
}
receipts, err := s.b.GetReceipts(ctx, blockHash) receipts, err := s.b.GetReceipts(ctx, blockHash)
if err != nil { if err != nil {
return nil, err return nil, err
@ -1705,9 +1734,8 @@ func (s *PublicTransactionPoolAPI) GetTransactionReceipt(ctx context.Context, ha
receipt := receipts[index] receipt := receipts[index]
// Derive the sender. // Derive the sender.
bigblock := new(big.Int).SetUint64(blockNumber) signer := types.MakeSigner(s.b.ChainConfig(), header.Number, header.Time)
signer := types.MakeSigner(s.b.ChainConfig(), bigblock) return marshalReceipt(ctx, s.b, receipt, header.Number, blockHash, blockNumber, signer, tx, int(index))
return marshalReceipt(ctx, s.b, receipt, bigblock, blockHash, blockNumber, signer, tx, int(index))
} }
// marshalReceipt marshals a transaction receipt into a JSON object. // 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 return common.Hash{}, err
} }
// Print a log with full tx details for manual investigations and interventions // 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) from, err := types.Sender(signer, tx)
if err != nil { if err != nil {
return common.Hash{}, err return common.Hash{}, err

415
internal/ethapi/api_test.go Normal file
View file

@ -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 <http://www.gnu.org/licenses/>.
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)
}
}
}

View file

@ -23,6 +23,8 @@ import (
"fmt" "fmt"
"math/big" "math/big"
"github.com/holiman/uint256"
"github.com/scroll-tech/go-ethereum/common" "github.com/scroll-tech/go-ethereum/common"
"github.com/scroll-tech/go-ethereum/common/hexutil" "github.com/scroll-tech/go-ethereum/common/hexutil"
"github.com/scroll-tech/go-ethereum/common/math" "github.com/scroll-tech/go-ethereum/common/math"
@ -52,6 +54,9 @@ type TransactionArgs struct {
// Introduced by AccessListTxType transaction. // Introduced by AccessListTxType transaction.
AccessList *types.AccessList `json:"accessList,omitempty"` AccessList *types.AccessList `json:"accessList,omitempty"`
ChainID *hexutil.Big `json:"chainId,omitempty"` ChainID *hexutil.Big `json:"chainId,omitempty"`
// For SetCodeTxType
AuthorizationList []types.SetCodeAuthorization `json:"authorizationList"`
} }
// from retrieves the transaction sender address. // from retrieves the transaction sender address.
@ -247,7 +252,7 @@ func (args *TransactionArgs) ToMessage(globalGasCap uint64, baseFee *big.Int) (t
if args.AccessList != nil { if args.AccessList != nil {
accessList = *args.AccessList 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 return msg, nil
} }
@ -256,6 +261,27 @@ func (args *TransactionArgs) ToMessage(globalGasCap uint64, baseFee *big.Int) (t
func (args *TransactionArgs) toTransaction() *types.Transaction { func (args *TransactionArgs) toTransaction() *types.Transaction {
var data types.TxData var data types.TxData
switch { 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: case args.MaxFeePerGas != nil:
al := types.AccessList{} al := types.AccessList{}
if args.AccessList != nil { if args.AccessList != nil {

View file

@ -47,7 +47,7 @@ func makeChain(n int, seed byte, parent *types.Block, empty bool) ([]*types.Bloc
block.SetCoinbase(common.Address{seed}) block.SetCoinbase(common.Address{seed})
// Add one tx to every secondblock // Add one tx to every secondblock
if !empty && i%2 == 0 { 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) 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 { if err != nil {
panic(err) panic(err)

View file

@ -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. // Include transactions to the miner to make blocks more interesting.
if parent == tc.genesis && i%22 == 0 { 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) 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 { if err != nil {
panic(err) panic(err)

View file

@ -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 the block number is multiple of 3, send a bonus transaction to the miner
if parent == genesis && i%3 == 0 { 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) 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 { if err != nil {
panic(err) panic(err)

View file

@ -136,7 +136,7 @@ func odrContractCall(ctx context.Context, db ethdb.Database, config *params.Chai
from := statedb.GetOrNewStateObject(bankAddr) from := statedb.GetOrNewStateObject(bankAddr)
from.SetBalance(math.MaxBig256) 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) context := core.NewEVMBlockContext(header, bc, config, nil)
txContext := core.NewEVMTxContext(msg) 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{}) //vmenv := core.NewEnv(statedb, config, bc, msg, header, vm.Config{})
gp := new(core.GasPool).AddGas(math.MaxUint64) 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) l1DataFee, _ := fees.EstimateL1DataFeeForMessage(msg, header.BaseFee, config, signer, statedb, header.Number)
result, _ := core.ApplyMessage(vmenv, msg, gp, l1DataFee) result, _ := core.ApplyMessage(vmenv, msg, gp, l1DataFee)
res = append(res, result.Return()...) res = append(res, result.Return()...)
@ -153,12 +153,12 @@ func odrContractCall(ctx context.Context, db ethdb.Database, config *params.Chai
header := lc.GetHeaderByHash(bhash) header := lc.GetHeaderByHash(bhash)
state := light.NewState(ctx, header, lc.Odr()) state := light.NewState(ctx, header, lc.Odr())
state.SetBalance(bankAddr, math.MaxBig256) 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) context := core.NewEVMBlockContext(header, lc, config, nil)
txContext := core.NewEVMTxContext(msg) txContext := core.NewEVMTxContext(msg)
vmenv := vm.NewEVM(context, txContext, state, config, vm.Config{NoBaseFee: true}) vmenv := vm.NewEVM(context, txContext, state, config, vm.Config{NoBaseFee: true})
gp := new(core.GasPool).AddGas(math.MaxUint64) 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) l1DataFee, _ := fees.EstimateL1DataFeeForMessage(msg, header.BaseFee, config, signer, state, header.Number)
result, _ := core.ApplyMessage(vmenv, msg, gp, l1DataFee) result, _ := core.ApplyMessage(vmenv, msg, gp, l1DataFee)
if state.Error() == nil { if state.Error() == nil {

View file

@ -53,7 +53,7 @@ func (leth *LightEthereum) stateAtTransaction(ctx context.Context, block *types.
return nil, vm.BlockContext{}, statedb, nil return nil, vm.BlockContext{}, statedb, nil
} }
// Recompute transactions up to the target index. // 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() { for idx, tx := range block.Transactions() {
// Assemble the transaction call message and return if the requested offset // Assemble the transaction call message and return if the requested offset
msg, _ := tx.AsMessage(signer, block.BaseFee()) msg, _ := tx.AsMessage(signer, block.BaseFee())

View file

@ -195,12 +195,12 @@ func odrContractCall(ctx context.Context, db ethdb.Database, bc *core.BlockChain
// Perform read-only call. // Perform read-only call.
st.SetBalance(testBankAddress, math.MaxBig256) 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) txContext := core.NewEVMTxContext(msg)
context := core.NewEVMBlockContext(header, chain, config, nil) context := core.NewEVMBlockContext(header, chain, config, nil)
vmenv := vm.NewEVM(context, txContext, st, config, vm.Config{NoBaseFee: true}) vmenv := vm.NewEVM(context, txContext, st, config, vm.Config{NoBaseFee: true})
gp := new(core.GasPool).AddGas(math.MaxUint64) 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) l1DataFee, _ := fees.EstimateL1DataFeeForMessage(msg, header.BaseFee, config, signer, st, header.Number)
result, _ := core.ApplyMessage(vmenv, msg, gp, l1DataFee) result, _ := core.ApplyMessage(vmenv, msg, gp, l1DataFee)
res = append(res, result.Return()...) res = append(res, result.Return()...)

View file

@ -175,7 +175,7 @@ func GetBlockReceipts(ctx context.Context, odr OdrBackend, hash common.Hash, num
genesis := rawdb.ReadCanonicalHash(odr.Database(), 0) genesis := rawdb.ReadCanonicalHash(odr.Database(), 0)
config := rawdb.ReadChainConfig(odr.Database(), genesis) 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 return nil, err
} }
rawdb.WriteReceipts(odr.Database(), hash, number, receipts) rawdb.WriteReceipts(odr.Database(), hash, number, receipts)

View file

@ -420,7 +420,7 @@ func (pool *TxPool) validateTx(ctx context.Context, tx *types.Transaction) error
} }
// Should supply enough intrinsic gas // 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 { if err != nil {
return err return err
} }

View file

@ -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 { if w.prioritizedTx != nil && w.current.header.Number.Uint64() > w.prioritizedTx.blockNumber {
w.prioritizedTx = nil w.prioritizedTx = nil
} }
@ -688,7 +688,7 @@ func (w *worker) processTxPool() (bool, error) {
// processTxnSlice // processTxnSlice
func (w *worker) processTxnSlice(txns types.Transactions) (bool, error) { func (w *worker) processTxnSlice(txns types.Transactions) (bool, error) {
txsMap := make(map[common.Address]types.Transactions) 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 { for _, tx := range txns {
acc, _ := types.Sender(signer, tx) acc, _ := types.Sender(signer, tx)
txsMap[acc] = append(txsMap[acc], tx) txsMap[acc] = append(txsMap[acc], tx)

View file

@ -499,6 +499,8 @@ var (
CurieBlock: big.NewInt(0), CurieBlock: big.NewInt(0),
DarwinTime: new(uint64), DarwinTime: new(uint64),
DarwinV2Time: new(uint64), DarwinV2Time: new(uint64),
EuclidTime: new(uint64),
EuclidV2Time: new(uint64),
TerminalTotalDifficulty: nil, TerminalTotalDifficulty: nil,
Ethash: new(EthashConfig), Ethash: new(EthashConfig),
Clique: nil, Clique: nil,

View file

@ -85,10 +85,11 @@ const (
SelfdestructRefundGas uint64 = 24000 // Refunded following a selfdestruct operation. 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. 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. 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) 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 TxAccessListAddressGas uint64 = 2400 // Per address specified in EIP 2930 access list
TxAccessListStorageKeyGas uint64 = 1900 // Per storage key 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 // These have been changed during the course of the chain
CallGasFrontier uint64 = 40 // Once per CALL operation & message call transaction. CallGasFrontier uint64 = 40 // Once per CALL operation & message call transaction.

View file

@ -24,7 +24,7 @@ import (
const ( const (
VersionMajor = 5 // Major version component of the current release VersionMajor = 5 // Major version component of the current release
VersionMinor = 8 // Minor version component of the current release VersionMinor = 8 // Minor version component of the current release
VersionPatch = 9 // Patch version component of the current release VersionPatch = 10 // Patch version component of the current release
VersionMeta = "mainnet" // Version metadata to append to the version string VersionMeta = "mainnet" // Version metadata to append to the version string
) )

View file

@ -26,6 +26,8 @@ func TestAsyncChecker(t *testing.T) {
// Create a database pre-initialize with a genesis block // Create a database pre-initialize with a genesis block
db := rawdb.NewMemoryDatabase() db := rawdb.NewMemoryDatabase()
chainConfig := params.TestChainConfig.Clone() chainConfig := params.TestChainConfig.Clone()
chainConfig.EuclidTime = nil
chainConfig.EuclidV2Time = nil
chainConfig.Scroll.UseZktrie = true chainConfig.Scroll.UseZktrie = true
(&core.Genesis{ (&core.Genesis{
Config: chainConfig, 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) { bs, _ := core.GenerateChain(chainConfig, chain.Genesis(), ethash.NewFaker(), db, 100, func(i int, block *core.BlockGen) {
for i := 0; i < 10; i++ { 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) tx, err := types.SignTx(types.NewTransaction(block.TxNonce(testAddr), testAddr, big.NewInt(1000), params.TxGas, block.BaseFee(), nil), signer, testKey)
if err != nil { if err != nil {
panic(err) panic(err)

View file

@ -163,7 +163,7 @@ func (l *Logger) logSha256(inputLen uint64) {
l.sha256Usage += numBlocks * blockRows 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.currentEnv = env
l.isCreate = create l.isCreate = create
if !l.isCreate { if !l.isCreate {

View file

@ -5,6 +5,7 @@ import (
"time" "time"
"github.com/scroll-tech/go-ethereum/common" "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/core/vm"
_ "github.com/scroll-tech/go-ethereum/eth/tracers/native" _ "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 // 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 { for _, tracer := range t.tracers {
tracer.CaptureStart(env, from, to, create, input, gas, value) tracer.CaptureStart(env, from, to, create, input, gas, value, authorizationResults)
} }
} }

View file

@ -105,7 +105,7 @@ func CreateTraceEnvHelper(chainConfig *params.ChainConfig, logConfig *vm.LogConf
commitAfterApply: commitAfterApply, commitAfterApply: commitAfterApply,
chainConfig: chainConfig, chainConfig: chainConfig,
coinbase: coinbase, coinbase: coinbase,
signer: types.MakeSigner(chainConfig, block.Number()), signer: types.MakeSigner(chainConfig, block.Number(), block.Time()),
state: statedb, state: statedb,
blockCtx: blockCtx, blockCtx: blockCtx,
StorageTrace: &types.StorageTrace{ StorageTrace: &types.StorageTrace{
@ -533,7 +533,7 @@ func (env *TraceEnv) fillBlockTrace(block *types.Block) (*types.BlockTrace, erro
txs := make([]*types.TransactionData, block.Transactions().Len()) txs := make([]*types.TransactionData, block.Transactions().Len())
for i, tx := range block.Transactions() { 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{ intrinsicStorageProofs := map[common.Address][]common.Hash{

View file

@ -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
}

View file

@ -26,6 +26,7 @@ func (s stTransaction) MarshalJSON() ([]byte, error) {
GasLimit []math.HexOrDecimal64 `json:"gasLimit"` GasLimit []math.HexOrDecimal64 `json:"gasLimit"`
Value []string `json:"value"` Value []string `json:"value"`
PrivateKey hexutil.Bytes `json:"secretKey"` PrivateKey hexutil.Bytes `json:"secretKey"`
AuthorizationList []*stAuthorization `json:"authorizationList,omitempty"`
} }
var enc stTransaction var enc stTransaction
enc.GasPrice = (*math.HexOrDecimal256)(s.GasPrice) enc.GasPrice = (*math.HexOrDecimal256)(s.GasPrice)
@ -43,6 +44,7 @@ func (s stTransaction) MarshalJSON() ([]byte, error) {
} }
enc.Value = s.Value enc.Value = s.Value
enc.PrivateKey = s.PrivateKey enc.PrivateKey = s.PrivateKey
enc.AuthorizationList = s.AuthorizationList
return json.Marshal(&enc) return json.Marshal(&enc)
} }
@ -59,6 +61,7 @@ func (s *stTransaction) UnmarshalJSON(input []byte) error {
GasLimit []math.HexOrDecimal64 `json:"gasLimit"` GasLimit []math.HexOrDecimal64 `json:"gasLimit"`
Value []string `json:"value"` Value []string `json:"value"`
PrivateKey *hexutil.Bytes `json:"secretKey"` PrivateKey *hexutil.Bytes `json:"secretKey"`
AuthorizationList []*stAuthorization `json:"authorizationList,omitempty"`
} }
var dec stTransaction var dec stTransaction
if err := json.Unmarshal(input, &dec); err != nil { if err := json.Unmarshal(input, &dec); err != nil {
@ -97,5 +100,8 @@ func (s *stTransaction) UnmarshalJSON(input []byte) error {
if dec.PrivateKey != nil { if dec.PrivateKey != nil {
s.PrivateKey = *dec.PrivateKey s.PrivateKey = *dec.PrivateKey
} }
if dec.AuthorizationList != nil {
s.AuthorizationList = dec.AuthorizationList
}
return nil return nil
} }

View file

@ -181,6 +181,7 @@ var Forks = map[string]*params.ChainConfig{
MuirGlacierBlock: big.NewInt(0), MuirGlacierBlock: big.NewInt(0),
BerlinBlock: big.NewInt(0), BerlinBlock: big.NewInt(0),
LondonBlock: big.NewInt(0), LondonBlock: big.NewInt(0),
CurieBlock: big.NewInt(0), // EIP-1559 is enabled in Curie fork
}, },
"ArrowGlacier": { "ArrowGlacier": {
ChainID: big.NewInt(1), ChainID: big.NewInt(1),
@ -232,6 +233,29 @@ var Forks = map[string]*params.ChainConfig{
BernoulliBlock: big.NewInt(0), BernoulliBlock: big.NewInt(0),
CurieBlock: 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 // Returns the set of defined fork names

View file

@ -26,6 +26,8 @@ import (
"golang.org/x/crypto/sha3" "golang.org/x/crypto/sha3"
"github.com/holiman/uint256"
"github.com/scroll-tech/go-ethereum/common" "github.com/scroll-tech/go-ethereum/common"
"github.com/scroll-tech/go-ethereum/common/hexutil" "github.com/scroll-tech/go-ethereum/common/hexutil"
"github.com/scroll-tech/go-ethereum/common/math" "github.com/scroll-tech/go-ethereum/common/math"
@ -111,6 +113,7 @@ type stTransaction struct {
GasLimit []uint64 `json:"gasLimit"` GasLimit []uint64 `json:"gasLimit"`
Value []string `json:"value"` Value []string `json:"value"`
PrivateKey []byte `json:"secretKey"` PrivateKey []byte `json:"secretKey"`
AuthorizationList []*stAuthorization `json:"authorizationList,omitempty"`
} }
type stTransactionMarshaling struct { type stTransactionMarshaling struct {
@ -122,6 +125,27 @@ type stTransactionMarshaling struct {
PrivateKey hexutil.Bytes 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. // GetChainConfig takes a fork definition and returns a chain config.
// The fork definition can be // The fork definition can be
// - a plain forkname, e.g. `Byzantium`, // - 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") 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, 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 return msg, nil
} }

View file

@ -55,7 +55,7 @@ func (tt *TransactionTest) Run(config *params.ChainConfig) error {
return nil, nil, err return nil, nil, err
} }
// Intrinsic gas // 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 { if err != nil {
return nil, nil, err return nil, nil, err
} }