diff --git a/accounts/abi/bind/backend.go b/accounts/abi/bind/backend.go
index 2e45e86ae2..38b3046970 100644
--- a/accounts/abi/bind/backend.go
+++ b/accounts/abi/bind/backend.go
@@ -84,6 +84,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)
@@ -93,38 +98,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.
@@ -133,6 +106,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/backends/simulated.go b/accounts/abi/bind/backends/simulated.go
index 2faf274dbd..9271566692 100644
--- a/accounts/abi/bind/backends/simulated.go
+++ b/accounts/abi/bind/backends/simulated.go
@@ -18,958 +18,35 @@ package backends
import (
"context"
- "errors"
- "fmt"
- "math/big"
- "sync"
- "time"
- "github.com/ethereum/go-ethereum"
- "github.com/ethereum/go-ethereum/accounts/abi"
- "github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/common/hexutil"
- "github.com/ethereum/go-ethereum/common/math"
- "github.com/ethereum/go-ethereum/consensus/ethash"
"github.com/ethereum/go-ethereum/core"
- "github.com/ethereum/go-ethereum/core/bloombits"
- "github.com/ethereum/go-ethereum/core/rawdb"
- "github.com/ethereum/go-ethereum/core/state"
- "github.com/ethereum/go-ethereum/core/types"
- "github.com/ethereum/go-ethereum/core/vm"
- "github.com/ethereum/go-ethereum/eth/filters"
- "github.com/ethereum/go-ethereum/ethdb"
- "github.com/ethereum/go-ethereum/event"
- "github.com/ethereum/go-ethereum/log"
- "github.com/ethereum/go-ethereum/params"
- "github.com/ethereum/go-ethereum/rpc"
+ "github.com/ethereum/go-ethereum/ethclient/simulated"
)
-// 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/ethereum/go-ethereum/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
-
- 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
+ *simulated.Backend
+ simulated.Client
}
-// NewSimulatedBackendWithDatabase 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 NewSimulatedBackendWithDatabase(database ethdb.Database, alloc core.GenesisAlloc, gasLimit uint64) *SimulatedBackend {
- genesis := core.Genesis{
- Config: params.AllEthashProtocolChanges,
- GasLimit: gasLimit,
- Alloc: alloc,
- }
- blockchain, _ := core.NewBlockChain(database, nil, &genesis, nil, ethash.NewFaker(), vm.Config{}, nil, nil)
-
- 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
+// 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)
}
// NewSimulatedBackend creates a new binding backend using a simulated blockchain
// for testing purposes.
+//
// A simulated backend always uses chainID 1337.
+//
+// Deprecated: please use simulated.Backend from package
+// github.com/ethereum/go-ethereum/ethclient/simulated instead.
func NewSimulatedBackend(alloc core.GenesisAlloc, gasLimit uint64) *SimulatedBackend {
- return NewSimulatedBackendWithDatabase(rawdb.NewMemoryDatabase(), alloc, gasLimit)
-}
-
-// 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, ethash.NewFaker(), b.database, 1, func(int, *core.BlockGen) {})
-
- b.pendingBlock = blocks[0]
- b.pendingState, _ = state.New(b.pendingBlock.Root(), b.blockchain.StateCache(), nil)
-}
-
-// 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
-}
-
-// 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.NotFound
- }
- 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.NotFound
-}
-
-// 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()),
+ b := simulated.New(alloc, gasLimit)
+ return &SimulatedBackend{
+ Backend: b,
+ Client: b.Client(),
}
}
-
-// 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
- }
- res, err := b.callContract(ctx, call, b.blockchain.CurrentBlock(), 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()
- }
- // Normalize the max fee per gas the call is willing to spend.
- var feeCap *big.Int
- if call.GasPrice != nil && (call.GasFeeCap != nil || call.GasTipCap != nil) {
- return 0, errors.New("both gasPrice and (maxFeePerGas or maxPriorityFeePerGas) specified")
- } else if call.GasPrice != nil {
- feeCap = call.GasPrice
- } else if call.GasFeeCap != nil {
- feeCap = call.GasFeeCap
- } else {
- feeCap = common.Big0
- }
- // Recap the highest gas allowance with account's balance.
- if feeCap.BitLen() != 0 {
- balance := b.pendingState.GetBalance(call.From) // from can't be nil
- available := new(big.Int).Set(balance)
- if call.Value != nil {
- if call.Value.Cmp(available) >= 0 {
- return 0, core.ErrInsufficientFundsForTransfer
- }
- available.Sub(available, call.Value)
- }
- allowance := new(big.Int).Div(available, feeCap)
- if allowance.IsUint64() && hi > allowance.Uint64() {
- transfer := call.Value
- if transfer == nil {
- transfer = new(big.Int)
- }
- log.Warn("Gas estimation capped by limited funds", "original", hi, "balance", balance,
- "sent", transfer, "feecap", feeCap, "fundable", allowance)
- hi = allowance.Uint64()
- }
- }
- 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, header *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")
- }
- if !b.blockchain.Config().IsLondon(header.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 = math.BigMin(new(big.Int).Add(call.GasTipCap, header.BaseFee), call.GasFeeCap)
- }
- }
- }
- // Ensure message is initialized properly.
- if call.Gas == 0 {
- call.Gas = 10 * header.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,
- SkipAccountChecks: true,
- }
-
- // Create a new environment which holds all relevant information
- // about the transaction and calling mechanisms.
- txContext := core.NewEVMTxContext(msg)
- evmContext := core.NewEVMBlockContext(header, b.blockchain, nil)
- vmEnv := vm.NewEVM(evmContext, txContext, stateDB, b.config, vm.Config{NoBaseFee: true})
- gasPool := new(core.GasPool).AddGas(math.MaxUint64)
-
- return core.ApplyMessage(vmEnv, msg, gasPool)
-}
-
-// 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(), block.Time())
- 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, ethash.NewFaker(), 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(), nil)
- 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 boundaries to run from genesis to chain head
- from := int64(0)
- if query.FromBlock != nil {
- from = query.FromBlock.Int64()
- }
- to := int64(-1)
- if query.ToBlock != nil {
- to = query.ToBlock.Int64()
- }
- // Construct the range filter
- filter = b.filterSystem.NewRangeFilter(from, to, query.Addresses, query.Topics)
- }
- // 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, nLog := range logs {
- res[i] = *nLog
- }
- 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 _, nlog := range logs {
- select {
- case ch <- *nlog:
- 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, ethash.NewFaker(), b.database, 1, func(number int, block *core.BlockGen) {
- block.OffsetTime(int64(adjustment.Seconds()))
- })
- stateDB, err := b.blockchain.State()
- if err != nil {
- return err
- }
- b.pendingBlock = blocks[0]
- b.pendingState, _ = state.New(b.pendingBlock.Root(), stateDB.Database(), nil)
- 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:
- return fb.bc.CurrentFinalBlock(), nil
- case rpc.SafeBlockNumber:
- return fb.bc.CurrentSafeBlock(), nil
- 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) 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) GetReceipts(ctx context.Context, hash common.Hash) (types.Receipts, error) {
- number := rawdb.ReadHeaderNumber(fb.db, hash)
- if number == nil {
- return nil, nil
- }
- header := rawdb.ReadHeader(fb.db, hash, *number)
- if header == nil {
- return nil, nil
- }
- return rawdb.ReadReceipts(fb.db, hash, *number, header.Time, fb.bc.Config()), nil
-}
-
-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 a2acf7ead5..0000000000
--- a/accounts/abi/bind/backends/simulated_test.go
+++ /dev/null
@@ -1,1483 +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"
-
- "github.com/ethereum/go-ethereum"
- "github.com/ethereum/go-ethereum/accounts/abi"
- "github.com/ethereum/go-ethereum/accounts/abi/bind"
- "github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/core"
- "github.com/ethereum/go-ethereum/core/types"
- "github.com/ethereum/go-ethereum/crypto"
- "github.com/ethereum/go-ethereum/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(core.GenesisAlloc)
- genAlloc[auth.From] = core.GenesisAccount{Balance: big.NewInt(9223372036854775807)}
-
- sim := NewSimulatedBackend(genAlloc, gasLimit)
- 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.NotFound {
- t.Fatalf("err should be `ethereum.NotFound` 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 {
- return NewSimulatedBackend(
- core.GenesisAlloc{
- testAddr: {Balance: big.NewInt(10000000000000000)},
- }, 10000000,
- )
-}
-
-func TestNewSimulatedBackend(t *testing.T) {
- t.Parallel()
- testAddr := crypto.PubkeyToAddress(testKey.PublicKey)
- expectedBal := big.NewInt(10000000000000000)
- sim := simTestBackend(testAddr)
- defer sim.Close()
-
- if sim.config != params.AllEthashProtocolChanges {
- t.Errorf("expected sim config to equal params.AllEthashProtocolChanges, got %v", sim.config)
- }
-
- if sim.blockchain.Config() != params.AllEthashProtocolChanges {
- t.Errorf("expected sim blockchain config to equal params.AllEthashProtocolChanges, got %v", sim.config)
- }
-
- 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 := NewSimulatedBackend(
- core.GenesisAlloc{}, 10000000,
- )
- 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.blockchain.Stop()
-
- // 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(10000000000000000)
- 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 := NewSimulatedBackend(
- core.GenesisAlloc{}, 10000000,
- )
- 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 := NewSimulatedBackend(
- core.GenesisAlloc{}, 10000000,
- )
- 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 := NewSimulatedBackend(
- core.GenesisAlloc{
- testAddr: {Balance: big.NewInt(10000000000000000)},
- }, 10000000,
- )
- 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.NewKeyedTransactorWithChainID(key, big.NewInt(1337))
-
- sim := NewSimulatedBackend(core.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()
-
- 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"),
- }, 21275, 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 := NewSimulatedBackend(core.GenesisAlloc{addr: {Balance: big.NewInt(params.Ether*2 + 2e17)}}, 10000000)
- 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},
-
- {"EstimateWithSuperhighPrice", ethereum.CallMsg{
- From: addr,
- To: &recipient,
- Gas: 0,
- GasPrice: big.NewInt(2e14), // gascost = 4.2ether
- Value: big.NewInt(100000000000),
- Data: nil,
- }, 21000, errors.New("gas required exceeds allowance (10999)")}, // 10999=(2.2ether-1000wei)/(2e14)
-
- {"EstimateEIP1559WithHighFees", ethereum.CallMsg{
- From: addr,
- To: &addr,
- Gas: 0,
- GasFeeCap: big.NewInt(1e14), // maxgascost = 2.1ether
- GasTipCap: big.NewInt(1),
- Value: big.NewInt(1e17), // the remaining balance for fee is 2.1ether
- Data: nil,
- }, params.TxGas, nil},
-
- {"EstimateEIP1559WithSuperHighFees", ethereum.CallMsg{
- From: addr,
- To: &addr,
- Gas: 0,
- GasFeeCap: big.NewInt(1e14), // maxgascost = 2.1ether
- GasTipCap: big.NewInt(1),
- Value: big.NewInt(1e17 + 1), // the remaining balance for fee is 2.1ether
- Data: nil,
- }, params.TxGas, errors.New("gas required exceeds allowance (20999)")}, // 20999=(2.2ether-0.1ether-1wei)/(1e14)
- }
- 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 c.expectError == nil && err != nil {
- t.Fatalf("test %d: didn't expect error, got %v", i, err)
- }
- 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 := NewSimulatedBackend(
- core.GenesisAlloc{},
- 10000000,
- )
- defer sim.Close()
- bgCtx := context.Background()
- gasPrice, err := sim.SuggestGasPrice(bgCtx)
- if err != nil {
- t.Errorf("could not get gas price: %v", err)
- }
- if gasPrice.Uint64() != sim.pendingBlock.Header().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, _ := types.SignTx(_tx, types.HomesteadSigner{}, testKey)
- sim.SendTransaction(context.Background(), tx)
- 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)
- sim.SendTransaction(context.Background(), tx)
- 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/bind_test.go b/accounts/abi/bind/bind_test.go
index a5f7afa73c..a6ffe7609d 100644
--- a/accounts/abi/bind/bind_test.go
+++ b/accounts/abi/bind/bind_test.go
@@ -305,6 +305,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)
}
@@ -512,6 +513,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)
}
@@ -1874,6 +1876,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/ethereum/go-ethereum/accounts/abi/bind"
@@ -1895,7 +1898,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)
}
@@ -1926,6 +1929,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/ethereum/go-ethereum/accounts/abi/bind"
@@ -1948,7 +1952,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)
}
@@ -1974,6 +1978,7 @@ var bindTests = []struct {
bytecode: []string{"0x608060405234801561001057600080fd5b5061042b806100206000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c8063c2bb515f1461003b578063cce7b04814610059575b600080fd5b610043610075565b60405161005091906101af565b60405180910390f35b610073600480360381019061006e91906103ac565b6100b5565b005b61007d6100b8565b604051806040016040528060405180602001604052806000815250815260200160405180602001604052806000815250815250905090565b50565b604051806040016040528060608152602001606081525090565b600081519050919050565b600082825260208201905092915050565b60005b8381101561010c5780820151818401526020810190506100f1565b8381111561011b576000848401525b50505050565b6000601f19601f8301169050919050565b600061013d826100d2565b61014781856100dd565b93506101578185602086016100ee565b61016081610121565b840191505092915050565b600060408301600083015184820360008601526101888282610132565b915050602083015184820360208601526101a28282610132565b9150508091505092915050565b600060208201905081810360008301526101c9818461016b565b905092915050565b6000604051905090565b600080fd5b600080fd5b600080fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b61022282610121565b810181811067ffffffffffffffff82111715610241576102406101ea565b5b80604052505050565b60006102546101d1565b90506102608282610219565b919050565b600080fd5b600080fd5b600080fd5b600067ffffffffffffffff82111561028f5761028e6101ea565b5b61029882610121565b9050602081019050919050565b82818337600083830152505050565b60006102c76102c284610274565b61024a565b9050828152602081018484840111156102e3576102e261026f565b5b6102ee8482856102a5565b509392505050565b600082601f83011261030b5761030a61026a565b5b813561031b8482602086016102b4565b91505092915050565b60006040828403121561033a576103396101e5565b5b610344604061024a565b9050600082013567ffffffffffffffff81111561036457610363610265565b5b610370848285016102f6565b600083015250602082013567ffffffffffffffff81111561039457610393610265565b5b6103a0848285016102f6565b60208301525092915050565b6000602082840312156103c2576103c16101db565b5b600082013567ffffffffffffffff8111156103e0576103df6101e0565b5b6103ec84828501610324565b9150509291505056fea264697066735822122033bca1606af9b6aeba1673f98c52003cec19338539fb44b86690ce82c51483b564736f6c634300080e0033"},
abi: []string{`[ { "anonymous": false, "inputs": [ { "indexed": false, "internalType": "int256", "name": "msg", "type": "int256" }, { "indexed": false, "internalType": "int256", "name": "_msg", "type": "int256" } ], "name": "log", "type": "event" }, { "inputs": [ { "components": [ { "internalType": "bytes", "name": "data", "type": "bytes" }, { "internalType": "bytes", "name": "_data", "type": "bytes" } ], "internalType": "struct oracle.request", "name": "req", "type": "tuple" } ], "name": "addRequest", "outputs": [], "stateMutability": "pure", "type": "function" }, { "inputs": [], "name": "getRequest", "outputs": [ { "components": [ { "internalType": "bytes", "name": "data", "type": "bytes" }, { "internalType": "bytes", "name": "_data", "type": "bytes" } ], "internalType": "struct oracle.request", "name": "", "type": "tuple" } ], "stateMutability": "pure", "type": "function" } ]`},
imports: `
+ "context"
"math/big"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
@@ -1996,7 +2001,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)
}
@@ -2014,6 +2019,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/ethereum/go-ethereum/accounts/abi/bind"
@@ -2034,7 +2040,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/util_test.go b/accounts/abi/bind/util_test.go
index 826426632c..244eeebdd0 100644
--- a/accounts/abi/bind/util_test.go
+++ b/accounts/abi/bind/util_test.go
@@ -24,11 +24,12 @@ import (
"time"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
- "github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
+ "github.com/ethereum/go-ethereum/ethclient/simulated"
+ "github.com/ethereum/go-ethereum/params"
)
var testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
@@ -55,7 +56,7 @@ var waitDeployedTests = map[string]struct {
func TestWaitDeployed(t *testing.T) {
t.Parallel()
for name, test := range waitDeployedTests {
- backend := backends.NewSimulatedBackend(
+ backend := simulated.New(
core.GenesisAlloc{
crypto.PubkeyToAddress(testKey.PublicKey): {Balance: big.NewInt(10000000000000000)},
},
@@ -64,11 +65,11 @@ 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))
- tx, _ = types.SignTx(tx, types.HomesteadSigner{}, testKey)
+ tx, _ = types.SignTx(tx, types.LatestSignerForChainID(big.NewInt(1337)), testKey)
// Wait for it to get mined in the background.
var (
@@ -78,12 +79,12 @@ func TestWaitDeployed(t *testing.T) {
ctx = context.Background()
)
go func() {
- address, err = bind.WaitDeployed(ctx, backend, tx)
+ address, err = bind.WaitDeployed(ctx, backend.Client(), tx)
close(mined)
}()
// Send and mine the transaction.
- backend.SendTransaction(ctx, tx)
+ backend.Client().SendTransaction(ctx, tx)
backend.Commit()
select {
@@ -101,8 +102,7 @@ func TestWaitDeployed(t *testing.T) {
}
func TestWaitDeployedCornerCases(t *testing.T) {
- t.Parallel()
- backend := backends.NewSimulatedBackend(
+ backend := simulated.New(
core.GenesisAlloc{
crypto.PubkeyToAddress(testKey.PublicKey): {Balance: big.NewInt(10000000000000000)},
},
@@ -110,33 +110,33 @@ func TestWaitDeployedCornerCases(t *testing.T) {
)
defer backend.Close()
- 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))
// Create a transaction to an account.
code := "6060604052600a8060106000396000f360606040526008565b00"
tx := types.NewTransaction(0, common.HexToAddress("0x01"), big.NewInt(0), 3000000, gasPrice, common.FromHex(code))
- tx, _ = types.SignTx(tx, types.HomesteadSigner{}, testKey)
+ tx, _ = types.SignTx(tx, types.LatestSigner(params.AllDevChainProtocolChanges), testKey)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
- backend.SendTransaction(ctx, tx)
+ backend.Client().SendTransaction(ctx, tx)
backend.Commit()
notContractCreation := errors.New("tx is not contract creation")
- if _, err := bind.WaitDeployed(ctx, backend, tx); err.Error() != notContractCreation.Error() {
+ if _, err := bind.WaitDeployed(ctx, backend.Client(), tx); err.Error() != notContractCreation.Error() {
t.Errorf("error mismatch: want %q, got %q, ", notContractCreation, err)
}
// Create a transaction that is not mined.
tx = types.NewContractCreation(1, big.NewInt(0), 3000000, gasPrice, common.FromHex(code))
- tx, _ = types.SignTx(tx, types.HomesteadSigner{}, testKey)
+ tx, _ = types.SignTx(tx, types.LatestSigner(params.AllDevChainProtocolChanges), testKey)
go func() {
contextCanceled := errors.New("context canceled")
- if _, err := bind.WaitDeployed(ctx, backend, tx); err.Error() != contextCanceled.Error() {
+ if _, err := bind.WaitDeployed(ctx, backend.Client(), tx); err.Error() != contextCanceled.Error() {
t.Errorf("error mismatch: want %q, got %q, ", contextCanceled, err)
}
}()
- backend.SendTransaction(ctx, tx)
+ backend.Client().SendTransaction(ctx, tx)
cancel()
}
diff --git a/eth/catalyst/simulated_beacon.go b/eth/catalyst/simulated_beacon.go
index d8b8641e6a..3c081074cc 100644
--- a/eth/catalyst/simulated_beacon.go
+++ b/eth/catalyst/simulated_beacon.go
@@ -19,16 +19,17 @@ package catalyst
import (
"crypto/rand"
"errors"
+ "math/big"
"sync"
"time"
"github.com/ethereum/go-ethereum/beacon/engine"
"github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/eth"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/node"
+ "github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rpc"
)
@@ -81,6 +82,11 @@ type SimulatedBeacon struct {
lastBlockTime uint64
}
+// NewSimulatedBeacon constructs a new simulated beacon chain.
+// Period sets the period in which blocks should be produced.
+//
+// - If period is set to 0, a block is produced on every transaction.
+// via Commit, Fork and AdjustTime.
func NewSimulatedBeacon(period uint64, eth *eth.Ethereum) (*SimulatedBeacon, error) {
block := eth.BlockChain().CurrentBlock()
current := engine.ForkchoiceStateV1{
@@ -116,7 +122,9 @@ func (c *SimulatedBeacon) setFeeRecipient(feeRecipient common.Address) {
// Start invokes the SimulatedBeacon life-cycle function in a goroutine.
func (c *SimulatedBeacon) Start() error {
if c.period == 0 {
- go c.loopOnDemand()
+ // if period is set to 0, do not mine at all
+ // this is used in the simulated backend where blocks
+ // are explicitly mined via Commit, AdjustTime and Fork
} else {
go c.loop()
}
@@ -131,10 +139,9 @@ func (c *SimulatedBeacon) Stop() error {
// sealBlock initiates payload building for a new block and creates a new block
// with the completed payload.
-func (c *SimulatedBeacon) sealBlock(withdrawals []*types.Withdrawal) error {
- tstamp := uint64(time.Now().Unix())
- if tstamp <= c.lastBlockTime {
- tstamp = c.lastBlockTime + 1
+func (c *SimulatedBeacon) sealBlock(withdrawals []*types.Withdrawal, timestamp uint64) error {
+ if timestamp <= c.lastBlockTime {
+ timestamp = c.lastBlockTime + 1
}
c.feeRecipientLock.Lock()
feeRecipient := c.feeRecipient
@@ -149,7 +156,7 @@ func (c *SimulatedBeacon) sealBlock(withdrawals []*types.Withdrawal) error {
var random [32]byte
rand.Read(random[:])
fcResponse, err := c.engineAPI.ForkchoiceUpdatedV2(c.curForkchoiceState, &engine.PayloadAttributes{
- Timestamp: tstamp,
+ Timestamp: timestamp,
SuggestedFeeRecipient: feeRecipient,
Withdrawals: withdrawals,
Random: random,
@@ -183,6 +190,7 @@ func (c *SimulatedBeacon) sealBlock(withdrawals []*types.Withdrawal) error {
return err
}
c.setCurrentState(payload.BlockHash, finalizedHash)
+
// Mark the block containing the payload as canonical
if _, err = c.engineAPI.ForkchoiceUpdatedV2(c.curForkchoiceState, nil); err != nil {
return err
@@ -191,32 +199,6 @@ func (c *SimulatedBeacon) sealBlock(withdrawals []*types.Withdrawal) error {
return nil
}
-// loopOnDemand runs the block production loop for "on-demand" configuration (period = 0)
-func (c *SimulatedBeacon) loopOnDemand() {
- var (
- newTxs = make(chan core.NewTxsEvent)
- sub = c.eth.TxPool().SubscribeTransactions(newTxs, true)
- )
- defer sub.Unsubscribe()
-
- for {
- select {
- case <-c.shutdownCh:
- return
- case w := <-c.withdrawals.pending:
- withdrawals := append(c.withdrawals.gatherPending(9), w)
- if err := c.sealBlock(withdrawals); err != nil {
- log.Warn("Error performing sealing work", "err", err)
- }
- case <-newTxs:
- withdrawals := c.withdrawals.gatherPending(10)
- if err := c.sealBlock(withdrawals); err != nil {
- log.Warn("Error performing sealing work", "err", err)
- }
- }
- }
-}
-
// loop runs the block production loop for non-zero period configuration
func (c *SimulatedBeacon) loop() {
timer := time.NewTimer(0)
@@ -226,7 +208,7 @@ func (c *SimulatedBeacon) loop() {
return
case <-timer.C:
withdrawals := c.withdrawals.gatherPending(10)
- if err := c.sealBlock(withdrawals); err != nil {
+ if err := c.sealBlock(withdrawals, uint64(time.Now().Unix())); err != nil {
log.Warn("Error performing sealing work", "err", err)
} else {
timer.Reset(time.Second * time.Duration(c.period))
@@ -235,8 +217,8 @@ func (c *SimulatedBeacon) loop() {
}
}
-// finalizedBlockHash returns the block hash of the finalized block corresponding to the given number
-// or nil if doesn't exist in the chain.
+// finalizedBlockHash returns the block hash of the finalized block corresponding
+// to the given number or nil if doesn't exist in the chain.
func (c *SimulatedBeacon) finalizedBlockHash(number uint64) *common.Hash {
var finalizedNumber uint64
if number%devEpochLength == 0 {
@@ -244,7 +226,6 @@ func (c *SimulatedBeacon) finalizedBlockHash(number uint64) *common.Hash {
} else {
finalizedNumber = (number - 1) / devEpochLength * devEpochLength
}
-
if finalizedBlock := c.eth.BlockChain().GetBlockByNumber(finalizedNumber); finalizedBlock != nil {
fh := finalizedBlock.Hash()
return &fh
@@ -261,11 +242,60 @@ func (c *SimulatedBeacon) setCurrentState(headHash, finalizedHash common.Hash) {
}
}
+// Commit seals a block on demand.
+func (c *SimulatedBeacon) Commit() common.Hash {
+ withdrawals := c.withdrawals.gatherPending(10)
+ if err := c.sealBlock(withdrawals, uint64(time.Now().Unix())); err != nil {
+ log.Warn("Error performing sealing work", "err", err)
+ }
+ return c.eth.BlockChain().CurrentBlock().Hash()
+}
+
+// Rollback un-sends previously added transactions.
+func (c *SimulatedBeacon) Rollback() {
+ // Flush all transactions from the transaction pools
+ maxUint256 := new(big.Int).Sub(new(big.Int).Lsh(common.Big1, 256), common.Big1)
+ c.eth.TxPool().SetGasTip(maxUint256)
+ // Set the gas tip back to accept new transactions
+ // TODO (Marius van der Wijden): set gas tip to parameter passed by config
+ c.eth.TxPool().SetGasTip(big.NewInt(params.GWei))
+}
+
+// Fork sets the head to the provided hash.
+func (c *SimulatedBeacon) Fork(parentHash common.Hash) error {
+ if len(c.eth.TxPool().Pending(false)) != 0 {
+ return errors.New("pending block dirty")
+ }
+ parent := c.eth.BlockChain().GetBlockByHash(parentHash)
+ if parent == nil {
+ return errors.New("parent not found")
+ }
+ return c.eth.BlockChain().SetHead(parent.NumberU64())
+}
+
+// AdjustTime creates a new block with an adjusted timestamp.
+func (c *SimulatedBeacon) AdjustTime(adjustment time.Duration) error {
+ if len(c.eth.TxPool().Pending(false)) != 0 {
+ return errors.New("could not adjust time on non-empty block")
+ }
+ parent := c.eth.BlockChain().CurrentBlock()
+ if parent == nil {
+ return errors.New("parent not found")
+ }
+ withdrawals := c.withdrawals.gatherPending(10)
+ return c.sealBlock(withdrawals, parent.Time+uint64(adjustment))
+}
+
func RegisterSimulatedBeaconAPIs(stack *node.Node, sim *SimulatedBeacon) {
+ api := &api{sim}
+ if sim.period == 0 {
+ // mine on demand if period is set to 0
+ go api.loop()
+ }
stack.RegisterAPIs([]rpc.API{
{
Namespace: "dev",
- Service: &api{sim},
+ Service: api,
Version: "1.0",
},
})
diff --git a/eth/catalyst/simulated_beacon_api.go b/eth/catalyst/simulated_beacon_api.go
index 93670257f6..73d0a5921d 100644
--- a/eth/catalyst/simulated_beacon_api.go
+++ b/eth/catalyst/simulated_beacon_api.go
@@ -18,19 +18,44 @@ package catalyst
import (
"context"
+ "time"
"github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/types"
+ "github.com/ethereum/go-ethereum/log"
)
type api struct {
- simBeacon *SimulatedBeacon
+ sim *SimulatedBeacon
+}
+
+func (a *api) loop() {
+ var (
+ newTxs = make(chan core.NewTxsEvent)
+ sub = a.sim.eth.TxPool().SubscribeTransactions(newTxs, true)
+ )
+ defer sub.Unsubscribe()
+
+ for {
+ select {
+ case <-a.sim.shutdownCh:
+ return
+ case w := <-a.sim.withdrawals.pending:
+ withdrawals := append(a.sim.withdrawals.gatherPending(9), w)
+ if err := a.sim.sealBlock(withdrawals, uint64(time.Now().Unix())); err != nil {
+ log.Warn("Error performing sealing work", "err", err)
+ }
+ case <-newTxs:
+ a.sim.Commit()
+ }
+ }
}
func (a *api) AddWithdrawal(ctx context.Context, withdrawal *types.Withdrawal) error {
- return a.simBeacon.withdrawals.add(withdrawal)
+ return a.sim.withdrawals.add(withdrawal)
}
func (a *api) SetFeeRecipient(ctx context.Context, feeRecipient common.Address) {
- a.simBeacon.setFeeRecipient(feeRecipient)
+ a.sim.setFeeRecipient(feeRecipient)
}
diff --git a/ethclient/simulated/backend.go b/ethclient/simulated/backend.go
new file mode 100644
index 0000000000..54675b6dd6
--- /dev/null
+++ b/ethclient/simulated/backend.go
@@ -0,0 +1,190 @@
+// Copyright 2023 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see .
+
+package simulated
+
+import (
+ "time"
+
+ "github.com/ethereum/go-ethereum"
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/core"
+ "github.com/ethereum/go-ethereum/eth"
+ "github.com/ethereum/go-ethereum/eth/catalyst"
+ "github.com/ethereum/go-ethereum/eth/downloader"
+ "github.com/ethereum/go-ethereum/eth/ethconfig"
+ "github.com/ethereum/go-ethereum/eth/filters"
+ "github.com/ethereum/go-ethereum/ethclient"
+ "github.com/ethereum/go-ethereum/node"
+ "github.com/ethereum/go-ethereum/p2p"
+ "github.com/ethereum/go-ethereum/params"
+ "github.com/ethereum/go-ethereum/rpc"
+)
+
+// Backend is a simulated blockchain. You can use it to test your contracts or
+// other code that interacts with the Ethereum chain.
+type Backend struct {
+ eth *eth.Ethereum
+ beacon *catalyst.SimulatedBeacon
+ client simClient
+}
+
+// simClient wraps ethclient. This exists to prevent extracting ethclient.Client
+// from the Client interface returned by Backend.
+type simClient struct {
+ *ethclient.Client
+}
+
+// Client exposes the methods provided by the Ethereum RPC 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
+}
+
+// New creates a new binding backend using a simulated blockchain
+// for testing purposes.
+// A simulated backend always uses chainID 1337.
+func New(alloc core.GenesisAlloc, gasLimit uint64) *Backend {
+ // Setup the node object
+ nodeConf := node.DefaultConfig
+ nodeConf.DataDir = ""
+ nodeConf.P2P = p2p.Config{NoDiscovery: true}
+ stack, err := node.New(&nodeConf)
+ if err != nil {
+ // This should never happen, if it does, please open an issue
+ panic(err)
+ }
+
+ // Setup ethereum
+ genesis := core.Genesis{
+ Config: params.AllDevChainProtocolChanges,
+ GasLimit: gasLimit,
+ Alloc: alloc,
+ }
+ conf := ethconfig.Defaults
+ conf.Genesis = &genesis
+ conf.SyncMode = downloader.FullSync
+ conf.TxPool.NoLocals = true
+ sim, err := newWithNode(stack, &conf, 0)
+ if err != nil {
+ // This should never happen, if it does, please open an issue
+ panic(err)
+ }
+ return sim
+}
+
+// newWithNode sets up a simulated backend on an existing node
+// this allows users to do persistent simulations.
+// The provided node must not be started and will be started by newWithNode
+func newWithNode(stack *node.Node, conf *eth.Config, blockPeriod uint64) (*Backend, error) {
+ backend, err := eth.New(stack, conf)
+ if err != nil {
+ return nil, err
+ }
+
+ // Register the filter system
+ filterSystem := filters.NewFilterSystem(backend.APIBackend, filters.Config{})
+ stack.RegisterAPIs([]rpc.API{{
+ Namespace: "eth",
+ Service: filters.NewFilterAPI(filterSystem, false),
+ }})
+
+ // Start the node
+ if err := stack.Start(); err != nil {
+ return nil, err
+ }
+
+ // Set up the simulated beacon
+ beacon, err := catalyst.NewSimulatedBeacon(blockPeriod, backend)
+ if err != nil {
+ return nil, err
+ }
+
+ // Reorg our chain back to genesis
+ if err := beacon.Fork(backend.BlockChain().GetCanonicalHash(0)); err != nil {
+ return nil, err
+ }
+
+ return &Backend{
+ eth: backend,
+ beacon: beacon,
+ client: simClient{ethclient.NewClient(stack.Attach())},
+ }, nil
+}
+
+// Close shuts down the simBackend.
+// The simulated backend can't be used afterwards.
+func (n *Backend) Close() error {
+ if n.client.Client != nil {
+ n.client.Close()
+ n.client = simClient{}
+ }
+ if n.beacon != nil {
+ err := n.beacon.Stop()
+ n.beacon = nil
+ return err
+ }
+ return nil
+}
+
+// Commit seals a block and moves the chain forward to a new empty block.
+func (n *Backend) Commit() common.Hash {
+ return n.beacon.Commit()
+}
+
+// Rollback removes all pending transactions, reverting to the last committed state.
+func (n *Backend) Rollback() {
+ n.beacon.Rollback()
+}
+
+// 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 (n *Backend) Fork(parentHash common.Hash) error {
+ return n.beacon.Fork(parentHash)
+}
+
+// AdjustTime changes the block timestamp and creates a new block.
+// It can only be called on empty blocks.
+func (n *Backend) AdjustTime(adjustment time.Duration) error {
+ return n.beacon.AdjustTime(adjustment)
+}
+
+// Client returns a client that accesses the simulated chain.
+func (n *Backend) Client() Client {
+ return n.client
+}
diff --git a/ethclient/simulated/backend_test.go b/ethclient/simulated/backend_test.go
new file mode 100644
index 0000000000..16a2acdf4f
--- /dev/null
+++ b/ethclient/simulated/backend_test.go
@@ -0,0 +1,309 @@
+// 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 (
+ "context"
+ "crypto/ecdsa"
+ "math/big"
+ "math/rand"
+ "testing"
+ "time"
+
+ "github.com/ethereum/go-ethereum/accounts/abi/bind"
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/core"
+ "github.com/ethereum/go-ethereum/core/types"
+ "github.com/ethereum/go-ethereum/crypto"
+ "github.com/ethereum/go-ethereum/params"
+)
+
+var _ bind.ContractBackend = (Client)(nil)
+
+var (
+ testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
+ testAddr = crypto.PubkeyToAddress(testKey.PublicKey)
+)
+
+func simTestBackend(testAddr common.Address) *Backend {
+ return New(
+ core.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 TestNewSim(t *testing.T) {
+ sim := New(core.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 TestAdjustTime(t *testing.T) {
+ sim := New(core.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) {
+ t.Errorf("adjusted time not equal to 60 seconds. prev: %v, new: %v", prevTime, newTime)
+ }
+}
+
+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())
+ }
+}
+
+// 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()
+
+ 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")
+ }
+}
+
+// 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()
+
+ 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)")
+ }
+}
+
+// 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()
+
+ 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")
+ }
+}
diff --git a/interfaces.go b/interfaces.go
index c4948191d1..1892309ed3 100644
--- a/interfaces.go
+++ b/interfaces.go
@@ -199,6 +199,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 {
@@ -239,3 +249,13 @@ type GasEstimator interface {
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)
+}