// Copyright 2023 The go-ethereum Authors // This file is part of the go-ethereum library. // // The go-ethereum library is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // The go-ethereum library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public License // along with the go-ethereum library. If not, see . package ethapi import ( "bytes" "context" "crypto/ecdsa" "errors" "math/big" "reflect" "slices" "testing" "time" "github.com/scroll-tech/go-ethereum" "github.com/scroll-tech/go-ethereum/accounts" "github.com/scroll-tech/go-ethereum/accounts/keystore" "github.com/scroll-tech/go-ethereum/common" "github.com/scroll-tech/go-ethereum/common/hexutil" "github.com/scroll-tech/go-ethereum/consensus" "github.com/scroll-tech/go-ethereum/consensus/ethash" "github.com/scroll-tech/go-ethereum/core" "github.com/scroll-tech/go-ethereum/core/bloombits" "github.com/scroll-tech/go-ethereum/core/rawdb" "github.com/scroll-tech/go-ethereum/core/state" "github.com/scroll-tech/go-ethereum/core/types" "github.com/scroll-tech/go-ethereum/core/vm" "github.com/scroll-tech/go-ethereum/crypto" "github.com/scroll-tech/go-ethereum/ethdb" "github.com/scroll-tech/go-ethereum/event" "github.com/scroll-tech/go-ethereum/params" "github.com/scroll-tech/go-ethereum/rpc" ) func newTestAccountManager(t *testing.T) (*accounts.Manager, accounts.Account) { var ( dir = t.TempDir() am = accounts.NewManager(nil) b = keystore.NewKeyStore(dir, 2, 1) testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") ) acc, err := b.ImportECDSA(testKey, "") if err != nil { t.Fatalf("failed to create test account: %v", err) } if err := b.Unlock(acc, ""); err != nil { t.Fatalf("failed to unlock account: %v\n", err) } am.AddBackend(b) return am, acc } type account struct { key *ecdsa.PrivateKey addr common.Address } func newAccounts(n int) (accounts []account) { for i := 0; i < n; i++ { key, _ := crypto.GenerateKey() addr := crypto.PubkeyToAddress(key.PublicKey) accounts = append(accounts, account{key: key, addr: addr}) } slices.SortFunc(accounts, func(a, b account) int { return bytes.Compare(a.addr[:], b.addr[:]) }) return accounts } type testBackend struct { db ethdb.Database chain *core.BlockChain pending *types.Block accman *accounts.Manager acc accounts.Account } func newTestBackend(t *testing.T, n int, gspec *core.Genesis, engine consensus.Engine, generator func(i int, b *core.BlockGen)) *testBackend { var ( cacheConfig = &core.CacheConfig{ TrieCleanLimit: 256, TrieDirtyLimit: 256, TrieTimeLimit: 5 * time.Minute, SnapshotLimit: 0, TrieDirtyDisabled: true, // Archive mode } ) accman, acc := newTestAccountManager(t) gspec.Alloc[acc.Address] = core.GenesisAccount{Balance: big.NewInt(params.Ether)} // Generate blocks for testing db := rawdb.NewMemoryDatabase() genesis := gspec.MustCommit(db) blocks, _ := core.GenerateChain(gspec.Config, genesis, engine, db, n, generator) txlookupLimit := uint64(0) chain, err := core.NewBlockChain(db, cacheConfig, gspec.Config, engine, vm.Config{}, nil, &txlookupLimit) if err != nil { t.Fatalf("failed to create tester chain: %v", err) } if n, err := chain.InsertChain(blocks); err != nil { t.Fatalf("block %d: failed to insert into chain: %v", n, err) } backend := &testBackend{db: db, chain: chain, accman: accman, acc: acc} return backend } func (b *testBackend) setPendingBlock(block *types.Block) { b.pending = block } func (b testBackend) SyncProgress() ethereum.SyncProgress { return ethereum.SyncProgress{} } func (b testBackend) SuggestGasTipCap(ctx context.Context) (*big.Int, error) { return big.NewInt(0), nil } func (b testBackend) FeeHistory(ctx context.Context, blockCount int, lastBlock rpc.BlockNumber, rewardPercentiles []float64) (*big.Int, [][]*big.Int, []*big.Int, []float64, error) { return nil, nil, nil, nil, nil } func (b testBackend) BlobBaseFee(ctx context.Context) *big.Int { return new(big.Int) } func (b testBackend) ChainDb() ethdb.Database { return b.db } func (b testBackend) AccountManager() *accounts.Manager { return b.accman } func (b testBackend) ExtRPCEnabled() bool { return false } func (b testBackend) RPCGasCap() uint64 { return 10000000 } func (b testBackend) RPCEVMTimeout() time.Duration { return time.Second } func (b testBackend) RPCTxFeeCap() float64 { return 0 } func (b testBackend) UnprotectedAllowed() bool { return false } func (b testBackend) SetHead(number uint64) {} func (b testBackend) HeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Header, error) { if number == rpc.LatestBlockNumber { return b.chain.CurrentHeader(), nil } if number == rpc.PendingBlockNumber && b.pending != nil { return b.pending.Header(), nil } return b.chain.GetHeaderByNumber(uint64(number)), nil } func (b testBackend) HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error) { return b.chain.GetHeaderByHash(hash), nil } func (b testBackend) HeaderByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*types.Header, error) { if blockNr, ok := blockNrOrHash.Number(); ok { return b.HeaderByNumber(ctx, blockNr) } if blockHash, ok := blockNrOrHash.Hash(); ok { return b.HeaderByHash(ctx, blockHash) } panic("unknown type rpc.BlockNumberOrHash") } func (b testBackend) CurrentHeader() *types.Header { return b.chain.CurrentHeader() } func (b testBackend) CurrentBlock() *types.Block { return b.chain.CurrentBlock() } func (b testBackend) BlockByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Block, error) { if number == rpc.LatestBlockNumber { head := b.chain.CurrentBlock() return b.chain.GetBlock(head.Hash(), head.Number().Uint64()), nil } if number == rpc.PendingBlockNumber { return b.pending, nil } return b.chain.GetBlockByNumber(uint64(number)), nil } func (b testBackend) BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error) { return b.chain.GetBlockByHash(hash), nil } func (b testBackend) BlockByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*types.Block, error) { if blockNr, ok := blockNrOrHash.Number(); ok { return b.BlockByNumber(ctx, blockNr) } if blockHash, ok := blockNrOrHash.Hash(); ok { return b.BlockByHash(ctx, blockHash) } panic("unknown type rpc.BlockNumberOrHash") } func (b testBackend) GetBody(ctx context.Context, hash common.Hash, number rpc.BlockNumber) (*types.Body, error) { return b.chain.GetBlock(hash, uint64(number.Int64())).Body(), nil } func (b testBackend) StateAndHeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*state.StateDB, *types.Header, error) { if number == rpc.PendingBlockNumber { panic("pending state not implemented") } header, err := b.HeaderByNumber(ctx, number) if err != nil { return nil, nil, err } if header == nil { return nil, nil, errors.New("header not found") } stateDb, err := b.chain.StateAt(header.Root) return stateDb, header, err } func (b testBackend) StateAndHeaderByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*state.StateDB, *types.Header, error) { if blockNr, ok := blockNrOrHash.Number(); ok { return b.StateAndHeaderByNumber(ctx, blockNr) } panic("only implemented for number") } func (b testBackend) Pending() (*types.Block, types.Receipts, *state.StateDB) { panic("implement me") } func (b testBackend) GetReceipts(ctx context.Context, hash common.Hash) (types.Receipts, error) { header, err := b.HeaderByHash(ctx, hash) if header == nil || err != nil { return nil, err } receipts := rawdb.ReadReceipts(b.db, hash, header.Number.Uint64(), b.chain.Config()) return receipts, nil } func (b testBackend) GetTd(ctx context.Context, hash common.Hash) *big.Int { if b.pending != nil && hash == b.pending.Hash() { return nil } return big.NewInt(1) } func (b testBackend) GetEVM(ctx context.Context, msg core.Message, state *state.StateDB, header *types.Header, vmConfig *vm.Config) (*vm.EVM, func() error, error) { vmError := func() error { return nil } if vmConfig == nil { vmConfig = b.chain.GetVMConfig() } txContext := core.NewEVMTxContext(msg) context := core.NewEVMBlockContext(header, b.chain, b.ChainConfig(), nil) return vm.NewEVM(context, txContext, state, b.chain.Config(), *vmConfig), vmError, nil } func (b testBackend) SubscribeChainEvent(ch chan<- core.ChainEvent) event.Subscription { panic("implement me") } func (b testBackend) SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription { panic("implement me") } func (b testBackend) SendTx(ctx context.Context, signedTx *types.Transaction) error { panic("implement me") } func (b testBackend) PendingBlockAndReceipts() (*types.Block, types.Receipts) { panic("implement me") } func (b testBackend) RemoveTx(txHash common.Hash) { panic("implement me") } func (b testBackend) StateAt(root common.Hash) (*state.StateDB, error) { panic("implement me") } func (b testBackend) SubscribeChainSideEvent(ch chan<- core.ChainSideEvent) event.Subscription { panic("implement me") } func (b testBackend) SubscribePendingLogsEvent(ch chan<- []*types.Log) event.Subscription { panic("implement me") } func (b testBackend) GetTransaction(ctx context.Context, txHash common.Hash) (*types.Transaction, common.Hash, uint64, uint64, error) { tx, blockHash, blockNumber, index := rawdb.ReadTransaction(b.db, txHash) return tx, blockHash, blockNumber, index, nil } func (b testBackend) GetPoolTransactions() (types.Transactions, error) { panic("implement me") } func (b testBackend) GetPoolTransaction(txHash common.Hash) *types.Transaction { panic("implement me") } func (b testBackend) GetPoolNonce(ctx context.Context, addr common.Address) (uint64, error) { return 0, nil } func (b testBackend) Stats() (pending int, queued int) { panic("implement me") } func (b testBackend) TxPoolContent() (map[common.Address]types.Transactions, map[common.Address]types.Transactions) { panic("implement me") } func (b testBackend) TxPoolContentFrom(addr common.Address) (types.Transactions, types.Transactions) { panic("implement me") } func (b testBackend) SubscribeNewTxsEvent(events chan<- core.NewTxsEvent) event.Subscription { panic("implement me") } func (b testBackend) ChainConfig() *params.ChainConfig { return b.chain.Config() } func (b testBackend) Engine() consensus.Engine { return b.chain.Engine() } func (b testBackend) GetLogs(ctx context.Context, blockHash common.Hash) ([][]*types.Log, error) { panic("implement me") } func (b testBackend) SubscribeRemovedLogsEvent(ch chan<- core.RemovedLogsEvent) event.Subscription { panic("implement me") } func (b testBackend) SubscribeLogsEvent(ch chan<- []*types.Log) event.Subscription { panic("implement me") } func (b testBackend) BloomStatus() (uint64, uint64) { panic("implement me") } func (b testBackend) ServiceFilter(ctx context.Context, session *bloombits.MatcherSession) { panic("implement me") } func TestEstimateGas(t *testing.T) { t.Parallel() // Initialize test accounts var ( accounts = newAccounts(3) genesis = &core.Genesis{ Config: params.TestChainConfig, Alloc: core.GenesisAlloc{ accounts[0].addr: {Balance: big.NewInt(params.Ether)}, accounts[1].addr: {Balance: big.NewInt(params.Ether), Code: append(types.DelegationPrefix, accounts[2].addr.Bytes()...)}, }, } genBlocks = 10 signer = types.HomesteadSigner{} ) api := NewPublicBlockChainAPI(newTestBackend(t, genBlocks, genesis, ethash.NewFaker(), func(i int, b *core.BlockGen) { // Transfer from account[0] to account[1] // value: 1000 wei // fee: 0 wei tx, _ := types.SignTx(types.NewTx(&types.LegacyTx{Nonce: uint64(i), To: &accounts[1].addr, Value: big.NewInt(1000), Gas: params.TxGas, GasPrice: b.BaseFee(), Data: nil}), signer, accounts[0].key) b.AddTx(tx) })) setCodeAuthorization, _ := types.SignSetCode(accounts[0].key, types.SetCodeAuthorization{ Address: accounts[0].addr, Nonce: uint64(genBlocks + 1), }) var testSuite = []struct { blockNumber rpc.BlockNumber call TransactionArgs want uint64 expectErr error }{ // Should be able to send to an EIP-7702 delegated account. { blockNumber: rpc.LatestBlockNumber, call: TransactionArgs{ From: &accounts[0].addr, To: &accounts[1].addr, Value: (*hexutil.Big)(big.NewInt(1)), }, want: 21000, }, // Should be able to send as EIP-7702 delegated account. { blockNumber: rpc.LatestBlockNumber, call: TransactionArgs{ From: &accounts[1].addr, To: &accounts[0].addr, Value: (*hexutil.Big)(big.NewInt(1)), }, want: 21000, }, // Should be able to estimate SetCodeTx. { blockNumber: rpc.LatestBlockNumber, call: TransactionArgs{ From: &accounts[0].addr, To: &accounts[1].addr, Value: (*hexutil.Big)(big.NewInt(0)), AuthorizationList: []types.SetCodeAuthorization{setCodeAuthorization}, }, want: 46000, }, // Should retrieve the code of 0xef0001 || accounts[0].addr and return an invalid opcode error. { blockNumber: rpc.LatestBlockNumber, call: TransactionArgs{ From: &accounts[0].addr, To: &accounts[0].addr, Value: (*hexutil.Big)(big.NewInt(0)), AuthorizationList: []types.SetCodeAuthorization{setCodeAuthorization}, }, expectErr: vm.NewErrInvalidOpCode(0xef), }, // SetCodeTx with empty authorization list should fail. { blockNumber: rpc.LatestBlockNumber, call: TransactionArgs{ From: &accounts[0].addr, To: &common.Address{}, Value: (*hexutil.Big)(big.NewInt(0)), AuthorizationList: []types.SetCodeAuthorization{}, }, expectErr: core.ErrEmptyAuthList, }, // SetCodeTx with nil `to` should fail. { blockNumber: rpc.LatestBlockNumber, call: TransactionArgs{ From: &accounts[0].addr, To: nil, Value: (*hexutil.Big)(big.NewInt(0)), AuthorizationList: []types.SetCodeAuthorization{setCodeAuthorization}, }, expectErr: core.ErrSetCodeTxCreate, }, } for i, tc := range testSuite { result, err := api.EstimateGas(context.Background(), tc.call, &rpc.BlockNumberOrHash{BlockNumber: &tc.blockNumber}) if tc.expectErr != nil { if err == nil { t.Errorf("test %d: want error %v, have nothing", i, tc.expectErr) continue } if !errors.Is(err, tc.expectErr) { if !reflect.DeepEqual(err, tc.expectErr) { t.Errorf("test %d: error mismatch, want %v, have %v", i, tc.expectErr, err) } } continue } if err != nil { t.Errorf("test %d: want no error, have %v", i, err) continue } if float64(result) > float64(tc.want) { t.Errorf("test %d, result mismatch, have\n%v\n, want\n%v\n", i, uint64(result), tc.want) } } }