From 3fad5e1ab4bc705be8753273e2f57fd4d30a962f Mon Sep 17 00:00:00 2001 From: Daniel Liu <139250065@qq.com> Date: Fri, 6 Mar 2026 13:39:10 +0800 Subject: [PATCH] refactor(ethclient): implement new sim backend #28202 (#2062) Introduce a new simulated backend implementation under ethclient/simulated and migrate bind-facing wrappers/tests to use it. Key changes: - Add the new backend entry points and behavior in ethclient/simulated, including chain control helpers used by tests (commit/rollback/fork/time adjustment). - Update legacy bind wrappers in accounts/abi/bind/backends to delegate to the new simulated backend while preserving old APIs. - Align bind v2/backend and utility tests to the new simulated backend client surface and transaction flow. - Refresh abigen/bind tests and shared interfaces impacted by the backend migration. Compatibility notes: - Keep backward-compatible wrapper constructors for existing contract test code. - Preserve legacy transaction paths in tests while supporting EIP-1559-aware flows where applicable. Validation: - Verified affected packages compile and pass tests. - Verified repository-wide go test ./... passes after follow-up compatibility fixes. --- accounts/abi/abigen/bind_test.go | 11 +- accounts/abi/bind/backends/simulated.go | 1030 +----------- accounts/abi/bind/backends/simulated_test.go | 1480 ------------------ accounts/abi/bind/v2/backend.go | 43 +- accounts/abi/bind/v2/util_test.go | 19 +- ethclient/simulated/backend.go | 1280 +++++++++++++++ ethclient/simulated/backend_test.go | 1304 +++++++++++++++ interfaces.go | 20 + 8 files changed, 2665 insertions(+), 2522 deletions(-) delete mode 100644 accounts/abi/bind/backends/simulated_test.go create mode 100644 ethclient/simulated/backend.go create mode 100644 ethclient/simulated/backend_test.go diff --git a/accounts/abi/abigen/bind_test.go b/accounts/abi/abigen/bind_test.go index d94445a98b..19deeae429 100644 --- a/accounts/abi/abigen/bind_test.go +++ b/accounts/abi/abigen/bind_test.go @@ -306,6 +306,7 @@ var bindTests = []struct { if err != nil { t.Fatalf("Failed to deploy interactor contract: %v", err) } + sim.Commit() if _, err := interactor.Transact(auth, "Transact string"); err != nil { t.Fatalf("Failed to transact with interactor contract: %v", err) } @@ -519,6 +520,7 @@ var bindTests = []struct { if err != nil { t.Fatalf("Failed to deploy defaulter contract: %v", err) } + sim.Commit() if _, err := (&DefaulterRaw{defaulter}).Transfer(auth); err != nil { t.Fatalf("Failed to invoke default method: %v", err) } @@ -1842,6 +1844,7 @@ var bindTests = []struct { []string{"0x6080604052348015600f57600080fd5b5060998061001e6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c8063726c638214602d575b600080fd5b60336035565b005b60405163024876cd60e61b815260016004820152600260248201526003604482015260640160405180910390fdfea264697066735822122093f786a1bc60216540cd999fbb4a6109e0fef20abcff6e9107fb2817ca968f3c64736f6c63430008070033"}, []string{`[{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"MyError","type":"error"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"MyError1","type":"error"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"MyError2","type":"error"},{"inputs":[{"internalType":"uint256","name":"a","type":"uint256"},{"internalType":"uint256","name":"b","type":"uint256"},{"internalType":"uint256","name":"c","type":"uint256"}],"name":"MyError3","type":"error"},{"inputs":[],"name":"Error","outputs":[],"stateMutability":"pure","type":"function"}]`}, ` + "context" "math/big" "github.com/XinFinOrg/XDPoSChain/accounts/abi/bind" @@ -1863,7 +1866,7 @@ var bindTests = []struct { t.Fatal(err) } sim.Commit() - _, err = bind.WaitDeployed(nil, sim, tx) + _, err = bind.WaitDeployed(context.Background(), sim, tx) if err != nil { t.Error(err) } @@ -1894,6 +1897,7 @@ var bindTests = []struct { bytecode: []string{`0x608060405234801561001057600080fd5b506040516101c43803806101c48339818101604052810190610032919061014a565b50610177565b6000604051905090565b600080fd5b600080fd5b6000601f19601f8301169050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6100958261004c565b810181811067ffffffffffffffff821117156100b4576100b361005d565b5b80604052505050565b60006100c7610038565b90506100d3828261008c565b919050565b6000819050919050565b6100eb816100d8565b81146100f657600080fd5b50565b600081519050610108816100e2565b92915050565b60006020828403121561012457610123610047565b5b61012e60206100bd565b9050600061013e848285016100f9565b60008301525092915050565b6000602082840312156101605761015f610042565b5b600061016e8482850161010e565b91505092915050565b603f806101856000396000f3fe6080604052600080fdfea2646970667358221220cdffa667affecefac5561f65f4a4ba914204a8d4eb859d8cd426fb306e5c12a364736f6c634300080a0033`}, abi: []string{`[{"inputs":[{"components":[{"internalType":"uint256","name":"field","type":"uint256"}],"internalType":"struct ConstructorWithStructParam.StructType","name":"st","type":"tuple"}],"stateMutability":"nonpayable","type":"constructor"}]`}, imports: ` + "context" "math/big" "github.com/XinFinOrg/XDPoSChain/accounts/abi/bind" @@ -1916,7 +1920,7 @@ var bindTests = []struct { } sim.Commit() - if _, err = bind.WaitDeployed(nil, sim, tx); err != nil { + if _, err = bind.WaitDeployed(context.Background(), sim, tx); err != nil { t.Logf("Deployment tx: %+v", tx) t.Errorf("bind.WaitDeployed(nil, %T, ) got err %v; want nil err", sim, err) } @@ -1983,6 +1987,7 @@ var bindTests = []struct { bytecode: []string{"0x608060405234801561001057600080fd5b5060dc8061001f6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c8063527a119f14602d575b600080fd5b60436004803603810190603f9190605b565b6045565b005b50565b6000813590506055816092565b92915050565b600060208284031215606e57606d608d565b5b6000607a848285016048565b91505092915050565b6000819050919050565b600080fd5b6099816083565b811460a357600080fd5b5056fea2646970667358221220d4f4525e2615516394055d369fb17df41c359e5e962734f27fd683ea81fd9db164736f6c63430008070033"}, abi: []string{`[{"inputs":[{"internalType":"uint256","name":"range","type":"uint256"}],"name":"functionWithKeywordParameter","outputs":[],"stateMutability":"pure","type":"function"}]`}, imports: ` + "context" "math/big" "github.com/XinFinOrg/XDPoSChain/accounts/abi/bind" @@ -2003,7 +2008,7 @@ var bindTests = []struct { } sim.Commit() - if _, err = bind.WaitDeployed(nil, sim, tx); err != nil { + if _, err = bind.WaitDeployed(context.Background(), sim, tx); err != nil { t.Errorf("error deploying the contract: %v", err) } `, diff --git a/accounts/abi/bind/backends/simulated.go b/accounts/abi/bind/backends/simulated.go index af120d342f..cb28aefcb4 100644 --- a/accounts/abi/bind/backends/simulated.go +++ b/accounts/abi/bind/backends/simulated.go @@ -18,71 +18,52 @@ package backends import ( "context" - "errors" - "fmt" - gomath "math" - "math/big" "os" - "sync" - "time" - ethereum "github.com/XinFinOrg/XDPoSChain" - "github.com/XinFinOrg/XDPoSChain/XDCx" - "github.com/XinFinOrg/XDPoSChain/XDCxlending" "github.com/XinFinOrg/XDPoSChain/accounts" - "github.com/XinFinOrg/XDPoSChain/accounts/abi" - "github.com/XinFinOrg/XDPoSChain/accounts/abi/bind" "github.com/XinFinOrg/XDPoSChain/accounts/keystore" "github.com/XinFinOrg/XDPoSChain/common" - "github.com/XinFinOrg/XDPoSChain/common/hexutil" - "github.com/XinFinOrg/XDPoSChain/common/math" - "github.com/XinFinOrg/XDPoSChain/consensus/XDPoS" - "github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/utils" - "github.com/XinFinOrg/XDPoSChain/consensus/ethash" "github.com/XinFinOrg/XDPoSChain/core" - "github.com/XinFinOrg/XDPoSChain/core/bloombits" - "github.com/XinFinOrg/XDPoSChain/core/rawdb" - "github.com/XinFinOrg/XDPoSChain/core/state" "github.com/XinFinOrg/XDPoSChain/core/types" - "github.com/XinFinOrg/XDPoSChain/core/vm" - "github.com/XinFinOrg/XDPoSChain/crypto" - "github.com/XinFinOrg/XDPoSChain/eth/filters" - "github.com/XinFinOrg/XDPoSChain/ethdb" - "github.com/XinFinOrg/XDPoSChain/event" - "github.com/XinFinOrg/XDPoSChain/log" - "github.com/XinFinOrg/XDPoSChain/node" + "github.com/XinFinOrg/XDPoSChain/ethclient/simulated" "github.com/XinFinOrg/XDPoSChain/params" - "github.com/XinFinOrg/XDPoSChain/rpc" ) -// This nil assignment ensures at compile time that SimulatedBackend implements bind.ContractBackend. -var _ bind.ContractBackend = (*SimulatedBackend)(nil) - -var ( - errBlockNumberUnsupported = errors.New("simulatedBackend cannot access blocks other than the latest block") - errBlockHashUnsupported = errors.New("simulatedBackend cannot access blocks by hash other than the latest block") - errBlockDoesNotExist = errors.New("block does not exist in blockchain") - errTransactionDoesNotExist = errors.New("transaction does not exist") -) - -// SimulatedBackend implements bind.ContractBackend, simulating a blockchain in -// the background. Its main purpose is to allow for easy testing of contract bindings. -// Simulated backend implements the following interfaces: -// ChainReader, ChainStateReader, ContractBackend, ContractCaller, ContractFilterer, ContractTransactor, -// DeployBackend, GasEstimator, GasPricer, LogFilterer, PendingContractCaller, TransactionReader, and TransactionSender +// SimulatedBackend is a simulated blockchain. +// Deprecated: use package github.com/XinFinOrg/XDPoSChain/ethclient/simulated instead. type SimulatedBackend struct { - database ethdb.Database // In memory database to store our testing data - blockchain *core.BlockChain // Ethereum blockchain to handle the consensus + *simulated.Backend +} - mu sync.Mutex - pendingBlock *types.Block // Currently pending block that will be imported on request - pendingState *state.StateDB // Currently pending state that will be the active on request - pendingReceipts types.Receipts // Currently receipts for the pending block +// Client returns a client that accesses the simulated chain. +func (b *SimulatedBackend) Client() simulated.Client { + return b.Backend.Client() +} - events *filters.EventSystem // for filtering log events live - filterSystem *filters.FilterSystem // for filtering database logs +// Fork sets the head to a new block, which is based on the provided parentHash. +func (b *SimulatedBackend) Fork(ctx context.Context, parentHash common.Hash) error { + return b.Backend.Fork(parentHash) +} - config *params.ChainConfig +// NewXDCSimulatedBackend creates a new backend for testing purpose. +func NewXDCSimulatedBackend(alloc types.GenesisAlloc, gasLimit uint64, chainConfig *params.ChainConfig) *SimulatedBackend { + b := simulated.New(alloc, gasLimit, chainConfig) + return &SimulatedBackend{ + Backend: b, + } +} + +// NewXDCSimulatedBackend creates a new backend for testing purpose. +// +// A simulated backend always uses chainID 1337. +// +// Deprecated: please use simulated.Backend from package +// github.com/XinFinOrg/XDPoSChain/ethclient/simulated instead. +func NewSimulatedBackend(alloc core.GenesisAlloc, gasLimit uint64) *SimulatedBackend { + b := simulated.New(alloc, gasLimit, params.AllEthashProtocolChanges) + return &SimulatedBackend{ + Backend: b, + } } func SimulateWalletAddressAndSignFn() (common.Address, func(account accounts.Account, hash []byte) ([]byte, error), error) { @@ -102,950 +83,3 @@ func SimulateWalletAddressAndSignFn() (common.Address, func(account accounts.Acc } return a1.Address, ks.SignHash, nil } - -// NewXDCSimulatedBackend creates a new backend for testing purpose. -func NewXDCSimulatedBackend(alloc types.GenesisAlloc, gasLimit uint64, chainConfig *params.ChainConfig) *SimulatedBackend { - database := rawdb.NewMemoryDatabase() - genesis := core.Genesis{ - GasLimit: gasLimit, // need this big, support initial smart contract - Config: chainConfig, - Alloc: alloc, - ExtraData: append(make([]byte, 32), make([]byte, crypto.SignatureLength)...), - } - genesis.MustCommit(database) - consensus := XDPoS.NewFaker(database, chainConfig) - - // Attach mock trading and lending service - var DefaultConfig = XDCx.Config{ - DataDir: "", - } - stack, err := node.New(&node.Config{DataDir: ""}) - if err != nil { - log.Error("Could not create new node: %v", err) - return nil - } - defer stack.Close() - - XDCXServ := XDCx.New(stack, &DefaultConfig) - - lendingServ := XDCxlending.New(stack, XDCXServ) - - consensus.GetXDCXService = func() utils.TradingService { - return XDCXServ - } - consensus.GetLendingService = func() utils.LendingService { - return lendingServ - } - - cacheConfig := &core.CacheConfig{ - TrieCleanLimit: 256, - TrieDirtyLimit: 256, - TrieTimeLimit: 5 * time.Minute, - Preimages: true, - } - blockchain, _ := core.NewBlockChain(database, cacheConfig, &genesis, consensus, vm.Config{}) - - backend := &SimulatedBackend{ - database: database, - blockchain: blockchain, - config: genesis.Config, - } - - filterBackend := &filterBackend{database, blockchain, backend} - backend.filterSystem = filters.NewFilterSystem(filterBackend, filters.Config{}) - backend.events = filters.NewEventSystem(backend.filterSystem, false) - - blockchain.Client = backend - - header := backend.blockchain.CurrentBlock() - block := backend.blockchain.GetBlock(header.Hash(), header.Number.Uint64()) - - backend.rollback(block) - return backend -} - -// SimulOldNewSimulatedBackendatedBackend creates a new binding backend based on the given database -// and uses a simulated blockchain for testing purposes. -// A simulated backend always uses chainID 1337. -func NewSimulatedBackend(alloc types.GenesisAlloc, gasLimit uint64) *SimulatedBackend { - database := rawdb.NewMemoryDatabase() - genesis := core.Genesis{Config: params.AllEthashProtocolChanges, GasLimit: gasLimit, Alloc: alloc} - genesis.MustCommit(database) - blockchain, _ := core.NewBlockChain(database, nil, &genesis, ethash.NewFaker(), vm.Config{}) - - backend := &SimulatedBackend{ - database: database, - blockchain: blockchain, - config: genesis.Config, - } - - filterBackend := &filterBackend{database, blockchain, backend} - backend.filterSystem = filters.NewFilterSystem(filterBackend, filters.Config{}) - backend.events = filters.NewEventSystem(backend.filterSystem, false) - - header := backend.blockchain.CurrentBlock() - block := backend.blockchain.GetBlock(header.Hash(), header.Number.Uint64()) - - backend.rollback(block) - return backend -} - -// Close terminates the underlying blockchain's update loop. -func (b *SimulatedBackend) Close() error { - b.blockchain.Stop() - return nil -} - -// Commit imports all the pending transactions as a single block and starts a -// fresh new state. -func (b *SimulatedBackend) Commit() common.Hash { - b.mu.Lock() - defer b.mu.Unlock() - - if _, err := b.blockchain.InsertChain([]*types.Block{b.pendingBlock}); err != nil { - panic(err) // This cannot happen unless the simulator is wrong, fail in that case - } - blockHash := b.pendingBlock.Hash() - - // Using the last inserted block here makes it possible to build on a side - // chain after a fork. - b.rollback(b.pendingBlock) - - return blockHash -} - -// Rollback aborts all pending transactions, reverting to the last committed state. -func (b *SimulatedBackend) Rollback() { - b.mu.Lock() - defer b.mu.Unlock() - - header := b.blockchain.CurrentBlock() - block := b.blockchain.GetBlock(header.Hash(), header.Number.Uint64()) - - b.rollback(block) -} - -func (b *SimulatedBackend) rollback(parent *types.Block) { - blocks, _ := core.GenerateChain(b.config, parent, b.blockchain.Engine(), b.database, 1, func(int, *core.BlockGen) {}) - stateDB, _ := b.blockchain.State() - - b.pendingBlock = blocks[0] - b.pendingState, _ = state.New(b.pendingBlock.Root(), stateDB.Database()) -} - -// Fork creates a side-chain that can be used to simulate reorgs. -// -// This function should be called with the ancestor block where the new side -// chain should be started. Transactions (old and new) can then be applied on -// top and Commit-ed. -// -// Note, the side-chain will only become canonical (and trigger the events) when -// it becomes longer. Until then CallContract will still operate on the current -// canonical chain. -// -// There is a % chance that the side chain becomes canonical at the same length -// to simulate live network behavior. -func (b *SimulatedBackend) Fork(ctx context.Context, parent common.Hash) error { - b.mu.Lock() - defer b.mu.Unlock() - - if len(b.pendingBlock.Transactions()) != 0 { - return errors.New("pending block dirty") - } - block, err := b.blockByHash(ctx, parent) - if err != nil { - return err - } - b.rollback(block) - return nil -} - -// stateByBlockNumber retrieves a state by a given blocknumber. -func (b *SimulatedBackend) stateByBlockNumber(ctx context.Context, blockNumber *big.Int) (*state.StateDB, error) { - if blockNumber == nil || blockNumber.Cmp(b.blockchain.CurrentBlock().Number) == 0 { - return b.blockchain.State() - } - block, err := b.blockByNumber(ctx, blockNumber) - if err != nil { - return nil, err - } - return b.blockchain.StateAt(block.Root()) -} - -// CodeAt returns the code associated with a certain account in the blockchain. -func (b *SimulatedBackend) CodeAt(ctx context.Context, contract common.Address, blockNumber *big.Int) ([]byte, error) { - b.mu.Lock() - defer b.mu.Unlock() - - stateDB, err := b.stateByBlockNumber(ctx, blockNumber) - if err != nil { - return nil, err - } - return stateDB.GetCode(contract), nil -} - -// CodeAtHash returns the code associated with a certain account in the blockchain. -func (b *SimulatedBackend) CodeAtHash(ctx context.Context, contract common.Address, blockHash common.Hash) ([]byte, error) { - b.mu.Lock() - defer b.mu.Unlock() - - header, err := b.headerByHash(blockHash) - if err != nil { - return nil, err - } - - stateDB, err := b.blockchain.StateAt(header.Root) - if err != nil { - return nil, err - } - - return stateDB.GetCode(contract), nil -} - -// BalanceAt returns the wei balance of a certain account in the blockchain. -func (b *SimulatedBackend) BalanceAt(ctx context.Context, contract common.Address, blockNumber *big.Int) (*big.Int, error) { - b.mu.Lock() - defer b.mu.Unlock() - - stateDB, err := b.stateByBlockNumber(ctx, blockNumber) - if err != nil { - return nil, err - } - return stateDB.GetBalance(contract), nil -} - -// NonceAt returns the nonce of a certain account in the blockchain. -func (b *SimulatedBackend) NonceAt(ctx context.Context, contract common.Address, blockNumber *big.Int) (uint64, error) { - b.mu.Lock() - defer b.mu.Unlock() - - stateDB, err := b.stateByBlockNumber(ctx, blockNumber) - if err != nil { - return 0, err - } - return stateDB.GetNonce(contract), nil -} - -// StorageAt returns the value of key in the storage of an account in the blockchain. -func (b *SimulatedBackend) StorageAt(ctx context.Context, contract common.Address, key common.Hash, blockNumber *big.Int) ([]byte, error) { - b.mu.Lock() - defer b.mu.Unlock() - - stateDB, err := b.stateByBlockNumber(ctx, blockNumber) - if err != nil { - return nil, err - } - val := stateDB.GetState(contract, key) - return val[:], nil -} - -// ForEachStorageAt returns func to read all keys, values in the storage -func (b *SimulatedBackend) ForEachStorageAt(ctx context.Context, contract common.Address, blockNumber *big.Int, f func(key, val common.Hash) bool) error { - b.mu.Lock() - defer b.mu.Unlock() - - if blockNumber != nil && blockNumber.Cmp(b.blockchain.CurrentBlock().Number) != 0 { - return errBlockNumberUnsupported - } - stateDB, _ := b.blockchain.State() - stateDB.ForEachStorage(contract, f) - return nil -} - -// TransactionReceipt returns the receipt of a transaction. -func (b *SimulatedBackend) TransactionReceipt(ctx context.Context, txHash common.Hash) (*types.Receipt, error) { - b.mu.Lock() - defer b.mu.Unlock() - - receipt, _, _, _ := rawdb.ReadReceipt(b.database, txHash, b.config) - if receipt == nil { - return nil, ethereum.ErrNotFound - } - return receipt, nil -} - -// TransactionByHash checks the pool of pending transactions in addition to the -// blockchain. The isPending return value indicates whether the transaction has been -// mined yet. Note that the transaction may not be part of the canonical chain even if -// it's not pending. -func (b *SimulatedBackend) TransactionByHash(ctx context.Context, txHash common.Hash) (*types.Transaction, bool, error) { - b.mu.Lock() - defer b.mu.Unlock() - - tx := b.pendingBlock.Transaction(txHash) - if tx != nil { - return tx, true, nil - } - tx, _, _, _ = rawdb.ReadTransaction(b.database, txHash) - if tx != nil { - return tx, false, nil - } - return nil, false, ethereum.ErrNotFound -} - -// BlockByHash retrieves a block based on the block hash. -func (b *SimulatedBackend) BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error) { - b.mu.Lock() - defer b.mu.Unlock() - - return b.blockByHash(ctx, hash) -} - -// blockByHash retrieves a block based on the block hash without Locking. -func (b *SimulatedBackend) blockByHash(ctx context.Context, hash common.Hash) (*types.Block, error) { - if hash == b.pendingBlock.Hash() { - return b.pendingBlock, nil - } - - block := b.blockchain.GetBlockByHash(hash) - if block != nil { - return block, nil - } - - return nil, errBlockDoesNotExist -} - -// BlockByNumber retrieves a block from the database by number, caching it -// (associated with its hash) if found. -func (b *SimulatedBackend) BlockByNumber(ctx context.Context, number *big.Int) (*types.Block, error) { - b.mu.Lock() - defer b.mu.Unlock() - - return b.blockByNumber(ctx, number) -} - -// blockByNumber retrieves a block from the database by number, caching it -// (associated with its hash) if found without Lock. -func (b *SimulatedBackend) blockByNumber(ctx context.Context, number *big.Int) (*types.Block, error) { - if number == nil || number.Cmp(b.pendingBlock.Number()) == 0 { - return b.blockByHash(ctx, b.blockchain.CurrentBlock().Hash()) - } - - block := b.blockchain.GetBlockByNumber(uint64(number.Int64())) - if block == nil { - return nil, errBlockDoesNotExist - } - - return block, nil -} - -// HeaderByHash returns a block header from the current canonical chain. -func (b *SimulatedBackend) HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error) { - b.mu.Lock() - defer b.mu.Unlock() - return b.headerByHash(hash) -} - -// headerByHash retrieves a header from the database by hash without Lock. -func (b *SimulatedBackend) headerByHash(hash common.Hash) (*types.Header, error) { - if hash == b.pendingBlock.Hash() { - return b.pendingBlock.Header(), nil - } - - header := b.blockchain.GetHeaderByHash(hash) - if header == nil { - return nil, errBlockDoesNotExist - } - - return header, nil -} - -// HeaderByNumber returns a block header from the current canonical chain. If number is -// nil, the latest known header is returned. -func (b *SimulatedBackend) HeaderByNumber(ctx context.Context, block *big.Int) (*types.Header, error) { - b.mu.Lock() - defer b.mu.Unlock() - - if block == nil || block.Cmp(b.pendingBlock.Number()) == 0 { - return b.blockchain.CurrentHeader(), nil - } - - return b.blockchain.GetHeaderByNumber(uint64(block.Int64())), nil -} - -// TransactionCount returns the number of transactions in a given block. -func (b *SimulatedBackend) TransactionCount(ctx context.Context, blockHash common.Hash) (uint, error) { - b.mu.Lock() - defer b.mu.Unlock() - - if blockHash == b.pendingBlock.Hash() { - return uint(b.pendingBlock.Transactions().Len()), nil - } - - block := b.blockchain.GetBlockByHash(blockHash) - if block == nil { - return uint(0), errBlockDoesNotExist - } - - return uint(block.Transactions().Len()), nil -} - -// TransactionInBlock returns the transaction for a specific block at a specific index. -func (b *SimulatedBackend) TransactionInBlock(ctx context.Context, blockHash common.Hash, index uint) (*types.Transaction, error) { - b.mu.Lock() - defer b.mu.Unlock() - - if blockHash == b.pendingBlock.Hash() { - transactions := b.pendingBlock.Transactions() - if uint(len(transactions)) < index+1 { - return nil, errTransactionDoesNotExist - } - - return transactions[index], nil - } - - block := b.blockchain.GetBlockByHash(blockHash) - if block == nil { - return nil, errBlockDoesNotExist - } - - transactions := block.Transactions() - if uint(len(transactions)) < index+1 { - return nil, errTransactionDoesNotExist - } - - return transactions[index], nil -} - -// PendingCodeAt returns the code associated with an account in the pending state. -func (b *SimulatedBackend) PendingCodeAt(ctx context.Context, contract common.Address) ([]byte, error) { - b.mu.Lock() - defer b.mu.Unlock() - - return b.pendingState.GetCode(contract), nil -} - -func newRevertError(result *core.ExecutionResult) *revertError { - reason, errUnpack := abi.UnpackRevert(result.Revert()) - err := errors.New("execution reverted") - if errUnpack == nil { - err = fmt.Errorf("execution reverted: %v", reason) - } - return &revertError{ - error: err, - reason: hexutil.Encode(result.Revert()), - } -} - -// revertError is an API error that encompasses an EVM revert with JSON error -// code and a binary data blob. -type revertError struct { - error - reason string // revert reason hex encoded -} - -// ErrorCode returns the JSON error code for a revert. -// See: https://github.com/ethereum/wiki/wiki/JSON-RPC-Error-Codes-Improvement-Proposal -func (e *revertError) ErrorCode() int { - return 3 -} - -// ErrorData returns the hex encoded revert reason. -func (e *revertError) ErrorData() interface{} { - return e.reason -} - -// CallContract executes a contract call. -func (b *SimulatedBackend) CallContract(ctx context.Context, call ethereum.CallMsg, blockNumber *big.Int) ([]byte, error) { - b.mu.Lock() - defer b.mu.Unlock() - - if blockNumber != nil && blockNumber.Cmp(b.blockchain.CurrentBlock().Number) != 0 { - return nil, errBlockNumberUnsupported - } - return b.callContractAtHead(ctx, call) -} - -// CallContractAtHash executes a contract call on a specific block hash. -func (b *SimulatedBackend) CallContractAtHash(ctx context.Context, call ethereum.CallMsg, blockHash common.Hash) ([]byte, error) { - b.mu.Lock() - defer b.mu.Unlock() - - if blockHash != b.blockchain.CurrentBlock().Hash() { - return nil, errBlockHashUnsupported - } - return b.callContractAtHead(ctx, call) -} - -// callContractAtHead executes a contract call against the latest block state. -func (b *SimulatedBackend) callContractAtHead(ctx context.Context, call ethereum.CallMsg) ([]byte, error) { - stateDB, err := b.blockchain.State() - if err != nil { - return nil, err - } - header := b.blockchain.CurrentBlock() - res, err := b.callContract(ctx, call, header, stateDB) - if err != nil { - return nil, err - } - // If the result contains a revert reason, try to unpack and return it. - if len(res.Revert()) > 0 { - return nil, newRevertError(res) - } - return res.Return(), res.Err -} - -// PendingCallContract executes a contract call on the pending state. -func (b *SimulatedBackend) PendingCallContract(ctx context.Context, call ethereum.CallMsg) ([]byte, error) { - b.mu.Lock() - defer b.mu.Unlock() - defer b.pendingState.RevertToSnapshot(b.pendingState.Snapshot()) - - res, err := b.callContract(ctx, call, b.pendingBlock.Header(), b.pendingState) - if err != nil { - return nil, err - } - // If the result contains a revert reason, try to unpack and return it. - if len(res.Revert()) > 0 { - return nil, newRevertError(res) - } - return res.Return(), res.Err -} - -// PendingNonceAt implements PendingStateReader.PendingNonceAt, retrieving -// the nonce currently pending for the account. -func (b *SimulatedBackend) PendingNonceAt(ctx context.Context, account common.Address) (uint64, error) { - b.mu.Lock() - defer b.mu.Unlock() - - return b.pendingState.GetOrNewStateObject(account).Nonce(), nil -} - -// SuggestGasPrice implements ContractTransactor.SuggestGasPrice. Since the simulated -// chain doesn't have miners, we just return a gas price of 1 for any call. -func (b *SimulatedBackend) SuggestGasPrice(ctx context.Context) (*big.Int, error) { - b.mu.Lock() - defer b.mu.Unlock() - - if b.pendingBlock.Header().BaseFee != nil { - return b.pendingBlock.Header().BaseFee, nil - } - return big.NewInt(1), nil -} - -// SuggestGasTipCap implements ContractTransactor.SuggestGasTipCap. Since the simulated -// chain doesn't have miners, we just return a gas tip of 1 for any call. -func (b *SimulatedBackend) SuggestGasTipCap(ctx context.Context) (*big.Int, error) { - return big.NewInt(1), nil -} - -// EstimateGas executes the requested code against the currently pending block/state and -// returns the used amount of gas. -func (b *SimulatedBackend) EstimateGas(ctx context.Context, call ethereum.CallMsg) (uint64, error) { - b.mu.Lock() - defer b.mu.Unlock() - - // Determine the lowest and highest possible gas limits to binary search in between - var ( - lo uint64 = params.TxGas - 1 - hi uint64 - cap uint64 - ) - if call.Gas >= params.TxGas { - hi = call.Gas - } else { - hi = b.pendingBlock.GasLimit() - } - cap = hi - - // Create a helper to check if a gas allowance results in an executable transaction - executable := func(gas uint64) (bool, *core.ExecutionResult, error) { - call.Gas = gas - - snapshot := b.pendingState.Snapshot() - res, err := b.callContract(ctx, call, b.pendingBlock.Header(), b.pendingState) - b.pendingState.RevertToSnapshot(snapshot) - - if err != nil { - if errors.Is(err, core.ErrIntrinsicGas) { - return true, nil, nil // Special case, raise gas limit - } - return true, nil, err // Bail out - } - return res.Failed(), res, nil - } - // Execute the binary search and hone in on an executable gas limit - for lo+1 < hi { - mid := (hi + lo) / 2 - failed, _, err := executable(mid) - - // If the error is not nil(consensus error), it means the provided message - // call or transaction will never be accepted no matter how much gas it is - // assigned. Return the error directly, don't struggle any more - if err != nil { - return 0, err - } - if failed { - lo = mid - } else { - hi = mid - } - } - // Reject the transaction as invalid if it still fails at the highest allowance - if hi == cap { - failed, result, err := executable(hi) - if err != nil { - return 0, err - } - if failed { - if result != nil && !errors.Is(result.Err, vm.ErrOutOfGas) { - if len(result.Revert()) > 0 { - return 0, newRevertError(result) - } - return 0, result.Err - } - // Otherwise, the specified gas cap is too low - return 0, fmt.Errorf("gas required exceeds allowance (%d)", cap) - } - } - return hi, nil -} - -// callContract implements common code between normal and pending contract calls. -// state is modified during execution, make sure to copy it if necessary. -func (b *SimulatedBackend) callContract(ctx context.Context, call ethereum.CallMsg, block *types.Header, stateDB *state.StateDB) (*core.ExecutionResult, error) { - // Gas prices post 1559 need to be initialized - if call.GasPrice != nil && (call.GasFeeCap != nil || call.GasTipCap != nil) { - return nil, errors.New("both gasPrice and (maxFeePerGas or maxPriorityFeePerGas) specified") - } - head := b.blockchain.CurrentHeader() - if !b.blockchain.Config().IsEIP1559(head.Number) { - // If there's no basefee, then it must be a non-1559 execution - if call.GasPrice == nil { - call.GasPrice = new(big.Int) - } - call.GasFeeCap, call.GasTipCap = call.GasPrice, call.GasPrice - } else { - // A basefee is provided, necessitating 1559-type execution - if call.GasPrice != nil { - // User specified the legacy gas field, convert to 1559 gas typing - call.GasFeeCap, call.GasTipCap = call.GasPrice, call.GasPrice - } else { - // User specified 1559 gas fields (or none), use those - if call.GasFeeCap == nil { - call.GasFeeCap = new(big.Int) - } - if call.GasTipCap == nil { - call.GasTipCap = new(big.Int) - } - // Backfill the legacy gasPrice for EVM execution, unless we're all zeroes - call.GasPrice = new(big.Int) - if call.GasFeeCap.BitLen() > 0 || call.GasTipCap.BitLen() > 0 { - call.GasPrice = new(big.Int).Add(call.GasTipCap, head.BaseFee) - if call.GasPrice.Cmp(call.GasFeeCap) > 0 { - call.GasPrice.Set(call.GasFeeCap) - } - } - } - } - // Ensure message is initialized properly. - if call.Gas == 0 { - call.Gas = 10 * head.GasLimit - } - if call.Value == nil { - call.Value = new(big.Int) - } - - // Set infinite balance to the fake caller account. - from := stateDB.GetOrNewStateObject(call.From) - from.SetBalance(math.MaxBig256) - - // Execute the call. - msg := &core.Message{ - From: call.From, - To: call.To, - Value: call.Value, - GasLimit: call.Gas, - GasPrice: call.GasPrice, - GasFeeCap: call.GasFeeCap, - GasTipCap: call.GasTipCap, - Data: call.Data, - AccessList: call.AccessList, - SkipNonceChecks: true, - SkipFromEOACheck: true, - } - feeCapacity := stateDB.GetTRC21FeeCapacityFromState() - if msg.To != nil { - if value, ok := feeCapacity[*msg.To]; ok { - msg.BalanceTokenFee = value - } - } - - // Create a new environment which holds all relevant information - // about the transaction and calling mechanisms. - txContext := core.NewEVMTxContext(msg) - evmContext := core.NewEVMBlockContext(block, b.blockchain, nil) - // Create a new environment which holds all relevant information - // about the transaction and calling mechanisms. - evm := vm.NewEVM(evmContext, stateDB, nil, b.config, vm.Config{NoBaseFee: true}) - evm.SetTxContext(txContext) - gaspool := new(core.GasPool).AddGas(gomath.MaxUint64) - return core.NewStateTransition(evm, msg, gaspool).TransitionDb(common.Address{}) -} - -// SendTransaction updates the pending block to include the given transaction. -func (b *SimulatedBackend) SendTransaction(ctx context.Context, tx *types.Transaction) error { - b.mu.Lock() - defer b.mu.Unlock() - - // Get the last block - block, err := b.blockByHash(ctx, b.pendingBlock.ParentHash()) - if err != nil { - return errors.New("could not fetch parent") - } - // Check transaction validity - signer := types.MakeSigner(b.blockchain.Config(), block.Number()) - sender, err := types.Sender(signer, tx) - if err != nil { - return fmt.Errorf("invalid transaction: %v", err) - } - nonce := b.pendingState.GetNonce(sender) - if tx.Nonce() != nonce { - return fmt.Errorf("invalid transaction nonce: got %d, want %d", tx.Nonce(), nonce) - } - // Include tx in chain - blocks, receipts := core.GenerateChain(b.config, block, b.blockchain.Engine(), b.database, 1, func(number int, block *core.BlockGen) { - for _, tx := range b.pendingBlock.Transactions() { - block.AddTxWithChain(b.blockchain, tx) - } - block.AddTxWithChain(b.blockchain, tx) - }) - stateDB, err := b.blockchain.State() - if err != nil { - return err - } - b.pendingBlock = blocks[0] - b.pendingState, _ = state.New(b.pendingBlock.Root(), stateDB.Database()) - b.pendingReceipts = receipts[0] - return nil -} - -// FilterLogs executes a log filter operation, blocking during execution and -// returning all the results in one batch. -// -// TODO(karalabe): Deprecate when the subscription one can return past data too. -func (b *SimulatedBackend) FilterLogs(ctx context.Context, query ethereum.FilterQuery) ([]types.Log, error) { - var filter *filters.Filter - if query.BlockHash != nil { - // Block filter requested, construct a single-shot filter - filter = b.filterSystem.NewBlockFilter(*query.BlockHash, query.Addresses, query.Topics) - } else { - // Initialize unset filter boundaried to run from genesis to chain head - from := rpc.EarliestBlockNumber.Int64() - if query.FromBlock != nil { - from = query.FromBlock.Int64() - } - to := rpc.LatestBlockNumber.Int64() - if query.ToBlock != nil { - to = query.ToBlock.Int64() - } - // Construct the range filter - filter = b.filterSystem.NewRangeFilter(from, to, query.Addresses, query.Topics, 0) - } - // Run the filter and return all the logs - logs, err := filter.Logs(ctx) - if err != nil { - return nil, err - } - res := make([]types.Log, len(logs)) - for i, log := range logs { - res[i] = *log - } - return res, nil -} - -// SubscribeFilterLogs creates a background log filtering operation, returning a -// subscription immediately, which can be used to stream the found events. -func (b *SimulatedBackend) SubscribeFilterLogs(ctx context.Context, query ethereum.FilterQuery, ch chan<- types.Log) (ethereum.Subscription, error) { - // Subscribe to contract events - sink := make(chan []*types.Log) - - sub, err := b.events.SubscribeLogs(query, sink) - if err != nil { - return nil, err - } - // Since we're getting logs in batches, we need to flatten them into a plain stream - return event.NewSubscription(func(quit <-chan struct{}) error { - defer sub.Unsubscribe() - for { - select { - case logs := <-sink: - for _, log := range logs { - select { - case ch <- *log: - case err := <-sub.Err(): - return err - case <-quit: - return nil - } - } - case err := <-sub.Err(): - return err - case <-quit: - return nil - } - } - }), nil -} - -// SubscribeNewHead returns an event subscription for a new header. -func (b *SimulatedBackend) SubscribeNewHead(ctx context.Context, ch chan<- *types.Header) (ethereum.Subscription, error) { - // subscribe to a new head - sink := make(chan *types.Header) - sub := b.events.SubscribeNewHeads(sink) - - return event.NewSubscription(func(quit <-chan struct{}) error { - defer sub.Unsubscribe() - for { - select { - case head := <-sink: - select { - case ch <- head: - case err := <-sub.Err(): - return err - case <-quit: - return nil - } - case err := <-sub.Err(): - return err - case <-quit: - return nil - } - } - }), nil -} - -// AdjustTime adds a time shift to the simulated clock. -// It can only be called on empty blocks. -func (b *SimulatedBackend) AdjustTime(adjustment time.Duration) error { - b.mu.Lock() - defer b.mu.Unlock() - - if len(b.pendingBlock.Transactions()) != 0 { - return errors.New("could not adjust time on non-empty block") - } - // Get the last block - block := b.blockchain.GetBlockByHash(b.pendingBlock.ParentHash()) - if block == nil { - return errors.New("could not find parent") - } - - blocks, _ := core.GenerateChain(b.config, block, b.blockchain.Engine(), b.database, 1, func(number int, block *core.BlockGen) { - block.OffsetTime(int64(adjustment.Seconds())) - }) - stateDB, _ := b.blockchain.State() - - b.pendingBlock = blocks[0] - b.pendingState, _ = state.New(b.pendingBlock.Root(), stateDB.Database()) - - return nil -} - -// Blockchain returns the underlying blockchain. -func (b *SimulatedBackend) BlockChain() *core.BlockChain { - return b.blockchain -} - -// filterBackend implements filters.Backend to support filtering for logs without -// taking bloom-bits acceleration structures into account. -type filterBackend struct { - db ethdb.Database - bc *core.BlockChain - backend *SimulatedBackend -} - -func (fb *filterBackend) ChainDb() ethdb.Database { return fb.db } -func (fb *filterBackend) EventMux() *event.TypeMux { panic("not supported") } - -func (fb *filterBackend) HeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Header, error) { - switch number { - case rpc.PendingBlockNumber: - if block := fb.backend.pendingBlock; block != nil { - return block.Header(), nil - } - return nil, nil - case rpc.LatestBlockNumber: - return fb.bc.CurrentHeader(), nil - case rpc.FinalizedBlockNumber: - if fb.bc.Config().XDPoS == nil { - return nil, errors.New("only XDPoS v2 supports committed block lookup") - } - current := fb.bc.CurrentBlock() - if fb.bc.Config().XDPoS.BlockConsensusVersion(current.Number) == params.ConsensusEngineVersion2 { - confirmedHash := fb.bc.Engine().(*XDPoS.XDPoS).EngineV2.GetLatestCommittedBlockInfo().Hash - return fb.bc.GetHeaderByHash(confirmedHash), nil - } - return nil, errors.New("only XDPoS v2 can lookup committed block") - default: - return fb.bc.GetHeaderByNumber(uint64(number.Int64())), nil - } -} - -func (fb *filterBackend) HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error) { - return fb.bc.GetHeaderByHash(hash), nil -} - -func (fb *filterBackend) GetReceipts(ctx context.Context, hash common.Hash) (types.Receipts, error) { - number := rawdb.ReadHeaderNumber(fb.db, hash) - if number == nil { - return nil, nil - } - return rawdb.ReadReceipts(fb.db, hash, *number, fb.bc.Config()), nil -} - -func (fb *filterBackend) GetBody(ctx context.Context, hash common.Hash, number rpc.BlockNumber) (*types.Body, error) { - if body := fb.bc.GetBody(hash); body != nil { - return body, nil - } - return nil, errors.New("block body not found") -} - -func (fb *filterBackend) PendingBlockAndReceipts() (*types.Block, types.Receipts) { - return fb.backend.pendingBlock, fb.backend.pendingReceipts -} - -func (fb *filterBackend) GetLogs(ctx context.Context, hash common.Hash, number uint64) ([][]*types.Log, error) { - logs := rawdb.ReadLogs(fb.db, hash, number) - return logs, nil -} - -func (fb *filterBackend) SubscribeNewTxsEvent(ch chan<- core.NewTxsEvent) event.Subscription { - return nullSubscription() -} - -func (fb *filterBackend) SubscribeChainEvent(ch chan<- core.ChainEvent) event.Subscription { - return fb.bc.SubscribeChainEvent(ch) -} - -func (fb *filterBackend) SubscribeRemovedLogsEvent(ch chan<- core.RemovedLogsEvent) event.Subscription { - return fb.bc.SubscribeRemovedLogsEvent(ch) -} - -func (fb *filterBackend) SubscribeLogsEvent(ch chan<- []*types.Log) event.Subscription { - return fb.bc.SubscribeLogsEvent(ch) -} - -func (fb *filterBackend) SubscribePendingLogsEvent(ch chan<- []*types.Log) event.Subscription { - return nullSubscription() -} - -func (fb *filterBackend) BloomStatus() (uint64, uint64) { return 4096, 0 } - -func (fb *filterBackend) ServiceFilter(ctx context.Context, ms *bloombits.MatcherSession) { - panic("not supported") -} - -func (fb *filterBackend) ChainConfig() *params.ChainConfig { - panic("not supported") -} - -func (fb *filterBackend) CurrentHeader() *types.Header { - panic("not supported") -} - -func nullSubscription() event.Subscription { - return event.NewSubscription(func(quit <-chan struct{}) error { - <-quit - return nil - }) -} diff --git a/accounts/abi/bind/backends/simulated_test.go b/accounts/abi/bind/backends/simulated_test.go deleted file mode 100644 index ce0f480d24..0000000000 --- a/accounts/abi/bind/backends/simulated_test.go +++ /dev/null @@ -1,1480 +0,0 @@ -// Copyright 2019 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 backends - -import ( - "bytes" - "context" - "errors" - "math/big" - "math/rand" - "reflect" - "strings" - "testing" - "time" - - ethereum "github.com/XinFinOrg/XDPoSChain" - "github.com/XinFinOrg/XDPoSChain/accounts/abi" - "github.com/XinFinOrg/XDPoSChain/accounts/abi/bind" - "github.com/XinFinOrg/XDPoSChain/common" - "github.com/XinFinOrg/XDPoSChain/core/types" - "github.com/XinFinOrg/XDPoSChain/crypto" - "github.com/XinFinOrg/XDPoSChain/params" -) - -func TestSimulatedBackend(t *testing.T) { - t.Parallel() - var gasLimit uint64 = 8000029 - key, _ := crypto.GenerateKey() //nolint:gosec - auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337)) - genAlloc := make(types.GenesisAlloc) - genAlloc[auth.From] = types.Account{Balance: big.NewInt(9223372036854775807)} - - config := *params.TestXDPoSMockChainConfig - config.Eip1559Block = big.NewInt(0) - sim := NewXDCSimulatedBackend(genAlloc, gasLimit, &config) - defer sim.Close() - - // should return an error if the tx is not found - txHash := common.HexToHash("2") - _, isPending, err := sim.TransactionByHash(context.Background(), txHash) - - if isPending { - t.Fatal("transaction should not be pending") - } - if err != ethereum.ErrNotFound { - t.Fatalf("err should be `ethereum.ErrNotFound` but received %v", err) - } - - // generate a transaction and confirm you can retrieve it - head, _ := sim.HeaderByNumber(context.Background(), nil) // Should be child's, good enough - gasPrice := new(big.Int).Add(head.BaseFee, big.NewInt(1)) - - code := `6060604052600a8060106000396000f360606040526008565b00` - var gas uint64 = 3000000 - tx := types.NewContractCreation(0, big.NewInt(0), gas, gasPrice, common.FromHex(code)) - tx, _ = types.SignTx(tx, types.HomesteadSigner{}, key) - - err = sim.SendTransaction(context.Background(), tx) - if err != nil { - t.Fatal("error sending transaction") - } - - txHash = tx.Hash() - _, isPending, err = sim.TransactionByHash(context.Background(), txHash) - if err != nil { - t.Fatalf("error getting transaction with hash: %v", txHash.String()) - } - if !isPending { - t.Fatal("transaction should have pending status") - } - - sim.Commit() - _, isPending, err = sim.TransactionByHash(context.Background(), txHash) - if err != nil { - t.Fatalf("error getting transaction with hash: %v", txHash.String()) - } - if isPending { - t.Fatal("transaction should not have pending status") - } -} - -var testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") - -// the following is based on this contract: -// -// contract T { -// event received(address sender, uint amount, bytes memo); -// event receivedAddr(address sender); -// -// function receive(bytes calldata memo) external payable returns (string memory res) { -// emit received(msg.sender, msg.value, memo); -// emit receivedAddr(msg.sender); -// return "hello world"; -// } -// } -const abiJSON = `[ { "constant": false, "inputs": [ { "name": "memo", "type": "bytes" } ], "name": "receive", "outputs": [ { "name": "res", "type": "string" } ], "payable": true, "stateMutability": "payable", "type": "function" }, { "anonymous": false, "inputs": [ { "indexed": false, "name": "sender", "type": "address" }, { "indexed": false, "name": "amount", "type": "uint256" }, { "indexed": false, "name": "memo", "type": "bytes" } ], "name": "received", "type": "event" }, { "anonymous": false, "inputs": [ { "indexed": false, "name": "sender", "type": "address" } ], "name": "receivedAddr", "type": "event" } ]` -const abiBin = `0x608060405234801561001057600080fd5b506102a0806100206000396000f3fe60806040526004361061003b576000357c010000000000000000000000000000000000000000000000000000000090048063a69b6ed014610040575b600080fd5b6100b76004803603602081101561005657600080fd5b810190808035906020019064010000000081111561007357600080fd5b82018360208201111561008557600080fd5b803590602001918460018302840111640100000000831117156100a757600080fd5b9091929391929390505050610132565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156100f75780820151818401526020810190506100dc565b50505050905090810190601f1680156101245780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b60607f75fd880d39c1daf53b6547ab6cb59451fc6452d27caa90e5b6649dd8293b9eed33348585604051808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001848152602001806020018281038252848482818152602001925080828437600081840152601f19601f8201169050808301925050509550505050505060405180910390a17f46923992397eac56cf13058aced2a1871933622717e27b24eabc13bf9dd329c833604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390a16040805190810160405280600b81526020017f68656c6c6f20776f726c6400000000000000000000000000000000000000000081525090509291505056fea165627a7a72305820ff0c57dad254cfeda48c9cfb47f1353a558bccb4d1bc31da1dae69315772d29e0029` -const deployedCode = `60806040526004361061003b576000357c010000000000000000000000000000000000000000000000000000000090048063a69b6ed014610040575b600080fd5b6100b76004803603602081101561005657600080fd5b810190808035906020019064010000000081111561007357600080fd5b82018360208201111561008557600080fd5b803590602001918460018302840111640100000000831117156100a757600080fd5b9091929391929390505050610132565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156100f75780820151818401526020810190506100dc565b50505050905090810190601f1680156101245780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b60607f75fd880d39c1daf53b6547ab6cb59451fc6452d27caa90e5b6649dd8293b9eed33348585604051808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001848152602001806020018281038252848482818152602001925080828437600081840152601f19601f8201169050808301925050509550505050505060405180910390a17f46923992397eac56cf13058aced2a1871933622717e27b24eabc13bf9dd329c833604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390a16040805190810160405280600b81526020017f68656c6c6f20776f726c6400000000000000000000000000000000000000000081525090509291505056fea165627a7a72305820ff0c57dad254cfeda48c9cfb47f1353a558bccb4d1bc31da1dae69315772d29e0029` - -// expected return value contains "hello world" -var expectedReturn = []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 11, 104, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} - -func simTestBackend(testAddr common.Address) *SimulatedBackend { - config := *params.TestXDPoSMockChainConfig - config.Eip1559Block = big.NewInt(0) - return NewXDCSimulatedBackend( - types.GenesisAlloc{ - testAddr: {Balance: big.NewInt(100000000000000000)}, - }, - 10000000, - &config, - ) -} - -func TestNewSimulatedBackend(t *testing.T) { - t.Parallel() - testAddr := crypto.PubkeyToAddress(testKey.PublicKey) - expectedBal := big.NewInt(100000000000000000) - sim := simTestBackend(testAddr) - defer sim.Close() - - stateDB, _ := sim.blockchain.State() - bal := stateDB.GetBalance(testAddr) - if bal.Cmp(expectedBal) != 0 { - t.Errorf("expected balance for test address not received. expected: %v actual: %v", expectedBal, bal) - } -} - -func TestAdjustTime(t *testing.T) { - t.Parallel() - sim := NewXDCSimulatedBackend( - types.GenesisAlloc{}, - 10000000, - params.TestXDPoSMockChainConfig, - ) - defer sim.Close() - - prevTime := sim.pendingBlock.Time() - if err := sim.AdjustTime(time.Second); err != nil { - t.Error(err) - } - newTime := sim.pendingBlock.Time() - - if newTime-prevTime != uint64(time.Second.Seconds()) { - t.Errorf("adjusted time not equal to a second. prev: %v, new: %v", prevTime, newTime) - } -} - -func TestNewAdjustTimeFail(t *testing.T) { - t.Parallel() - testAddr := crypto.PubkeyToAddress(testKey.PublicKey) - sim := simTestBackend(testAddr) - defer sim.Close() - - // Create tx and send - head, _ := sim.HeaderByNumber(context.Background(), nil) // Should be child's, good enough - gasPrice := new(big.Int).Add(head.BaseFee, big.NewInt(1)) - - tx := types.NewTransaction(0, testAddr, big.NewInt(1000), params.TxGas, gasPrice, nil) - signedTx, err := types.SignTx(tx, types.HomesteadSigner{}, testKey) - if err != nil { - t.Errorf("could not sign tx: %v", err) - } - sim.SendTransaction(context.Background(), signedTx) - // AdjustTime should fail on non-empty block - if err := sim.AdjustTime(time.Second); err == nil { - t.Error("Expected adjust time to error on non-empty block") - } - sim.Commit() - - prevTime := sim.pendingBlock.Time() - if err := sim.AdjustTime(time.Minute); err != nil { - t.Error(err) - } - newTime := sim.pendingBlock.Time() - if newTime-prevTime != uint64(time.Minute.Seconds()) { - t.Errorf("adjusted time not equal to a minute. prev: %v, new: %v", prevTime, newTime) - } - // Put a transaction after adjusting time - tx2 := types.NewTransaction(1, testAddr, big.NewInt(1000), params.TxGas, gasPrice, nil) - signedTx2, err := types.SignTx(tx2, types.HomesteadSigner{}, testKey) - if err != nil { - t.Errorf("could not sign tx: %v", err) - } - sim.SendTransaction(context.Background(), signedTx2) - sim.Commit() - newTime = sim.pendingBlock.Time() - if newTime-prevTime >= uint64(time.Minute.Seconds()) { - t.Errorf("time adjusted, but shouldn't be: prev: %v, new: %v", prevTime, newTime) - } -} - -func TestBalanceAt(t *testing.T) { - t.Parallel() - testAddr := crypto.PubkeyToAddress(testKey.PublicKey) - expectedBal := big.NewInt(100000000000000000) - sim := simTestBackend(testAddr) - defer sim.Close() - bgCtx := context.Background() - - bal, err := sim.BalanceAt(bgCtx, testAddr, nil) - if err != nil { - t.Error(err) - } - - if bal.Cmp(expectedBal) != 0 { - t.Errorf("expected balance for test address not received. expected: %v actual: %v", expectedBal, bal) - } -} - -func TestBlockByHash(t *testing.T) { - t.Parallel() - sim := NewXDCSimulatedBackend( - types.GenesisAlloc{}, - 10000000, - params.TestXDPoSMockChainConfig, - ) - defer sim.Close() - bgCtx := context.Background() - - block, err := sim.BlockByNumber(bgCtx, nil) - if err != nil { - t.Errorf("could not get recent block: %v", err) - } - blockByHash, err := sim.BlockByHash(bgCtx, block.Hash()) - if err != nil { - t.Errorf("could not get recent block: %v", err) - } - - if block.Hash() != blockByHash.Hash() { - t.Errorf("did not get expected block") - } -} - -func TestBlockByNumber(t *testing.T) { - t.Parallel() - sim := NewXDCSimulatedBackend( - types.GenesisAlloc{}, - 10000000, - params.TestXDPoSMockChainConfig, - ) - defer sim.Close() - bgCtx := context.Background() - - block, err := sim.BlockByNumber(bgCtx, nil) - if err != nil { - t.Errorf("could not get recent block: %v", err) - } - if block.NumberU64() != 0 { - t.Errorf("did not get most recent block, instead got block number %v", block.NumberU64()) - } - - // create one block - sim.Commit() - - block, err = sim.BlockByNumber(bgCtx, nil) - if err != nil { - t.Errorf("could not get recent block: %v", err) - } - if block.NumberU64() != 1 { - t.Errorf("did not get most recent block, instead got block number %v", block.NumberU64()) - } - - blockByNumber, err := sim.BlockByNumber(bgCtx, big.NewInt(1)) - if err != nil { - t.Errorf("could not get block by number: %v", err) - } - if blockByNumber.Hash() != block.Hash() { - t.Errorf("did not get the same block with height of 1 as before") - } -} - -func TestNonceAt(t *testing.T) { - t.Parallel() - testAddr := crypto.PubkeyToAddress(testKey.PublicKey) - - sim := simTestBackend(testAddr) - defer sim.Close() - bgCtx := context.Background() - - nonce, err := sim.NonceAt(bgCtx, testAddr, big.NewInt(0)) - if err != nil { - t.Errorf("could not get nonce for test addr: %v", err) - } - - if nonce != uint64(0) { - t.Errorf("received incorrect nonce. expected 0, got %v", nonce) - } - - // create a signed transaction to send - head, _ := sim.HeaderByNumber(context.Background(), nil) // Should be child's, good enough - gasPrice := new(big.Int).Add(head.BaseFee, big.NewInt(1)) - - tx := types.NewTransaction(nonce, testAddr, big.NewInt(1000), params.TxGas, gasPrice, nil) - signedTx, err := types.SignTx(tx, types.HomesteadSigner{}, testKey) - if err != nil { - t.Errorf("could not sign tx: %v", err) - } - - // send tx to simulated backend - err = sim.SendTransaction(bgCtx, signedTx) - if err != nil { - t.Errorf("could not add tx to pending block: %v", err) - } - sim.Commit() - - newNonce, err := sim.NonceAt(bgCtx, testAddr, big.NewInt(1)) - if err != nil { - t.Errorf("could not get nonce for test addr: %v", err) - } - - if newNonce != nonce+uint64(1) { - t.Errorf("received incorrect nonce. expected 1, got %v", nonce) - } - // create some more blocks - sim.Commit() - // Check that we can get data for an older block/state - newNonce, err = sim.NonceAt(bgCtx, testAddr, big.NewInt(1)) - if err != nil { - t.Fatalf("could not get nonce for test addr: %v", err) - } - if newNonce != nonce+uint64(1) { - t.Fatalf("received incorrect nonce. expected 1, got %v", nonce) - } -} - -func TestSendTransaction(t *testing.T) { - t.Parallel() - testAddr := crypto.PubkeyToAddress(testKey.PublicKey) - - sim := simTestBackend(testAddr) - defer sim.Close() - bgCtx := context.Background() - - // create a signed transaction to send - head, _ := sim.HeaderByNumber(context.Background(), nil) // Should be child's, good enough - gasPrice := new(big.Int).Add(head.BaseFee, big.NewInt(1)) - - tx := types.NewTransaction(uint64(0), testAddr, big.NewInt(1000), params.TxGas, gasPrice, nil) - signedTx, err := types.SignTx(tx, types.HomesteadSigner{}, testKey) - if err != nil { - t.Errorf("could not sign tx: %v", err) - } - - // send tx to simulated backend - err = sim.SendTransaction(bgCtx, signedTx) - if err != nil { - t.Errorf("could not add tx to pending block: %v", err) - } - sim.Commit() - - block, err := sim.BlockByNumber(bgCtx, big.NewInt(1)) - if err != nil { - t.Errorf("could not get block at height 1: %v", err) - } - - if signedTx.Hash() != block.Transactions()[0].Hash() { - t.Errorf("did not commit sent transaction. expected hash %v got hash %v", block.Transactions()[0].Hash(), signedTx.Hash()) - } -} - -func TestTransactionByHash(t *testing.T) { - t.Parallel() - testAddr := crypto.PubkeyToAddress(testKey.PublicKey) - - sim := simTestBackend(testAddr) - defer sim.Close() - bgCtx := context.Background() - - // create a signed transaction to send - head, _ := sim.HeaderByNumber(context.Background(), nil) // Should be child's, good enough - gasPrice := new(big.Int).Add(head.BaseFee, big.NewInt(1)) - - tx := types.NewTransaction(uint64(0), testAddr, big.NewInt(1000), params.TxGas, gasPrice, nil) - signedTx, err := types.SignTx(tx, types.HomesteadSigner{}, testKey) - if err != nil { - t.Errorf("could not sign tx: %v", err) - } - - // send tx to simulated backend - err = sim.SendTransaction(bgCtx, signedTx) - if err != nil { - t.Errorf("could not add tx to pending block: %v", err) - } - - // ensure tx is committed pending - receivedTx, pending, err := sim.TransactionByHash(bgCtx, signedTx.Hash()) - if err != nil { - t.Errorf("could not get transaction by hash %v: %v", signedTx.Hash(), err) - } - if !pending { - t.Errorf("expected transaction to be in pending state") - } - if receivedTx.Hash() != signedTx.Hash() { - t.Errorf("did not received committed transaction. expected hash %v got hash %v", signedTx.Hash(), receivedTx.Hash()) - } - - sim.Commit() - - // ensure tx is not and committed pending - receivedTx, pending, err = sim.TransactionByHash(bgCtx, signedTx.Hash()) - if err != nil { - t.Errorf("could not get transaction by hash %v: %v", signedTx.Hash(), err) - } - if pending { - t.Errorf("expected transaction to not be in pending state") - } - if receivedTx.Hash() != signedTx.Hash() { - t.Errorf("did not received committed transaction. expected hash %v got hash %v", signedTx.Hash(), receivedTx.Hash()) - } -} - -func TestEstimateGas(t *testing.T) { - t.Parallel() - /* - pragma solidity ^0.6.4; - contract GasEstimation { - function PureRevert() public { revert(); } - function Revert() public { revert("revert reason");} - function OOG() public { for (uint i = 0; ; i++) {}} - function Assert() public { assert(false);} - function Valid() public {} - } - */ - const contractAbi = "[{\"inputs\":[],\"name\":\"Assert\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"OOG\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"PureRevert\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"Revert\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"Valid\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]" - const contractBin = "0x60806040523480156100115760006000fd5b50610017565b61016e806100266000396000f3fe60806040523480156100115760006000fd5b506004361061005c5760003560e01c806350f6fe3414610062578063aa8b1d301461006c578063b9b046f914610076578063d8b9839114610080578063e09fface1461008a5761005c565b60006000fd5b61006a610094565b005b6100746100ad565b005b61007e6100b5565b005b6100886100c2565b005b610092610135565b005b6000600090505b5b808060010191505061009b565b505b565b60006000fd5b565b600015156100bf57fe5b5b565b6040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252600d8152602001807f72657665727420726561736f6e0000000000000000000000000000000000000081526020015060200191505060405180910390fd5b565b5b56fea2646970667358221220345bbcbb1a5ecf22b53a78eaebf95f8ee0eceff6d10d4b9643495084d2ec934a64736f6c63430006040033" - - key, _ := crypto.GenerateKey() - addr := crypto.PubkeyToAddress(key.PublicKey) - // opts := bind.NewKeyedTransactor(key) - opts, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337)) - - sim := NewXDCSimulatedBackend( - types.GenesisAlloc{addr: {Balance: big.NewInt(params.Ether)}}, - 10000000, - params.TestXDPoSMockChainConfig, - ) - defer sim.Close() - - parsed, _ := abi.JSON(strings.NewReader(contractAbi)) - contractAddr, _, _, _ := bind.DeployContract(opts, parsed, common.FromHex(contractBin), sim) - sim.Commit() - - var cases = []struct { - name string - message ethereum.CallMsg - expect uint64 - expectError error - expectData interface{} - }{ - {"plain transfer(valid)", ethereum.CallMsg{ - From: addr, - To: &addr, - Gas: 0, - GasPrice: big.NewInt(0), - Value: big.NewInt(1), - Data: nil, - }, params.TxGas, nil, nil}, - - {"plain transfer(invalid)", ethereum.CallMsg{ - From: addr, - To: &contractAddr, - Gas: 0, - GasPrice: big.NewInt(0), - Value: big.NewInt(1), - Data: nil, - }, 0, errors.New("execution reverted"), nil}, - - {"Revert", ethereum.CallMsg{ - From: addr, - To: &contractAddr, - Gas: 0, - GasPrice: big.NewInt(0), - Value: nil, - Data: common.Hex2Bytes("d8b98391"), - }, 0, errors.New("execution reverted: revert reason"), - "0x08c379a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000d72657665727420726561736f6e00000000000000000000000000000000000000"}, - - {"PureRevert", ethereum.CallMsg{ - From: addr, - To: &contractAddr, - Gas: 0, - GasPrice: big.NewInt(0), - Value: nil, - Data: common.Hex2Bytes("aa8b1d30"), - }, 0, errors.New("execution reverted"), nil}, - - {"OOG", ethereum.CallMsg{ - From: addr, - To: &contractAddr, - Gas: 100000, - GasPrice: big.NewInt(0), - Value: nil, - Data: common.Hex2Bytes("50f6fe34"), - }, 0, errors.New("gas required exceeds allowance (100000)"), nil}, - - {"Assert", ethereum.CallMsg{ - From: addr, - To: &contractAddr, - Gas: 100000, - GasPrice: big.NewInt(0), - Value: nil, - Data: common.Hex2Bytes("b9b046f9"), - }, 0, errors.New("invalid opcode: INVALID"), nil}, - - {"Valid", ethereum.CallMsg{ - From: addr, - To: &contractAddr, - Gas: 100000, - GasPrice: big.NewInt(0), - Value: nil, - Data: common.Hex2Bytes("e09fface"), - }, 21483, nil, nil}, - } - for _, c := range cases { - got, err := sim.EstimateGas(context.Background(), c.message) - if c.expectError != nil { - if err == nil { - t.Fatalf("Expect error, got nil") - } - if c.expectError.Error() != err.Error() { - t.Fatalf("Expect error, want %v, got %v", c.expectError, err) - } - if c.expectData != nil { - if err, ok := err.(*revertError); !ok { - t.Fatalf("Expect revert error, got %T", err) - } else if !reflect.DeepEqual(err.ErrorData(), c.expectData) { - t.Fatalf("Error data mismatch, want %v, got %v", c.expectData, err.ErrorData()) - } - } - continue - } - if got != c.expect { - t.Fatalf("Gas estimation mismatch, want %d, got %d", c.expect, got) - } - } -} - -func TestEstimateGasWithPrice(t *testing.T) { - t.Parallel() - key, _ := crypto.GenerateKey() - addr := crypto.PubkeyToAddress(key.PublicKey) - - sim := NewXDCSimulatedBackend( - types.GenesisAlloc{addr: {Balance: big.NewInt(params.Ether*2 + 2e17)}}, - 10000000, - params.TestXDPoSMockChainConfig, - ) - defer sim.Close() - - recipient := common.HexToAddress("deadbeef") - var cases = []struct { - name string - message ethereum.CallMsg - expect uint64 - expectError error - }{ - {"EstimateWithoutPrice", ethereum.CallMsg{ - From: addr, - To: &recipient, - Gas: 0, - GasPrice: big.NewInt(0), - Value: big.NewInt(100000000000), - Data: nil, - }, 21000, nil}, - - {"EstimateWithPrice", ethereum.CallMsg{ - From: addr, - To: &recipient, - Gas: 0, - GasPrice: big.NewInt(100000000000), - Value: big.NewInt(100000000000), - Data: nil, - }, 21000, nil}, - - {"EstimateWithVeryHighPrice", ethereum.CallMsg{ - From: addr, - To: &recipient, - Gas: 0, - GasPrice: big.NewInt(1e14), // gascost = 2.1ether - Value: big.NewInt(1e17), // the remaining balance for fee is 2.1ether - Data: nil, - }, 21000, nil}, - } - for i, c := range cases { - got, err := sim.EstimateGas(context.Background(), c.message) - if c.expectError != nil { - if err == nil { - t.Fatalf("test %d: expect error, got nil", i) - } - if c.expectError.Error() != err.Error() { - t.Fatalf("test %d: expect error, want %v, got %v", i, c.expectError, err) - } - continue - } - if got != c.expect { - t.Fatalf("test %d: gas estimation mismatch, want %d, got %d", i, c.expect, got) - } - } -} - -func TestHeaderByHash(t *testing.T) { - t.Parallel() - testAddr := crypto.PubkeyToAddress(testKey.PublicKey) - - sim := simTestBackend(testAddr) - defer sim.Close() - bgCtx := context.Background() - - header, err := sim.HeaderByNumber(bgCtx, nil) - if err != nil { - t.Errorf("could not get recent block: %v", err) - } - headerByHash, err := sim.HeaderByHash(bgCtx, header.Hash()) - if err != nil { - t.Errorf("could not get recent block: %v", err) - } - - if header.Hash() != headerByHash.Hash() { - t.Errorf("did not get expected block") - } -} - -func TestHeaderByNumber(t *testing.T) { - t.Parallel() - testAddr := crypto.PubkeyToAddress(testKey.PublicKey) - - sim := simTestBackend(testAddr) - defer sim.Close() - bgCtx := context.Background() - - latestBlockHeader, err := sim.HeaderByNumber(bgCtx, nil) - if err != nil { - t.Errorf("could not get header for tip of chain: %v", err) - } - if latestBlockHeader == nil { - t.Errorf("received a nil block header") - } else if latestBlockHeader.Number.Uint64() != uint64(0) { - t.Errorf("expected block header number 0, instead got %v", latestBlockHeader.Number.Uint64()) - } - - sim.Commit() - - latestBlockHeader, err = sim.HeaderByNumber(bgCtx, nil) - if err != nil { - t.Errorf("could not get header for blockheight of 1: %v", err) - } - - blockHeader, err := sim.HeaderByNumber(bgCtx, big.NewInt(1)) - if err != nil { - t.Errorf("could not get header for blockheight of 1: %v", err) - } - - if blockHeader.Hash() != latestBlockHeader.Hash() { - t.Errorf("block header and latest block header are not the same") - } - if blockHeader.Number.Int64() != int64(1) { - t.Errorf("did not get blockheader for block 1. instead got block %v", blockHeader.Number.Int64()) - } - - block, err := sim.BlockByNumber(bgCtx, big.NewInt(1)) - if err != nil { - t.Errorf("could not get block for blockheight of 1: %v", err) - } - - if block.Hash() != blockHeader.Hash() { - t.Errorf("block hash and block header hash do not match. expected %v, got %v", block.Hash(), blockHeader.Hash()) - } -} - -func TestTransactionCount(t *testing.T) { - t.Parallel() - testAddr := crypto.PubkeyToAddress(testKey.PublicKey) - - sim := simTestBackend(testAddr) - defer sim.Close() - bgCtx := context.Background() - currentBlock, err := sim.BlockByNumber(bgCtx, nil) - if err != nil || currentBlock == nil { - t.Error("could not get current block") - } - - count, err := sim.TransactionCount(bgCtx, currentBlock.Hash()) - if err != nil { - t.Error("could not get current block's transaction count") - } - - if count != 0 { - t.Errorf("expected transaction count of %v does not match actual count of %v", 0, count) - } - - // create a signed transaction to send - head, _ := sim.HeaderByNumber(context.Background(), nil) // Should be child's, good enough - gasPrice := new(big.Int).Add(head.BaseFee, big.NewInt(1)) - - tx := types.NewTransaction(uint64(0), testAddr, big.NewInt(1000), params.TxGas, gasPrice, nil) - signedTx, err := types.SignTx(tx, types.HomesteadSigner{}, testKey) - if err != nil { - t.Errorf("could not sign tx: %v", err) - } - - // send tx to simulated backend - err = sim.SendTransaction(bgCtx, signedTx) - if err != nil { - t.Errorf("could not add tx to pending block: %v", err) - } - - sim.Commit() - - lastBlock, err := sim.BlockByNumber(bgCtx, nil) - if err != nil { - t.Errorf("could not get header for tip of chain: %v", err) - } - - count, err = sim.TransactionCount(bgCtx, lastBlock.Hash()) - if err != nil { - t.Error("could not get current block's transaction count") - } - - if count != 1 { - t.Errorf("expected transaction count of %v does not match actual count of %v", 1, count) - } -} - -func TestTransactionInBlock(t *testing.T) { - t.Parallel() - testAddr := crypto.PubkeyToAddress(testKey.PublicKey) - - sim := simTestBackend(testAddr) - defer sim.Close() - bgCtx := context.Background() - - transaction, err := sim.TransactionInBlock(bgCtx, sim.pendingBlock.Hash(), uint(0)) - if err == nil && err != errTransactionDoesNotExist { - t.Errorf("expected a transaction does not exist error to be received but received %v", err) - } - if transaction != nil { - t.Errorf("expected transaction to be nil but received %v", transaction) - } - - // expect pending nonce to be 0 since account has not been used - pendingNonce, err := sim.PendingNonceAt(bgCtx, testAddr) - if err != nil { - t.Errorf("did not get the pending nonce: %v", err) - } - - if pendingNonce != uint64(0) { - t.Errorf("expected pending nonce of 0 got %v", pendingNonce) - } - - // create a signed transaction to send - head, _ := sim.HeaderByNumber(context.Background(), nil) // Should be child's, good enough - gasPrice := new(big.Int).Add(head.BaseFee, big.NewInt(1)) - - tx := types.NewTransaction(uint64(0), testAddr, big.NewInt(1000), params.TxGas, gasPrice, nil) - signedTx, err := types.SignTx(tx, types.HomesteadSigner{}, testKey) - if err != nil { - t.Errorf("could not sign tx: %v", err) - } - - // send tx to simulated backend - err = sim.SendTransaction(bgCtx, signedTx) - if err != nil { - t.Errorf("could not add tx to pending block: %v", err) - } - - sim.Commit() - - lastBlock, err := sim.BlockByNumber(bgCtx, nil) - if err != nil { - t.Errorf("could not get header for tip of chain: %v", err) - } - - transaction, err = sim.TransactionInBlock(bgCtx, lastBlock.Hash(), uint(1)) - if err == nil && err != errTransactionDoesNotExist { - t.Errorf("expected a transaction does not exist error to be received but received %v", err) - } - if transaction != nil { - t.Errorf("expected transaction to be nil but received %v", transaction) - } - - transaction, err = sim.TransactionInBlock(bgCtx, lastBlock.Hash(), uint(0)) - if err != nil { - t.Errorf("could not get transaction in the lastest block with hash %v: %v", lastBlock.Hash().String(), err) - } - - if signedTx.Hash().String() != transaction.Hash().String() { - t.Errorf("received transaction that did not match the sent transaction. expected hash %v, got hash %v", signedTx.Hash().String(), transaction.Hash().String()) - } -} - -func TestPendingNonceAt(t *testing.T) { - t.Parallel() - testAddr := crypto.PubkeyToAddress(testKey.PublicKey) - - sim := simTestBackend(testAddr) - defer sim.Close() - bgCtx := context.Background() - - // expect pending nonce to be 0 since account has not been used - pendingNonce, err := sim.PendingNonceAt(bgCtx, testAddr) - if err != nil { - t.Errorf("did not get the pending nonce: %v", err) - } - - if pendingNonce != uint64(0) { - t.Errorf("expected pending nonce of 0 got %v", pendingNonce) - } - - // create a signed transaction to send - head, _ := sim.HeaderByNumber(context.Background(), nil) // Should be child's, good enough - gasPrice := new(big.Int).Add(head.BaseFee, big.NewInt(1)) - - tx := types.NewTransaction(uint64(0), testAddr, big.NewInt(1000), params.TxGas, gasPrice, nil) - signedTx, err := types.SignTx(tx, types.HomesteadSigner{}, testKey) - if err != nil { - t.Errorf("could not sign tx: %v", err) - } - - // send tx to simulated backend - err = sim.SendTransaction(bgCtx, signedTx) - if err != nil { - t.Errorf("could not add tx to pending block: %v", err) - } - - // expect pending nonce to be 1 since account has submitted one transaction - pendingNonce, err = sim.PendingNonceAt(bgCtx, testAddr) - if err != nil { - t.Errorf("did not get the pending nonce: %v", err) - } - - if pendingNonce != uint64(1) { - t.Errorf("expected pending nonce of 1 got %v", pendingNonce) - } - - // make a new transaction with a nonce of 1 - tx = types.NewTransaction(uint64(1), testAddr, big.NewInt(1000), params.TxGas, gasPrice, nil) - signedTx, err = types.SignTx(tx, types.HomesteadSigner{}, testKey) - if err != nil { - t.Errorf("could not sign tx: %v", err) - } - err = sim.SendTransaction(bgCtx, signedTx) - if err != nil { - t.Errorf("could not send tx: %v", err) - } - - // expect pending nonce to be 2 since account now has two transactions - pendingNonce, err = sim.PendingNonceAt(bgCtx, testAddr) - if err != nil { - t.Errorf("did not get the pending nonce: %v", err) - } - - if pendingNonce != uint64(2) { - t.Errorf("expected pending nonce of 2 got %v", pendingNonce) - } -} - -func TestTransactionReceipt(t *testing.T) { - t.Parallel() - testAddr := crypto.PubkeyToAddress(testKey.PublicKey) - - sim := simTestBackend(testAddr) - defer sim.Close() - bgCtx := context.Background() - - // create a signed transaction to send - head, _ := sim.HeaderByNumber(context.Background(), nil) // Should be child's, good enough - gasPrice := new(big.Int).Add(head.BaseFee, big.NewInt(1)) - - tx := types.NewTransaction(uint64(0), testAddr, big.NewInt(1000), params.TxGas, gasPrice, nil) - signedTx, err := types.SignTx(tx, types.HomesteadSigner{}, testKey) - if err != nil { - t.Errorf("could not sign tx: %v", err) - } - - // send tx to simulated backend - err = sim.SendTransaction(bgCtx, signedTx) - if err != nil { - t.Errorf("could not add tx to pending block: %v", err) - } - sim.Commit() - - receipt, err := sim.TransactionReceipt(bgCtx, signedTx.Hash()) - if err != nil { - t.Errorf("could not get transaction receipt: %v", err) - } - - if receipt.ContractAddress != testAddr && receipt.TxHash != signedTx.Hash() { - t.Errorf("received receipt is not correct: %v", receipt) - } -} - -func TestSuggestGasPrice(t *testing.T) { - t.Parallel() - sim := NewXDCSimulatedBackend( - types.GenesisAlloc{}, - 10000000, - params.TestXDPoSMockChainConfig, - ) - defer sim.Close() - bgCtx := context.Background() - gasPrice, err := sim.SuggestGasPrice(bgCtx) - if err != nil { - t.Errorf("could not get gas price: %v", err) - } - baseFee := sim.pendingBlock.Header().BaseFee - if baseFee == nil { - baseFee = big.NewInt(1) - } - if gasPrice.Uint64() != baseFee.Uint64() { - t.Errorf("gas price was not expected value of %v. actual: %v", sim.pendingBlock.Header().BaseFee.Uint64(), gasPrice.Uint64()) - } -} - -func TestPendingCodeAt(t *testing.T) { - t.Parallel() - testAddr := crypto.PubkeyToAddress(testKey.PublicKey) - sim := simTestBackend(testAddr) - defer sim.Close() - bgCtx := context.Background() - code, err := sim.CodeAt(bgCtx, testAddr, nil) - if err != nil { - t.Errorf("could not get code at test addr: %v", err) - } - if len(code) != 0 { - t.Errorf("got code for account that does not have contract code") - } - - parsed, err := abi.JSON(strings.NewReader(abiJSON)) - if err != nil { - t.Errorf("could not get code at test addr: %v", err) - } - auth, _ := bind.NewKeyedTransactorWithChainID(testKey, big.NewInt(1337)) - contractAddr, tx, contract, err := bind.DeployContract(auth, parsed, common.FromHex(abiBin), sim) - if err != nil { - t.Errorf("could not deploy contract: %v tx: %v contract: %v", err, tx, contract) - } - - code, err = sim.PendingCodeAt(bgCtx, contractAddr) - if err != nil { - t.Errorf("could not get code at test addr: %v", err) - } - if len(code) == 0 { - t.Errorf("did not get code for account that has contract code") - } - // ensure code received equals code deployed - if !bytes.Equal(code, common.FromHex(deployedCode)) { - t.Errorf("code received did not match expected deployed code:\n expected %v\n actual %v", common.FromHex(deployedCode), code) - } -} - -func TestCodeAt(t *testing.T) { - t.Parallel() - testAddr := crypto.PubkeyToAddress(testKey.PublicKey) - sim := simTestBackend(testAddr) - defer sim.Close() - bgCtx := context.Background() - code, err := sim.CodeAt(bgCtx, testAddr, nil) - if err != nil { - t.Errorf("could not get code at test addr: %v", err) - } - if len(code) != 0 { - t.Errorf("got code for account that does not have contract code") - } - - parsed, err := abi.JSON(strings.NewReader(abiJSON)) - if err != nil { - t.Errorf("could not get code at test addr: %v", err) - } - auth, _ := bind.NewKeyedTransactorWithChainID(testKey, big.NewInt(1337)) - contractAddr, tx, contract, err := bind.DeployContract(auth, parsed, common.FromHex(abiBin), sim) - if err != nil { - t.Errorf("could not deploy contract: %v tx: %v contract: %v", err, tx, contract) - } - - sim.Commit() - code, err = sim.CodeAt(bgCtx, contractAddr, nil) - if err != nil { - t.Errorf("could not get code at test addr: %v", err) - } - if len(code) == 0 { - t.Errorf("did not get code for account that has contract code") - } - // ensure code received equals code deployed - if !bytes.Equal(code, common.FromHex(deployedCode)) { - t.Errorf("code received did not match expected deployed code:\n expected %v\n actual %v", common.FromHex(deployedCode), code) - } -} - -func TestCodeAtHash(t *testing.T) { - t.Parallel() - testAddr := crypto.PubkeyToAddress(testKey.PublicKey) - sim := simTestBackend(testAddr) - defer sim.Close() - bgCtx := context.Background() - code, err := sim.CodeAtHash(bgCtx, testAddr, sim.blockchain.CurrentHeader().Hash()) - if err != nil { - t.Errorf("could not get code at test addr: %v", err) - } - if len(code) != 0 { - t.Errorf("got code for account that does not have contract code") - } - - parsed, err := abi.JSON(strings.NewReader(abiJSON)) - if err != nil { - t.Errorf("could not get code at test addr: %v", err) - } - auth, _ := bind.NewKeyedTransactorWithChainID(testKey, big.NewInt(1337)) - contractAddr, tx, contract, err := bind.DeployContract(auth, parsed, common.FromHex(abiBin), sim) - if err != nil { - t.Errorf("could not deploy contract: %v tx: %v contract: %v", err, tx, contract) - } - - blockHash := sim.Commit() - code, err = sim.CodeAtHash(bgCtx, contractAddr, blockHash) - if err != nil { - t.Errorf("could not get code at test addr: %v", err) - } - if len(code) == 0 { - t.Errorf("did not get code for account that has contract code") - } - // ensure code received equals code deployed - if !bytes.Equal(code, common.FromHex(deployedCode)) { - t.Errorf("code received did not match expected deployed code:\n expected %v\n actual %v", common.FromHex(deployedCode), code) - } -} - -// When receive("X") is called with sender 0x00... and value 1, it produces this tx receipt: -// -// receipt{status=1 cgas=23949 bloom=00000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000040200000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 logs=[log: b6818c8064f645cd82d99b59a1a267d6d61117ef [75fd880d39c1daf53b6547ab6cb59451fc6452d27caa90e5b6649dd8293b9eed] 000000000000000000000000376c47978271565f56deb45495afa69e59c16ab200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000158 9ae378b6d4409eada347a5dc0c180f186cb62dc68fcc0f043425eb917335aa28 0 95d429d309bb9d753954195fe2d69bd140b4ae731b9b5b605c34323de162cf00 0]} -func TestPendingAndCallContract(t *testing.T) { - t.Parallel() - testAddr := crypto.PubkeyToAddress(testKey.PublicKey) - sim := simTestBackend(testAddr) - defer sim.Close() - bgCtx := context.Background() - - parsed, err := abi.JSON(strings.NewReader(abiJSON)) - if err != nil { - t.Errorf("could not get code at test addr: %v", err) - } - contractAuth, _ := bind.NewKeyedTransactorWithChainID(testKey, big.NewInt(1337)) - addr, _, _, err := bind.DeployContract(contractAuth, parsed, common.FromHex(abiBin), sim) - if err != nil { - t.Errorf("could not deploy contract: %v", err) - } - - input, err := parsed.Pack("receive", []byte("X")) - if err != nil { - t.Errorf("could not pack receive function on contract: %v", err) - } - - // make sure you can call the contract in pending state - res, err := sim.PendingCallContract(bgCtx, ethereum.CallMsg{ - From: testAddr, - To: &addr, - Data: input, - }) - if err != nil { - t.Errorf("could not call receive method on contract: %v", err) - } - if len(res) == 0 { - t.Errorf("result of contract call was empty: %v", res) - } - - // while comparing against the byte array is more exact, also compare against the human readable string for readability - if !bytes.Equal(res, expectedReturn) || !strings.Contains(string(res), "hello world") { - t.Errorf("response from calling contract was expected to be 'hello world' instead received %v", string(res)) - } - - blockHash := sim.Commit() - - // make sure you can call the contract - res, err = sim.CallContract(bgCtx, ethereum.CallMsg{ - From: testAddr, - To: &addr, - Data: input, - }, nil) - if err != nil { - t.Errorf("could not call receive method on contract: %v", err) - } - if len(res) == 0 { - t.Errorf("result of contract call was empty: %v", res) - } - - if !bytes.Equal(res, expectedReturn) || !strings.Contains(string(res), "hello world") { - t.Errorf("response from calling contract was expected to be 'hello world' instead received %v", string(res)) - } - - // make sure you can call the contract by hash - res, err = sim.CallContractAtHash(bgCtx, ethereum.CallMsg{ - From: testAddr, - To: &addr, - Data: input, - }, blockHash) - if err != nil { - t.Errorf("could not call receive method on contract: %v", err) - } - if len(res) == 0 { - t.Errorf("result of contract call was empty: %v", res) - } - - if !bytes.Equal(res, expectedReturn) || !strings.Contains(string(res), "hello world") { - t.Errorf("response from calling contract was expected to be 'hello world' instead received %v", string(res)) - } -} - -// This test is based on the following contract: -/* -contract Reverter { - function revertString() public pure{ - require(false, "some error"); - } - function revertNoString() public pure { - require(false, ""); - } - function revertASM() public pure { - assembly { - revert(0x0, 0x0) - } - } - function noRevert() public pure { - assembly { - // Assembles something that looks like require(false, "some error") but is not reverted - mstore(0x0, 0x08c379a000000000000000000000000000000000000000000000000000000000) - mstore(0x4, 0x0000000000000000000000000000000000000000000000000000000000000020) - mstore(0x24, 0x000000000000000000000000000000000000000000000000000000000000000a) - mstore(0x44, 0x736f6d65206572726f7200000000000000000000000000000000000000000000) - return(0x0, 0x64) - } - } -} -*/ -func TestCallContractRevert(t *testing.T) { - t.Parallel() - testAddr := crypto.PubkeyToAddress(testKey.PublicKey) - sim := simTestBackend(testAddr) - defer sim.Close() - bgCtx := context.Background() - - reverterABI := `[{"inputs": [],"name": "noRevert","outputs": [],"stateMutability": "pure","type": "function"},{"inputs": [],"name": "revertASM","outputs": [],"stateMutability": "pure","type": "function"},{"inputs": [],"name": "revertNoString","outputs": [],"stateMutability": "pure","type": "function"},{"inputs": [],"name": "revertString","outputs": [],"stateMutability": "pure","type": "function"}]` - reverterBin := "608060405234801561001057600080fd5b506101d3806100206000396000f3fe608060405234801561001057600080fd5b506004361061004c5760003560e01c80634b409e01146100515780639b340e361461005b5780639bd6103714610065578063b7246fc11461006f575b600080fd5b610059610079565b005b6100636100ca565b005b61006d6100cf565b005b610077610145565b005b60006100c8576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526000815260200160200191505060405180910390fd5b565b600080fd5b6000610143576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252600a8152602001807f736f6d65206572726f720000000000000000000000000000000000000000000081525060200191505060405180910390fd5b565b7f08c379a0000000000000000000000000000000000000000000000000000000006000526020600452600a6024527f736f6d65206572726f720000000000000000000000000000000000000000000060445260646000f3fea2646970667358221220cdd8af0609ec4996b7360c7c780bad5c735740c64b1fffc3445aa12d37f07cb164736f6c63430006070033" - - parsed, err := abi.JSON(strings.NewReader(reverterABI)) - if err != nil { - t.Errorf("could not get code at test addr: %v", err) - } - contractAuth, _ := bind.NewKeyedTransactorWithChainID(testKey, big.NewInt(1337)) - addr, _, _, err := bind.DeployContract(contractAuth, parsed, common.FromHex(reverterBin), sim) - if err != nil { - t.Errorf("could not deploy contract: %v", err) - } - - inputs := make(map[string]interface{}, 3) - inputs["revertASM"] = nil - inputs["revertNoString"] = "" - inputs["revertString"] = "some error" - - call := make([]func([]byte) ([]byte, error), 2) - call[0] = func(input []byte) ([]byte, error) { - return sim.PendingCallContract(bgCtx, ethereum.CallMsg{ - From: testAddr, - To: &addr, - Data: input, - }) - } - call[1] = func(input []byte) ([]byte, error) { - return sim.CallContract(bgCtx, ethereum.CallMsg{ - From: testAddr, - To: &addr, - Data: input, - }, nil) - } - - // Run pending calls then commit - for _, cl := range call { - for key, val := range inputs { - input, err := parsed.Pack(key) - if err != nil { - t.Errorf("could not pack %v function on contract: %v", key, err) - } - - res, err := cl(input) - if err == nil { - t.Errorf("call to %v was not reverted", key) - } - if res != nil { - t.Errorf("result from %v was not nil: %v", key, res) - } - if val != nil { - rerr, ok := err.(*revertError) - if !ok { - t.Errorf("expect revert error") - } - if rerr.Error() != "execution reverted: "+val.(string) { - t.Errorf("error was malformed: got %v want %v", rerr.Error(), val) - } - } else { - // revert(0x0,0x0) - if err.Error() != "execution reverted" { - t.Errorf("error was malformed: got %v want %v", err, "execution reverted") - } - } - } - input, err := parsed.Pack("noRevert") - if err != nil { - t.Errorf("could not pack noRevert function on contract: %v", err) - } - res, err := cl(input) - if err != nil { - t.Error("call to noRevert was reverted") - } - if res == nil { - t.Errorf("result from noRevert was nil") - } - sim.Commit() - } -} - -// TestFork check that the chain length after a reorg is correct. -// Steps: -// 1. Save the current block which will serve as parent for the fork. -// 2. Mine n blocks with n ∈ [0, 20]. -// 3. Assert that the chain length is n. -// 4. Fork by using the parent block as ancestor. -// 5. Mine n+1 blocks which should trigger a reorg. -// 6. Assert that the chain length is n+1. -// Since Commit() was called 2n+1 times in total, -// having a chain length of just n+1 means that a reorg occurred. -func TestFork(t *testing.T) { - t.Parallel() - testAddr := crypto.PubkeyToAddress(testKey.PublicKey) - sim := simTestBackend(testAddr) - defer sim.Close() - // 1. - parent := sim.blockchain.CurrentBlock() - // 2. - n := int(rand.Int31n(21)) - for i := 0; i < n; i++ { - sim.Commit() - } - // 3. - if sim.blockchain.CurrentBlock().Number.Uint64() != uint64(n) { - t.Error("wrong chain length") - } - // 4. - sim.Fork(context.Background(), parent.Hash()) - // 5. - for i := 0; i < n+1; i++ { - sim.Commit() - } - // 6. - if sim.blockchain.CurrentBlock().Number.Uint64() != uint64(n+1) { - t.Error("wrong chain length") - } -} - -/* -Example contract to test event emission: - - pragma solidity >=0.7.0 <0.9.0; - contract Callable { - event Called(); - function Call() public { emit Called(); } - } -*/ -const callableAbi = "[{\"anonymous\":false,\"inputs\":[],\"name\":\"Called\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"Call\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]" - -const callableBin = "6080604052348015600f57600080fd5b5060998061001e6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c806334e2292114602d575b600080fd5b60336035565b005b7f81fab7a4a0aa961db47eefc81f143a5220e8c8495260dd65b1356f1d19d3c7b860405160405180910390a156fea2646970667358221220029436d24f3ac598ceca41d4d712e13ced6d70727f4cdc580667de66d2f51d8b64736f6c63430008010033" - -// TestForkLogsReborn check that the simulated reorgs -// correctly remove and reborn logs. -// Steps: -// 1. Deploy the Callable contract. -// 2. Set up an event subscription. -// 3. Save the current block which will serve as parent for the fork. -// 4. Send a transaction. -// 5. Check that the event was included. -// 6. Fork by using the parent block as ancestor. -// 7. Mine two blocks to trigger a reorg. -// 8. Check that the event was removed. -// 9. Re-send the transaction and mine a block. -// 10. Check that the event was reborn. -func TestForkLogsReborn(t *testing.T) { - t.Parallel() - testAddr := crypto.PubkeyToAddress(testKey.PublicKey) - sim := simTestBackend(testAddr) - defer sim.Close() - // 1. - parsed, _ := abi.JSON(strings.NewReader(callableAbi)) - auth, _ := bind.NewKeyedTransactorWithChainID(testKey, big.NewInt(1337)) - _, _, contract, err := bind.DeployContract(auth, parsed, common.FromHex(callableBin), sim) - if err != nil { - t.Errorf("deploying contract: %v", err) - } - sim.Commit() - // 2. - logs, sub, err := contract.WatchLogs(nil, "Called") - if err != nil { - t.Errorf("watching logs: %v", err) - } - defer sub.Unsubscribe() - // 3. - parent := sim.blockchain.CurrentBlock() - // 4. - tx, err := contract.Transact(auth, "Call") - if err != nil { - t.Errorf("transacting: %v", err) - } - sim.Commit() - // 5. - log := <-logs - if log.TxHash != tx.Hash() { - t.Error("wrong event tx hash") - } - if log.Removed { - t.Error("Event should be included") - } - // 6. - if err := sim.Fork(context.Background(), parent.Hash()); err != nil { - t.Errorf("forking: %v", err) - } - // 7. - sim.Commit() - sim.Commit() - // 8. - log = <-logs - if log.TxHash != tx.Hash() { - t.Error("wrong event tx hash") - } - if !log.Removed { - t.Error("Event should be removed") - } - // 9. - if err := sim.SendTransaction(context.Background(), tx); err != nil { - t.Errorf("sending transaction: %v", err) - } - sim.Commit() - // 10. - log = <-logs - if log.TxHash != tx.Hash() { - t.Error("wrong event tx hash") - } - if log.Removed { - t.Error("Event should be included") - } -} - -// TestForkResendTx checks that re-sending a TX after a fork -// is possible and does not cause a "nonce mismatch" panic. -// Steps: -// 1. Save the current block which will serve as parent for the fork. -// 2. Send a transaction. -// 3. Check that the TX is included in block 1. -// 4. Fork by using the parent block as ancestor. -// 5. Mine a block, Re-send the transaction and mine another one. -// 6. Check that the TX is now included in block 2. -func TestForkResendTx(t *testing.T) { - t.Parallel() - testAddr := crypto.PubkeyToAddress(testKey.PublicKey) - sim := simTestBackend(testAddr) - defer sim.Close() - // 1. - parent := sim.blockchain.CurrentBlock() - - // 2. - head, _ := sim.HeaderByNumber(context.Background(), nil) // Should be child's, good enough - gasPrice := new(big.Int).Add(head.BaseFee, big.NewInt(1)) - _tx := types.NewTransaction(0, testAddr, big.NewInt(1000), params.TxGas, gasPrice, nil) - tx, err := types.SignTx(_tx, types.HomesteadSigner{}, testKey) - if err != nil { - t.Fatalf("could not sign transaction: %v", err) - } - if err = sim.SendTransaction(context.Background(), tx); err != nil { - t.Fatalf("sending transaction: %v", err) - } - sim.Commit() - - // 3. - receipt, _ := sim.TransactionReceipt(context.Background(), tx.Hash()) - if h := receipt.BlockNumber.Uint64(); h != 1 { - t.Errorf("TX included in wrong block: %d", h) - } - - // 4. - if err := sim.Fork(context.Background(), parent.Hash()); err != nil { - t.Errorf("forking: %v", err) - } - - // 5. - sim.Commit() - if err := sim.SendTransaction(context.Background(), tx); err != nil { - t.Errorf("sending transaction: %v", err) - } - sim.Commit() - - // 6. - receipt, _ = sim.TransactionReceipt(context.Background(), tx.Hash()) - if h := receipt.BlockNumber.Uint64(); h != 2 { - t.Errorf("TX included in wrong block: %d", h) - } -} - -func TestCommitReturnValue(t *testing.T) { - t.Parallel() - testAddr := crypto.PubkeyToAddress(testKey.PublicKey) - sim := simTestBackend(testAddr) - defer sim.Close() - - startBlockHeight := sim.blockchain.CurrentBlock().Number.Uint64() - - // Test if Commit returns the correct block hash - h1 := sim.Commit() - if h1 != sim.blockchain.CurrentBlock().Hash() { - t.Error("Commit did not return the hash of the last block.") - } - - // Create a block in the original chain (containing a transaction to force different block hashes) - head, _ := sim.HeaderByNumber(context.Background(), nil) // Should be child's, good enough - gasPrice := new(big.Int).Add(head.BaseFee, big.NewInt(1)) - _tx := types.NewTransaction(0, testAddr, big.NewInt(1000), params.TxGas, gasPrice, nil) - tx, _ := types.SignTx(_tx, types.HomesteadSigner{}, testKey) - if err := sim.SendTransaction(context.Background(), tx); err != nil { - t.Errorf("sending transaction: %v", err) - } - - h2 := sim.Commit() - - // Create another block in the original chain - sim.Commit() - - // Fork at the first bock - if err := sim.Fork(context.Background(), h1); err != nil { - t.Errorf("forking: %v", err) - } - - // Test if Commit returns the correct block hash after the reorg - h2fork := sim.Commit() - if h2 == h2fork { - t.Error("The block in the fork and the original block are the same block!") - } - if sim.blockchain.GetHeader(h2fork, startBlockHeight+2) == nil { - t.Error("Could not retrieve the just created block (side-chain)") - } -} - -// TestAdjustTimeAfterFork ensures that after a fork, AdjustTime uses the pending fork -// block's parent rather than the canonical head's parent. -func TestAdjustTimeAfterFork(t *testing.T) { - t.Parallel() - testAddr := crypto.PubkeyToAddress(testKey.PublicKey) - sim := simTestBackend(testAddr) - defer sim.Close() - - sim.Commit() // h1 - h1 := sim.blockchain.CurrentHeader().Hash() - sim.Commit() // h2 - sim.Fork(context.Background(), h1) - sim.AdjustTime(1 * time.Second) - sim.Commit() - - head := sim.blockchain.CurrentHeader() - if head.Number == common.Big2 && head.ParentHash != h1 { - t.Errorf("failed to build block on fork") - } -} diff --git a/accounts/abi/bind/v2/backend.go b/accounts/abi/bind/v2/backend.go index fabae44632..16ee054543 100644 --- a/accounts/abi/bind/v2/backend.go +++ b/accounts/abi/bind/v2/backend.go @@ -89,6 +89,11 @@ type BlockHashContractCaller interface { // used when the user does not provide some needed values, but rather leaves it up // to the transactor to decide. type ContractTransactor interface { + ethereum.GasEstimator + ethereum.GasPricer + ethereum.GasPricer1559 + ethereum.TransactionSender + // HeaderByNumber returns a block header from the current canonical chain. If // number is nil, the latest known header is returned. HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error) @@ -98,38 +103,6 @@ type ContractTransactor interface { // PendingNonceAt retrieves the current pending nonce associated with an account. PendingNonceAt(ctx context.Context, account common.Address) (uint64, error) - - // SuggestGasPrice retrieves the currently suggested gas price to allow a timely - // execution of a transaction. - SuggestGasPrice(ctx context.Context) (*big.Int, error) - - // SuggestGasTipCap retrieves the currently suggested 1559 priority fee to allow - // a timely execution of a transaction. - SuggestGasTipCap(ctx context.Context) (*big.Int, error) - - // EstimateGas tries to estimate the gas needed to execute a specific - // transaction based on the current pending state of the backend blockchain. - // There is no guarantee that this is the true gas limit requirement as other - // transactions may be added or removed by miners, but it should provide a basis - // for setting a reasonable default. - EstimateGas(ctx context.Context, call ethereum.CallMsg) (gas uint64, err error) - - // SendTransaction injects the transaction into the pending pool for execution. - SendTransaction(ctx context.Context, tx *types.Transaction) error -} - -// ContractFilterer defines the methods needed to access log events using one-off -// queries or continuous event subscriptions. -type ContractFilterer interface { - // FilterLogs executes a log filter operation, blocking during execution and - // returning all the results in one batch. - // - // TODO(karalabe): Deprecate when the subscription one can return past data too. - FilterLogs(ctx context.Context, query ethereum.FilterQuery) ([]types.Log, error) - - // SubscribeFilterLogs creates a background log filtering operation, returning - // a subscription immediately, which can be used to stream the found events. - SubscribeFilterLogs(ctx context.Context, query ethereum.FilterQuery, ch chan<- types.Log) (ethereum.Subscription, error) } // DeployBackend wraps the operations needed by WaitMined and WaitDeployed. @@ -138,6 +111,12 @@ type DeployBackend interface { CodeAt(ctx context.Context, account common.Address, blockNumber *big.Int) ([]byte, error) } +// ContractFilterer defines the methods needed to access log events using one-off +// queries or continuous event subscriptions. +type ContractFilterer interface { + ethereum.LogFilterer +} + // ContractBackend defines the methods needed to work with contracts on a read-write basis. type ContractBackend interface { ContractCaller diff --git a/accounts/abi/bind/v2/util_test.go b/accounts/abi/bind/v2/util_test.go index e268b4308a..3318e846ba 100644 --- a/accounts/abi/bind/v2/util_test.go +++ b/accounts/abi/bind/v2/util_test.go @@ -28,6 +28,7 @@ import ( "github.com/XinFinOrg/XDPoSChain/common" "github.com/XinFinOrg/XDPoSChain/core/types" "github.com/XinFinOrg/XDPoSChain/crypto" + "github.com/XinFinOrg/XDPoSChain/ethclient/simulated" "github.com/XinFinOrg/XDPoSChain/params" ) @@ -57,7 +58,7 @@ func TestWaitDeployed(t *testing.T) { config := *params.TestXDPoSMockChainConfig config.Eip1559Block = big.NewInt(0) for name, test := range waitDeployedTests { - backend := backends.NewXDCSimulatedBackend( + backend := simulated.New( types.GenesisAlloc{ crypto.PubkeyToAddress(testKey.PublicKey): {Balance: big.NewInt(100000000000000000)}, }, @@ -67,7 +68,7 @@ func TestWaitDeployed(t *testing.T) { defer backend.Close() // Create the transaction - head, _ := backend.HeaderByNumber(context.Background(), nil) // Should be child's, good enough + head, _ := backend.Client().HeaderByNumber(context.Background(), nil) // Should be child's, good enough gasPrice := new(big.Int).Add(head.BaseFee, big.NewInt(1)) tx := types.NewContractCreation(0, big.NewInt(0), test.gas, gasPrice, common.FromHex(test.code)) @@ -81,12 +82,12 @@ func TestWaitDeployed(t *testing.T) { ctx = context.Background() ) go func() { - address, err = bind.WaitDeployed(ctx, backend, tx.Hash()) + address, err = bind.WaitDeployed(ctx, backend.Client(), tx.Hash()) close(mined) }() // Send and mine the transaction. - if err := backend.SendTransaction(ctx, tx); err != nil { + if err := backend.Client().SendTransaction(ctx, tx); err != nil { t.Errorf("test %q: failed to send transaction: %v", name, err) } backend.Commit() @@ -116,7 +117,7 @@ func TestWaitDeployedCornerCases(t *testing.T) { 10000000, &config, ) - head, _ = backend.HeaderByNumber(context.Background(), nil) // Should be child's, good enough + head, _ = backend.Client().HeaderByNumber(context.Background(), nil) // Should be child's, good enough gasPrice = new(big.Int).Add(head.BaseFee, big.NewInt(1)) signer = types.LatestSigner(&config) code = common.FromHex("6060604052600a8060106000396000f360606040526008565b00") @@ -133,11 +134,11 @@ func TestWaitDeployedCornerCases(t *testing.T) { GasPrice: gasPrice, Data: code, }) - if err := backend.SendTransaction(ctx, tx); err != nil { + if err := backend.Client().SendTransaction(ctx, tx); err != nil { t.Errorf("failed to send transaction: %q", err) } backend.Commit() - if _, err := bind.WaitDeployed(ctx, backend, tx.Hash()); err != bind.ErrNoAddressInReceipt { + if _, err := bind.WaitDeployed(ctx, backend.Client(), tx.Hash()); err != bind.ErrNoAddressInReceipt { t.Errorf("error mismatch: want %q, got %q, ", bind.ErrNoAddressInReceipt, err) } @@ -154,13 +155,13 @@ func TestWaitDeployedCornerCases(t *testing.T) { done := make(chan struct{}) go func() { defer close(done) - _, err := bind.WaitDeployed(ctx, backend, tx.Hash()) + _, err := bind.WaitDeployed(ctx, backend.Client(), tx.Hash()) if !errors.Is(err, context.Canceled) { t.Errorf("error mismatch: want %v, got %v", context.Canceled, err) } }() - if err := backend.SendTransaction(ctx, tx); err != nil { + if err := backend.Client().SendTransaction(ctx, tx); err != nil { t.Errorf("failed to send transaction: %q", err) } cancel() diff --git a/ethclient/simulated/backend.go b/ethclient/simulated/backend.go new file mode 100644 index 0000000000..153c326b30 --- /dev/null +++ b/ethclient/simulated/backend.go @@ -0,0 +1,1280 @@ +// Copyright 2015 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 simulated + +import ( + "context" + "errors" + "fmt" + gomath "math" + "math/big" + "os" + "sort" + "sync" + "time" + + ethereum "github.com/XinFinOrg/XDPoSChain" + "github.com/XinFinOrg/XDPoSChain/XDCx" + "github.com/XinFinOrg/XDPoSChain/XDCxlending" + "github.com/XinFinOrg/XDPoSChain/accounts" + "github.com/XinFinOrg/XDPoSChain/accounts/abi" + "github.com/XinFinOrg/XDPoSChain/accounts/abi/bind" + "github.com/XinFinOrg/XDPoSChain/accounts/keystore" + "github.com/XinFinOrg/XDPoSChain/common" + "github.com/XinFinOrg/XDPoSChain/common/hexutil" + "github.com/XinFinOrg/XDPoSChain/common/math" + "github.com/XinFinOrg/XDPoSChain/consensus/XDPoS" + "github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/utils" + "github.com/XinFinOrg/XDPoSChain/consensus/ethash" + "github.com/XinFinOrg/XDPoSChain/core" + "github.com/XinFinOrg/XDPoSChain/core/bloombits" + "github.com/XinFinOrg/XDPoSChain/core/rawdb" + "github.com/XinFinOrg/XDPoSChain/core/state" + "github.com/XinFinOrg/XDPoSChain/core/types" + "github.com/XinFinOrg/XDPoSChain/core/vm" + "github.com/XinFinOrg/XDPoSChain/crypto" + "github.com/XinFinOrg/XDPoSChain/eth/filters" + "github.com/XinFinOrg/XDPoSChain/ethdb" + "github.com/XinFinOrg/XDPoSChain/event" + "github.com/XinFinOrg/XDPoSChain/log" + "github.com/XinFinOrg/XDPoSChain/node" + "github.com/XinFinOrg/XDPoSChain/params" + "github.com/XinFinOrg/XDPoSChain/rpc" +) + +// This nil assignment ensures at compile time that SimulatedBackend implements bind.ContractBackend. +var _ bind.ContractBackend = (*Backend)(nil) + +var ( + errBlockNumberUnsupported = errors.New("simulatedBackend cannot access blocks other than the latest block") + errBlockHashUnsupported = errors.New("simulatedBackend cannot access blocks by hash other than the latest block") + errBlockDoesNotExist = errors.New("block does not exist in blockchain") + errTransactionDoesNotExist = errors.New("transaction does not exist") +) + +// SimulatedBackend implements bind.ContractBackend, simulating a blockchain in +// the background. Its main purpose is to allow for easy testing of contract bindings. +// Simulated backend implements the following interfaces: +// ChainReader, ChainStateReader, ContractBackend, ContractCaller, ContractFilterer, ContractTransactor, +// DeployBackend, GasEstimator, GasPricer, LogFilterer, PendingContractCaller, TransactionReader, and TransactionSender +type Backend struct { + database ethdb.Database // In memory database to store our testing data + blockchain *core.BlockChain // Ethereum blockchain to handle the consensus + + mu sync.Mutex + pendingBlock *types.Block // Currently pending block that will be imported on request + pendingState *state.StateDB // Currently pending state that will be the active on request + pendingReceipts types.Receipts // Currently receipts for the pending block + + events *filters.EventSystem // for filtering log events live + filterSystem *filters.FilterSystem // for filtering database logs + + config *params.ChainConfig +} + +// Client exposes the methods provided by the simulated backend client. +type Client interface { + ethereum.BlockNumberReader + ethereum.ChainReader + ethereum.ChainStateReader + ethereum.ContractCaller + ethereum.GasEstimator + ethereum.GasPricer + ethereum.GasPricer1559 + ethereum.FeeHistoryReader + ethereum.LogFilterer + ethereum.PendingStateReader + ethereum.PendingContractCaller + ethereum.TransactionReader + ethereum.TransactionSender + ethereum.ChainIDReader +} + +func SimulateWalletAddressAndSignFn() (common.Address, func(account accounts.Account, hash []byte) ([]byte, error), error) { + veryLightScryptN := 2 + veryLightScryptP := 1 + dir, _ := os.MkdirTemp("", "eth-SimulateWalletAddressAndSignFn-test") + defer os.RemoveAll(dir) + + ks := keystore.NewKeyStore(dir, veryLightScryptN, veryLightScryptP) + pass := "" // not used but required by API + a1, err := ks.NewAccount(pass) + if err != nil { + return common.Address{}, nil, err + } + if err := ks.Unlock(a1, ""); err != nil { + return a1.Address, nil, err + } + return a1.Address, ks.SignHash, nil +} + +// New creates a new simulated backend for testing. +// +// If chainconfig is supplied, XDPoS-compatible backend is used. +// Otherwise an ethash backend is used. +func New(alloc types.GenesisAlloc, gasLimit uint64, chainconfig ...*params.ChainConfig) *Backend { + if len(chainconfig) > 0 && chainconfig[0] != nil { + if chainconfig[0].XDPoS != nil { + return newXDCSimulatedBackend(alloc, gasLimit, chainconfig[0]) + } + return newEthashSimulatedBackendWithConfig(alloc, gasLimit, chainconfig[0]) + } + return newEthashSimulatedBackend(alloc, gasLimit) +} + +// newXDCSimulatedBackend creates a new backend for testing purpose. +func newXDCSimulatedBackend(alloc types.GenesisAlloc, gasLimit uint64, chainConfig *params.ChainConfig) *Backend { + database := rawdb.NewMemoryDatabase() + genesis := core.Genesis{ + GasLimit: gasLimit, // need this big, support initial smart contract + Config: chainConfig, + Alloc: alloc, + ExtraData: append(make([]byte, 32), make([]byte, crypto.SignatureLength)...), + } + genesis.MustCommit(database) + consensus := XDPoS.NewFaker(database, chainConfig) + + // Attach mock trading and lending service + var DefaultConfig = XDCx.Config{ + DataDir: "", + } + stack, err := node.New(&node.Config{DataDir: ""}) + if err != nil { + log.Error("Could not create new node: %v", err) + return nil + } + defer stack.Close() + + XDCXServ := XDCx.New(stack, &DefaultConfig) + + lendingServ := XDCxlending.New(stack, XDCXServ) + + consensus.GetXDCXService = func() utils.TradingService { + return XDCXServ + } + consensus.GetLendingService = func() utils.LendingService { + return lendingServ + } + + cacheConfig := &core.CacheConfig{ + TrieCleanLimit: 256, + TrieDirtyLimit: 256, + TrieTimeLimit: 5 * time.Minute, + Preimages: true, + } + blockchain, _ := core.NewBlockChain(database, cacheConfig, &genesis, consensus, vm.Config{}) + + backend := &Backend{ + database: database, + blockchain: blockchain, + config: genesis.Config, + } + + filterBackend := &filterBackend{database, blockchain, backend} + backend.filterSystem = filters.NewFilterSystem(filterBackend, filters.Config{}) + backend.events = filters.NewEventSystem(backend.filterSystem, false) + + blockchain.Client = backend + + header := backend.blockchain.CurrentBlock() + block := backend.blockchain.GetBlock(header.Hash(), header.Number.Uint64()) + + backend.rollback(block) + return backend +} + +// newEthashSimulatedBackend creates a new binding backend based on the given database +// and uses a simulated blockchain for testing purposes. +// A simulated backend always uses chainID 1337. +func newEthashSimulatedBackend(alloc types.GenesisAlloc, gasLimit uint64) *Backend { + config := *params.AllEthashProtocolChanges + if config.Eip1559Block == nil { + config.Eip1559Block = big.NewInt(0) + } + return newEthashSimulatedBackendWithConfig(alloc, gasLimit, &config) +} + +func newEthashSimulatedBackendWithConfig(alloc types.GenesisAlloc, gasLimit uint64, chainConfig *params.ChainConfig) *Backend { + database := rawdb.NewMemoryDatabase() + config := *chainConfig + genesis := core.Genesis{Config: &config, GasLimit: gasLimit, Alloc: alloc} + genesis.MustCommit(database) + blockchain, _ := core.NewBlockChain(database, nil, &genesis, ethash.NewFaker(), vm.Config{}) + + backend := &Backend{ + database: database, + blockchain: blockchain, + config: genesis.Config, + } + + filterBackend := &filterBackend{database, blockchain, backend} + backend.filterSystem = filters.NewFilterSystem(filterBackend, filters.Config{}) + backend.events = filters.NewEventSystem(backend.filterSystem, false) + + header := backend.blockchain.CurrentBlock() + block := backend.blockchain.GetBlock(header.Hash(), header.Number.Uint64()) + + backend.rollback(block) + return backend +} + +// Close terminates the underlying blockchain's update loop. +func (b *Backend) Close() error { + b.blockchain.Stop() + return nil +} + +// Commit imports all the pending transactions as a single block and starts a +// fresh new state. +func (b *Backend) Commit() common.Hash { + b.mu.Lock() + defer b.mu.Unlock() + + if _, err := b.blockchain.InsertChain([]*types.Block{b.pendingBlock}); err != nil { + panic(err) // This cannot happen unless the simulator is wrong, fail in that case + } + blockHash := b.pendingBlock.Hash() + + // Using the last inserted block here makes it possible to build on a side + // chain after a fork. + b.rollback(b.pendingBlock) + + return blockHash +} + +// Rollback aborts all pending transactions, reverting to the last committed state. +func (b *Backend) Rollback() { + b.mu.Lock() + defer b.mu.Unlock() + + header := b.blockchain.CurrentBlock() + block := b.blockchain.GetBlock(header.Hash(), header.Number.Uint64()) + + b.rollback(block) +} + +func (b *Backend) rollback(parent *types.Block) { + blocks, _ := core.GenerateChain(b.config, parent, b.blockchain.Engine(), b.database, 1, func(int, *core.BlockGen) {}) + stateDB, _ := b.blockchain.State() + + b.pendingBlock = blocks[0] + b.pendingState, _ = state.New(b.pendingBlock.Root(), stateDB.Database()) +} + +// Fork creates a side-chain that can be used to simulate reorgs. +// +// This function should be called with the ancestor block where the new side +// chain should be started. Transactions (old and new) can then be applied on +// top and Commit-ed. +// +// Note, the side-chain will only become canonical (and trigger the events) when +// it becomes longer. Until then CallContract will still operate on the current +// canonical chain. +// +// There is a % chance that the side chain becomes canonical at the same length +// to simulate live network behavior. +func (b *Backend) Fork(parent common.Hash) error { + b.mu.Lock() + defer b.mu.Unlock() + + if len(b.pendingBlock.Transactions()) != 0 { + return errors.New("pending block dirty") + } + block, err := b.blockByHash(context.Background(), parent) + if err != nil { + return err + } + b.rollback(block) + return nil +} + +// BlockNumber returns the current block number. +func (b *Backend) BlockNumber(ctx context.Context) (uint64, error) { + b.mu.Lock() + defer b.mu.Unlock() + + return b.blockchain.CurrentBlock().Number.Uint64(), nil +} + +// ChainID returns the chain ID configured for this backend. +func (b *Backend) ChainID(ctx context.Context) (*big.Int, error) { + b.mu.Lock() + defer b.mu.Unlock() + + if b.config == nil || b.config.ChainID == nil { + return nil, errors.New("chain id not configured") + } + return new(big.Int).Set(b.config.ChainID), nil +} + +// Client returns a client interface to the simulated chain. +func (b *Backend) Client() Client { + return b +} + +// stateByBlockNumber retrieves a state by a given blocknumber. +func (b *Backend) stateByBlockNumber(ctx context.Context, blockNumber *big.Int) (*state.StateDB, error) { + if blockNumber == nil || blockNumber.Cmp(b.blockchain.CurrentBlock().Number) == 0 { + return b.blockchain.State() + } + block, err := b.blockByNumber(ctx, blockNumber) + if err != nil { + return nil, err + } + return b.blockchain.StateAt(block.Root()) +} + +// CodeAt returns the code associated with a certain account in the blockchain. +func (b *Backend) CodeAt(ctx context.Context, contract common.Address, blockNumber *big.Int) ([]byte, error) { + b.mu.Lock() + defer b.mu.Unlock() + + stateDB, err := b.stateByBlockNumber(ctx, blockNumber) + if err != nil { + return nil, err + } + return stateDB.GetCode(contract), nil +} + +// CodeAtHash returns the code associated with a certain account in the blockchain. +func (b *Backend) CodeAtHash(ctx context.Context, contract common.Address, blockHash common.Hash) ([]byte, error) { + b.mu.Lock() + defer b.mu.Unlock() + + header, err := b.headerByHash(blockHash) + if err != nil { + return nil, err + } + + stateDB, err := b.blockchain.StateAt(header.Root) + if err != nil { + return nil, err + } + + return stateDB.GetCode(contract), nil +} + +// BalanceAt returns the wei balance of a certain account in the blockchain. +func (b *Backend) BalanceAt(ctx context.Context, contract common.Address, blockNumber *big.Int) (*big.Int, error) { + b.mu.Lock() + defer b.mu.Unlock() + + stateDB, err := b.stateByBlockNumber(ctx, blockNumber) + if err != nil { + return nil, err + } + return stateDB.GetBalance(contract), nil +} + +// NonceAt returns the nonce of a certain account in the blockchain. +func (b *Backend) NonceAt(ctx context.Context, contract common.Address, blockNumber *big.Int) (uint64, error) { + b.mu.Lock() + defer b.mu.Unlock() + + stateDB, err := b.stateByBlockNumber(ctx, blockNumber) + if err != nil { + return 0, err + } + return stateDB.GetNonce(contract), nil +} + +// StorageAt returns the value of key in the storage of an account in the blockchain. +func (b *Backend) StorageAt(ctx context.Context, contract common.Address, key common.Hash, blockNumber *big.Int) ([]byte, error) { + b.mu.Lock() + defer b.mu.Unlock() + + stateDB, err := b.stateByBlockNumber(ctx, blockNumber) + if err != nil { + return nil, err + } + val := stateDB.GetState(contract, key) + return val[:], nil +} + +// ForEachStorageAt returns func to read all keys, values in the storage +func (b *Backend) ForEachStorageAt(ctx context.Context, contract common.Address, blockNumber *big.Int, f func(key, val common.Hash) bool) error { + b.mu.Lock() + defer b.mu.Unlock() + + if blockNumber != nil && blockNumber.Cmp(b.blockchain.CurrentBlock().Number) != 0 { + return errBlockNumberUnsupported + } + stateDB, _ := b.blockchain.State() + stateDB.ForEachStorage(contract, f) + return nil +} + +// TransactionReceipt returns the receipt of a transaction. +func (b *Backend) TransactionReceipt(ctx context.Context, txHash common.Hash) (*types.Receipt, error) { + b.mu.Lock() + defer b.mu.Unlock() + + receipt, _, _, _ := rawdb.ReadReceipt(b.database, txHash, b.config) + if receipt == nil { + return nil, ethereum.ErrNotFound + } + return receipt, nil +} + +// TransactionByHash checks the pool of pending transactions in addition to the +// blockchain. The isPending return value indicates whether the transaction has been +// mined yet. Note that the transaction may not be part of the canonical chain even if +// it's not pending. +func (b *Backend) TransactionByHash(ctx context.Context, txHash common.Hash) (*types.Transaction, bool, error) { + b.mu.Lock() + defer b.mu.Unlock() + + tx := b.pendingBlock.Transaction(txHash) + if tx != nil { + return tx, true, nil + } + tx, _, _, _ = rawdb.ReadTransaction(b.database, txHash) + if tx != nil { + return tx, false, nil + } + return nil, false, ethereum.ErrNotFound +} + +// BlockByHash retrieves a block based on the block hash. +func (b *Backend) BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error) { + b.mu.Lock() + defer b.mu.Unlock() + + return b.blockByHash(ctx, hash) +} + +// blockByHash retrieves a block based on the block hash without Locking. +func (b *Backend) blockByHash(ctx context.Context, hash common.Hash) (*types.Block, error) { + if hash == b.pendingBlock.Hash() { + return b.pendingBlock, nil + } + + block := b.blockchain.GetBlockByHash(hash) + if block != nil { + return block, nil + } + + return nil, errBlockDoesNotExist +} + +// BlockByNumber retrieves a block from the database by number, caching it +// (associated with its hash) if found. +func (b *Backend) BlockByNumber(ctx context.Context, number *big.Int) (*types.Block, error) { + b.mu.Lock() + defer b.mu.Unlock() + + return b.blockByNumber(ctx, number) +} + +// blockByNumber retrieves a block from the database by number, caching it +// (associated with its hash) if found without Lock. +func (b *Backend) blockByNumber(ctx context.Context, number *big.Int) (*types.Block, error) { + if number == nil || number.Cmp(b.pendingBlock.Number()) == 0 { + return b.blockByHash(ctx, b.blockchain.CurrentBlock().Hash()) + } + + block := b.blockchain.GetBlockByNumber(uint64(number.Int64())) + if block == nil { + return nil, errBlockDoesNotExist + } + + return block, nil +} + +// HeaderByHash returns a block header from the current canonical chain. +func (b *Backend) HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error) { + b.mu.Lock() + defer b.mu.Unlock() + return b.headerByHash(hash) +} + +// headerByHash retrieves a header from the database by hash without Lock. +func (b *Backend) headerByHash(hash common.Hash) (*types.Header, error) { + if hash == b.pendingBlock.Hash() { + return b.pendingBlock.Header(), nil + } + + header := b.blockchain.GetHeaderByHash(hash) + if header == nil { + return nil, errBlockDoesNotExist + } + + return header, nil +} + +// HeaderByNumber returns a block header from the current canonical chain. If number is +// nil, the latest known header is returned. +func (b *Backend) HeaderByNumber(ctx context.Context, block *big.Int) (*types.Header, error) { + b.mu.Lock() + defer b.mu.Unlock() + + if block == nil || block.Cmp(b.pendingBlock.Number()) == 0 { + return b.blockchain.CurrentHeader(), nil + } + + return b.blockchain.GetHeaderByNumber(uint64(block.Int64())), nil +} + +// TransactionCount returns the number of transactions in a given block. +func (b *Backend) TransactionCount(ctx context.Context, blockHash common.Hash) (uint, error) { + b.mu.Lock() + defer b.mu.Unlock() + + if blockHash == b.pendingBlock.Hash() { + return uint(b.pendingBlock.Transactions().Len()), nil + } + + block := b.blockchain.GetBlockByHash(blockHash) + if block == nil { + return uint(0), errBlockDoesNotExist + } + + return uint(block.Transactions().Len()), nil +} + +// TransactionInBlock returns the transaction for a specific block at a specific index. +func (b *Backend) TransactionInBlock(ctx context.Context, blockHash common.Hash, index uint) (*types.Transaction, error) { + b.mu.Lock() + defer b.mu.Unlock() + + if blockHash == b.pendingBlock.Hash() { + transactions := b.pendingBlock.Transactions() + if uint(len(transactions)) < index+1 { + return nil, errTransactionDoesNotExist + } + + return transactions[index], nil + } + + block := b.blockchain.GetBlockByHash(blockHash) + if block == nil { + return nil, errBlockDoesNotExist + } + + transactions := block.Transactions() + if uint(len(transactions)) < index+1 { + return nil, errTransactionDoesNotExist + } + + return transactions[index], nil +} + +// PendingCodeAt returns the code associated with an account in the pending state. +func (b *Backend) PendingCodeAt(ctx context.Context, contract common.Address) ([]byte, error) { + b.mu.Lock() + defer b.mu.Unlock() + + return b.pendingState.GetCode(contract), nil +} + +// PendingBalanceAt returns the wei balance of an account in the pending state. +func (b *Backend) PendingBalanceAt(ctx context.Context, account common.Address) (*big.Int, error) { + b.mu.Lock() + defer b.mu.Unlock() + + return b.pendingState.GetBalance(account), nil +} + +// PendingStorageAt returns the storage value of an account in the pending state. +func (b *Backend) PendingStorageAt(ctx context.Context, account common.Address, key common.Hash) ([]byte, error) { + b.mu.Lock() + defer b.mu.Unlock() + + value := b.pendingState.GetState(account, key) + return value[:], nil +} + +// PendingTransactionCount returns the number of pending transactions in the pending block. +func (b *Backend) PendingTransactionCount(ctx context.Context) (uint, error) { + b.mu.Lock() + defer b.mu.Unlock() + + return uint(len(b.pendingBlock.Transactions())), nil +} + +func newRevertError(result *core.ExecutionResult) *revertError { + reason, errUnpack := abi.UnpackRevert(result.Revert()) + err := errors.New("execution reverted") + if errUnpack == nil { + err = fmt.Errorf("execution reverted: %v", reason) + } + return &revertError{ + error: err, + reason: hexutil.Encode(result.Revert()), + } +} + +// revertError is an API error that encompasses an EVM revert with JSON error +// code and a binary data blob. +type revertError struct { + error + reason string // revert reason hex encoded +} + +// ErrorCode returns the JSON error code for a revert. +// See: https://github.com/ethereum/wiki/wiki/JSON-RPC-Error-Codes-Improvement-Proposal +func (e *revertError) ErrorCode() int { + return 3 +} + +// ErrorData returns the hex encoded revert reason. +func (e *revertError) ErrorData() interface{} { + return e.reason +} + +// CallContract executes a contract call. +func (b *Backend) CallContract(ctx context.Context, call ethereum.CallMsg, blockNumber *big.Int) ([]byte, error) { + b.mu.Lock() + defer b.mu.Unlock() + + if blockNumber != nil && blockNumber.Cmp(b.blockchain.CurrentBlock().Number) != 0 { + return nil, errBlockNumberUnsupported + } + return b.callContractAtHead(ctx, call) +} + +// CallContractAtHash executes a contract call on a specific block hash. +func (b *Backend) CallContractAtHash(ctx context.Context, call ethereum.CallMsg, blockHash common.Hash) ([]byte, error) { + b.mu.Lock() + defer b.mu.Unlock() + + if blockHash != b.blockchain.CurrentBlock().Hash() { + return nil, errBlockHashUnsupported + } + return b.callContractAtHead(ctx, call) +} + +// callContractAtHead executes a contract call against the latest block state. +func (b *Backend) callContractAtHead(ctx context.Context, call ethereum.CallMsg) ([]byte, error) { + stateDB, err := b.blockchain.State() + if err != nil { + return nil, err + } + header := b.blockchain.CurrentBlock() + res, err := b.callContract(ctx, call, header, stateDB) + if err != nil { + return nil, err + } + // If the result contains a revert reason, try to unpack and return it. + if len(res.Revert()) > 0 { + return nil, newRevertError(res) + } + return res.Return(), res.Err +} + +// PendingCallContract executes a contract call on the pending state. +func (b *Backend) PendingCallContract(ctx context.Context, call ethereum.CallMsg) ([]byte, error) { + b.mu.Lock() + defer b.mu.Unlock() + defer b.pendingState.RevertToSnapshot(b.pendingState.Snapshot()) + + res, err := b.callContract(ctx, call, b.pendingBlock.Header(), b.pendingState) + if err != nil { + return nil, err + } + // If the result contains a revert reason, try to unpack and return it. + if len(res.Revert()) > 0 { + return nil, newRevertError(res) + } + return res.Return(), res.Err +} + +// PendingNonceAt implements PendingStateReader.PendingNonceAt, retrieving +// the nonce currently pending for the account. +func (b *Backend) PendingNonceAt(ctx context.Context, account common.Address) (uint64, error) { + b.mu.Lock() + defer b.mu.Unlock() + + return b.pendingState.GetOrNewStateObject(account).Nonce(), nil +} + +// SuggestGasPrice implements ContractTransactor.SuggestGasPrice. Since the simulated +// chain doesn't have miners, we just return a gas price of 1 for any call. +func (b *Backend) SuggestGasPrice(ctx context.Context) (*big.Int, error) { + b.mu.Lock() + defer b.mu.Unlock() + + if b.pendingBlock.Header().BaseFee != nil { + return b.pendingBlock.Header().BaseFee, nil + } + return big.NewInt(1), nil +} + +// SuggestGasTipCap implements ContractTransactor.SuggestGasTipCap. Since the simulated +// chain doesn't have miners, we just return a gas tip of 1 for any call. +func (b *Backend) SuggestGasTipCap(ctx context.Context) (*big.Int, error) { + return big.NewInt(1), nil +} + +// FeeHistory retrieves recent fee market data from the simulated chain. +func (b *Backend) FeeHistory(ctx context.Context, blockCount uint64, lastBlock *big.Int, rewardPercentiles []float64) (*ethereum.FeeHistory, error) { + b.mu.Lock() + defer b.mu.Unlock() + + for i, p := range rewardPercentiles { + if p < 0 || p > 100 { + return nil, fmt.Errorf("invalid reward percentile: %f", p) + } + if i > 0 && p < rewardPercentiles[i-1] { + return nil, fmt.Errorf("invalid reward percentile: %f", p) + } + } + + head := b.blockchain.CurrentBlock().Number.Uint64() + end := head + if lastBlock != nil { + if lastBlock.Sign() < 0 { + return nil, errors.New("invalid last block") + } + if lastBlock.Uint64() < head { + end = lastBlock.Uint64() + } + } + + available := end + 1 + if blockCount > available { + blockCount = available + } + if blockCount == 0 { + return ðereum.FeeHistory{ + OldestBlock: new(big.Int).SetUint64(end + 1), + Reward: nil, + BaseFee: nil, + GasUsedRatio: nil, + }, nil + } + + start := end + 1 - blockCount + history := ðereum.FeeHistory{ + OldestBlock: new(big.Int).SetUint64(start), + BaseFee: make([]*big.Int, blockCount+1), + GasUsedRatio: make([]float64, blockCount), + } + if len(rewardPercentiles) > 0 { + history.Reward = make([][]*big.Int, blockCount) + } + + type pricedGas struct { + tip *big.Int + gas uint64 + } + + for i := uint64(0); i < blockCount; i++ { + number := start + i + block := b.blockchain.GetBlockByNumber(number) + if block == nil { + return nil, errBlockDoesNotExist + } + header := block.Header() + + if header.BaseFee != nil { + history.BaseFee[i] = new(big.Int).Set(header.BaseFee) + } + if header.GasLimit > 0 { + history.GasUsedRatio[i] = float64(header.GasUsed) / float64(header.GasLimit) + } + + if len(rewardPercentiles) == 0 { + continue + } + + txs := block.Transactions() + if len(txs) == 0 { + history.Reward[i] = make([]*big.Int, len(rewardPercentiles)) + for j := range history.Reward[i] { + history.Reward[i][j] = new(big.Int) + } + continue + } + + prices := make([]pricedGas, 0, len(txs)) + var totalGas uint64 + for _, tx := range txs { + tip := tx.EffectiveGasTipValue(header.BaseFee) + prices = append(prices, pricedGas{tip: tip, gas: tx.Gas()}) + totalGas += tx.Gas() + } + sort.Slice(prices, func(a, b int) bool { + return prices[a].tip.Cmp(prices[b].tip) < 0 + }) + + history.Reward[i] = make([]*big.Int, len(rewardPercentiles)) + for j, p := range rewardPercentiles { + if totalGas == 0 { + history.Reward[i][j] = new(big.Int) + continue + } + threshold := uint64(gomath.Ceil(float64(totalGas) * (p / 100.0))) + if threshold == 0 { + threshold = 1 + } + var cumulative uint64 + selected := prices[len(prices)-1].tip + for _, item := range prices { + cumulative += item.gas + if cumulative >= threshold { + selected = item.tip + break + } + } + history.Reward[i][j] = new(big.Int).Set(selected) + } + } + + last := b.blockchain.GetBlockByNumber(end) + if last == nil { + return nil, errBlockDoesNotExist + } + if last.BaseFee() != nil { + history.BaseFee[blockCount] = new(big.Int).Set(last.BaseFee()) + } + + return history, nil +} + +// EstimateGas executes the requested code against the currently pending block/state and +// returns the used amount of gas. +func (b *Backend) EstimateGas(ctx context.Context, call ethereum.CallMsg) (uint64, error) { + b.mu.Lock() + defer b.mu.Unlock() + + // Determine the lowest and highest possible gas limits to binary search in between + var ( + lo uint64 = params.TxGas - 1 + hi uint64 + cap uint64 + ) + if call.Gas >= params.TxGas { + hi = call.Gas + } else { + hi = b.pendingBlock.GasLimit() + } + cap = hi + + // Create a helper to check if a gas allowance results in an executable transaction + executable := func(gas uint64) (bool, *core.ExecutionResult, error) { + call.Gas = gas + + snapshot := b.pendingState.Snapshot() + res, err := b.callContract(ctx, call, b.pendingBlock.Header(), b.pendingState) + b.pendingState.RevertToSnapshot(snapshot) + + if err != nil { + if errors.Is(err, core.ErrIntrinsicGas) { + return true, nil, nil // Special case, raise gas limit + } + return true, nil, err // Bail out + } + return res.Failed(), res, nil + } + // Execute the binary search and hone in on an executable gas limit + for lo+1 < hi { + mid := (hi + lo) / 2 + failed, _, err := executable(mid) + + // If the error is not nil(consensus error), it means the provided message + // call or transaction will never be accepted no matter how much gas it is + // assigned. Return the error directly, don't struggle any more + if err != nil { + return 0, err + } + if failed { + lo = mid + } else { + hi = mid + } + } + // Reject the transaction as invalid if it still fails at the highest allowance + if hi == cap { + failed, result, err := executable(hi) + if err != nil { + return 0, err + } + if failed { + if result != nil && !errors.Is(result.Err, vm.ErrOutOfGas) { + if len(result.Revert()) > 0 { + return 0, newRevertError(result) + } + return 0, result.Err + } + // Otherwise, the specified gas cap is too low + return 0, fmt.Errorf("gas required exceeds allowance (%d)", cap) + } + } + return hi, nil +} + +// callContract implements common code between normal and pending contract calls. +// state is modified during execution, make sure to copy it if necessary. +func (b *Backend) callContract(ctx context.Context, call ethereum.CallMsg, block *types.Header, stateDB *state.StateDB) (*core.ExecutionResult, error) { + // Gas prices post 1559 need to be initialized + if call.GasPrice != nil && (call.GasFeeCap != nil || call.GasTipCap != nil) { + return nil, errors.New("both gasPrice and (maxFeePerGas or maxPriorityFeePerGas) specified") + } + head := b.blockchain.CurrentHeader() + if !b.blockchain.Config().IsEIP1559(head.Number) { + // If there's no basefee, then it must be a non-1559 execution + if call.GasPrice == nil { + call.GasPrice = new(big.Int) + } + call.GasFeeCap, call.GasTipCap = call.GasPrice, call.GasPrice + } else { + // A basefee is provided, necessitating 1559-type execution + if call.GasPrice != nil { + // User specified the legacy gas field, convert to 1559 gas typing + call.GasFeeCap, call.GasTipCap = call.GasPrice, call.GasPrice + } else { + // User specified 1559 gas fields (or none), use those + if call.GasFeeCap == nil { + call.GasFeeCap = new(big.Int) + } + if call.GasTipCap == nil { + call.GasTipCap = new(big.Int) + } + // Backfill the legacy gasPrice for EVM execution, unless we're all zeroes + call.GasPrice = new(big.Int) + if call.GasFeeCap.BitLen() > 0 || call.GasTipCap.BitLen() > 0 { + call.GasPrice = new(big.Int).Add(call.GasTipCap, head.BaseFee) + if call.GasPrice.Cmp(call.GasFeeCap) > 0 { + call.GasPrice.Set(call.GasFeeCap) + } + } + } + } + // Ensure message is initialized properly. + if call.Gas == 0 { + call.Gas = 10 * head.GasLimit + } + if call.Value == nil { + call.Value = new(big.Int) + } + + // Set infinite balance to the fake caller account. + from := stateDB.GetOrNewStateObject(call.From) + from.SetBalance(math.MaxBig256) + + // Execute the call. + msg := &core.Message{ + From: call.From, + To: call.To, + Value: call.Value, + GasLimit: call.Gas, + GasPrice: call.GasPrice, + GasFeeCap: call.GasFeeCap, + GasTipCap: call.GasTipCap, + Data: call.Data, + AccessList: call.AccessList, + SkipNonceChecks: true, + SkipFromEOACheck: true, + } + feeCapacity := stateDB.GetTRC21FeeCapacityFromState() + if msg.To != nil { + if value, ok := feeCapacity[*msg.To]; ok { + msg.BalanceTokenFee = value + } + } + + // Create a new environment which holds all relevant information + // about the transaction and calling mechanisms. + txContext := core.NewEVMTxContext(msg) + evmContext := core.NewEVMBlockContext(block, b.blockchain, nil) + // Create a new environment which holds all relevant information + // about the transaction and calling mechanisms. + evm := vm.NewEVM(evmContext, stateDB, nil, b.config, vm.Config{NoBaseFee: true}) + evm.SetTxContext(txContext) + gaspool := new(core.GasPool).AddGas(gomath.MaxUint64) + return core.NewStateTransition(evm, msg, gaspool).TransitionDb(common.Address{}) +} + +// SendTransaction updates the pending block to include the given transaction. +func (b *Backend) SendTransaction(ctx context.Context, tx *types.Transaction) error { + b.mu.Lock() + defer b.mu.Unlock() + + // Get the last block + block, err := b.blockByHash(ctx, b.pendingBlock.ParentHash()) + if err != nil { + return errors.New("could not fetch parent") + } + // Check transaction validity + signer := types.MakeSigner(b.blockchain.Config(), block.Number()) + sender, err := types.Sender(signer, tx) + if err != nil { + return fmt.Errorf("invalid transaction: %v", err) + } + nonce := b.pendingState.GetNonce(sender) + if tx.Nonce() != nonce { + return fmt.Errorf("invalid transaction nonce: got %d, want %d", tx.Nonce(), nonce) + } + // Include tx in chain + blocks, receipts := core.GenerateChain(b.config, block, b.blockchain.Engine(), b.database, 1, func(number int, block *core.BlockGen) { + for _, tx := range b.pendingBlock.Transactions() { + block.AddTxWithChain(b.blockchain, tx) + } + block.AddTxWithChain(b.blockchain, tx) + }) + stateDB, err := b.blockchain.State() + if err != nil { + return err + } + b.pendingBlock = blocks[0] + b.pendingState, _ = state.New(b.pendingBlock.Root(), stateDB.Database()) + b.pendingReceipts = receipts[0] + return nil +} + +// FilterLogs executes a log filter operation, blocking during execution and +// returning all the results in one batch. +// +// TODO(karalabe): Deprecate when the subscription one can return past data too. +func (b *Backend) FilterLogs(ctx context.Context, query ethereum.FilterQuery) ([]types.Log, error) { + var filter *filters.Filter + if query.BlockHash != nil { + // Block filter requested, construct a single-shot filter + filter = b.filterSystem.NewBlockFilter(*query.BlockHash, query.Addresses, query.Topics) + } else { + // Initialize unset filter boundaried to run from genesis to chain head + from := rpc.EarliestBlockNumber.Int64() + if query.FromBlock != nil { + from = query.FromBlock.Int64() + } + to := rpc.LatestBlockNumber.Int64() + if query.ToBlock != nil { + to = query.ToBlock.Int64() + } + // Construct the range filter + filter = b.filterSystem.NewRangeFilter(from, to, query.Addresses, query.Topics, 0) + } + // Run the filter and return all the logs + logs, err := filter.Logs(ctx) + if err != nil { + return nil, err + } + res := make([]types.Log, len(logs)) + for i, log := range logs { + res[i] = *log + } + return res, nil +} + +// SubscribeFilterLogs creates a background log filtering operation, returning a +// subscription immediately, which can be used to stream the found events. +func (b *Backend) SubscribeFilterLogs(ctx context.Context, query ethereum.FilterQuery, ch chan<- types.Log) (ethereum.Subscription, error) { + // Subscribe to contract events + sink := make(chan []*types.Log) + + sub, err := b.events.SubscribeLogs(query, sink) + if err != nil { + return nil, err + } + // Since we're getting logs in batches, we need to flatten them into a plain stream + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case logs := <-sink: + for _, log := range logs { + select { + case ch <- *log: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// SubscribeNewHead returns an event subscription for a new header. +func (b *Backend) SubscribeNewHead(ctx context.Context, ch chan<- *types.Header) (ethereum.Subscription, error) { + // subscribe to a new head + sink := make(chan *types.Header) + sub := b.events.SubscribeNewHeads(sink) + + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case head := <-sink: + select { + case ch <- head: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// AdjustTime adds a time shift to the simulated clock. +// It can only be called on empty blocks. +func (b *Backend) AdjustTime(adjustment time.Duration) error { + b.mu.Lock() + defer b.mu.Unlock() + + if len(b.pendingBlock.Transactions()) != 0 { + return errors.New("could not adjust time on non-empty block") + } + // Get the last block + block := b.blockchain.GetBlockByHash(b.pendingBlock.ParentHash()) + if block == nil { + return errors.New("could not find parent") + } + // Determine the default timestamp increment used by chain generation, + // then offset relative to that value so the resulting timestamp shift + // equals the requested adjustment exactly. + probeBlocks, _ := core.GenerateChain(b.config, block, b.blockchain.Engine(), b.database, 1, func(int, *core.BlockGen) {}) + if len(probeBlocks) == 0 { + return errors.New("could not generate probe block") + } + defaultStep := int64(probeBlocks[0].Time()) - int64(block.Time()) + + blocks, _ := core.GenerateChain(b.config, block, b.blockchain.Engine(), b.database, 1, func(number int, block *core.BlockGen) { + block.OffsetTime(int64(adjustment.Seconds()) - defaultStep) + }) + if _, err := b.blockchain.InsertChain(blocks); err != nil { + return err + } + b.pendingBlock = blocks[0] + b.rollback(b.pendingBlock) + + stateDB, _ := b.blockchain.State() + b.pendingState, _ = state.New(b.pendingBlock.Root(), stateDB.Database()) + + return nil +} + +// Blockchain returns the underlying blockchain. +func (b *Backend) BlockChain() *core.BlockChain { + return b.blockchain +} + +// filterBackend implements filters.Backend to support filtering for logs without +// taking bloom-bits acceleration structures into account. +type filterBackend struct { + db ethdb.Database + bc *core.BlockChain + backend *Backend +} + +func (fb *filterBackend) ChainDb() ethdb.Database { return fb.db } +func (fb *filterBackend) EventMux() *event.TypeMux { panic("not supported") } + +func (fb *filterBackend) HeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Header, error) { + switch number { + case rpc.PendingBlockNumber: + if block := fb.backend.pendingBlock; block != nil { + return block.Header(), nil + } + return nil, nil + case rpc.LatestBlockNumber: + return fb.bc.CurrentHeader(), nil + case rpc.FinalizedBlockNumber: + if fb.bc.Config().XDPoS == nil { + return nil, errors.New("only XDPoS v2 supports committed block lookup") + } + current := fb.bc.CurrentBlock() + if fb.bc.Config().XDPoS.BlockConsensusVersion(current.Number) == params.ConsensusEngineVersion2 { + confirmedHash := fb.bc.Engine().(*XDPoS.XDPoS).EngineV2.GetLatestCommittedBlockInfo().Hash + return fb.bc.GetHeaderByHash(confirmedHash), nil + } + return nil, errors.New("only XDPoS v2 can lookup committed block") + default: + return fb.bc.GetHeaderByNumber(uint64(number.Int64())), nil + } +} + +func (fb *filterBackend) HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error) { + return fb.bc.GetHeaderByHash(hash), nil +} + +func (fb *filterBackend) GetReceipts(ctx context.Context, hash common.Hash) (types.Receipts, error) { + number := rawdb.ReadHeaderNumber(fb.db, hash) + if number == nil { + return nil, nil + } + return rawdb.ReadReceipts(fb.db, hash, *number, fb.bc.Config()), nil +} + +func (fb *filterBackend) GetBody(ctx context.Context, hash common.Hash, number rpc.BlockNumber) (*types.Body, error) { + if body := fb.bc.GetBody(hash); body != nil { + return body, nil + } + return nil, errors.New("block body not found") +} + +func (fb *filterBackend) PendingBlockAndReceipts() (*types.Block, types.Receipts) { + return fb.backend.pendingBlock, fb.backend.pendingReceipts +} + +func (fb *filterBackend) GetLogs(ctx context.Context, hash common.Hash, number uint64) ([][]*types.Log, error) { + logs := rawdb.ReadLogs(fb.db, hash, number) + return logs, nil +} + +func (fb *filterBackend) SubscribeNewTxsEvent(ch chan<- core.NewTxsEvent) event.Subscription { + return nullSubscription() +} + +func (fb *filterBackend) SubscribeChainEvent(ch chan<- core.ChainEvent) event.Subscription { + return fb.bc.SubscribeChainEvent(ch) +} + +func (fb *filterBackend) SubscribeRemovedLogsEvent(ch chan<- core.RemovedLogsEvent) event.Subscription { + return fb.bc.SubscribeRemovedLogsEvent(ch) +} + +func (fb *filterBackend) SubscribeLogsEvent(ch chan<- []*types.Log) event.Subscription { + return fb.bc.SubscribeLogsEvent(ch) +} + +func (fb *filterBackend) SubscribePendingLogsEvent(ch chan<- []*types.Log) event.Subscription { + return nullSubscription() +} + +func (fb *filterBackend) BloomStatus() (uint64, uint64) { return 4096, 0 } + +func (fb *filterBackend) ServiceFilter(ctx context.Context, ms *bloombits.MatcherSession) { + panic("not supported") +} + +func (fb *filterBackend) ChainConfig() *params.ChainConfig { + panic("not supported") +} + +func (fb *filterBackend) CurrentHeader() *types.Header { + panic("not supported") +} + +func nullSubscription() event.Subscription { + return event.NewSubscription(func(quit <-chan struct{}) error { + <-quit + return nil + }) +} diff --git a/ethclient/simulated/backend_test.go b/ethclient/simulated/backend_test.go new file mode 100644 index 0000000000..2a85d16ef3 --- /dev/null +++ b/ethclient/simulated/backend_test.go @@ -0,0 +1,1304 @@ +// Copyright 2019 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 simulated + +import ( + "bytes" + "context" + "crypto/ecdsa" + "errors" + "math/big" + "math/rand" + "reflect" + "strings" + "testing" + "time" + + ethereum "github.com/XinFinOrg/XDPoSChain" + "github.com/XinFinOrg/XDPoSChain/accounts/abi" + "github.com/XinFinOrg/XDPoSChain/accounts/abi/bind" + "github.com/XinFinOrg/XDPoSChain/common" + "github.com/XinFinOrg/XDPoSChain/core/types" + "github.com/XinFinOrg/XDPoSChain/crypto" + "github.com/XinFinOrg/XDPoSChain/params" +) + +var _ bind.ContractBackend = (Client)(nil) + +var ( + testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + testAddr = crypto.PubkeyToAddress(testKey.PublicKey) +) + +const callableAbi = "[{\"anonymous\":false,\"inputs\":[],\"name\":\"Called\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"Call\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]" + +const callableBin = "6080604052348015600f57600080fd5b5060998061001e6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c806334e2292114602d575b600080fd5b60336035565b005b7f81fab7a4a0aa961db47eefc81f143a5220e8c8495260dd65b1356f1d19d3c7b860405160405180910390a156fea2646970667358221220029436d24f3ac598ceca41d4d712e13ced6d70727f4cdc580667de66d2f51d8b64736f6c63430008010033" + +const abiJSON = `[ { "constant": false, "inputs": [ { "name": "memo", "type": "bytes" } ], "name": "receive", "outputs": [ { "name": "res", "type": "string" } ], "payable": true, "stateMutability": "payable", "type": "function" }, { "anonymous": false, "inputs": [ { "indexed": false, "name": "sender", "type": "address" }, { "indexed": false, "name": "amount", "type": "uint256" }, { "indexed": false, "name": "memo", "type": "bytes" } ], "name": "received", "type": "event" }, { "anonymous": false, "inputs": [ { "indexed": false, "name": "sender", "type": "address" } ], "name": "receivedAddr", "type": "event" } ]` + +const abiBin = `0x608060405234801561001057600080fd5b506102a0806100206000396000f3fe60806040526004361061003b576000357c010000000000000000000000000000000000000000000000000000000090048063a69b6ed014610040575b600080fd5b6100b76004803603602081101561005657600080fd5b810190808035906020019064010000000081111561007357600080fd5b82018360208201111561008557600080fd5b803590602001918460018302840111640100000000831117156100a757600080fd5b9091929391929390505050610132565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156100f75780820151818401526020810190506100dc565b50505050905090810190601f1680156101245780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b60607f75fd880d39c1daf53b6547ab6cb59451fc6452d27caa90e5b6649dd8293b9eed33348585604051808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001848152602001806020018281038252848482818152602001925080828437600081840152601f19601f8201169050808301925050509550505050505060405180910390a17f46923992397eac56cf13058aced2a1871933622717e27b24eabc13bf9dd329c833604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390a16040805190810160405280600b81526020017f68656c6c6f20776f726c6400000000000000000000000000000000000000000081525090509291505056fea165627a7a72305820ff0c57dad254cfeda48c9cfb47f1353a558bccb4d1bc31da1dae69315772d29e0029` + +const deployedCode = `60806040526004361061003b576000357c010000000000000000000000000000000000000000000000000000000090048063a69b6ed014610040575b600080fd5b6100b76004803603602081101561005657600080fd5b810190808035906020019064010000000081111561007357600080fd5b82018360208201111561008557600080fd5b803590602001918460018302840111640100000000831117156100a757600080fd5b9091929391929390505050610132565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156100f75780820151818401526020810190506100dc565b50505050905090810190601f1680156101245780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b60607f75fd880d39c1daf53b6547ab6cb59451fc6452d27caa90e5b6649dd8293b9eed33348585604051808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001848152602001806020018281038252848482818152602001925080828437600081840152601f19601f8201169050808301925050509550505050505060405180910390a17f46923992397eac56cf13058aced2a1871933622717e27b24eabc13bf9dd329c833604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390a16040805190810160405280600b81526020017f68656c6c6f20776f726c6400000000000000000000000000000000000000000081525090509291505056fea165627a7a72305820ff0c57dad254cfeda48c9cfb47f1353a558bccb4d1bc31da1dae69315772d29e0029` + +var expectedReturn = []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 11, 104, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} + +func simTestBackend(testAddr common.Address) *Backend { + return New( + types.GenesisAlloc{ + testAddr: {Balance: big.NewInt(10000000000000000)}, + }, 10000000, + ) +} + +func newTx(sim *Backend, key *ecdsa.PrivateKey) (*types.Transaction, error) { + client := sim.Client() + + // create a signed transaction to send + head, _ := client.HeaderByNumber(context.Background(), nil) // Should be child's, good enough + gasPrice := new(big.Int).Add(head.BaseFee, big.NewInt(1)) + addr := crypto.PubkeyToAddress(key.PublicKey) + chainid, _ := client.ChainID(context.Background()) + nonce, err := client.PendingNonceAt(context.Background(), addr) + if err != nil { + return nil, err + } + tx := types.NewTx(&types.DynamicFeeTx{ + ChainID: chainid, + Nonce: nonce, + GasTipCap: big.NewInt(1), + GasFeeCap: gasPrice, + Gas: 21000, + To: &addr, + }) + return types.SignTx(tx, types.LatestSignerForChainID(chainid), key) +} + +func newContractCreationTx(sim *Backend, key *ecdsa.PrivateKey, bytecode []byte, gas uint64) (*types.Transaction, common.Address, error) { + client := sim.Client() + + head, _ := client.HeaderByNumber(context.Background(), nil) // Should be child's, good enough + gasFeeCap := new(big.Int).Add(head.BaseFee, big.NewInt(1)) + from := crypto.PubkeyToAddress(key.PublicKey) + chainID, _ := client.ChainID(context.Background()) + nonce, err := client.PendingNonceAt(context.Background(), from) + if err != nil { + return nil, common.Address{}, err + } + tx := types.NewTx(&types.DynamicFeeTx{ + ChainID: chainID, + Nonce: nonce, + GasTipCap: big.NewInt(1), + GasFeeCap: gasFeeCap, + Gas: gas, + Data: bytecode, + }) + signed, err := types.SignTx(tx, types.LatestSignerForChainID(chainID), key) + if err != nil { + return nil, common.Address{}, err + } + return signed, crypto.CreateAddress(from, nonce), nil +} + +func TestSimulatedBackend(t *testing.T) { + t.Parallel() + key, _ := crypto.GenerateKey() + auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337)) + alloc := types.GenesisAlloc{auth.From: {Balance: big.NewInt(9223372036854775807)}} + sim := New(alloc, 8000029) + defer sim.Close() + + client := sim.Client() + ctx := context.Background() + + _, pending, err := client.TransactionByHash(ctx, common.HexToHash("0x2")) + if pending || !errors.Is(err, ethereum.ErrNotFound) { + t.Fatalf("expected not found and not pending, got err=%v pending=%v", err, pending) + } + + head, _ := client.HeaderByNumber(ctx, nil) + gasPrice := new(big.Int).Add(head.BaseFee, big.NewInt(1)) + tx := types.NewContractCreation(0, big.NewInt(0), 3000000, gasPrice, common.FromHex("6060604052600a8060106000396000f360606040526008565b00")) + tx, _ = types.SignTx(tx, types.HomesteadSigner{}, key) + if err := client.SendTransaction(ctx, tx); err != nil { + t.Fatalf("send tx failed: %v", err) + } + _, pending, err = client.TransactionByHash(ctx, tx.Hash()) + if err != nil || !pending { + t.Fatalf("expected pending tx, err=%v pending=%v", err, pending) + } + sim.Commit() + _, pending, err = client.TransactionByHash(ctx, tx.Hash()) + if err != nil || pending { + t.Fatalf("expected mined tx, err=%v pending=%v", err, pending) + } +} + +func TestNewSimulatedBackend(t *testing.T) { + t.Parallel() + sim := simTestBackend(testAddr) + defer sim.Close() + bal, err := sim.Client().BalanceAt(context.Background(), testAddr, nil) + if err != nil { + t.Fatal(err) + } + if bal.Cmp(big.NewInt(10000000000000000)) != 0 { + t.Fatalf("unexpected balance %v", bal) + } +} + +func TestAdjustTime(t *testing.T) { + sim := New(types.GenesisAlloc{}, 10_000_000) + defer sim.Close() + + client := sim.Client() + block1, _ := client.BlockByNumber(context.Background(), nil) + + // Create a block + if err := sim.AdjustTime(time.Minute); err != nil { + t.Fatal(err) + } + block2, _ := client.BlockByNumber(context.Background(), nil) + prevTime := block1.Time() + newTime := block2.Time() + if newTime-prevTime != uint64(time.Minute.Seconds()) { + t.Errorf("adjusted time not equal to 60 seconds. prev: %v, new: %v", prevTime, newTime) + } +} + +func TestNewAdjustTimeFail(t *testing.T) { + t.Parallel() + sim := simTestBackend(testAddr) + defer sim.Close() + client := sim.Client() + ctx := context.Background() + + head, _ := client.HeaderByNumber(ctx, nil) + gasPrice := new(big.Int).Add(head.BaseFee, big.NewInt(1)) + tx := types.NewTransaction(0, testAddr, big.NewInt(1000), params.TxGas, gasPrice, nil) + signedTx, _ := types.SignTx(tx, types.HomesteadSigner{}, testKey) + _ = client.SendTransaction(ctx, signedTx) + if err := sim.AdjustTime(time.Second); err == nil { + t.Fatal("expected adjust time to fail on non-empty block") + } + sim.Commit() + + prevTime := sim.pendingBlock.Time() + if err := sim.AdjustTime(time.Minute); err != nil { + t.Fatal(err) + } + newTime := sim.pendingBlock.Time() + if newTime-prevTime != uint64(time.Minute.Seconds()) { + t.Fatalf("adjusted time mismatch") + } + + tx2 := types.NewTransaction(1, testAddr, big.NewInt(1000), params.TxGas, gasPrice, nil) + signedTx2, _ := types.SignTx(tx2, types.HomesteadSigner{}, testKey) + _ = client.SendTransaction(ctx, signedTx2) + sim.Commit() + newTime = sim.pendingBlock.Time() + if newTime < prevTime { + t.Fatalf("time moved backwards unexpectedly") + } +} + +func TestBalanceAt(t *testing.T) { + t.Parallel() + sim := simTestBackend(testAddr) + defer sim.Close() + bal, err := sim.Client().BalanceAt(context.Background(), testAddr, nil) + if err != nil { + t.Fatal(err) + } + if bal.Cmp(big.NewInt(10000000000000000)) != 0 { + t.Fatalf("unexpected balance %v", bal) + } +} + +func TestBlockByHash(t *testing.T) { + t.Parallel() + sim := New(types.GenesisAlloc{}, 10000000) + defer sim.Close() + client := sim.Client() + ctx := context.Background() + block, _ := client.BlockByNumber(ctx, nil) + byHash, err := client.BlockByHash(ctx, block.Hash()) + if err != nil || byHash.Hash() != block.Hash() { + t.Fatalf("block by hash mismatch: err=%v", err) + } +} + +func TestBlockByNumber(t *testing.T) { + t.Parallel() + sim := New(types.GenesisAlloc{}, 10000000) + defer sim.Close() + client := sim.Client() + ctx := context.Background() + block, _ := client.BlockByNumber(ctx, nil) + if block.NumberU64() != 0 { + t.Fatalf("expected block 0") + } + sim.Commit() + latest, _ := client.BlockByNumber(ctx, nil) + if latest.NumberU64() != 1 { + t.Fatalf("expected block 1") + } + one, err := client.BlockByNumber(ctx, big.NewInt(1)) + if err != nil || one.Hash() != latest.Hash() { + t.Fatalf("block by number mismatch: err=%v", err) + } +} + +func TestNonceAt(t *testing.T) { + t.Parallel() + sim := simTestBackend(testAddr) + defer sim.Close() + client := sim.Client() + ctx := context.Background() + nonce, _ := client.NonceAt(ctx, testAddr, big.NewInt(0)) + if nonce != 0 { + t.Fatalf("expected nonce 0") + } + head, _ := client.HeaderByNumber(ctx, nil) + gasPrice := new(big.Int).Add(head.BaseFee, big.NewInt(1)) + tx := types.NewTransaction(nonce, testAddr, big.NewInt(1000), params.TxGas, gasPrice, nil) + signed, _ := types.SignTx(tx, types.HomesteadSigner{}, testKey) + _ = client.SendTransaction(ctx, signed) + sim.Commit() + n1, _ := client.NonceAt(ctx, testAddr, big.NewInt(1)) + if n1 != 1 { + t.Fatalf("expected nonce 1") + } + sim.Commit() + n1Again, _ := client.NonceAt(ctx, testAddr, big.NewInt(1)) + if n1Again != 1 { + t.Fatalf("expected historical nonce 1") + } +} + +func TestSendTransaction(t *testing.T) { + sim := simTestBackend(testAddr) + defer sim.Close() + + client := sim.Client() + ctx := context.Background() + + signedTx, err := newTx(sim, testKey) + if err != nil { + t.Errorf("could not create transaction: %v", err) + } + // send tx to simulated backend + err = client.SendTransaction(ctx, signedTx) + if err != nil { + t.Errorf("could not add tx to pending block: %v", err) + } + sim.Commit() + block, err := client.BlockByNumber(ctx, big.NewInt(1)) + if err != nil { + t.Errorf("could not get block at height 1: %v", err) + } + + if signedTx.Hash() != block.Transactions()[0].Hash() { + t.Errorf("did not commit sent transaction. expected hash %v got hash %v", block.Transactions()[0].Hash(), signedTx.Hash()) + } +} + +func TestTransactionByHash(t *testing.T) { + t.Parallel() + sim := simTestBackend(testAddr) + defer sim.Close() + + client := sim.Client() + ctx := context.Background() + + head, _ := client.HeaderByNumber(ctx, nil) + gasPrice := new(big.Int).Add(head.BaseFee, big.NewInt(1)) + tx := types.NewTransaction(0, testAddr, big.NewInt(1000), params.TxGas, gasPrice, nil) + signedTx, err := types.SignTx(tx, types.HomesteadSigner{}, testKey) + if err != nil { + t.Fatalf("could not sign tx: %v", err) + } + if err := client.SendTransaction(ctx, signedTx); err != nil { + t.Fatalf("could not send tx: %v", err) + } + + receivedTx, pending, err := client.TransactionByHash(ctx, signedTx.Hash()) + if err != nil || !pending || receivedTx.Hash() != signedTx.Hash() { + t.Fatalf("expected pending tx by hash, err=%v pending=%v", err, pending) + } + + sim.Commit() + receivedTx, pending, err = client.TransactionByHash(ctx, signedTx.Hash()) + if err != nil || pending || receivedTx.Hash() != signedTx.Hash() { + t.Fatalf("expected mined tx by hash, err=%v pending=%v", err, pending) + } +} + +func TestEstimateGas(t *testing.T) { + t.Parallel() + const contractAbi = "[{\"inputs\":[],\"name\":\"Assert\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"OOG\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"PureRevert\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"Revert\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"Valid\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]" + const contractBin = "0x60806040523480156100115760006000fd5b50610017565b61016e806100266000396000f3fe60806040523480156100115760006000fd5b506004361061005c5760003560e01c806350f6fe3414610062578063aa8b1d301461006c578063b9b046f914610076578063d8b9839114610080578063e09fface1461008a5761005c565b60006000fd5b61006a610094565b005b6100746100ad565b005b61007e6100b5565b005b6100886100c2565b005b610092610135565b005b6000600090505b5b808060010191505061009b565b505b565b60006000fd5b565b600015156100bf57fe5b5b565b6040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252600d8152602001807f72657665727420726561736f6e0000000000000000000000000000000000000081526020015060200191505060405180910390fd5b565b5b56fea2646970667358221220345bbcbb1a5ecf22b53a78eaebf95f8ee0eceff6d10d4b9643495084d2ec934a64736f6c63430006040033" + + key, _ := crypto.GenerateKey() + addr := crypto.PubkeyToAddress(key.PublicKey) + opts, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337)) + sim := New(types.GenesisAlloc{addr: {Balance: big.NewInt(params.Ether)}}, 10000000) + defer sim.Close() + + parsed, _ := abi.JSON(strings.NewReader(contractAbi)) + contractAddr, _, _, _ := bind.DeployContract(opts, parsed, common.FromHex(contractBin), sim) + sim.Commit() + + cases := []struct { + message ethereum.CallMsg + expect uint64 + expectError error + expectData interface{} + }{ + {ethereum.CallMsg{From: addr, To: &addr, GasPrice: big.NewInt(0), Value: big.NewInt(1)}, params.TxGas, nil, nil}, + {ethereum.CallMsg{From: addr, To: &contractAddr, GasPrice: big.NewInt(0), Value: big.NewInt(1)}, 0, errors.New("execution reverted"), nil}, + {ethereum.CallMsg{From: addr, To: &contractAddr, GasPrice: big.NewInt(0), Data: common.Hex2Bytes("d8b98391")}, 0, errors.New("execution reverted: revert reason"), "0x08c379a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000d72657665727420726561736f6e00000000000000000000000000000000000000"}, + {ethereum.CallMsg{From: addr, To: &contractAddr, GasPrice: big.NewInt(0), Data: common.Hex2Bytes("aa8b1d30")}, 0, errors.New("execution reverted"), nil}, + {ethereum.CallMsg{From: addr, To: &contractAddr, Gas: 100000, GasPrice: big.NewInt(0), Data: common.Hex2Bytes("50f6fe34")}, 0, errors.New("gas required exceeds allowance (100000)"), nil}, + {ethereum.CallMsg{From: addr, To: &contractAddr, Gas: 100000, GasPrice: big.NewInt(0), Data: common.Hex2Bytes("b9b046f9")}, 0, errors.New("invalid opcode: INVALID"), nil}, + {ethereum.CallMsg{From: addr, To: &contractAddr, Gas: 100000, GasPrice: big.NewInt(0), Data: common.Hex2Bytes("e09fface")}, 21483, nil, nil}, + } + for _, c := range cases { + got, err := sim.EstimateGas(context.Background(), c.message) + if c.expectError != nil { + if err == nil || err.Error() != c.expectError.Error() { + t.Fatalf("expected error %v, got %v", c.expectError, err) + } + if c.expectData != nil { + rerr, ok := err.(*revertError) + if !ok || !reflect.DeepEqual(rerr.ErrorData(), c.expectData) { + t.Fatalf("revert data mismatch") + } + } + continue + } + if got != c.expect { + t.Fatalf("gas mismatch, want %d got %d", c.expect, got) + } + } +} + +func TestEstimateGasWithPrice(t *testing.T) { + t.Parallel() + key, _ := crypto.GenerateKey() + addr := crypto.PubkeyToAddress(key.PublicKey) + sim := New(types.GenesisAlloc{addr: {Balance: big.NewInt(params.Ether*2 + 2e17)}}, 10000000) + defer sim.Close() + + recipient := common.HexToAddress("deadbeef") + cases := []ethereum.CallMsg{ + {From: addr, To: &recipient, GasPrice: big.NewInt(0), Value: big.NewInt(100000000000)}, + {From: addr, To: &recipient, GasPrice: big.NewInt(100000000000), Value: big.NewInt(100000000000)}, + {From: addr, To: &recipient, GasPrice: big.NewInt(1e14), Value: big.NewInt(1e17)}, + } + for i, c := range cases { + got, err := sim.EstimateGas(context.Background(), c) + if err != nil || got != 21000 { + t.Fatalf("case %d failed, gas=%d err=%v", i, got, err) + } + } +} + +func TestHeaderByHash(t *testing.T) { + t.Parallel() + sim := simTestBackend(testAddr) + defer sim.Close() + ctx := context.Background() + client := sim.Client() + head, _ := client.HeaderByNumber(ctx, nil) + byHash, err := client.HeaderByHash(ctx, head.Hash()) + if err != nil || byHash.Hash() != head.Hash() { + t.Fatalf("header by hash mismatch: err=%v", err) + } +} + +func TestHeaderByNumber(t *testing.T) { + t.Parallel() + sim := simTestBackend(testAddr) + defer sim.Close() + ctx := context.Background() + client := sim.Client() + head0, _ := client.HeaderByNumber(ctx, nil) + if head0.Number.Uint64() != 0 { + t.Fatalf("expected head 0") + } + sim.Commit() + latest, _ := client.HeaderByNumber(ctx, nil) + head1, _ := client.HeaderByNumber(ctx, big.NewInt(1)) + if head1.Hash() != latest.Hash() || head1.Number.Uint64() != 1 { + t.Fatalf("header by number mismatch") + } + block1, _ := client.BlockByNumber(ctx, big.NewInt(1)) + if block1.Hash() != head1.Hash() { + t.Fatalf("block/header hash mismatch") + } +} + +func TestTransactionCount(t *testing.T) { + t.Parallel() + sim := simTestBackend(testAddr) + defer sim.Close() + ctx := context.Background() + client := sim.Client() + cur, _ := client.BlockByNumber(ctx, nil) + count, _ := sim.TransactionCount(ctx, cur.Hash()) + if count != 0 { + t.Fatalf("expected 0 tx count") + } + head, _ := client.HeaderByNumber(ctx, nil) + gasPrice := new(big.Int).Add(head.BaseFee, big.NewInt(1)) + tx := types.NewTransaction(0, testAddr, big.NewInt(1000), params.TxGas, gasPrice, nil) + signed, _ := types.SignTx(tx, types.HomesteadSigner{}, testKey) + _ = client.SendTransaction(ctx, signed) + sim.Commit() + last, _ := client.BlockByNumber(ctx, nil) + count, _ = sim.TransactionCount(ctx, last.Hash()) + if count != 1 { + t.Fatalf("expected 1 tx count") + } +} + +func TestTransactionInBlock(t *testing.T) { + t.Parallel() + sim := simTestBackend(testAddr) + defer sim.Close() + ctx := context.Background() + client := sim.Client() + if tx, err := sim.TransactionInBlock(ctx, sim.pendingBlock.Hash(), 0); err == nil || tx != nil { + t.Fatalf("expected missing tx in empty pending block") + } + head, _ := client.HeaderByNumber(ctx, nil) + gasPrice := new(big.Int).Add(head.BaseFee, big.NewInt(1)) + tx := types.NewTransaction(0, testAddr, big.NewInt(1000), params.TxGas, gasPrice, nil) + signed, _ := types.SignTx(tx, types.HomesteadSigner{}, testKey) + _ = client.SendTransaction(ctx, signed) + sim.Commit() + last, _ := client.BlockByNumber(ctx, nil) + if tx1, err := sim.TransactionInBlock(ctx, last.Hash(), 1); err == nil || tx1 != nil { + t.Fatalf("expected missing tx at index 1") + } + tx0, err := sim.TransactionInBlock(ctx, last.Hash(), 0) + if err != nil || tx0.Hash() != signed.Hash() { + t.Fatalf("tx in block mismatch: err=%v", err) + } +} + +func TestPendingNonceAt(t *testing.T) { + t.Parallel() + sim := simTestBackend(testAddr) + defer sim.Close() + ctx := context.Background() + client := sim.Client() + p0, _ := client.PendingNonceAt(ctx, testAddr) + if p0 != 0 { + t.Fatalf("expected pending nonce 0") + } + head, _ := client.HeaderByNumber(ctx, nil) + gasPrice := new(big.Int).Add(head.BaseFee, big.NewInt(1)) + tx0 := types.NewTransaction(0, testAddr, big.NewInt(1000), params.TxGas, gasPrice, nil) + signed0, _ := types.SignTx(tx0, types.HomesteadSigner{}, testKey) + _ = client.SendTransaction(ctx, signed0) + p1, _ := client.PendingNonceAt(ctx, testAddr) + if p1 != 1 { + t.Fatalf("expected pending nonce 1") + } + tx1 := types.NewTransaction(1, testAddr, big.NewInt(1000), params.TxGas, gasPrice, nil) + signed1, _ := types.SignTx(tx1, types.HomesteadSigner{}, testKey) + _ = client.SendTransaction(ctx, signed1) + p2, _ := client.PendingNonceAt(ctx, testAddr) + if p2 != 2 { + t.Fatalf("expected pending nonce 2") + } +} + +func TestTransactionReceipt(t *testing.T) { + t.Parallel() + sim := simTestBackend(testAddr) + defer sim.Close() + ctx := context.Background() + client := sim.Client() + head, _ := client.HeaderByNumber(ctx, nil) + gasPrice := new(big.Int).Add(head.BaseFee, big.NewInt(1)) + tx := types.NewTransaction(0, testAddr, big.NewInt(1000), params.TxGas, gasPrice, nil) + signed, _ := types.SignTx(tx, types.HomesteadSigner{}, testKey) + _ = client.SendTransaction(ctx, signed) + sim.Commit() + receipt, err := client.TransactionReceipt(ctx, signed.Hash()) + if err != nil { + t.Fatal(err) + } + if receipt.TxHash != signed.Hash() { + t.Fatalf("receipt tx hash mismatch") + } +} + +func TestSuggestGasPrice(t *testing.T) { + t.Parallel() + sim := New(types.GenesisAlloc{}, 10000000) + defer sim.Close() + price, err := sim.Client().SuggestGasPrice(context.Background()) + if err != nil { + t.Fatal(err) + } + baseFee := sim.pendingBlock.Header().BaseFee + if baseFee == nil { + baseFee = big.NewInt(1) + } + if price.Cmp(baseFee) != 0 { + t.Fatalf("unexpected gas price %v want %v", price, baseFee) + } +} + +func TestPendingCodeAt(t *testing.T) { + t.Parallel() + sim := simTestBackend(testAddr) + defer sim.Close() + ctx := context.Background() + client := sim.Client() + code, _ := client.CodeAt(ctx, testAddr, nil) + if len(code) != 0 { + t.Fatalf("expected no code at EOA") + } + parsed, _ := abi.JSON(strings.NewReader(abiJSON)) + auth, _ := bind.NewKeyedTransactorWithChainID(testKey, big.NewInt(1337)) + contractAddr, _, _, err := bind.DeployContract(auth, parsed, common.FromHex(abiBin), sim) + if err != nil { + t.Fatal(err) + } + pendingCode, err := client.PendingCodeAt(ctx, contractAddr) + if err != nil || len(pendingCode) == 0 { + t.Fatalf("pending code unavailable: err=%v", err) + } + if !bytes.Equal(pendingCode, common.FromHex(deployedCode)) { + t.Fatalf("pending code mismatch") + } +} + +func TestCodeAt(t *testing.T) { + t.Parallel() + sim := simTestBackend(testAddr) + defer sim.Close() + ctx := context.Background() + client := sim.Client() + code, _ := client.CodeAt(ctx, testAddr, nil) + if len(code) != 0 { + t.Fatalf("expected no code at EOA") + } + parsed, _ := abi.JSON(strings.NewReader(abiJSON)) + auth, _ := bind.NewKeyedTransactorWithChainID(testKey, big.NewInt(1337)) + contractAddr, _, _, err := bind.DeployContract(auth, parsed, common.FromHex(abiBin), sim) + if err != nil { + t.Fatal(err) + } + sim.Commit() + code, err = client.CodeAt(ctx, contractAddr, nil) + if err != nil || len(code) == 0 { + t.Fatalf("code unavailable: err=%v", err) + } + if !bytes.Equal(code, common.FromHex(deployedCode)) { + t.Fatalf("code mismatch") + } +} + +func TestCodeAtHash(t *testing.T) { + t.Parallel() + sim := simTestBackend(testAddr) + defer sim.Close() + ctx := context.Background() + client := sim.Client() + head, _ := client.HeaderByNumber(ctx, nil) + code, err := sim.CodeAtHash(ctx, testAddr, head.Hash()) + if err != nil || len(code) != 0 { + t.Fatalf("expected no code at EOA: err=%v", err) + } + parsed, _ := abi.JSON(strings.NewReader(abiJSON)) + auth, _ := bind.NewKeyedTransactorWithChainID(testKey, big.NewInt(1337)) + contractAddr, _, _, err := bind.DeployContract(auth, parsed, common.FromHex(abiBin), sim) + if err != nil { + t.Fatal(err) + } + blockHash := sim.Commit() + code, err = sim.CodeAtHash(ctx, contractAddr, blockHash) + if err != nil || len(code) == 0 { + t.Fatalf("code at hash unavailable: err=%v", err) + } + if !bytes.Equal(code, common.FromHex(deployedCode)) { + t.Fatalf("code at hash mismatch") + } +} + +func TestPendingAndCallContract(t *testing.T) { + t.Parallel() + sim := simTestBackend(testAddr) + defer sim.Close() + ctx := context.Background() + parsed, _ := abi.JSON(strings.NewReader(abiJSON)) + auth, _ := bind.NewKeyedTransactorWithChainID(testKey, big.NewInt(1337)) + addr, _, _, err := bind.DeployContract(auth, parsed, common.FromHex(abiBin), sim) + if err != nil { + t.Fatal(err) + } + input, _ := parsed.Pack("receive", []byte("X")) + res, err := sim.PendingCallContract(ctx, ethereum.CallMsg{From: testAddr, To: &addr, Data: input}) + if err != nil || len(res) == 0 { + t.Fatalf("pending call failed: err=%v", err) + } + if !bytes.Equal(res, expectedReturn) || !strings.Contains(string(res), "hello world") { + t.Fatalf("unexpected pending call return") + } + blockHash := sim.Commit() + res, err = sim.CallContract(ctx, ethereum.CallMsg{From: testAddr, To: &addr, Data: input}, nil) + if err != nil || len(res) == 0 { + t.Fatalf("call failed: err=%v", err) + } + if !bytes.Equal(res, expectedReturn) || !strings.Contains(string(res), "hello world") { + t.Fatalf("unexpected call return") + } + res, err = sim.CallContractAtHash(ctx, ethereum.CallMsg{From: testAddr, To: &addr, Data: input}, blockHash) + if err != nil || len(res) == 0 { + t.Fatalf("call at hash failed: err=%v", err) + } + if !bytes.Equal(res, expectedReturn) || !strings.Contains(string(res), "hello world") { + t.Fatalf("unexpected call at hash return") + } +} + +func TestCallContractRevert(t *testing.T) { + t.Parallel() + sim := simTestBackend(testAddr) + defer sim.Close() + + client := sim.Client() + ctx := context.Background() + + bytecode := common.FromHex("6005600c60003960056000f360006000fd") + tx, contractAddr, err := newContractCreationTx(sim, testKey, bytecode, 300000) + if err != nil { + t.Fatalf("could not create deploy tx: %v", err) + } + if err := client.SendTransaction(ctx, tx); err != nil { + t.Fatalf("could not send deploy tx: %v", err) + } + sim.Commit() + + _, err = client.CallContract(ctx, ethereum.CallMsg{From: testAddr, To: &contractAddr}, nil) + if err == nil || !strings.Contains(err.Error(), "execution reverted") { + t.Fatalf("expected execution reverted error, got: %v", err) + } +} + +func TestFork(t *testing.T) { + t.Parallel() + testAddr := crypto.PubkeyToAddress(testKey.PublicKey) + sim := simTestBackend(testAddr) + defer sim.Close() + + client := sim.Client() + ctx := context.Background() + + // 1. + parent, _ := client.HeaderByNumber(ctx, nil) + + // 2. + n := int(rand.Int31n(21)) + for i := 0; i < n; i++ { + sim.Commit() + } + + // 3. + b, _ := client.BlockNumber(ctx) + if b != uint64(n) { + t.Error("wrong chain length") + } + + // 4. + sim.Fork(parent.Hash()) + + // 5. + for i := 0; i < n+1; i++ { + sim.Commit() + } + + // 6. + b, _ = client.BlockNumber(ctx) + if b != uint64(n+1) { + t.Error("wrong chain length") + } +} + +func TestForkLogsReborn(t *testing.T) { + t.Parallel() + sim, client, ctx, auth, contract, _, _, parentHash := setupForkLogsRebornScenario(t) + defer sim.Close() + + var err error + + logs, sub, err := contract.WatchLogs(nil, "Called") + if err != nil { + t.Fatalf("watching logs: %v", err) + } + defer sub.Unsubscribe() + + tx, err := contract.Transact(auth, "Call") + if err != nil { + t.Fatalf("sending contract tx: %v", err) + } + sim.Commit() + + lg := mustReadWatchedLog(t, logs, sub, "included") + if lg.TxHash != tx.Hash() { + t.Fatalf("wrong included event tx hash: got %s want %s", lg.TxHash, tx.Hash()) + } + if lg.Removed { + t.Fatalf("event should be included") + } + + if err := sim.Fork(parentHash); err != nil { + t.Fatalf("forking: %v", err) + } + sim.Commit() + sim.Commit() + + lg = mustReadWatchedLog(t, logs, sub, "removed") + if lg.TxHash != tx.Hash() { + t.Fatalf("wrong removed event tx hash: got %s want %s", lg.TxHash, tx.Hash()) + } + if !lg.Removed { + t.Fatalf("event should be removed after reorg") + } + + if err := client.SendTransaction(ctx, tx); err != nil { + t.Fatalf("re-sending transaction: %v", err) + } + sim.Commit() + + lg = mustReadWatchedLog(t, logs, sub, "reborn") + if lg.TxHash != tx.Hash() { + t.Fatalf("wrong reborn event tx hash: got %s want %s", lg.TxHash, tx.Hash()) + } + if lg.Removed { + t.Fatalf("event should be reborn as included") + } +} + +func TestForkResendTx(t *testing.T) { + t.Parallel() + testAddr := crypto.PubkeyToAddress(testKey.PublicKey) + sim := simTestBackend(testAddr) + defer sim.Close() + + client := sim.Client() + ctx := context.Background() + + // 1. + parent, _ := client.HeaderByNumber(ctx, nil) + + // 2. + tx, err := newTx(sim, testKey) + if err != nil { + t.Fatalf("could not create transaction: %v", err) + } + client.SendTransaction(ctx, tx) + sim.Commit() + + // 3. + receipt, _ := client.TransactionReceipt(ctx, tx.Hash()) + if h := receipt.BlockNumber.Uint64(); h != 1 { + t.Errorf("TX included in wrong block: %d", h) + } + + // 4. + if err := sim.Fork(parent.Hash()); err != nil { + t.Errorf("forking: %v", err) + } + + // 5. + sim.Commit() + if err := client.SendTransaction(ctx, tx); err != nil { + t.Fatalf("sending transaction: %v", err) + } + sim.Commit() + receipt, _ = client.TransactionReceipt(ctx, tx.Hash()) + if h := receipt.BlockNumber.Uint64(); h != 2 { + t.Errorf("TX included in wrong block: %d", h) + } +} + +func TestCommitReturnValue(t *testing.T) { + t.Parallel() + testAddr := crypto.PubkeyToAddress(testKey.PublicKey) + sim := simTestBackend(testAddr) + defer sim.Close() + + client := sim.Client() + ctx := context.Background() + + // Test if Commit returns the correct block hash + h1 := sim.Commit() + cur, _ := client.HeaderByNumber(ctx, nil) + if h1 != cur.Hash() { + t.Error("Commit did not return the hash of the last block.") + } + + // Create a block in the original chain (containing a transaction to force different block hashes) + head, _ := client.HeaderByNumber(ctx, nil) // Should be child's, good enough + gasPrice := new(big.Int).Add(head.BaseFee, big.NewInt(1)) + _tx := types.NewTransaction(0, testAddr, big.NewInt(1000), params.TxGas, gasPrice, nil) + tx, _ := types.SignTx(_tx, types.HomesteadSigner{}, testKey) + client.SendTransaction(ctx, tx) + + h2 := sim.Commit() + + // Create another block in the original chain + sim.Commit() + + // Fork at the first bock + if err := sim.Fork(h1); err != nil { + t.Errorf("forking: %v", err) + } + + // Test if Commit returns the correct block hash after the reorg + h2fork := sim.Commit() + if h2 == h2fork { + t.Error("The block in the fork and the original block are the same block!") + } + if header, err := client.HeaderByHash(ctx, h2fork); err != nil || header == nil { + t.Error("Could not retrieve the just created block (side-chain)") + } +} + +func TestAdjustTimeAfterFork(t *testing.T) { + t.Parallel() + testAddr := crypto.PubkeyToAddress(testKey.PublicKey) + sim := simTestBackend(testAddr) + defer sim.Close() + + client := sim.Client() + ctx := context.Background() + + sim.Commit() // h1 + h1, _ := client.HeaderByNumber(ctx, nil) + + sim.Commit() // h2 + sim.Fork(h1.Hash()) + sim.AdjustTime(1 * time.Second) + sim.Commit() + + head, _ := client.HeaderByNumber(ctx, nil) + if head.Number.Uint64() == 2 && head.ParentHash != h1.Hash() { + t.Errorf("failed to build block on fork") + } +} + +func TestNewSim(t *testing.T) { + sim := New(types.GenesisAlloc{}, 30_000_000) + defer sim.Close() + + client := sim.Client() + num, err := client.BlockNumber(context.Background()) + if err != nil { + t.Fatal(err) + } + if num != 0 { + t.Fatalf("expected 0 got %v", num) + } + // Create a block + sim.Commit() + num, err = client.BlockNumber(context.Background()) + if err != nil { + t.Fatal(err) + } + if num != 1 { + t.Fatalf("expected 1 got %v", num) + } +} + +func TestTransactionByHashLifecycle(t *testing.T) { + t.Parallel() + sim := simTestBackend(testAddr) + defer sim.Close() + + client := sim.Client() + ctx := context.Background() + + if _, pending, err := client.TransactionByHash(ctx, common.HexToHash("0x1234")); !errors.Is(err, ethereum.ErrNotFound) || pending { + t.Fatalf("expected not found and not pending, got err=%v pending=%v", err, pending) + } + + tx, err := newTx(sim, testKey) + if err != nil { + t.Fatalf("could not create tx: %v", err) + } + if err := client.SendTransaction(ctx, tx); err != nil { + t.Fatalf("could not send tx: %v", err) + } + + got, pending, err := client.TransactionByHash(ctx, tx.Hash()) + if err != nil || !pending || got.Hash() != tx.Hash() { + t.Fatalf("expected pending tx before commit, got err=%v pending=%v", err, pending) + } + + sim.Commit() + got, pending, err = client.TransactionByHash(ctx, tx.Hash()) + if err != nil || pending || got.Hash() != tx.Hash() { + t.Fatalf("expected mined tx after commit, got err=%v pending=%v", err, pending) + } +} + +func TestTransactionReceiptLifecycle(t *testing.T) { + t.Parallel() + sim := simTestBackend(testAddr) + defer sim.Close() + + client := sim.Client() + ctx := context.Background() + + if _, err := client.TransactionReceipt(ctx, common.HexToHash("0x1234")); !errors.Is(err, ethereum.ErrNotFound) { + t.Fatalf("expected not found before tx mining, got %v", err) + } + + tx, err := newTx(sim, testKey) + if err != nil { + t.Fatalf("could not create tx: %v", err) + } + if err := client.SendTransaction(ctx, tx); err != nil { + t.Fatalf("could not send tx: %v", err) + } + sim.Commit() + + receipt, err := client.TransactionReceipt(ctx, tx.Hash()) + if err != nil { + t.Fatalf("could not fetch receipt: %v", err) + } + if receipt.TxHash != tx.Hash() || receipt.BlockNumber == nil || receipt.BlockNumber.Uint64() != 1 { + t.Fatalf("unexpected receipt content: tx=%s block=%v", receipt.TxHash, receipt.BlockNumber) + } +} + +func TestSuggestGasPriceAndTipCap(t *testing.T) { + t.Parallel() + sim := simTestBackend(testAddr) + defer sim.Close() + + client := sim.Client() + ctx := context.Background() + + head, err := client.HeaderByNumber(ctx, nil) + if err != nil { + t.Fatalf("could not fetch header: %v", err) + } + price, err := client.SuggestGasPrice(ctx) + if err != nil { + t.Fatalf("could not suggest gas price: %v", err) + } + if head.BaseFee != nil && price.Cmp(head.BaseFee) != 0 { + t.Fatalf("unexpected suggested gas price: got %v want %v", price, head.BaseFee) + } + tip, err := client.SuggestGasTipCap(ctx) + if err != nil { + t.Fatalf("could not suggest tip cap: %v", err) + } + if tip.Cmp(big.NewInt(1)) != 0 { + t.Fatalf("unexpected tip cap: got %v want 1", tip) + } +} + +func TestEstimateGasSimpleTransfer(t *testing.T) { + t.Parallel() + sim := simTestBackend(testAddr) + defer sim.Close() + + client := sim.Client() + ctx := context.Background() + + gas, err := client.EstimateGas(ctx, ethereum.CallMsg{ + From: testAddr, + To: &testAddr, + }) + if err != nil { + t.Fatalf("estimate gas failed: %v", err) + } + if gas < params.TxGas { + t.Fatalf("estimated gas too low: got %d want >= %d", gas, params.TxGas) + } +} + +func TestFeeHistoryBasic(t *testing.T) { + t.Parallel() + sim := simTestBackend(testAddr) + defer sim.Close() + + client := sim.Client() + ctx := context.Background() + + for i := 0; i < 3; i++ { + tx, err := newTx(sim, testKey) + if err != nil { + t.Fatalf("could not create tx: %v", err) + } + if err := client.SendTransaction(ctx, tx); err != nil { + t.Fatalf("could not send tx: %v", err) + } + sim.Commit() + } + + history, err := client.FeeHistory(ctx, 2, nil, []float64{50, 90}) + if err != nil { + t.Fatalf("fee history failed: %v", err) + } + if history == nil || history.OldestBlock == nil { + t.Fatalf("fee history response is incomplete") + } + if len(history.BaseFee) != 3 || len(history.GasUsedRatio) != 2 || len(history.Reward) != 2 { + t.Fatalf("unexpected fee history lengths: base=%d gas=%d reward=%d", len(history.BaseFee), len(history.GasUsedRatio), len(history.Reward)) + } + for i, rewards := range history.Reward { + if len(rewards) != 2 { + t.Fatalf("unexpected reward percentiles at block %d: got %d", i, len(rewards)) + } + } +} + +func TestPendingCodeAtAndCodeAt(t *testing.T) { + t.Parallel() + sim := simTestBackend(testAddr) + defer sim.Close() + + client := sim.Client() + ctx := context.Background() + + bytecode := common.FromHex("6005600c60003960056000f360006000fd") + tx, contractAddr, err := newContractCreationTx(sim, testKey, bytecode, 300000) + if err != nil { + t.Fatalf("could not create deploy tx: %v", err) + } + + code, err := client.CodeAt(ctx, contractAddr, nil) + if err != nil { + t.Fatalf("could not query code before deploy: %v", err) + } + if len(code) != 0 { + t.Fatalf("expected empty code before deployment, got length %d", len(code)) + } + + if err := client.SendTransaction(ctx, tx); err != nil { + t.Fatalf("could not send deploy tx: %v", err) + } + pendingCode, err := client.PendingCodeAt(ctx, contractAddr) + if err != nil { + t.Fatalf("could not query pending code: %v", err) + } + if len(pendingCode) == 0 { + t.Fatalf("expected pending code for contract") + } + + sim.Commit() + code, err = client.CodeAt(ctx, contractAddr, nil) + if err != nil { + t.Fatalf("could not query code after deploy: %v", err) + } + if len(code) == 0 { + t.Fatalf("expected non-empty code after deployment") + } +} + +func TestForkLogsRebornFilterLogs(t *testing.T) { + t.Parallel() + sim, client, ctx, auth, contract, contractAddr, calledEventID, parentHash := setupForkLogsRebornScenario(t) + defer sim.Close() + + var err error + tx, err := contract.Transact(auth, "Call") + if err != nil { + t.Fatalf("sending contract tx: %v", err) + } + sim.Commit() + + query := calledEventQuery(contractAddr, calledEventID, nil, nil) + logs, err := client.FilterLogs(ctx, query) + if err != nil { + t.Fatalf("filter logs before fork: %v", err) + } + if len(logs) != 1 || logs[0].TxHash != tx.Hash() { + t.Fatalf("expected exactly one canonical log before fork, got len=%d tx=%v", len(logs), logs) + } + + if err := sim.Fork(parentHash); err != nil { + t.Fatalf("forking: %v", err) + } + sim.Commit() + sim.Commit() + + logs, err = client.FilterLogs(ctx, query) + if err != nil { + t.Fatalf("filter logs after reorg removal: %v", err) + } + if len(logs) != 0 { + t.Fatalf("expected no canonical logs after reorg removal, got len=%d", len(logs)) + } + + if err := client.SendTransaction(ctx, tx); err != nil { + t.Fatalf("re-sending transaction: %v", err) + } + sim.Commit() + + logs, err = client.FilterLogs(ctx, query) + if err != nil { + t.Fatalf("filter logs after reborn: %v", err) + } + if len(logs) != 1 || logs[0].TxHash != tx.Hash() { + t.Fatalf("expected exactly one canonical log after reborn, got len=%d tx=%v", len(logs), logs) + } +} + +func TestForkLogsRebornFilterLogsWithRange(t *testing.T) { + t.Parallel() + sim, client, ctx, auth, contract, contractAddr, calledEventID, parentHash := setupForkLogsRebornScenario(t) + defer sim.Close() + + var err error + tx, err := contract.Transact(auth, "Call") + if err != nil { + t.Fatalf("sending contract tx: %v", err) + } + sim.Commit() // block 2 + + queryBlock2 := calledEventQuery(contractAddr, calledEventID, big.NewInt(2), big.NewInt(2)) + logs, err := client.FilterLogs(ctx, queryBlock2) + if err != nil { + t.Fatalf("filter logs in [2,2] before reorg: %v", err) + } + if len(logs) != 1 || logs[0].TxHash != tx.Hash() || logs[0].BlockNumber != 2 { + t.Fatalf("expected one log at block 2 before reorg, got len=%d logs=%v", len(logs), logs) + } + + if err := sim.Fork(parentHash); err != nil { + t.Fatalf("forking: %v", err) + } + sim.Commit() // block 2 on new branch + sim.Commit() // block 3 on new branch + + logs, err = client.FilterLogs(ctx, queryBlock2) + if err != nil { + t.Fatalf("filter logs in [2,2] after reorg removal: %v", err) + } + if len(logs) != 0 { + t.Fatalf("expected zero logs at block 2 after reorg removal, got len=%d", len(logs)) + } + + if err := client.SendTransaction(ctx, tx); err != nil { + t.Fatalf("re-sending transaction: %v", err) + } + sim.Commit() // block 4 on new branch + + queryBlock4 := calledEventQuery(contractAddr, calledEventID, big.NewInt(4), big.NewInt(4)) + logs, err = client.FilterLogs(ctx, queryBlock4) + if err != nil { + t.Fatalf("filter logs in [4,4] after reborn: %v", err) + } + if len(logs) != 1 || logs[0].TxHash != tx.Hash() || logs[0].BlockNumber != 4 { + t.Fatalf("expected one log at block 4 after reborn, got len=%d logs=%v", len(logs), logs) + } +} + +// TestFork check that the chain length after a reorg is correct. +// Steps: +// 1. Save the current block which will serve as parent for the fork. +// 2. Mine n blocks with n ∈ [0, 20]. +// 3. Assert that the chain length is n. +// 4. Fork by using the parent block as ancestor. +// 5. Mine n+1 blocks which should trigger a reorg. +// 6. Assert that the chain length is n+1. +// Since Commit() was called 2n+1 times in total, +// having a chain length of just n+1 means that a reorg occurred. + +// TestForkResendTx checks that re-sending a TX after a fork +// is possible and does not cause a "nonce mismatch" panic. +// Steps: +// 1. Save the current block which will serve as parent for the fork. +// 2. Send a transaction. +// 3. Check that the TX is included in block 1. +// 4. Fork by using the parent block as ancestor. +// 5. Mine a block, Re-send the transaction and mine another one. +// 6. Check that the TX is now included in block 2. + +// TestAdjustTimeAfterFork ensures that after a fork, AdjustTime uses the pending fork +// block's parent rather than the canonical head's parent. + +func setupForkLogsRebornScenario(t *testing.T) (*Backend, Client, context.Context, *bind.TransactOpts, *bind.BoundContract, common.Address, common.Hash, common.Hash) { + t.Helper() + + sim := simTestBackend(testAddr) + client := sim.Client() + ctx := context.Background() + + parsed, err := abi.JSON(strings.NewReader(callableAbi)) + if err != nil { + t.Fatalf("parsing callable ABI: %v", err) + } + calledEvent, ok := parsed.Events["Called"] + if !ok { + t.Fatalf("missing Called event in ABI") + } + chainID, err := client.ChainID(ctx) + if err != nil { + t.Fatalf("fetching chain id: %v", err) + } + auth, err := bind.NewKeyedTransactorWithChainID(testKey, chainID) + if err != nil { + t.Fatalf("creating transactor: %v", err) + } + contractAddr, _, contract, err := bind.DeployContract(auth, parsed, common.FromHex(callableBin), client) + if err != nil { + t.Fatalf("deploying contract: %v", err) + } + sim.Commit() + + parent, err := client.HeaderByNumber(ctx, nil) + if err != nil { + t.Fatalf("fetching parent header: %v", err) + } + + return sim, client, ctx, auth, contract, contractAddr, calledEvent.ID, parent.Hash() +} + +func mustReadWatchedLog(t *testing.T, logs <-chan types.Log, sub ethereum.Subscription, step string) types.Log { + t.Helper() + + select { + case lg := <-logs: + return lg + case err := <-sub.Err(): + t.Fatalf("subscription error at %s: %v", step, err) + case <-time.After(2 * time.Second): + t.Fatalf("timeout waiting for log at %s", step) + } + return types.Log{} +} + +func calledEventQuery(contractAddr common.Address, calledEventID common.Hash, fromBlock, toBlock *big.Int) ethereum.FilterQuery { + return ethereum.FilterQuery{ + FromBlock: fromBlock, + ToBlock: toBlock, + Addresses: []common.Address{contractAddr}, + Topics: [][]common.Hash{{calledEventID}}, + } +} diff --git a/interfaces.go b/interfaces.go index 7d13a6d3dc..e54b16589d 100644 --- a/interfaces.go +++ b/interfaces.go @@ -186,6 +186,16 @@ type GasPricer interface { SuggestGasPrice(ctx context.Context) (*big.Int, error) } +// GasPricer1559 provides access to the EIP-1559 gas price oracle. +type GasPricer1559 interface { + SuggestGasTipCap(ctx context.Context) (*big.Int, error) +} + +// FeeHistoryReader provides access to the fee history oracle. +type FeeHistoryReader interface { + FeeHistory(ctx context.Context, blockCount uint64, lastBlock *big.Int, rewardPercentiles []float64) (*FeeHistory, error) +} + // FeeHistory provides recent fee market data that consumers can use to determine // a reasonable maxPriorityFeePerGas value. type FeeHistory struct { @@ -227,6 +237,16 @@ type PendingStateEventer interface { SubscribePendingTransactions(ctx context.Context, ch chan<- *types.Transaction) (Subscription, error) } +// BlockNumberReader provides access to the current block number. +type BlockNumberReader interface { + BlockNumber(ctx context.Context) (uint64, error) +} + +// ChainIDReader provides access to the chain ID. +type ChainIDReader interface { + ChainID(ctx context.Context) (*big.Int, error) +} + // OverrideAccount specifies the state of an account to be overridden. type OverrideAccount struct { // Nonce sets nonce of the account. Note: the nonce override will only