decred.org/dcrdex@v1.0.5/client/asset/eth/eth_test.go (about)

     1  //go:build !harness && !rpclive
     2  
     3  // These tests will not be run if the harness build tag is set.
     4  
     5  package eth
     6  
     7  import (
     8  	"bytes"
     9  	"context"
    10  	"crypto/ecdsa"
    11  	"crypto/sha256"
    12  	"encoding/hex"
    13  	"errors"
    14  	"fmt"
    15  	"math/big"
    16  	"math/rand"
    17  	"sort"
    18  	"strings"
    19  	"sync"
    20  	"testing"
    21  	"time"
    22  
    23  	"decred.org/dcrdex/client/asset"
    24  	"decred.org/dcrdex/dex"
    25  	"decred.org/dcrdex/dex/config"
    26  	"decred.org/dcrdex/dex/encode"
    27  	dexeth "decred.org/dcrdex/dex/networks/eth"
    28  	swapv0 "decred.org/dcrdex/dex/networks/eth/contracts/v0"
    29  	"github.com/ethereum/go-ethereum"
    30  	"github.com/ethereum/go-ethereum/accounts"
    31  	"github.com/ethereum/go-ethereum/accounts/abi/bind"
    32  	"github.com/ethereum/go-ethereum/common"
    33  	"github.com/ethereum/go-ethereum/core/types"
    34  	"github.com/ethereum/go-ethereum/crypto"
    35  	"github.com/ethereum/go-ethereum/params"
    36  )
    37  
    38  const (
    39  	txConfsNeededToConfirm = 3
    40  )
    41  
    42  var (
    43  	_       ethFetcher = (*testNode)(nil)
    44  	tLogger            = dex.StdOutLogger("ETHTEST", dex.LevelTrace)
    45  
    46  	testAddressA = common.HexToAddress("dd93b447f7eBCA361805eBe056259853F3912E04")
    47  	testAddressB = common.HexToAddress("8d83B207674bfd53B418a6E47DA148F5bFeCc652")
    48  	testAddressC = common.HexToAddress("2b84C791b79Ee37De042AD2ffF1A253c3ce9bc27")
    49  
    50  	ethGases   = dexeth.VersionedGases[0]
    51  	tokenGases = dexeth.Tokens[usdcTokenID].NetTokens[dex.Simnet].SwapContracts[0].Gas
    52  
    53  	tETH = &dex.Asset{
    54  		// Version meaning?
    55  		ID:         60,
    56  		Symbol:     "ETH",
    57  		MaxFeeRate: 100,
    58  		SwapConf:   1,
    59  	}
    60  
    61  	tBTC = &dex.Asset{
    62  		ID:         0,
    63  		Symbol:     "btc",
    64  		Version:    0, // match btc.version
    65  		MaxFeeRate: 10,
    66  		SwapConf:   1,
    67  	}
    68  
    69  	tToken = &dex.Asset{
    70  		ID:         usdcTokenID,
    71  		Symbol:     "usdc.eth",
    72  		Version:    0,
    73  		MaxFeeRate: 20,
    74  		SwapConf:   1,
    75  	}
    76  
    77  	signer = types.LatestSigner(params.AllEthashProtocolChanges)
    78  
    79  	// simBackend = backends.NewSimulatedBackend(core.GenesisAlloc{
    80  	// 	testAddressA: core.GenesisAccount{Balance: dexeth.GweiToWei(5e10)},
    81  	// }, 1e9)
    82  )
    83  
    84  type tGetTxRes struct {
    85  	tx     *types.Transaction
    86  	height int64
    87  }
    88  
    89  type testNode struct {
    90  	acct            *accounts.Account
    91  	addr            common.Address
    92  	connectErr      error
    93  	bestHdr         *types.Header
    94  	bestHdrErr      error
    95  	syncProg        ethereum.SyncProgress
    96  	syncProgT       uint64
    97  	syncProgErr     error
    98  	bal             *big.Int
    99  	balErr          error
   100  	signDataErr     error
   101  	privKey         *ecdsa.PrivateKey
   102  	swapVers        map[uint32]struct{} // For SwapConfirmations -> swap. TODO for other contractor methods
   103  	swapMap         map[[32]byte]*dexeth.SwapState
   104  	refundable      bool
   105  	baseFee         *big.Int
   106  	tip             *big.Int
   107  	netFeeStateErr  error
   108  	confNonce       uint64
   109  	confNonceErr    error
   110  	getTxRes        *types.Transaction
   111  	getTxResMap     map[common.Hash]*tGetTxRes
   112  	getTxHeight     int64
   113  	getTxErr        error
   114  	receipt         *types.Receipt
   115  	receiptTx       *types.Transaction
   116  	receiptErr      error
   117  	receipts        map[common.Hash]*types.Receipt
   118  	receiptTxs      map[common.Hash]*types.Transaction
   119  	receiptErrs     map[common.Hash]error
   120  	hdrByHash       *types.Header
   121  	lastSignedTx    *types.Transaction
   122  	sentTxs         int
   123  	sendTxTx        *types.Transaction
   124  	sendTxErr       error
   125  	simBackend      bind.ContractBackend
   126  	maxFeeRate      *big.Int
   127  	tContractor     *tContractor
   128  	tokenContractor *tTokenContractor
   129  	contractor      contractor
   130  	tokenParent     *assetWallet // only set for tokens
   131  	txConfirmations map[common.Hash]uint32
   132  	txConfsErr      map[common.Hash]error
   133  }
   134  
   135  func newBalance(current, in, out uint64) *Balance {
   136  	return &Balance{
   137  		Current:    dexeth.GweiToWei(current),
   138  		PendingIn:  dexeth.GweiToWei(in),
   139  		PendingOut: dexeth.GweiToWei(out),
   140  	}
   141  }
   142  
   143  func (n *testNode) newTransaction(nonce uint64, value *big.Int) *types.Transaction {
   144  	to := common.BytesToAddress(encode.RandomBytes(20))
   145  	tx, err := types.SignTx(types.NewTx(&types.DynamicFeeTx{
   146  		Nonce:   nonce,
   147  		Value:   value,
   148  		Gas:     50_000,
   149  		To:      &to,
   150  		ChainID: n.chainConfig().ChainID,
   151  	}), signer, n.privKey)
   152  	if err != nil {
   153  		panic("tx signing error")
   154  	}
   155  	return tx
   156  }
   157  
   158  func (n *testNode) address() common.Address {
   159  	return n.addr
   160  }
   161  
   162  func (n *testNode) connect(ctx context.Context) error {
   163  	return n.connectErr
   164  }
   165  
   166  func (n *testNode) contractBackend() bind.ContractBackend {
   167  	return n.simBackend
   168  }
   169  
   170  func (n *testNode) chainConfig() *params.ChainConfig {
   171  	return params.AllEthashProtocolChanges
   172  }
   173  
   174  func (n *testNode) getConfirmedNonce(context.Context) (uint64, error) {
   175  	return n.confNonce, n.confNonceErr
   176  }
   177  
   178  func (n *testNode) getTransaction(ctx context.Context, hash common.Hash) (*types.Transaction, int64, error) {
   179  	if n.getTxErr != nil {
   180  		return nil, 0, n.getTxErr
   181  	}
   182  
   183  	if n.getTxResMap != nil {
   184  		if tx, ok := n.getTxResMap[hash]; ok {
   185  			return tx.tx, tx.height, nil
   186  		} else {
   187  			return nil, 0, asset.CoinNotFoundError
   188  		}
   189  	}
   190  
   191  	return n.getTxRes, n.getTxHeight, n.getTxErr
   192  }
   193  
   194  func (n *testNode) txOpts(ctx context.Context, val, maxGas uint64, maxFeeRate, tipRate, nonce *big.Int) (*bind.TransactOpts, error) {
   195  	if maxFeeRate == nil {
   196  		maxFeeRate = n.maxFeeRate
   197  	}
   198  	txOpts := newTxOpts(ctx, n.addr, val, maxGas, maxFeeRate, dexeth.GweiToWei(2))
   199  	txOpts.Nonce = big.NewInt(1)
   200  	return txOpts, nil
   201  }
   202  
   203  func (n *testNode) currentFees(ctx context.Context) (baseFees, tipCap *big.Int, err error) {
   204  	return n.baseFee, n.tip, n.netFeeStateErr
   205  }
   206  
   207  func (n *testNode) shutdown() {}
   208  
   209  func (n *testNode) bestHeader(ctx context.Context) (*types.Header, error) {
   210  	return n.bestHdr, n.bestHdrErr
   211  }
   212  func (n *testNode) addressBalance(ctx context.Context, addr common.Address) (*big.Int, error) {
   213  	return n.bal, n.balErr
   214  }
   215  func (n *testNode) unlock(pw string) error {
   216  	return nil
   217  }
   218  func (n *testNode) lock() error {
   219  	return nil
   220  }
   221  func (n *testNode) locked() bool {
   222  	return false
   223  }
   224  func (n *testNode) syncProgress(context.Context) (prog *ethereum.SyncProgress, bestBlockUNIXTime uint64, err error) {
   225  	return &n.syncProg, n.syncProgT, n.syncProgErr
   226  }
   227  func (n *testNode) peerCount() uint32 {
   228  	return 1
   229  }
   230  
   231  func (n *testNode) signData(data []byte) (sig, pubKey []byte, err error) {
   232  	if n.signDataErr != nil {
   233  		return nil, nil, n.signDataErr
   234  	}
   235  
   236  	if n.privKey == nil {
   237  		return nil, nil, nil
   238  	}
   239  
   240  	sig, err = crypto.Sign(crypto.Keccak256(data), n.privKey)
   241  	if err != nil {
   242  		return nil, nil, err
   243  	}
   244  
   245  	return sig, crypto.FromECDSAPub(&n.privKey.PublicKey), nil
   246  }
   247  func (n *testNode) sendTransaction(ctx context.Context, txOpts *bind.TransactOpts, to common.Address, data []byte, filts ...acceptabilityFilter) (*types.Transaction, error) {
   248  	n.sentTxs++
   249  	return n.sendTxTx, n.sendTxErr
   250  }
   251  func (n *testNode) sendSignedTransaction(ctx context.Context, tx *types.Transaction, filts ...acceptabilityFilter) error {
   252  	n.lastSignedTx = tx
   253  	return nil
   254  }
   255  
   256  func tTx(gasFeeCap, gasTipCap, value uint64, to *common.Address, data []byte, gasLimit uint64) *types.Transaction {
   257  	return types.NewTx(&types.DynamicFeeTx{
   258  		GasFeeCap: dexeth.GweiToWei(gasFeeCap),
   259  		GasTipCap: dexeth.GweiToWei(gasTipCap),
   260  		To:        to,
   261  		Value:     dexeth.GweiToWei(value),
   262  		Data:      data,
   263  		Gas:       gasLimit,
   264  	})
   265  }
   266  
   267  func (n *testNode) transactionConfirmations(_ context.Context, txHash common.Hash) (uint32, error) {
   268  	if n.txConfirmations == nil {
   269  		return 0, nil
   270  	}
   271  	return n.txConfirmations[txHash], n.txConfsErr[txHash]
   272  }
   273  
   274  func (n *testNode) headerByHash(_ context.Context, txHash common.Hash) (*types.Header, error) {
   275  	return n.hdrByHash, nil
   276  }
   277  
   278  func (n *testNode) transactionReceipt(ctx context.Context, txHash common.Hash) (*types.Receipt, error) {
   279  	if n.receiptErr != nil {
   280  		return nil, n.receiptErr
   281  	}
   282  	if n.receipt != nil {
   283  		return n.receipt, nil
   284  	}
   285  
   286  	return n.receipts[txHash], n.receiptErrs[txHash]
   287  }
   288  
   289  func (n *testNode) transactionAndReceipt(ctx context.Context, txHash common.Hash) (*types.Receipt, *types.Transaction, error) {
   290  	if n.receiptErr != nil {
   291  		return nil, nil, n.receiptErr
   292  	}
   293  	if n.receipt != nil {
   294  		return n.receipt, n.receiptTx, nil
   295  	}
   296  
   297  	return n.receipts[txHash], n.receiptTxs[txHash], n.receiptErrs[txHash]
   298  }
   299  
   300  func (n *testNode) nonce(ctx context.Context) (*big.Int, *big.Int, error) {
   301  	return big.NewInt(0), big.NewInt(1), nil
   302  }
   303  
   304  func (n *testNode) setBalanceError(w *assetWallet, err error) {
   305  	n.balErr = err
   306  	n.tokenContractor.balErr = err
   307  	w.balances.m = nil
   308  }
   309  
   310  type tMempoolNode struct {
   311  	*testNode
   312  	pendingTxs []*types.Transaction
   313  }
   314  
   315  func (n *tMempoolNode) pendingTransactions() ([]*types.Transaction, error) {
   316  	return n.pendingTxs, nil
   317  }
   318  
   319  type tContractor struct {
   320  	gasEstimates      *dexeth.Gases
   321  	swapMap           map[[32]byte]*dexeth.SwapState
   322  	swapErr           error
   323  	initTx            *types.Transaction
   324  	initErr           error
   325  	redeemTx          *types.Transaction
   326  	redeemErr         error
   327  	refundTx          *types.Transaction
   328  	refundErr         error
   329  	initGasErr        error
   330  	redeemGasErr      error
   331  	refundGasErr      error
   332  	redeemGasOverride *uint64
   333  	valueIn           map[common.Hash]uint64
   334  	valueOut          map[common.Hash]uint64
   335  	valueErr          error
   336  	refundable        bool
   337  	refundableErr     error
   338  	lastRedeemOpts    *bind.TransactOpts
   339  	lastRedeems       []*asset.Redemption
   340  	lastRefund        struct {
   341  		// tx          *types.Transaction
   342  		secretHash [32]byte
   343  		maxFeeRate *big.Int
   344  		// contractVer uint32
   345  	}
   346  }
   347  
   348  func (c *tContractor) swap(ctx context.Context, secretHash [32]byte) (*dexeth.SwapState, error) {
   349  	if c.swapErr != nil {
   350  		return nil, c.swapErr
   351  	}
   352  	swap, ok := c.swapMap[secretHash]
   353  	if !ok {
   354  		return nil, errors.New("swap not in map")
   355  	}
   356  	return swap, nil
   357  }
   358  
   359  func (c *tContractor) initiate(*bind.TransactOpts, []*asset.Contract) (*types.Transaction, error) {
   360  	return c.initTx, c.initErr
   361  }
   362  
   363  func (c *tContractor) redeem(txOpts *bind.TransactOpts, redeems []*asset.Redemption) (*types.Transaction, error) {
   364  	c.lastRedeemOpts = txOpts
   365  	c.lastRedeems = redeems
   366  	return c.redeemTx, c.redeemErr
   367  }
   368  
   369  func (c *tContractor) refund(opts *bind.TransactOpts, secretHash [32]byte) (*types.Transaction, error) {
   370  	c.lastRefund.secretHash = secretHash
   371  	c.lastRefund.maxFeeRate = opts.GasFeeCap
   372  	return c.refundTx, c.refundErr
   373  }
   374  
   375  func (c *tContractor) estimateInitGas(ctx context.Context, n int) (uint64, error) {
   376  	return c.gasEstimates.SwapN(n), c.initGasErr
   377  }
   378  
   379  func (c *tContractor) estimateRedeemGas(ctx context.Context, secrets [][32]byte) (uint64, error) {
   380  	if c.redeemGasOverride != nil {
   381  		return *c.redeemGasOverride, nil
   382  	}
   383  	return c.gasEstimates.RedeemN(len(secrets)), c.redeemGasErr
   384  }
   385  
   386  func (c *tContractor) estimateRefundGas(ctx context.Context, secretHash [32]byte) (uint64, error) {
   387  	return c.gasEstimates.Refund, c.refundGasErr
   388  }
   389  
   390  func (c *tContractor) value(_ context.Context, tx *types.Transaction) (incoming, outgoing uint64, err error) {
   391  	return c.valueIn[tx.Hash()], c.valueOut[tx.Hash()], c.valueErr
   392  }
   393  
   394  func (c *tContractor) isRefundable(secretHash [32]byte) (bool, error) {
   395  	return c.refundable, c.refundableErr
   396  }
   397  
   398  func (c *tContractor) voidUnusedNonce() {}
   399  
   400  type tTokenContractor struct {
   401  	*tContractor
   402  	tokenAddr           common.Address
   403  	bal                 *big.Int
   404  	balErr              error
   405  	allow               *big.Int
   406  	allowErr            error
   407  	approveTx           *types.Transaction
   408  	approved            bool
   409  	approveErr          error
   410  	approveEstimate     uint64
   411  	approveEstimateErr  error
   412  	transferTx          *types.Transaction
   413  	transferErr         error
   414  	transferEstimate    uint64
   415  	transferEstimateErr error
   416  }
   417  
   418  var _ tokenContractor = (*tTokenContractor)(nil)
   419  
   420  func (c *tTokenContractor) tokenAddress() common.Address {
   421  	return c.tokenAddr
   422  }
   423  
   424  func (c *tTokenContractor) balance(context.Context) (*big.Int, error) {
   425  	return c.bal, c.balErr
   426  }
   427  
   428  func (c *tTokenContractor) allowance(context.Context) (*big.Int, error) {
   429  	return c.allow, c.allowErr
   430  }
   431  
   432  func (c *tTokenContractor) approve(*bind.TransactOpts, *big.Int) (*types.Transaction, error) {
   433  	if c.approveErr == nil {
   434  		c.approved = true
   435  	}
   436  	return c.approveTx, c.approveErr
   437  }
   438  
   439  func (c *tTokenContractor) estimateApproveGas(context.Context, *big.Int) (uint64, error) {
   440  	return c.approveEstimate, c.approveEstimateErr
   441  }
   442  
   443  func (c *tTokenContractor) transfer(*bind.TransactOpts, common.Address, *big.Int) (*types.Transaction, error) {
   444  	return c.transferTx, c.transferErr
   445  }
   446  
   447  func (c *tTokenContractor) parseTransfer(*types.Receipt) (uint64, error) {
   448  	return 0, nil
   449  }
   450  
   451  func (c *tTokenContractor) estimateTransferGas(context.Context, *big.Int) (uint64, error) {
   452  	return c.transferEstimate, c.transferEstimateErr
   453  }
   454  
   455  type tTxDB struct {
   456  	storeTxCalled  bool
   457  	storeTxErr     error
   458  	removeTxCalled bool
   459  	removeTxErr    error
   460  	txToGet        *extendedWalletTx
   461  	getTxErr       error
   462  }
   463  
   464  var _ txDB = (*tTxDB)(nil)
   465  
   466  func (db *tTxDB) Connect(ctx context.Context) (*sync.WaitGroup, error) {
   467  	return &sync.WaitGroup{}, nil
   468  }
   469  func (db *tTxDB) storeTx(wt *extendedWalletTx) error {
   470  	db.storeTxCalled = true
   471  	return db.storeTxErr
   472  }
   473  func (db *tTxDB) removeTx(_ /* id */ string) error {
   474  	db.removeTxCalled = true
   475  	return db.removeTxErr
   476  }
   477  func (db *tTxDB) getTxs(n int, refID *common.Hash, past bool, tokenID *uint32) ([]*asset.WalletTransaction, error) {
   478  	return nil, nil
   479  }
   480  
   481  // getTx gets a single transaction. It is not an error if the tx is not known.
   482  // In that case, a nil tx is returned.
   483  func (db *tTxDB) getTx(txHash common.Hash) (tx *extendedWalletTx, _ error) {
   484  	return db.txToGet, db.getTxErr
   485  }
   486  func (db *tTxDB) getPendingTxs() ([]*extendedWalletTx, error) {
   487  	return nil, nil
   488  }
   489  func (db *tTxDB) close() error {
   490  	return nil
   491  }
   492  
   493  // func TestCheckUnconfirmedTxs(t *testing.T) {
   494  // 	const tipHeight = 50
   495  // 	const baseFeeGwei = 100
   496  // 	const gasTipCapGwei = 2
   497  
   498  // 	type tExtendedWalletTx struct {
   499  // 		wt           *extendedWalletTx
   500  // 		confs        uint32
   501  // 		gasUsed      uint64
   502  // 		txReceiptErr error
   503  // 	}
   504  
   505  // 	newExtendedWalletTx := func(assetID uint32, nonce int64, maxFees uint64, currBlockNumber uint64, txReceiptConfs uint32,
   506  // 		txReceiptGasUsed uint64, txReceiptErr error, timeStamp int64, savedToDB bool) *tExtendedWalletTx {
   507  // 		var tokenID *uint32
   508  // 		if assetID != BipID {
   509  // 			tokenID = &assetID
   510  // 		}
   511  
   512  // 		return &tExtendedWalletTx{
   513  // 			wt: &extendedWalletTx{
   514  // 				WalletTransaction: &asset.WalletTransaction{
   515  // 					BlockNumber: currBlockNumber,
   516  // 					TokenID:     tokenID,
   517  // 					Fees:        maxFees,
   518  // 				},
   519  // 				SubmissionTime: uint64(timeStamp),
   520  // 				nonce:          big.NewInt(nonce),
   521  // 				savedToDB:      savedToDB,
   522  // 			},
   523  // 			confs:        txReceiptConfs,
   524  // 			gasUsed:      txReceiptGasUsed,
   525  // 			txReceiptErr: txReceiptErr,
   526  // 		}
   527  // 	}
   528  
   529  // 	gasFee := func(gasUsed uint64) uint64 {
   530  // 		return gasUsed * (baseFeeGwei + gasTipCapGwei)
   531  // 	}
   532  
   533  // 	now := time.Now().Unix()
   534  
   535  // 	tests := []struct {
   536  // 		name           string
   537  // 		assetID        uint32
   538  // 		unconfirmedTxs []*tExtendedWalletTx
   539  // 		confirmedNonce uint64
   540  
   541  // 		expTxsAfter       []*extendedWalletTx
   542  // 		expStoreTxCalled  bool
   543  // 		expRemoveTxCalled bool
   544  // 		storeTxErr        error
   545  // 		removeTxErr       error
   546  // 	}{
   547  // 		{
   548  // 			name:    "coin not found",
   549  // 			assetID: BipID,
   550  // 			unconfirmedTxs: []*tExtendedWalletTx{
   551  // 				newExtendedWalletTx(BipID, 5, 1e7, 0, 0, 0, asset.CoinNotFoundError, now, true),
   552  // 			},
   553  // 			expTxsAfter: []*extendedWalletTx{
   554  // 				newExtendedWalletTx(BipID, 5, 1e7, 0, 0, 0, asset.CoinNotFoundError, now, true).wt,
   555  // 			},
   556  // 		},
   557  // 		{
   558  // 			name:    "still in mempool",
   559  // 			assetID: BipID,
   560  // 			unconfirmedTxs: []*tExtendedWalletTx{
   561  // 				newExtendedWalletTx(BipID, 5, 1e7, 0, 0, 0, nil, now, true),
   562  // 			},
   563  // 			expTxsAfter: []*extendedWalletTx{
   564  // 				newExtendedWalletTx(BipID, 5, 1e7, 0, 0, 0, nil, now, true).wt,
   565  // 			},
   566  // 		},
   567  // 		{
   568  // 			name:    "1 confirmation",
   569  // 			assetID: BipID,
   570  // 			unconfirmedTxs: []*tExtendedWalletTx{
   571  // 				newExtendedWalletTx(BipID, 5, 1e7, 0, 1, 6e5, nil, now, true),
   572  // 			},
   573  // 			expTxsAfter: []*extendedWalletTx{
   574  // 				newExtendedWalletTx(BipID, 5, gasFee(6e5), tipHeight, 0, 0, nil, now, true).wt,
   575  // 			},
   576  // 			expStoreTxCalled: true,
   577  // 		},
   578  // 		{
   579  // 			name:    "3 confirmations",
   580  // 			assetID: BipID,
   581  // 			unconfirmedTxs: []*tExtendedWalletTx{
   582  // 				newExtendedWalletTx(BipID, 1, gasFee(6e5), tipHeight, 3, 6e5, nil, now, true),
   583  // 			},
   584  // 			expTxsAfter:      []*extendedWalletTx{},
   585  // 			expStoreTxCalled: true,
   586  // 		},
   587  // 		{
   588  // 			name:    "3 confirmations, leave in unconfirmed txs if txDB.storeTx fails",
   589  // 			assetID: BipID,
   590  // 			unconfirmedTxs: []*tExtendedWalletTx{
   591  // 				newExtendedWalletTx(BipID, 5, gasFee(6e5), tipHeight-2, 3, 6e5, nil, now, true),
   592  // 			},
   593  // 			expTxsAfter: []*extendedWalletTx{
   594  // 				newExtendedWalletTx(BipID, 5, gasFee(6e5), tipHeight-2, 3, 6e5, nil, now, true).wt,
   595  // 			},
   596  // 			expStoreTxCalled: true,
   597  // 			storeTxErr:       errors.New("test error"),
   598  // 		},
   599  // 		{
   600  // 			name:    "was confirmed but now not found",
   601  // 			assetID: BipID,
   602  // 			unconfirmedTxs: []*tExtendedWalletTx{
   603  // 				newExtendedWalletTx(BipID, 5, 1e7, tipHeight-1, 0, 0, asset.CoinNotFoundError, now, true),
   604  // 			},
   605  // 			expTxsAfter: []*extendedWalletTx{
   606  // 				newExtendedWalletTx(BipID, 5, 1e7, 0, 0, 0, asset.CoinNotFoundError, now, true).wt,
   607  // 			},
   608  // 			expStoreTxCalled: true,
   609  // 		},
   610  // 	}
   611  
   612  // 	for _, tt := range tests {
   613  // 		t.Run(tt.name, func(t *testing.T) {
   614  // 			_, eth, node, shutdown := tassetWallet(tt.assetID)
   615  // 			defer shutdown()
   616  
   617  // 			node.tokenContractor.bal = unlimitedAllowance
   618  // 			node.receipts = make(map[common.Hash]*types.Receipt)
   619  // 			node.receiptTxs = make(map[common.Hash]*types.Transaction)
   620  // 			node.receiptErrs = make(map[common.Hash]error)
   621  // 			node.hdrByHash = &types.Header{
   622  // 				BaseFee: dexeth.GweiToWei(baseFeeGwei),
   623  // 			}
   624  // 			node.confNonce = tt.confirmedNonce
   625  // 			eth.connected.Store(true)
   626  // 			eth.tipMtx.Lock()
   627  // 			eth.currentTip = &types.Header{Number: new(big.Int).SetUint64(tipHeight)}
   628  // 			eth.tipMtx.Unlock()
   629  
   630  // 			txDB := &tTxDB{
   631  // 				storeTxErr:  tt.storeTxErr,
   632  // 				removeTxErr: tt.removeTxErr,
   633  // 			}
   634  // 			eth.txDB = txDB
   635  
   636  // 			for _, pt := range tt.unconfirmedTxs {
   637  // 				txHash := common.BytesToHash(encode.RandomBytes(32))
   638  // 				pt.wt.ID = txHash.String()
   639  // 				pt.wt.txHash = txHash
   640  // 				eth.pendingTxs = append(eth.pendingTxs, pt.wt)
   641  // 				var blockNumber *big.Int
   642  // 				if pt.confs > 0 {
   643  // 					blockNumber = big.NewInt(int64(tipHeight - pt.confs + 1))
   644  // 				}
   645  // 				node.receipts[txHash] = &types.Receipt{BlockNumber: blockNumber, GasUsed: pt.gasUsed}
   646  
   647  // 				node.receiptTxs[txHash] = types.NewTx(&types.DynamicFeeTx{
   648  // 					GasTipCap: dexeth.GweiToWei(gasTipCapGwei),
   649  // 					GasFeeCap: dexeth.GweiToWei(2 * baseFeeGwei),
   650  // 				})
   651  // 				node.receiptErrs[txHash] = pt.txReceiptErr
   652  // 			}
   653  
   654  // 			eth.checkPendingTxs()
   655  
   656  // 			if len(eth.pendingTxs) != len(tt.expTxsAfter) {
   657  // 				t.Fatalf("expected %d unconfirmed txs, got %d", len(tt.expTxsAfter), len(eth.pendingTxs))
   658  // 			}
   659  // 			for i, expTx := range tt.expTxsAfter {
   660  // 				tx := eth.pendingTxs[i]
   661  // 				if expTx.nonce.Cmp(tx.nonce) != 0 {
   662  // 					t.Fatalf("expected tx index %d to have nonce %d, not %d", i, expTx.nonce, tx.nonce)
   663  // 				}
   664  // 			}
   665  
   666  // 			if txDB.storeTxCalled != tt.expStoreTxCalled {
   667  // 				t.Fatalf("expected storeTx called %v, got %v", tt.expStoreTxCalled, txDB.storeTxCalled)
   668  // 			}
   669  // 			if txDB.removeTxCalled != tt.expRemoveTxCalled {
   670  // 				t.Fatalf("expected removeTx called %v, got %v", tt.expRemoveTxCalled, txDB.removeTxCalled)
   671  // 			}
   672  // 		})
   673  // 	}
   674  // }
   675  
   676  func TestCheckPendingTxs(t *testing.T) {
   677  	_, eth, node, shutdown := tassetWallet(BipID)
   678  	defer shutdown()
   679  
   680  	const tip = 12552
   681  	const finalized = tip - txConfsNeededToConfirm + 1
   682  	now := uint64(time.Now().Unix())
   683  	finalizedStamp := now - txConfsNeededToConfirm*10
   684  	rebroadcastable := now - 300
   685  	mature := now - 600 // able to send actions
   686  	agedOut := now - uint64(txAgeOut.Seconds()) - 1
   687  
   688  	val := dexeth.GweiToWei(1)
   689  	extendedTx := func(nonce, blockNum, blockStamp, submissionStamp uint64) *extendedWalletTx {
   690  		pendingTx := eth.extendedTx(node.newTransaction(nonce, val), asset.Send, 1, nil)
   691  		pendingTx.BlockNumber = blockNum
   692  		pendingTx.Confirmed = blockNum > 0 && blockNum <= finalized
   693  		pendingTx.Timestamp = blockStamp
   694  		pendingTx.SubmissionTime = submissionStamp
   695  		pendingTx.lastBroadcast = time.Unix(int64(submissionStamp), 0)
   696  		pendingTx.lastFeeCheck = time.Unix(int64(submissionStamp), 0)
   697  		return pendingTx
   698  	}
   699  
   700  	newReceipt := func(confs uint64) *types.Receipt {
   701  		r := &types.Receipt{
   702  			EffectiveGasPrice: big.NewInt(1),
   703  		}
   704  		if confs > 0 {
   705  			r.BlockNumber = big.NewInt(int64(tip - confs + 1))
   706  		}
   707  		return r
   708  	}
   709  
   710  	emitChan := make(chan asset.WalletNotification, 128)
   711  	eth.emit = asset.NewWalletEmitter(emitChan, BipID, eth.log)
   712  
   713  	getAction := func(t *testing.T) string {
   714  		for {
   715  			select {
   716  			case ni := <-emitChan:
   717  				if n, ok := ni.(*asset.ActionRequiredNote); ok {
   718  					return n.ActionID
   719  				}
   720  			default:
   721  				t.Fatalf("no ActionRequiredNote found")
   722  			}
   723  		}
   724  	}
   725  
   726  	for _, tt := range []*struct {
   727  		name        string
   728  		dbErr       error
   729  		pendingTxs  []*extendedWalletTx
   730  		receipts    []*types.Receipt
   731  		receiptErrs []error
   732  		txs         []bool
   733  		noncesAfter []uint64
   734  		actionID    string
   735  		recast      bool
   736  	}{
   737  		{
   738  			name: "first of two is confirmed",
   739  			pendingTxs: []*extendedWalletTx{
   740  				extendedTx(0, finalized, finalizedStamp, finalizedStamp),
   741  				extendedTx(1, finalized+1, finalizedStamp, finalizedStamp), // won't even be checked.
   742  			},
   743  			noncesAfter: []uint64{1},
   744  		},
   745  		{
   746  			name: "second one is confirmed first",
   747  			pendingTxs: []*extendedWalletTx{
   748  				extendedTx(2, 0, 0, rebroadcastable),
   749  				extendedTx(3, finalized, finalizedStamp, finalizedStamp),
   750  			},
   751  			noncesAfter: []uint64{2, 3},
   752  			receipts:    []*types.Receipt{nil, nil},
   753  			txs:         []bool{false, true},
   754  			receiptErrs: []error{asset.CoinNotFoundError, nil},
   755  			actionID:    actionTypeLostNonce,
   756  		},
   757  		{
   758  			name: "confirm one with receipt",
   759  			pendingTxs: []*extendedWalletTx{
   760  				extendedTx(4, 0, 0, finalizedStamp),
   761  			},
   762  			receipts: []*types.Receipt{newReceipt(txConfsNeededToConfirm)},
   763  		},
   764  		{
   765  			name: "old and unindexed",
   766  			pendingTxs: []*extendedWalletTx{
   767  				extendedTx(5, 0, 0, agedOut),
   768  			},
   769  			noncesAfter: []uint64{5},
   770  			receipts:    []*types.Receipt{nil},
   771  			receiptErrs: []error{asset.CoinNotFoundError},
   772  			txs:         []bool{false},
   773  			actionID:    actionTypeLostNonce,
   774  		},
   775  		{
   776  			name: "mature and indexed, low fees",
   777  			pendingTxs: []*extendedWalletTx{
   778  				extendedTx(6, 0, 0, mature),
   779  			},
   780  			noncesAfter: []uint64{6},
   781  			receipts:    []*types.Receipt{newReceipt(0)},
   782  			actionID:    actionTypeTooCheap,
   783  		}, {
   784  			name: "missing nonces",
   785  			pendingTxs: []*extendedWalletTx{
   786  				extendedTx(8, finalized, finalizedStamp, finalizedStamp),
   787  				extendedTx(11, finalized+1, finalizedStamp, finalizedStamp),
   788  			},
   789  			noncesAfter: []uint64{11},
   790  			actionID:    actionTypeMissingNonces,
   791  		},
   792  	} {
   793  		t.Run(tt.name, func(t *testing.T) {
   794  			eth.confirmedNonceAt = tt.pendingTxs[0].Nonce
   795  			eth.pendingNonceAt = new(big.Int).Add(tt.pendingTxs[len(tt.pendingTxs)-1].Nonce, big.NewInt(1))
   796  
   797  			node.lastSignedTx = nil
   798  			eth.currentTip = &types.Header{Number: new(big.Int).SetUint64(tip)}
   799  			eth.pendingTxs = tt.pendingTxs
   800  			for i, r := range tt.receipts {
   801  				pendingTx := tt.pendingTxs[i]
   802  				if tt.receiptErrs != nil {
   803  					node.receiptErrs[pendingTx.txHash] = tt.receiptErrs[i]
   804  				}
   805  				if r == nil {
   806  					continue
   807  				}
   808  				node.receipts[pendingTx.txHash] = r
   809  				if len(tt.txs) < i+1 || tt.txs[i] {
   810  					node.receiptTxs[pendingTx.txHash], _ = pendingTx.tx()
   811  				}
   812  				if pendingTx.Timestamp == 0 && r.BlockNumber != nil && r.BlockNumber.Uint64() != 0 {
   813  					node.hdrByHash = &types.Header{
   814  						Number: r.BlockNumber,
   815  						Time:   now,
   816  					}
   817  				}
   818  			}
   819  			eth.checkPendingTxs()
   820  			if len(eth.pendingTxs) != len(tt.noncesAfter) {
   821  				t.Fatalf("wrong number of pending txs. expected %d got %d", len(tt.noncesAfter), len(eth.pendingTxs))
   822  			}
   823  			for i, pendingTx := range eth.pendingTxs {
   824  				if pendingTx.Nonce.Uint64() != tt.noncesAfter[i] {
   825  					t.Fatalf("Expected nonce %d at index %d, but got nonce %s", tt.noncesAfter[i], i, pendingTx.Nonce)
   826  				}
   827  			}
   828  			if tt.actionID != "" {
   829  				if actionID := getAction(t); actionID != tt.actionID {
   830  					t.Fatalf("expected action %s, got %s", tt.actionID, actionID)
   831  				}
   832  			}
   833  			if tt.recast != (node.lastSignedTx != nil) {
   834  				t.Fatalf("wrong recast result recast = %t, lastSignedTx = %t", tt.recast, node.lastSignedTx != nil)
   835  			}
   836  		})
   837  	}
   838  }
   839  
   840  func TestTakeAction(t *testing.T) {
   841  	_, eth, node, shutdown := tassetWallet(BipID)
   842  	defer shutdown()
   843  
   844  	aGwei := dexeth.GweiToWei(1)
   845  
   846  	pendingTx := eth.extendedTx(node.newTransaction(0, aGwei), asset.Send, 1, nil)
   847  	eth.pendingTxs = []*extendedWalletTx{pendingTx}
   848  
   849  	feeCap := new(big.Int).Mul(aGwei, big.NewInt(5))
   850  	tipCap := new(big.Int).Mul(aGwei, big.NewInt(2))
   851  	replacementTx, _ := types.SignTx(types.NewTx(&types.DynamicFeeTx{
   852  		Nonce:     1,
   853  		GasTipCap: tipCap,
   854  		GasFeeCap: feeCap,
   855  		Gas:       50_000,
   856  		ChainID:   node.chainConfig().ChainID,
   857  	}), signer, node.privKey)
   858  	node.sendTxTx = replacementTx
   859  
   860  	tooCheapAction := []byte(fmt.Sprintf(`{"txID":"%s","bump":true}`, pendingTx.ID))
   861  	if err := eth.TakeAction(actionTypeTooCheap, tooCheapAction); err != nil {
   862  		t.Fatalf("TakeAction error: %v", err)
   863  	}
   864  
   865  	newPendingTx := eth.pendingTxs[0]
   866  	if pendingTx.txHash == newPendingTx.txHash {
   867  		t.Fatal("tx wasn't replaced")
   868  	}
   869  	tx, _ := newPendingTx.tx()
   870  	if tx.GasFeeCap().Cmp(feeCap) != 0 {
   871  		t.Fatalf("wrong fee cap. wanted %s, got %s", feeCap, tx.GasFeeCap())
   872  	}
   873  	if tx.GasTipCap().Cmp(tipCap) != 0 {
   874  		t.Fatalf("wrong tip cap. wanted %s, got %s", tipCap, tx.GasTipCap())
   875  	}
   876  	if !newPendingTx.savedToDB {
   877  		t.Fatal("didn't save to DB")
   878  	}
   879  
   880  	pendingTx = eth.extendedTx(node.newTransaction(1, aGwei), asset.Send, 1, nil)
   881  	eth.pendingTxs = []*extendedWalletTx{pendingTx}
   882  	pendingTx.SubmissionTime = 0
   883  	// Neglecting to bump should reset submission time.
   884  	tooCheapAction = []byte(fmt.Sprintf(`{"txID":"%s","bump":false}`, pendingTx.ID))
   885  	if err := eth.TakeAction(actionTypeTooCheap, tooCheapAction); err != nil {
   886  		t.Fatalf("TakeAction bump=false error: %v", err)
   887  	}
   888  	tx, _ = pendingTx.tx()
   889  	if tx.GasTipCap().Uint64() != 0 {
   890  		t.Fatal("The fee was bumped. The fee shouldn't have been bumped.")
   891  	}
   892  	if pendingTx.actionIgnored.IsZero() {
   893  		t.Fatalf("The ignore time wasn't reset")
   894  	}
   895  	if len(eth.pendingTxs) != 1 {
   896  		t.Fatalf("Tx was removed")
   897  	}
   898  
   899  	// Nonce-replaced tx
   900  	eth.pendingTxs = []*extendedWalletTx{pendingTx}
   901  	lostNonceAction := []byte(fmt.Sprintf(`{"txID":"%s","abandon":true}`, pendingTx.ID))
   902  	if err := eth.TakeAction(actionTypeLostNonce, lostNonceAction); err != nil {
   903  		t.Fatalf("TakeAction replacment=false, abandon=true error: %v", err)
   904  	}
   905  	if len(eth.pendingTxs) != 0 {
   906  		t.Fatalf("Tx wasn't abandoned")
   907  	}
   908  	eth.pendingTxs = []*extendedWalletTx{pendingTx}
   909  	node.getTxRes = replacementTx
   910  	lostNonceAction = []byte(fmt.Sprintf(`{"txID":"%s","abandon":false,"replacementID":"%s"}`, pendingTx.ID, replacementTx.Hash()))
   911  	if err := eth.TakeAction(actionTypeLostNonce, lostNonceAction); err != nil {
   912  		t.Fatalf("TakeAction replacment=true, error: %v", err)
   913  	}
   914  	newPendingTx = eth.pendingTxs[0]
   915  	if newPendingTx.txHash != replacementTx.Hash() {
   916  		t.Fatalf("replacement tx wasn't accepted")
   917  	}
   918  	// wrong nonce is an error though
   919  	pendingTx = eth.extendedTx(node.newTransaction(5050, aGwei), asset.Send, 1, nil)
   920  	eth.pendingTxs = []*extendedWalletTx{pendingTx}
   921  	lostNonceAction = []byte(fmt.Sprintf(`{"txID":"%s","abandon":false,"replacementID":"%s"}`, pendingTx.ID, replacementTx.Hash()))
   922  	if err := eth.TakeAction(actionTypeLostNonce, lostNonceAction); err == nil {
   923  		t.Fatalf("no error for wrong nonce")
   924  	}
   925  
   926  	// Missing nonces
   927  	tx5 := eth.extendedTx(node.newTransaction(5, aGwei), asset.Send, 1, nil)
   928  	eth.pendingTxs = []*extendedWalletTx{tx5}
   929  	eth.confirmedNonceAt = big.NewInt(2)
   930  	eth.pendingNonceAt = big.NewInt(6)
   931  	nonceRecoveryAction := []byte(`{"recover":true}`)
   932  	node.sentTxs = 0
   933  	if err := eth.TakeAction(actionTypeMissingNonces, nonceRecoveryAction); err != nil {
   934  		t.Fatalf("error for nonce recover: %v", err)
   935  	}
   936  	if node.sentTxs != 3 {
   937  		t.Fatalf("expected 2 new txs. saw %d", node.sentTxs)
   938  	}
   939  
   940  }
   941  
   942  func TestCheckForNewBlocks(t *testing.T) {
   943  	header0 := &types.Header{Number: new(big.Int)}
   944  	header1 := &types.Header{Number: big.NewInt(1)}
   945  	tests := []struct {
   946  		name              string
   947  		hashErr, blockErr error
   948  		bestHeader        *types.Header
   949  		hasTipChange      bool
   950  	}{{
   951  		name:         "ok",
   952  		bestHeader:   header1,
   953  		hasTipChange: true,
   954  	}, {
   955  		name:       "ok same hash",
   956  		bestHeader: header0,
   957  	}}
   958  
   959  	for _, test := range tests {
   960  		ctx, cancel := context.WithCancel(context.Background())
   961  		node := &testNode{}
   962  		notes := make(chan asset.WalletNotification, 1)
   963  		emit := asset.NewWalletEmitter(notes, BipID, tLogger)
   964  
   965  		node.bestHdr = test.bestHeader
   966  		node.bestHdrErr = test.blockErr
   967  		w := &ETHWallet{
   968  			assetWallet: &assetWallet{
   969  				baseWallet: &baseWallet{
   970  					node:             node,
   971  					addr:             node.address(),
   972  					ctx:              ctx,
   973  					log:              tLogger,
   974  					currentTip:       header0,
   975  					confirmedNonceAt: new(big.Int),
   976  					pendingNonceAt:   new(big.Int),
   977  					txDB:             &tTxDB{},
   978  					finalizeConfs:    txConfsNeededToConfirm,
   979  				},
   980  				log:     tLogger.SubLogger("ETH"),
   981  				emit:    emit,
   982  				assetID: BipID,
   983  			},
   984  		}
   985  		w.wallets = map[uint32]*assetWallet{BipID: w.assetWallet}
   986  		w.assetWallet.connected.Store(true)
   987  		w.checkForNewBlocks(ctx)
   988  
   989  		if test.hasTipChange {
   990  		out:
   991  			for {
   992  				select {
   993  				case ni := <-notes:
   994  					switch n := ni.(type) {
   995  					case *asset.TipChangeNote:
   996  						if n.Tip != test.bestHeader.Number.Uint64() {
   997  							t.Fatalf("expected tip change but didn't get it")
   998  						}
   999  						break out
  1000  					}
  1001  				case <-time.After(time.Second * 5):
  1002  					t.Fatal("timed out waiting for tip change")
  1003  				}
  1004  			}
  1005  		} else {
  1006  			if w.currentTip.Number.Cmp(test.bestHeader.Number) != 0 {
  1007  				t.Fatalf("tip was changed. wasn't supposed to be changed")
  1008  			}
  1009  		}
  1010  		cancel()
  1011  
  1012  	}
  1013  }
  1014  
  1015  func TestSyncStatus(t *testing.T) {
  1016  	tests := []struct {
  1017  		name                string
  1018  		syncProg            ethereum.SyncProgress
  1019  		syncProgErr         error
  1020  		subSecs             uint64
  1021  		wantErr, wantSynced bool
  1022  		wantRatio           float32
  1023  	}{{
  1024  		name: "ok synced",
  1025  		syncProg: ethereum.SyncProgress{
  1026  			CurrentBlock: 25,
  1027  			HighestBlock: 25,
  1028  		},
  1029  		wantRatio:  1,
  1030  		wantSynced: true,
  1031  	}, {
  1032  		name: "ok header too old",
  1033  		syncProg: ethereum.SyncProgress{
  1034  			CurrentBlock: 25,
  1035  			HighestBlock: 25,
  1036  		},
  1037  		subSecs:    dexeth.MaxBlockInterval + 1,
  1038  		wantRatio:  0.999,
  1039  		wantSynced: false,
  1040  	}, {
  1041  		name: "sync progress error",
  1042  		syncProg: ethereum.SyncProgress{
  1043  			CurrentBlock: 25,
  1044  			HighestBlock: 0,
  1045  		},
  1046  		syncProgErr: errors.New("test error"),
  1047  		wantErr:     true,
  1048  	}}
  1049  
  1050  	for _, test := range tests {
  1051  		nowInSecs := uint64(time.Now().Unix())
  1052  		ctx, cancel := context.WithCancel(context.Background())
  1053  		node := &testNode{
  1054  			syncProg:    test.syncProg,
  1055  			syncProgT:   nowInSecs - test.subSecs,
  1056  			syncProgErr: test.syncProgErr,
  1057  		}
  1058  		eth := &baseWallet{
  1059  			node:          node,
  1060  			addr:          node.address(),
  1061  			ctx:           ctx,
  1062  			log:           tLogger,
  1063  			finalizeConfs: txConfsNeededToConfirm,
  1064  		}
  1065  		ss, err := eth.SyncStatus()
  1066  		cancel()
  1067  		if test.wantErr {
  1068  			if err == nil {
  1069  				t.Fatalf("expected error for test %q", test.name)
  1070  			}
  1071  			continue
  1072  		}
  1073  		if err != nil {
  1074  			t.Fatalf("unexpected error for test %q: %v", test.name, err)
  1075  		}
  1076  		if ss.Synced != test.wantSynced {
  1077  			t.Fatalf("want synced %v got %v for test %q", test.wantSynced, ss.Synced, test.name)
  1078  		}
  1079  		if ss.BlockProgress() != test.wantRatio {
  1080  			t.Fatalf("want ratio %v got %v for test %q", test.wantRatio, ss.BlockProgress(), test.name)
  1081  		}
  1082  	}
  1083  }
  1084  
  1085  func newTestNode(assetID uint32) *tMempoolNode {
  1086  	privKey, _ := crypto.HexToECDSA("9447129055a25c8496fca9e5ee1b9463e47e6043ff0c288d07169e8284860e34")
  1087  	addr := common.HexToAddress("2b84C791b79Ee37De042AD2ffF1A253c3ce9bc27")
  1088  	acct := &accounts.Account{
  1089  		Address: addr,
  1090  	}
  1091  
  1092  	tc := &tContractor{
  1093  		gasEstimates: ethGases,
  1094  		swapMap:      make(map[[32]byte]*dexeth.SwapState),
  1095  		valueIn:      make(map[common.Hash]uint64),
  1096  		valueOut:     make(map[common.Hash]uint64),
  1097  	}
  1098  
  1099  	var c contractor = tc
  1100  
  1101  	ttc := &tTokenContractor{
  1102  		tContractor: tc,
  1103  		allow:       new(big.Int),
  1104  	}
  1105  	if assetID != BipID {
  1106  		ttc.tContractor.gasEstimates = &tokenGases
  1107  		c = ttc
  1108  	}
  1109  
  1110  	return &tMempoolNode{
  1111  		testNode: &testNode{
  1112  			acct:            acct,
  1113  			addr:            acct.Address,
  1114  			maxFeeRate:      dexeth.GweiToWei(100),
  1115  			baseFee:         dexeth.GweiToWei(100),
  1116  			tip:             dexeth.GweiToWei(2),
  1117  			privKey:         privKey,
  1118  			contractor:      c,
  1119  			tContractor:     tc,
  1120  			tokenContractor: ttc,
  1121  			txConfirmations: make(map[common.Hash]uint32),
  1122  			txConfsErr:      make(map[common.Hash]error),
  1123  			receipts:        make(map[common.Hash]*types.Receipt),
  1124  			receiptErrs:     make(map[common.Hash]error),
  1125  			receiptTxs:      make(map[common.Hash]*types.Transaction),
  1126  		},
  1127  	}
  1128  }
  1129  
  1130  func tassetWallet(assetID uint32) (asset.Wallet, *assetWallet, *tMempoolNode, context.CancelFunc) {
  1131  	node := newTestNode(assetID)
  1132  	ctx, cancel := context.WithCancel(context.Background())
  1133  	var c contractor = node.tContractor
  1134  	if assetID != BipID {
  1135  		c = node.tokenContractor
  1136  	}
  1137  
  1138  	versionedGases := make(map[uint32]*dexeth.Gases)
  1139  	if assetID == BipID { // just make a copy
  1140  		for ver, g := range dexeth.VersionedGases {
  1141  			versionedGases[ver] = g
  1142  		}
  1143  	} else {
  1144  		netToken := dexeth.Tokens[assetID].NetTokens[dex.Simnet]
  1145  		for ver, c := range netToken.SwapContracts {
  1146  			versionedGases[ver] = &c.Gas
  1147  		}
  1148  	}
  1149  
  1150  	emitChan := make(chan asset.WalletNotification, 128)
  1151  	go func() {
  1152  		for {
  1153  			select {
  1154  			case <-ctx.Done():
  1155  				return
  1156  			case <-emitChan:
  1157  			}
  1158  		}
  1159  	}()
  1160  
  1161  	aw := &assetWallet{
  1162  		baseWallet: &baseWallet{
  1163  			baseChainID:      BipID,
  1164  			chainID:          dexeth.ChainIDs[dex.Simnet],
  1165  			tokens:           dexeth.Tokens,
  1166  			addr:             node.addr,
  1167  			net:              dex.Simnet,
  1168  			node:             node,
  1169  			ctx:              ctx,
  1170  			log:              tLogger,
  1171  			gasFeeLimitV:     defaultGasFeeLimit,
  1172  			pendingNonceAt:   new(big.Int),
  1173  			confirmedNonceAt: new(big.Int),
  1174  			pendingTxs:       make([]*extendedWalletTx, 0),
  1175  			txDB:             &tTxDB{},
  1176  			currentTip:       &types.Header{Number: new(big.Int)},
  1177  			finalizeConfs:    txConfsNeededToConfirm,
  1178  		},
  1179  		versionedGases:     versionedGases,
  1180  		maxSwapGas:         versionedGases[0].Swap,
  1181  		maxRedeemGas:       versionedGases[0].Redeem,
  1182  		log:                tLogger.SubLogger(strings.ToUpper(dex.BipIDSymbol(assetID))),
  1183  		assetID:            assetID,
  1184  		contractors:        map[uint32]contractor{0: c},
  1185  		findRedemptionReqs: make(map[[32]byte]*findRedemptionRequest),
  1186  		evmify:             dexeth.GweiToWei,
  1187  		atomize:            dexeth.WeiToGwei,
  1188  		pendingTxCheckBal:  new(big.Int),
  1189  		pendingApprovals:   make(map[uint32]*pendingApproval),
  1190  		approvalCache:      make(map[uint32]bool),
  1191  		// move up after review
  1192  		wi:   WalletInfo,
  1193  		emit: asset.NewWalletEmitter(emitChan, BipID, tLogger),
  1194  	}
  1195  	aw.wallets = map[uint32]*assetWallet{
  1196  		BipID: aw,
  1197  	}
  1198  
  1199  	var w asset.Wallet
  1200  	if assetID == BipID {
  1201  		w = &ETHWallet{assetWallet: aw}
  1202  		aw.wallets = map[uint32]*assetWallet{
  1203  			BipID: aw,
  1204  		}
  1205  	} else {
  1206  		node.tokenParent = &assetWallet{
  1207  			baseWallet:       aw.baseWallet,
  1208  			log:              tLogger.SubLogger("ETH"),
  1209  			contractors:      map[uint32]contractor{0: node.tContractor},
  1210  			assetID:          BipID,
  1211  			atomize:          dexeth.WeiToGwei,
  1212  			pendingApprovals: make(map[uint32]*pendingApproval),
  1213  			approvalCache:    make(map[uint32]bool),
  1214  			emit:             asset.NewWalletEmitter(emitChan, BipID, tLogger),
  1215  		}
  1216  		w = &TokenWallet{
  1217  			assetWallet: aw,
  1218  			cfg:         &tokenWalletConfig{},
  1219  			parent:      node.tokenParent,
  1220  			token:       dexeth.Tokens[usdcTokenID],
  1221  			netToken:    dexeth.Tokens[usdcTokenID].NetTokens[dex.Simnet],
  1222  		}
  1223  		aw.wallets = map[uint32]*assetWallet{
  1224  			usdcTokenID: aw,
  1225  			BipID:       node.tokenParent,
  1226  		}
  1227  	}
  1228  
  1229  	return w, aw, node, cancel
  1230  }
  1231  
  1232  func TestBalanceWithMempool(t *testing.T) {
  1233  	tinyBal := newBalance(0, 0, 0)
  1234  	tinyBal.Current = big.NewInt(dexeth.GweiFactor - 1)
  1235  
  1236  	tests := []struct {
  1237  		name         string
  1238  		bal          *Balance
  1239  		balErr       error
  1240  		wantErr      bool
  1241  		wantBal      uint64
  1242  		wantImmature uint64
  1243  		wantLocked   uint64
  1244  		token        bool
  1245  	}{{
  1246  		name:         "ok zero",
  1247  		bal:          newBalance(0, 0, 0),
  1248  		wantBal:      0,
  1249  		wantImmature: 0,
  1250  	}, {
  1251  		name:    "ok rounded down",
  1252  		bal:     tinyBal,
  1253  		wantBal: 0,
  1254  	}, {
  1255  		name:    "ok one",
  1256  		bal:     newBalance(1, 0, 0),
  1257  		wantBal: 1,
  1258  	}, {
  1259  		name:    "ok one token",
  1260  		bal:     newBalance(1, 0, 0),
  1261  		wantBal: 1,
  1262  		token:   true,
  1263  	}, {
  1264  		name:       "ok pending out",
  1265  		bal:        newBalance(4e8, 0, 1.4e8),
  1266  		wantBal:    2.6e8,
  1267  		wantLocked: 0,
  1268  	}, {
  1269  		name:       "ok pending out token",
  1270  		bal:        newBalance(4e8, 0, 1.4e8),
  1271  		wantBal:    2.6e8,
  1272  		wantLocked: 0,
  1273  		token:      true,
  1274  	}, {
  1275  		name:         "ok pending in",
  1276  		bal:          newBalance(1e8, 3e8, 0),
  1277  		wantBal:      1e8,
  1278  		wantImmature: 3e8,
  1279  	}, {
  1280  		name:         "ok pending in token",
  1281  		bal:          newBalance(1e8, 3e8, 0),
  1282  		wantBal:      1e8,
  1283  		wantImmature: 3e8,
  1284  		token:        true,
  1285  	}, {
  1286  		name:         "ok pending out and in",
  1287  		bal:          newBalance(4e8, 2e8, 1e8),
  1288  		wantBal:      3e8,
  1289  		wantLocked:   0,
  1290  		wantImmature: 2e8,
  1291  	}, {
  1292  		name:         "ok pending out and in token",
  1293  		bal:          newBalance(4e8, 2e8, 1e8),
  1294  		wantBal:      3e8,
  1295  		wantLocked:   0,
  1296  		wantImmature: 2e8,
  1297  		token:        true,
  1298  	}, {
  1299  		// 	name:         "ok max int",
  1300  		// 	bal:          maxWei,
  1301  		// 	pendingTxs:   []*types.Transaction{},
  1302  		// 	wantBal:      maxInt,
  1303  		// 	wantImmature: 0,
  1304  		// }, {
  1305  		// 	name:       "over max int",
  1306  		// 	bal:        overMaxWei,
  1307  		// 	pendingTxs: []*types.Transaction{},
  1308  		// 	wantErr:    true,
  1309  		// }, {
  1310  		name:    "node balance error",
  1311  		bal:     newBalance(0, 0, 0),
  1312  		balErr:  errors.New("test error"),
  1313  		wantErr: true,
  1314  	}}
  1315  
  1316  	for _, test := range tests {
  1317  		var assetID uint32 = BipID
  1318  		if test.token {
  1319  			assetID = usdcTokenID
  1320  		}
  1321  
  1322  		_, eth, node, shutdown := tassetWallet(assetID)
  1323  		defer shutdown()
  1324  
  1325  		if test.token {
  1326  			node.tokenContractor.bal = test.bal.Current
  1327  			node.tokenContractor.balErr = test.balErr
  1328  		} else {
  1329  			node.bal = test.bal.Current
  1330  			node.balErr = test.balErr
  1331  		}
  1332  
  1333  		var nonce uint64
  1334  		newTx := func(value *big.Int) *types.Transaction {
  1335  			nonce++
  1336  			return node.newTransaction(nonce, value)
  1337  		}
  1338  
  1339  		if test.bal.PendingIn.Cmp(new(big.Int)) > 0 {
  1340  			tx := newTx(new(big.Int))
  1341  			node.pendingTxs = append(node.pendingTxs, tx)
  1342  			node.tContractor.valueIn[tx.Hash()] = dexeth.WeiToGwei(test.bal.PendingIn)
  1343  		}
  1344  		if test.bal.PendingOut.Cmp(new(big.Int)) > 0 {
  1345  			if test.token {
  1346  				tx := newTx(nil)
  1347  				node.pendingTxs = append(node.pendingTxs, tx)
  1348  				node.tContractor.valueOut[tx.Hash()] = dexeth.WeiToGwei(test.bal.PendingOut)
  1349  			} else {
  1350  				node.pendingTxs = append(node.pendingTxs, newTx(test.bal.PendingOut))
  1351  			}
  1352  		}
  1353  
  1354  		bal, err := eth.Balance()
  1355  		if test.wantErr {
  1356  			if err == nil {
  1357  				t.Fatalf("expected error for test %q", test.name)
  1358  			}
  1359  			continue
  1360  		}
  1361  		if err != nil {
  1362  			t.Fatalf("unexpected error for test %q: %v", test.name, err)
  1363  		}
  1364  		if bal.Available != test.wantBal {
  1365  			t.Fatalf("want available balance %v got %v for test %q", test.wantBal, bal.Available, test.name)
  1366  		}
  1367  		if bal.Immature != test.wantImmature {
  1368  			t.Fatalf("want immature balance %v got %v for test %q", test.wantImmature, bal.Immature, test.name)
  1369  		}
  1370  		if bal.Locked != test.wantLocked {
  1371  			t.Fatalf("want locked balance %v got %v for test %q", test.wantLocked, bal.Locked, test.name)
  1372  		}
  1373  	}
  1374  }
  1375  
  1376  func TestBalanceNoMempool(t *testing.T) {
  1377  	var nonceCounter int64
  1378  
  1379  	newExtendedWalletTx := func(assetID uint32, amt, maxFees, blockNum uint64, txType asset.TransactionType) *extendedWalletTx {
  1380  		var tokenID *uint32
  1381  		if assetID != BipID {
  1382  			tokenID = &assetID
  1383  		}
  1384  		txHash := common.BytesToHash(encode.RandomBytes(32))
  1385  
  1386  		et := &extendedWalletTx{
  1387  			WalletTransaction: &asset.WalletTransaction{
  1388  				ID:          txHash.String(),
  1389  				Type:        txType,
  1390  				Amount:      amt,
  1391  				BlockNumber: blockNum,
  1392  				TokenID:     tokenID,
  1393  				Fees:        maxFees,
  1394  			},
  1395  			Nonce:  big.NewInt(nonceCounter),
  1396  			txHash: txHash,
  1397  		}
  1398  		nonceCounter++
  1399  
  1400  		return et
  1401  	}
  1402  
  1403  	tests := []struct {
  1404  		name           string
  1405  		assetID        uint32
  1406  		unconfirmedTxs []*extendedWalletTx
  1407  		expPendingIn   uint64
  1408  		expPendingOut  uint64
  1409  	}{
  1410  		{
  1411  			name:    "single eth tx",
  1412  			assetID: BipID,
  1413  			unconfirmedTxs: []*extendedWalletTx{
  1414  				newExtendedWalletTx(BipID, 1, 2, 0, asset.Send),
  1415  			},
  1416  			expPendingOut: 3,
  1417  		},
  1418  		{
  1419  			name:    "eth with token fees",
  1420  			assetID: BipID,
  1421  			unconfirmedTxs: []*extendedWalletTx{
  1422  				newExtendedWalletTx(usdcTokenID, 4, 5, 0, asset.Send),
  1423  			},
  1424  			expPendingOut: 5,
  1425  		},
  1426  		{
  1427  			name:    "token with 1 tx and other ignored assets",
  1428  			assetID: usdcTokenID,
  1429  			unconfirmedTxs: []*extendedWalletTx{
  1430  				newExtendedWalletTx(usdcTokenID, 4, 5, 0, asset.Send),
  1431  				newExtendedWalletTx(usdcTokenID+1, 8, 9, 0, asset.Send),
  1432  			},
  1433  			expPendingOut: 4,
  1434  		},
  1435  		{
  1436  			name:    "token with 1 tx incoming",
  1437  			assetID: usdcTokenID,
  1438  			unconfirmedTxs: []*extendedWalletTx{
  1439  				newExtendedWalletTx(usdcTokenID, 15, 5, 0, asset.Redeem),
  1440  			},
  1441  			expPendingIn: 15,
  1442  		},
  1443  		{
  1444  			name:    "eth mixed txs",
  1445  			assetID: BipID,
  1446  			unconfirmedTxs: []*extendedWalletTx{
  1447  				newExtendedWalletTx(BipID, 1, 2, 0, asset.Swap),       // 3 eth out
  1448  				newExtendedWalletTx(usdcTokenID, 3, 4, 1, asset.Send), // confirmed
  1449  				newExtendedWalletTx(usdcTokenID, 5, 6, 0, asset.Swap), // 6 eth out
  1450  				newExtendedWalletTx(BipID, 7, 1, 0, asset.Refund),     // 1 eth out, 7 eth in
  1451  			},
  1452  			expPendingOut: 10,
  1453  			expPendingIn:  7,
  1454  		},
  1455  		{
  1456  			name:    "already confirmed, but still waiting for txConfsNeededToConfirm",
  1457  			assetID: usdcTokenID,
  1458  			unconfirmedTxs: []*extendedWalletTx{
  1459  				newExtendedWalletTx(usdcTokenID, 15, 5, 1, asset.Redeem),
  1460  			},
  1461  		},
  1462  	}
  1463  
  1464  	for _, tt := range tests {
  1465  		t.Run(tt.name, func(t *testing.T) {
  1466  			_, eth, node, shutdown := tassetWallet(tt.assetID)
  1467  			defer shutdown()
  1468  			eth.node = node.testNode // no mempool
  1469  			node.bal = unlimitedAllowance
  1470  			node.tokenContractor.bal = unlimitedAllowance
  1471  			eth.connected.Store(true)
  1472  
  1473  			eth.pendingTxs = tt.unconfirmedTxs
  1474  
  1475  			bal, err := eth.balanceWithTxPool()
  1476  			if err != nil {
  1477  				t.Fatalf("balanceWithTxPool error: %v", err)
  1478  			}
  1479  
  1480  			if in := dexeth.WeiToGwei(bal.PendingIn); in != tt.expPendingIn {
  1481  				t.Fatalf("wrong PendingIn. wanted %d, got %d", tt.expPendingIn, in)
  1482  			}
  1483  
  1484  			if out := dexeth.WeiToGwei(bal.PendingOut); out != tt.expPendingOut {
  1485  				t.Fatalf("wrong PendingOut. wanted %d, got %d", tt.expPendingOut, out)
  1486  			}
  1487  		})
  1488  	}
  1489  }
  1490  
  1491  func TestFeeRate(t *testing.T) {
  1492  	ctx, cancel := context.WithCancel(context.Background())
  1493  	defer cancel()
  1494  	node := &testNode{}
  1495  	eth := &baseWallet{
  1496  		node:          node,
  1497  		ctx:           ctx,
  1498  		log:           tLogger,
  1499  		finalizeConfs: txConfsNeededToConfirm,
  1500  		currentTip:    &types.Header{Number: big.NewInt(100)},
  1501  	}
  1502  
  1503  	maxInt := ^int64(0)
  1504  	tests := []struct {
  1505  		name           string
  1506  		baseFee        *big.Int
  1507  		tip            *big.Int
  1508  		netFeeStateErr error
  1509  		wantFeeRate    uint64
  1510  	}{
  1511  		{
  1512  			name:        "ok",
  1513  			baseFee:     big.NewInt(100e9),
  1514  			tip:         big.NewInt(2e9),
  1515  			wantFeeRate: 202,
  1516  		},
  1517  		{
  1518  			name:           "net fee state error",
  1519  			netFeeStateErr: errors.New("test error"),
  1520  		},
  1521  		{
  1522  			name:    "overflow error",
  1523  			baseFee: big.NewInt(maxInt),
  1524  			tip:     big.NewInt(1),
  1525  		},
  1526  	}
  1527  
  1528  	for _, test := range tests {
  1529  		eth.currentFees.blockNum = 0
  1530  		node.baseFee = test.baseFee
  1531  		node.tip = test.tip
  1532  		node.netFeeStateErr = test.netFeeStateErr
  1533  		feeRate := eth.FeeRate()
  1534  		if feeRate != test.wantFeeRate {
  1535  			t.Fatalf("%v: expected fee rate %d but got %d", test.name, test.wantFeeRate, feeRate)
  1536  		}
  1537  	}
  1538  }
  1539  
  1540  func TestRefund(t *testing.T) {
  1541  	t.Run("eth", func(t *testing.T) { testRefund(t, BipID) })
  1542  	t.Run("token", func(t *testing.T) { testRefund(t, usdcTokenID) })
  1543  }
  1544  
  1545  func testRefund(t *testing.T, assetID uint32) {
  1546  	_, eth, node, shutdown := tassetWallet(assetID)
  1547  	defer shutdown()
  1548  
  1549  	const feeSuggestion = 100
  1550  	const gweiBal = 1e9
  1551  	const ogRefundReserves = 1e8
  1552  
  1553  	v1Contractor := &tContractor{
  1554  		swapMap:      make(map[[32]byte]*dexeth.SwapState, 1),
  1555  		gasEstimates: ethGases,
  1556  		redeemTx:     types.NewTx(&types.DynamicFeeTx{}),
  1557  	}
  1558  	var v1c contractor = v1Contractor
  1559  
  1560  	gasesV1 := &dexeth.Gases{Refund: 1e5}
  1561  	if assetID == BipID {
  1562  		eth.versionedGases[1] = gasesV1
  1563  	} else {
  1564  		eth.versionedGases[1] = &dexeth.Tokens[usdcTokenID].NetTokens[dex.Simnet].SwapContracts[0].Gas
  1565  		v1c = &tTokenContractor{tContractor: v1Contractor}
  1566  	}
  1567  
  1568  	eth.contractors[1] = v1c
  1569  
  1570  	var secretHash [32]byte
  1571  	copy(secretHash[:], encode.RandomBytes(32))
  1572  	v0Contract := dexeth.EncodeContractData(0, secretHash)
  1573  	ss := &dexeth.SwapState{Value: dexeth.GweiToWei(1)}
  1574  	v0Contractor := node.tContractor
  1575  
  1576  	v0Contractor.swapMap[secretHash] = ss
  1577  	v1Contractor.swapMap[secretHash] = ss
  1578  
  1579  	tests := []struct {
  1580  		name            string
  1581  		badContract     bool
  1582  		isRefundable    bool
  1583  		isRefundableErr error
  1584  		refundErr       error
  1585  		wantLocked      uint64
  1586  		wantErr         bool
  1587  		wantZeroHash    bool
  1588  		swapStep        dexeth.SwapStep
  1589  		swapErr         error
  1590  		useV1Gases      bool
  1591  	}{
  1592  		{
  1593  			name:         "ok",
  1594  			swapStep:     dexeth.SSInitiated,
  1595  			isRefundable: true,
  1596  			wantLocked:   ogRefundReserves - feeSuggestion*dexeth.RefundGas(0),
  1597  		},
  1598  		{
  1599  			name:         "ok v1",
  1600  			swapStep:     dexeth.SSInitiated,
  1601  			isRefundable: true,
  1602  			wantLocked:   ogRefundReserves - feeSuggestion*dexeth.RefundGas(1),
  1603  			useV1Gases:   true,
  1604  		},
  1605  		{
  1606  			name:         "ok refunded",
  1607  			swapStep:     dexeth.SSRefunded,
  1608  			isRefundable: true,
  1609  			wantLocked:   ogRefundReserves - feeSuggestion*dexeth.RefundGas(0),
  1610  			wantZeroHash: true,
  1611  		},
  1612  		{
  1613  			name:     "swap error",
  1614  			swapStep: dexeth.SSInitiated,
  1615  			swapErr:  errors.New("test error"),
  1616  			wantErr:  true,
  1617  		},
  1618  		{
  1619  			name:            "is refundable error",
  1620  			isRefundable:    true,
  1621  			isRefundableErr: errors.New("test error"),
  1622  			wantErr:         true,
  1623  		},
  1624  		{
  1625  			name:         "is refundable false",
  1626  			isRefundable: false,
  1627  			wantErr:      true,
  1628  		},
  1629  		{
  1630  			name:         "refund error",
  1631  			isRefundable: true,
  1632  			refundErr:    errors.New("test error"),
  1633  			wantErr:      true,
  1634  		},
  1635  		{
  1636  			name:         "cannot decode contract",
  1637  			badContract:  true,
  1638  			isRefundable: true,
  1639  			wantErr:      true,
  1640  		},
  1641  	}
  1642  
  1643  	for _, test := range tests {
  1644  		contract := v0Contract
  1645  		c := v0Contractor
  1646  		if test.useV1Gases {
  1647  			contract = dexeth.EncodeContractData(1, secretHash)
  1648  			c = v1Contractor
  1649  		} else if test.badContract {
  1650  			contract = []byte{}
  1651  		}
  1652  
  1653  		c.refundable = test.isRefundable
  1654  		c.refundableErr = test.isRefundableErr
  1655  		c.refundErr = test.refundErr
  1656  		node.bal = dexeth.GweiToWei(gweiBal)
  1657  		c.swapErr = test.swapErr
  1658  		ss.State = test.swapStep
  1659  		eth.lockedFunds.refundReserves = ogRefundReserves
  1660  
  1661  		var txHash common.Hash
  1662  		if test.isRefundable {
  1663  			tx := types.NewTx(&types.DynamicFeeTx{})
  1664  			txHash = tx.Hash()
  1665  			c.refundTx = tx
  1666  		}
  1667  
  1668  		refundID, err := eth.Refund(nil, contract, feeSuggestion)
  1669  
  1670  		if test.wantErr {
  1671  			if err == nil {
  1672  				t.Fatalf(`%v: expected error but did not get: %v`, test.name, err)
  1673  			}
  1674  			continue
  1675  		}
  1676  		if err != nil {
  1677  			t.Fatalf(`%v: unexpected error: %v`, test.name, err)
  1678  		}
  1679  
  1680  		if test.wantZeroHash {
  1681  			// No on chain refund expected if status was already refunded.
  1682  			zeroHash := common.Hash{}
  1683  			if !bytes.Equal(refundID, zeroHash[:]) {
  1684  				t.Fatalf(`%v: expected refund tx hash: %x = returned id: %s`, test.name, zeroHash, refundID)
  1685  			}
  1686  		} else {
  1687  			if !bytes.Equal(refundID, txHash[:]) {
  1688  				t.Fatalf(`%v: expected refund tx hash: %x = returned id: %s`, test.name, txHash, refundID)
  1689  			}
  1690  
  1691  			if secretHash != c.lastRefund.secretHash {
  1692  				t.Fatalf(`%v: secret hash in contract %x != used to call refund %x`,
  1693  					test.name, secretHash, c.lastRefund.secretHash)
  1694  			}
  1695  
  1696  			if dexeth.GweiToWei(feeSuggestion).Cmp(c.lastRefund.maxFeeRate) != 0 {
  1697  				t.Fatalf(`%v: fee suggestion %v != used to call refund %v`,
  1698  					test.name, dexeth.GweiToWei(feeSuggestion), c.lastRefund.maxFeeRate)
  1699  			}
  1700  		}
  1701  	}
  1702  }
  1703  
  1704  // badCoin fulfills the asset.Coin interface, but the ID does not match the ID expected in the
  1705  // ETH wallet code.
  1706  type badCoin uint64
  1707  
  1708  func (*badCoin) ID() dex.Bytes {
  1709  	return []byte{123}
  1710  }
  1711  func (*badCoin) String() string {
  1712  	return "abc"
  1713  }
  1714  func (*badCoin) TxID() string {
  1715  	return "abc"
  1716  }
  1717  func (b *badCoin) Value() uint64 {
  1718  	return uint64(*b)
  1719  }
  1720  
  1721  func TestFundOrderReturnCoinsFundingCoins(t *testing.T) {
  1722  	t.Run("eth", func(t *testing.T) { testFundOrderReturnCoinsFundingCoins(t, BipID) })
  1723  	t.Run("token", func(t *testing.T) { testFundOrderReturnCoinsFundingCoins(t, usdcTokenID) })
  1724  }
  1725  
  1726  func testFundOrderReturnCoinsFundingCoins(t *testing.T, assetID uint32) {
  1727  	w, eth, node, shutdown := tassetWallet(assetID)
  1728  	defer shutdown()
  1729  	walletBalanceGwei := uint64(dexeth.GweiFactor)
  1730  	fromAsset := tETH
  1731  	if assetID == BipID {
  1732  		node.bal = dexeth.GweiToWei(walletBalanceGwei)
  1733  	} else {
  1734  		fromAsset = tToken
  1735  		node.tokenContractor.bal = dexeth.GweiToWei(walletBalanceGwei)
  1736  		node.tokenContractor.allow = unlimitedAllowance
  1737  		node.tokenParent.node.(*tMempoolNode).bal = dexeth.GweiToWei(walletBalanceGwei)
  1738  	}
  1739  
  1740  	checkBalance := func(wallet *assetWallet, expectedAvailable, expectedLocked uint64, testName string) {
  1741  		t.Helper()
  1742  		balance, err := wallet.Balance()
  1743  		if err != nil {
  1744  			t.Fatalf("%v: unexpected error %v", testName, err)
  1745  		}
  1746  		if balance.Available != expectedAvailable {
  1747  			t.Fatalf("%v: expected %v funds to be available but got %v", testName, expectedAvailable, balance.Available)
  1748  		}
  1749  		if balance.Locked != expectedLocked {
  1750  			t.Fatalf("%v: expected %v funds to be locked but got %v", testName, expectedLocked, balance.Locked)
  1751  		}
  1752  	}
  1753  
  1754  	type fundOrderTest struct {
  1755  		testName    string
  1756  		wantErr     bool
  1757  		coinValue   uint64
  1758  		coinAddress string
  1759  	}
  1760  	checkFundOrderResult := func(coins asset.Coins, redeemScripts []dex.Bytes, err error, test fundOrderTest) {
  1761  		t.Helper()
  1762  		if test.wantErr && err == nil {
  1763  			t.Fatalf("%v: expected error but didn't get", test.testName)
  1764  		}
  1765  		if test.wantErr {
  1766  			return
  1767  		}
  1768  		if err != nil {
  1769  			t.Fatalf("%s: unexpected error: %v", test.testName, err)
  1770  		}
  1771  		if len(coins) != 1 {
  1772  			t.Fatalf("%s: expected 1 coins but got %v", test.testName, len(coins))
  1773  		}
  1774  		if len(redeemScripts) != 1 {
  1775  			t.Fatalf("%s: expected 1 redeem script but got %v", test.testName, len(redeemScripts))
  1776  		}
  1777  		rc, is := coins[0].(asset.RecoveryCoin)
  1778  		if !is {
  1779  			t.Fatalf("%s: funding coin is not a RecoveryCoin", test.testName)
  1780  		}
  1781  
  1782  		if assetID == BipID {
  1783  			_, err = decodeFundingCoin(rc.RecoveryID())
  1784  		} else {
  1785  			_, err = decodeTokenFundingCoin(rc.RecoveryID())
  1786  		}
  1787  		if err != nil {
  1788  			t.Fatalf("%s: unexpected error: %v", test.testName, err)
  1789  		}
  1790  		if coins[0].Value() != test.coinValue {
  1791  			t.Fatalf("%s: wrong value. expected %v but got %v", test.testName, test.coinValue, coins[0].Value())
  1792  		}
  1793  	}
  1794  
  1795  	order := asset.Order{
  1796  		Version:       fromAsset.Version,
  1797  		Value:         walletBalanceGwei / 2,
  1798  		MaxSwapCount:  2,
  1799  		MaxFeeRate:    fromAsset.MaxFeeRate,
  1800  		RedeemVersion: tBTC.Version, // not important if not a token
  1801  		RedeemAssetID: tBTC.ID,
  1802  	}
  1803  
  1804  	// Test fund order with less than available funds
  1805  	coins1, redeemScripts1, _, err := w.FundOrder(&order)
  1806  	expectedOrderFees := eth.gases(fromAsset.Version).Swap * order.MaxFeeRate * order.MaxSwapCount
  1807  	expectedFees := expectedOrderFees
  1808  	expectedCoinValue := order.Value
  1809  	if assetID == BipID {
  1810  		expectedCoinValue += expectedOrderFees
  1811  	}
  1812  
  1813  	checkFundOrderResult(coins1, redeemScripts1, err, fundOrderTest{
  1814  		testName:    "more than enough",
  1815  		coinValue:   expectedCoinValue,
  1816  		coinAddress: node.addr.String(),
  1817  	})
  1818  	checkBalance(eth, walletBalanceGwei-expectedCoinValue, expectedCoinValue, "more than enough")
  1819  
  1820  	// Test fund order with 1 more than available funds
  1821  	order.Value = walletBalanceGwei - expectedCoinValue + 1
  1822  	if assetID == BipID {
  1823  		order.Value -= expectedOrderFees
  1824  	}
  1825  	coins, redeemScripts, _, err := w.FundOrder(&order)
  1826  	checkFundOrderResult(coins, redeemScripts, err, fundOrderTest{
  1827  		testName: "not enough",
  1828  		wantErr:  true,
  1829  	})
  1830  	checkBalance(eth, walletBalanceGwei-expectedCoinValue, expectedCoinValue, "not enough")
  1831  
  1832  	// Test fund order with funds equal to available
  1833  	order.Value = order.Value - 1
  1834  	expVal := order.Value
  1835  	if assetID == BipID {
  1836  		expVal += expectedFees
  1837  	}
  1838  	coins2, redeemScripts2, _, err := w.FundOrder(&order)
  1839  	checkFundOrderResult(coins2, redeemScripts2, err, fundOrderTest{
  1840  		testName:    "just enough",
  1841  		coinValue:   expVal,
  1842  		coinAddress: node.addr.String(),
  1843  	})
  1844  	checkBalance(eth, 0, walletBalanceGwei, "just enough")
  1845  
  1846  	// Test returning funds > locked returns locked funds to 0
  1847  	err = w.ReturnCoins([]asset.Coin{coins1[0], coins1[0]})
  1848  	if err != nil {
  1849  		t.Fatalf("unexpected error: %v", err)
  1850  	}
  1851  	checkBalance(eth, walletBalanceGwei, 0, "after return too much")
  1852  
  1853  	// Fund order with funds equal to available
  1854  	order.Value = walletBalanceGwei
  1855  	if assetID == BipID {
  1856  		order.Value -= expectedOrderFees
  1857  	}
  1858  	_, _, _, err = w.FundOrder(&order)
  1859  	if err != nil {
  1860  		t.Fatalf("unexpected error: %v", err)
  1861  	}
  1862  	checkBalance(eth, 0, walletBalanceGwei, "just enough 2")
  1863  
  1864  	// Test returning coin with invalid ID
  1865  	var badCoin badCoin
  1866  	err = w.ReturnCoins([]asset.Coin{&badCoin})
  1867  	if err == nil {
  1868  		t.Fatalf("expected error but did not get")
  1869  	}
  1870  
  1871  	// Test returning correct coins returns all funds
  1872  	err = w.ReturnCoins([]asset.Coin{coins1[0], coins2[0]})
  1873  	if err != nil {
  1874  		t.Fatalf("unexpected error: %v", err)
  1875  	}
  1876  	checkBalance(eth, walletBalanceGwei, 0, "returned correct amount")
  1877  
  1878  	node.setBalanceError(eth, errors.New("test error"))
  1879  	_, _, _, err = w.FundOrder(&order)
  1880  	if err == nil {
  1881  		t.Fatalf("balance error should cause error but did not")
  1882  	}
  1883  	node.setBalanceError(eth, nil)
  1884  
  1885  	// Test that funding without allowance causes error
  1886  	if assetID != BipID {
  1887  		eth.approvalCache = make(map[uint32]bool)
  1888  		node.tokenContractor.allow = big.NewInt(0)
  1889  		_, _, _, err = w.FundOrder(&order)
  1890  		if err == nil {
  1891  			t.Fatalf("no allowance should cause error but did not")
  1892  		}
  1893  		node.tokenContractor.allow = unlimitedAllowance
  1894  	}
  1895  
  1896  	// Test eth wallet gas fee limit > server MaxFeeRate causes error
  1897  	tmpGasFeeLimit := eth.gasFeeLimit()
  1898  	eth.gasFeeLimitV = order.MaxFeeRate - 1
  1899  	_, _, _, err = w.FundOrder(&order)
  1900  	if err == nil {
  1901  		t.Fatalf("eth wallet gas fee limit > server MaxFeeRate should cause error")
  1902  	}
  1903  	eth.gasFeeLimitV = tmpGasFeeLimit
  1904  
  1905  	w2, eth2, _, shutdown2 := tassetWallet(assetID)
  1906  	defer shutdown2()
  1907  	eth2.node = node
  1908  	eth2.contractors[0] = node.tokenContractor
  1909  	node.tokenContractor.bal = dexeth.GweiToWei(walletBalanceGwei)
  1910  
  1911  	// Test reloading coins from first order
  1912  	coinVal := coins1[0].Value()
  1913  	coins, err = w2.FundingCoins([]dex.Bytes{parseRecoveryID(coins1[0])})
  1914  	if err != nil {
  1915  		t.Fatalf("unexpected error: %v", err)
  1916  	}
  1917  	if len(coins) != 1 {
  1918  		t.Fatalf("expected 1 coins but got %v", len(coins))
  1919  	}
  1920  	if coins[0].Value() != coinVal {
  1921  		t.Fatalf("funding coin value %v != expected %v", coins[0].Value(), coins[1].Value())
  1922  	}
  1923  	checkBalance(eth2, walletBalanceGwei-coinVal, coinVal, "funding1")
  1924  
  1925  	// Test reloading more coins than are available in balance
  1926  	rid := parseRecoveryID(coins1[0])
  1927  	if assetID != BipID {
  1928  		rid = createTokenFundingCoin(node.addr, coinVal+1, 1).RecoveryID()
  1929  	}
  1930  	_, err = w2.FundingCoins([]dex.Bytes{rid})
  1931  	if err == nil {
  1932  		t.Fatalf("expected error but didn't get one")
  1933  	}
  1934  	checkBalance(eth2, walletBalanceGwei-coinVal, coinVal, "after funding error 1")
  1935  
  1936  	// Test funding coins with bad coin ID
  1937  
  1938  	_, err = w2.FundingCoins([]dex.Bytes{append(parseRecoveryID(coins1[0]), 0x0a)})
  1939  	if err == nil {
  1940  		t.Fatalf("expected error but did not get")
  1941  	}
  1942  	checkBalance(eth2, walletBalanceGwei-coinVal, coinVal, "after funding error 3")
  1943  
  1944  	// Test funding coins with coin from different address
  1945  	var differentAddress [20]byte
  1946  	decodedHex, _ := hex.DecodeString("8d83B207674bfd53B418a6E47DA148F5bFeCc652")
  1947  	copy(differentAddress[:], decodedHex)
  1948  	var nonce [8]byte
  1949  	copy(nonce[:], encode.RandomBytes(8))
  1950  
  1951  	differentKindaCoin := (&coin{
  1952  		id: randomHash(), // e.g. tx hash
  1953  	})
  1954  	_, err = w2.FundingCoins([]dex.Bytes{differentKindaCoin.ID()})
  1955  	if err == nil {
  1956  		t.Fatalf("expected error for unknown coin id format, but did not get")
  1957  	}
  1958  
  1959  	differentAddressCoin := createFundingCoin(differentAddress, 100000)
  1960  	_, err = w2.FundingCoins([]dex.Bytes{differentAddressCoin.ID()})
  1961  	if err == nil {
  1962  		t.Fatalf("expected error for wrong address, but did not get")
  1963  	}
  1964  	checkBalance(eth2, walletBalanceGwei-coinVal, coinVal, "after funding error 4")
  1965  
  1966  	// Test funding coins with balance error
  1967  	node.balErr = errors.New("test error")
  1968  	_, err = w2.FundingCoins([]dex.Bytes{badCoin.ID()})
  1969  	if err == nil {
  1970  		t.Fatalf("expected error but did not get")
  1971  	}
  1972  	node.balErr = nil
  1973  	checkBalance(eth2, walletBalanceGwei-coinVal, coinVal, "after funding error 5")
  1974  
  1975  	// Reloading coins from second order
  1976  	coins, err = w2.FundingCoins([]dex.Bytes{parseRecoveryID(coins2[0])})
  1977  	if err != nil {
  1978  		t.Fatalf("unexpected error: %v", err)
  1979  	}
  1980  	if len(coins) != 1 {
  1981  		t.Fatalf("expected 1 coins but got %v", len(coins))
  1982  	}
  1983  	if coins[0].Value() != coins2[0].Value() {
  1984  		t.Fatalf("funding coin value %v != expected %v", coins[0].Value(), coins[1].Value())
  1985  	}
  1986  	checkBalance(eth2, 0, walletBalanceGwei, "funding2")
  1987  
  1988  	// return coin with wrong kind and incorrect address
  1989  	err = w2.ReturnCoins([]asset.Coin{differentKindaCoin})
  1990  	if err == nil {
  1991  		t.Fatalf("expected error for unknown coin ID format, but did not get")
  1992  	}
  1993  
  1994  	err = w2.ReturnCoins([]asset.Coin{differentAddressCoin})
  1995  	if err == nil {
  1996  		t.Fatalf("expected error for wrong address, but did not get")
  1997  	}
  1998  
  1999  	// return all coins
  2000  	err = w2.ReturnCoins([]asset.Coin{coins1[0], coins2[0]})
  2001  	if err != nil {
  2002  		t.Fatalf("unexpected error")
  2003  	}
  2004  	checkBalance(eth2, walletBalanceGwei, 0, "return coins after funding")
  2005  
  2006  	// Test funding coins with two coins at the same time
  2007  	_, err = w2.FundingCoins([]dex.Bytes{parseRecoveryID(coins1[0]), parseRecoveryID(coins2[0])})
  2008  	if err != nil {
  2009  		t.Fatalf("unexpected error: %v", err)
  2010  	}
  2011  	checkBalance(eth2, 0, walletBalanceGwei, "funding3")
  2012  }
  2013  
  2014  func TestFundMultiOrder(t *testing.T) {
  2015  	t.Run("eth", func(t *testing.T) { testFundMultiOrder(t, BipID) })
  2016  	t.Run("token", func(t *testing.T) { testFundMultiOrder(t, usdcTokenID) })
  2017  }
  2018  
  2019  func testFundMultiOrder(t *testing.T, assetID uint32) {
  2020  	w, eth, node, shutdown := tassetWallet(assetID)
  2021  
  2022  	defer shutdown()
  2023  
  2024  	fromAsset := tETH
  2025  	swapGas := dexeth.VersionedGases[fromAsset.Version].Swap
  2026  	if assetID != BipID {
  2027  		fromAsset = tToken
  2028  		node.tokenContractor.allow = unlimitedAllowance
  2029  		swapGas = dexeth.Tokens[usdcTokenID].NetTokens[dex.Simnet].
  2030  			SwapContracts[fromAsset.Version].Gas.Swap
  2031  	}
  2032  
  2033  	type test struct {
  2034  		name       string
  2035  		multiOrder *asset.MultiOrder
  2036  		maxLock    uint64
  2037  		bal        uint64
  2038  		tokenBal   uint64
  2039  		parentBal  uint64
  2040  
  2041  		ethOnly   bool
  2042  		tokenOnly bool
  2043  
  2044  		expectErr bool
  2045  	}
  2046  
  2047  	tests := []test{
  2048  		{
  2049  			name:      "ok",
  2050  			bal:       uint64(dexeth.GweiFactor) + swapGas*4*fromAsset.MaxFeeRate,
  2051  			tokenBal:  uint64(dexeth.GweiFactor),
  2052  			parentBal: uint64(dexeth.GweiFactor),
  2053  			multiOrder: &asset.MultiOrder{
  2054  				Version:    fromAsset.Version,
  2055  				MaxFeeRate: fromAsset.MaxFeeRate,
  2056  				Values: []*asset.MultiOrderValue{
  2057  					{
  2058  						Value:        uint64(dexeth.GweiFactor) / 2,
  2059  						MaxSwapCount: 2,
  2060  					},
  2061  					{
  2062  						Value:        uint64(dexeth.GweiFactor) / 2,
  2063  						MaxSwapCount: 2,
  2064  					},
  2065  				},
  2066  			},
  2067  		},
  2068  		{
  2069  			name:      "maxLock just enough, eth",
  2070  			bal:       uint64(dexeth.GweiFactor) + swapGas*4*fromAsset.MaxFeeRate,
  2071  			tokenBal:  uint64(dexeth.GweiFactor),
  2072  			parentBal: uint64(dexeth.GweiFactor),
  2073  			multiOrder: &asset.MultiOrder{
  2074  				Version:    fromAsset.Version,
  2075  				MaxFeeRate: fromAsset.MaxFeeRate,
  2076  				Values: []*asset.MultiOrderValue{
  2077  					{
  2078  						Value:        uint64(dexeth.GweiFactor) / 2,
  2079  						MaxSwapCount: 2,
  2080  					},
  2081  					{
  2082  						Value:        uint64(dexeth.GweiFactor) / 2,
  2083  						MaxSwapCount: 2,
  2084  					},
  2085  				},
  2086  			},
  2087  			maxLock: uint64(dexeth.GweiFactor) + swapGas*4*fromAsset.MaxFeeRate,
  2088  		},
  2089  		{
  2090  			name:      "maxLock not enough, eth",
  2091  			ethOnly:   true,
  2092  			bal:       uint64(dexeth.GweiFactor) + swapGas*4*fromAsset.MaxFeeRate,
  2093  			tokenBal:  uint64(dexeth.GweiFactor),
  2094  			parentBal: uint64(dexeth.GweiFactor),
  2095  			multiOrder: &asset.MultiOrder{
  2096  				Version:    fromAsset.Version,
  2097  				MaxFeeRate: fromAsset.MaxFeeRate,
  2098  				Values: []*asset.MultiOrderValue{
  2099  					{
  2100  						Value:        uint64(dexeth.GweiFactor) / 2,
  2101  						MaxSwapCount: 2,
  2102  					},
  2103  					{
  2104  						Value:        uint64(dexeth.GweiFactor) / 2,
  2105  						MaxSwapCount: 2,
  2106  					},
  2107  				},
  2108  			},
  2109  			maxLock:   uint64(dexeth.GweiFactor) + swapGas*4*fromAsset.MaxFeeRate - 1,
  2110  			expectErr: true,
  2111  		},
  2112  		{
  2113  			name:      "maxLock just enough, token",
  2114  			tokenOnly: true,
  2115  			bal:       uint64(dexeth.GweiFactor) + swapGas*4*fromAsset.MaxFeeRate,
  2116  			tokenBal:  uint64(dexeth.GweiFactor),
  2117  			parentBal: uint64(dexeth.GweiFactor),
  2118  			multiOrder: &asset.MultiOrder{
  2119  				Version:    fromAsset.Version,
  2120  				MaxFeeRate: fromAsset.MaxFeeRate,
  2121  				Values: []*asset.MultiOrderValue{
  2122  					{
  2123  						Value:        uint64(dexeth.GweiFactor) / 2,
  2124  						MaxSwapCount: 2,
  2125  					},
  2126  					{
  2127  						Value:        uint64(dexeth.GweiFactor) / 2,
  2128  						MaxSwapCount: 2,
  2129  					},
  2130  				},
  2131  			},
  2132  			maxLock: uint64(dexeth.GweiFactor),
  2133  		},
  2134  		{
  2135  			name:      "maxLock not enough, eth",
  2136  			tokenOnly: true,
  2137  			bal:       uint64(dexeth.GweiFactor) + swapGas*4*fromAsset.MaxFeeRate,
  2138  			tokenBal:  uint64(dexeth.GweiFactor),
  2139  			parentBal: uint64(dexeth.GweiFactor),
  2140  			multiOrder: &asset.MultiOrder{
  2141  				Version:    fromAsset.Version,
  2142  				MaxFeeRate: fromAsset.MaxFeeRate,
  2143  				Values: []*asset.MultiOrderValue{
  2144  					{
  2145  						Value:        uint64(dexeth.GweiFactor) / 2,
  2146  						MaxSwapCount: 2,
  2147  					},
  2148  					{
  2149  						Value:        uint64(dexeth.GweiFactor) / 2,
  2150  						MaxSwapCount: 2,
  2151  					},
  2152  				},
  2153  			},
  2154  			maxLock: uint64(dexeth.GweiFactor) + swapGas*4*fromAsset.MaxFeeRate,
  2155  		},
  2156  
  2157  		{
  2158  			name:      "insufficient balance",
  2159  			bal:       uint64(dexeth.GweiFactor) + swapGas*4*fromAsset.MaxFeeRate - 1,
  2160  			tokenBal:  uint64(dexeth.GweiFactor) - 1,
  2161  			parentBal: uint64(dexeth.GweiFactor),
  2162  			multiOrder: &asset.MultiOrder{
  2163  				Version:    fromAsset.Version,
  2164  				MaxFeeRate: fromAsset.MaxFeeRate,
  2165  				Values: []*asset.MultiOrderValue{
  2166  					{
  2167  						Value:        uint64(dexeth.GweiFactor) / 2,
  2168  						MaxSwapCount: 2,
  2169  					},
  2170  					{
  2171  						Value:        uint64(dexeth.GweiFactor) / 2,
  2172  						MaxSwapCount: 2,
  2173  					},
  2174  				},
  2175  			},
  2176  			expectErr: true,
  2177  		},
  2178  		{
  2179  			name:      "parent balance ok",
  2180  			tokenOnly: true,
  2181  			tokenBal:  uint64(dexeth.GweiFactor),
  2182  			parentBal: swapGas * 4 * fromAsset.MaxFeeRate,
  2183  			multiOrder: &asset.MultiOrder{
  2184  				Version:    fromAsset.Version,
  2185  				MaxFeeRate: fromAsset.MaxFeeRate,
  2186  				Values: []*asset.MultiOrderValue{
  2187  					{
  2188  						Value:        uint64(dexeth.GweiFactor) / 2,
  2189  						MaxSwapCount: 2,
  2190  					},
  2191  					{
  2192  						Value:        uint64(dexeth.GweiFactor) / 2,
  2193  						MaxSwapCount: 2,
  2194  					},
  2195  				},
  2196  			},
  2197  		},
  2198  		{
  2199  			name:      "insufficient parent balance",
  2200  			tokenOnly: true,
  2201  			tokenBal:  uint64(dexeth.GweiFactor),
  2202  			parentBal: swapGas*4*fromAsset.MaxFeeRate - 1,
  2203  			multiOrder: &asset.MultiOrder{
  2204  				Version:    fromAsset.Version,
  2205  				MaxFeeRate: fromAsset.MaxFeeRate,
  2206  				Values: []*asset.MultiOrderValue{
  2207  					{
  2208  						Value:        uint64(dexeth.GweiFactor) / 2,
  2209  						MaxSwapCount: 2,
  2210  					},
  2211  					{
  2212  						Value:        uint64(dexeth.GweiFactor) / 2,
  2213  						MaxSwapCount: 2,
  2214  					},
  2215  				},
  2216  			},
  2217  			expectErr: true,
  2218  		},
  2219  	}
  2220  
  2221  	for _, test := range tests {
  2222  		node.setBalanceError(eth, nil)
  2223  		if assetID == BipID {
  2224  			if test.tokenOnly {
  2225  				continue
  2226  			}
  2227  			node.bal = dexeth.GweiToWei(test.bal)
  2228  		} else {
  2229  			if test.ethOnly {
  2230  				continue
  2231  			}
  2232  			node.tokenContractor.bal = dexeth.GweiToWei(test.tokenBal)
  2233  			node.tokenParent.node.(*tMempoolNode).bal = dexeth.GweiToWei(test.parentBal)
  2234  		}
  2235  		eth.lockedFunds.initiateReserves = 0
  2236  		eth.baseWallet.wallets[BipID].lockedFunds.initiateReserves = 0
  2237  
  2238  		allCoins, redeemScripts, _, err := w.FundMultiOrder(test.multiOrder, test.maxLock)
  2239  		if test.expectErr {
  2240  			if err == nil {
  2241  				t.Fatalf("%s: expected error but did not get one", test.name)
  2242  			}
  2243  			continue
  2244  		}
  2245  		if err != nil {
  2246  			t.Fatalf("%s: unexpected error: %v", test.name, err)
  2247  		}
  2248  		if len(allCoins) != len(test.multiOrder.Values) {
  2249  			t.Fatalf("%s: expected %d coins but got %d", test.name, len(test.multiOrder.Values), len(allCoins))
  2250  		}
  2251  		if len(redeemScripts) != len(test.multiOrder.Values) {
  2252  			t.Fatalf("%s: expected %d redeem scripts but got %d", test.name, len(test.multiOrder.Values), len(redeemScripts))
  2253  		}
  2254  		for i, coins := range allCoins {
  2255  			if len(coins) != 1 {
  2256  				t.Fatalf("%s: expected 1 coin but got %d", test.name, len(coins))
  2257  			}
  2258  			expectedValue := test.multiOrder.Values[i].Value
  2259  			if assetID == BipID {
  2260  				expectedValue += swapGas * test.multiOrder.Values[i].MaxSwapCount * fromAsset.MaxFeeRate
  2261  			}
  2262  			if coins[0].Value() != expectedValue {
  2263  				t.Fatalf("%s: expected coin %d value %d but got %d", test.name, i, expectedValue, coins[0].Value())
  2264  			}
  2265  		}
  2266  	}
  2267  }
  2268  
  2269  func TestPreSwap(t *testing.T) {
  2270  	const baseFee, tip = 42, 2
  2271  	const feeSuggestion = 90 // ignored by eth's PreSwap
  2272  	const lotSize = 10e9
  2273  	oneFee := ethGases.Swap * tETH.MaxFeeRate
  2274  	refund := ethGases.Refund * tETH.MaxFeeRate
  2275  	oneLock := lotSize + oneFee + refund
  2276  
  2277  	oneFeeToken := tokenGases.Swap*tToken.MaxFeeRate + tokenGases.Refund*tToken.MaxFeeRate
  2278  
  2279  	type testData struct {
  2280  		name      string
  2281  		bal       uint64
  2282  		balErr    error
  2283  		lots      uint64
  2284  		token     bool
  2285  		parentBal uint64
  2286  
  2287  		wantErr       bool
  2288  		wantLots      uint64
  2289  		wantValue     uint64
  2290  		wantMaxFees   uint64
  2291  		wantWorstCase uint64
  2292  		wantBestCase  uint64
  2293  	}
  2294  
  2295  	tests := []testData{
  2296  		{
  2297  			name: "no balance",
  2298  			bal:  0,
  2299  			lots: 1,
  2300  
  2301  			wantErr: true,
  2302  		},
  2303  		{
  2304  			name: "not enough for fees",
  2305  			bal:  lotSize,
  2306  			lots: 1,
  2307  
  2308  			wantErr: true,
  2309  		},
  2310  		{
  2311  			name:      "not enough for fees - token",
  2312  			bal:       lotSize,
  2313  			parentBal: oneFeeToken - 1,
  2314  			lots:      1,
  2315  			token:     true,
  2316  
  2317  			wantErr: true,
  2318  		},
  2319  		{
  2320  			name: "one lot enough for fees",
  2321  			bal:  oneLock,
  2322  			lots: 1,
  2323  
  2324  			wantLots:      1,
  2325  			wantValue:     lotSize,
  2326  			wantMaxFees:   tETH.MaxFeeRate * ethGases.Swap,
  2327  			wantBestCase:  (baseFee + tip) * ethGases.Swap,
  2328  			wantWorstCase: (baseFee + tip) * ethGases.Swap,
  2329  		},
  2330  		{
  2331  			name:      "one lot enough for fees - token",
  2332  			bal:       lotSize,
  2333  			lots:      1,
  2334  			parentBal: oneFeeToken,
  2335  			token:     true,
  2336  
  2337  			wantLots:      1,
  2338  			wantValue:     lotSize,
  2339  			wantMaxFees:   tToken.MaxFeeRate * tokenGases.Swap,
  2340  			wantBestCase:  (baseFee + tip) * tokenGases.Swap,
  2341  			wantWorstCase: (baseFee + tip) * tokenGases.Swap,
  2342  		},
  2343  		{
  2344  			name: "more lots than max lots",
  2345  			bal:  oneLock*2 - 1,
  2346  			lots: 2,
  2347  
  2348  			wantErr: true,
  2349  		},
  2350  		{
  2351  			name:      "more lots than max lots - token",
  2352  			bal:       lotSize*2 - 1,
  2353  			lots:      2,
  2354  			token:     true,
  2355  			parentBal: oneFeeToken * 2,
  2356  
  2357  			wantErr: true,
  2358  		},
  2359  		{
  2360  			name: "fewer than max lots",
  2361  			bal:  10 * oneLock,
  2362  			lots: 4,
  2363  
  2364  			wantLots:      4,
  2365  			wantValue:     4 * lotSize,
  2366  			wantMaxFees:   4 * tETH.MaxFeeRate * ethGases.Swap,
  2367  			wantBestCase:  (baseFee + tip) * ethGases.Swap,
  2368  			wantWorstCase: 4 * (baseFee + tip) * ethGases.Swap,
  2369  		},
  2370  		{
  2371  			name:      "fewer than max lots - token",
  2372  			bal:       10 * lotSize,
  2373  			lots:      4,
  2374  			token:     true,
  2375  			parentBal: oneFeeToken * 4,
  2376  
  2377  			wantLots:      4,
  2378  			wantValue:     4 * lotSize,
  2379  			wantMaxFees:   4 * tToken.MaxFeeRate * tokenGases.Swap,
  2380  			wantBestCase:  (baseFee + tip) * tokenGases.Swap,
  2381  			wantWorstCase: 4 * (baseFee + tip) * tokenGases.Swap,
  2382  		},
  2383  		{
  2384  			name:   "balanceError",
  2385  			bal:    5 * lotSize,
  2386  			balErr: errors.New("test error"),
  2387  			lots:   1,
  2388  
  2389  			wantErr: true,
  2390  		},
  2391  		{
  2392  			name:   "balanceError - token",
  2393  			bal:    5 * lotSize,
  2394  			balErr: errors.New("test error"),
  2395  			lots:   1,
  2396  			token:  true,
  2397  
  2398  			wantErr: true,
  2399  		},
  2400  	}
  2401  
  2402  	runTest := func(t *testing.T, test testData) {
  2403  		var assetID uint32 = BipID
  2404  		assetCfg := tETH
  2405  		if test.token {
  2406  			assetID = usdcTokenID
  2407  			assetCfg = tToken
  2408  		}
  2409  
  2410  		w, _, node, shutdown := tassetWallet(assetID)
  2411  		defer shutdown()
  2412  		node.baseFee, node.tip = dexeth.GweiToWei(baseFee), dexeth.GweiToWei(tip)
  2413  
  2414  		if test.token {
  2415  			node.tContractor.gasEstimates = &tokenGases
  2416  			node.tokenContractor.bal = dexeth.GweiToWei(test.bal)
  2417  			node.bal = dexeth.GweiToWei(test.parentBal)
  2418  		} else {
  2419  			node.bal = dexeth.GweiToWei(test.bal)
  2420  		}
  2421  
  2422  		node.balErr = test.balErr
  2423  
  2424  		preSwap, err := w.PreSwap(&asset.PreSwapForm{
  2425  			Version:       assetCfg.Version,
  2426  			LotSize:       lotSize,
  2427  			Lots:          test.lots,
  2428  			MaxFeeRate:    assetCfg.MaxFeeRate,
  2429  			FeeSuggestion: feeSuggestion, // ignored
  2430  			RedeemVersion: tBTC.Version,
  2431  			RedeemAssetID: tBTC.ID,
  2432  		})
  2433  
  2434  		if test.wantErr {
  2435  			if err == nil {
  2436  				t.Fatalf("expected error")
  2437  			}
  2438  			return
  2439  		}
  2440  		if err != nil {
  2441  			t.Fatalf("unexpected error: %v", err)
  2442  		}
  2443  
  2444  		est := preSwap.Estimate
  2445  
  2446  		if est.Lots != test.wantLots {
  2447  			t.Fatalf("want lots %v got %v", test.wantLots, est.Lots)
  2448  		}
  2449  		if est.Value != test.wantValue {
  2450  			t.Fatalf("want value %v got %v", test.wantValue, est.Value)
  2451  		}
  2452  		if est.MaxFees != test.wantMaxFees {
  2453  			t.Fatalf("want maxFees %v got %v", test.wantMaxFees, est.MaxFees)
  2454  		}
  2455  		if est.RealisticBestCase != test.wantBestCase {
  2456  			t.Fatalf("want best case %v got %v", test.wantBestCase, est.RealisticBestCase)
  2457  		}
  2458  		if est.RealisticWorstCase != test.wantWorstCase {
  2459  			t.Fatalf("want worst case %v got %v", test.wantWorstCase, est.RealisticWorstCase)
  2460  		}
  2461  	}
  2462  
  2463  	for _, test := range tests {
  2464  		t.Run(test.name, func(t *testing.T) {
  2465  			runTest(t, test)
  2466  		})
  2467  	}
  2468  }
  2469  
  2470  func TestSwap(t *testing.T) {
  2471  	t.Run("eth", func(t *testing.T) { testSwap(t, BipID) })
  2472  	t.Run("token", func(t *testing.T) { testSwap(t, usdcTokenID) })
  2473  }
  2474  
  2475  func testSwap(t *testing.T, assetID uint32) {
  2476  	w, eth, node, shutdown := tassetWallet(assetID)
  2477  	defer shutdown()
  2478  
  2479  	receivingAddress := "0x2b84C791b79Ee37De042AD2ffF1A253c3ce9bc27"
  2480  	node.tContractor.initTx = types.NewTx(&types.DynamicFeeTx{})
  2481  
  2482  	coinIDsForAmounts := func(coinAmounts []uint64, n uint64) []dex.Bytes {
  2483  		coinIDs := make([]dex.Bytes, 0, len(coinAmounts))
  2484  		for _, amt := range coinAmounts {
  2485  			if assetID == BipID {
  2486  				coinIDs = append(coinIDs, createFundingCoin(eth.addr, amt).RecoveryID())
  2487  			} else {
  2488  				fees := n * tokenGases.Swap * tToken.MaxFeeRate
  2489  				coinIDs = append(coinIDs, createTokenFundingCoin(eth.addr, amt, fees).RecoveryID())
  2490  			}
  2491  		}
  2492  		return coinIDs
  2493  	}
  2494  
  2495  	refreshWalletAndFundCoins := func(bal uint64, coinAmounts []uint64, n uint64) asset.Coins {
  2496  		if assetID == BipID {
  2497  			node.bal = ethToWei(bal)
  2498  		} else {
  2499  			node.tokenContractor.bal = ethToWei(bal)
  2500  			node.bal = ethToWei(10)
  2501  		}
  2502  
  2503  		eth.lockedFunds.initiateReserves = 0
  2504  		eth.lockedFunds.redemptionReserves = 0
  2505  		eth.lockedFunds.refundReserves = 0
  2506  		coins, err := w.FundingCoins(coinIDsForAmounts(coinAmounts, n))
  2507  		if err != nil {
  2508  			t.Fatalf("FundingCoins error: %v", err)
  2509  		}
  2510  		return coins
  2511  	}
  2512  
  2513  	gasNeededForSwaps := func(numSwaps int) uint64 {
  2514  		if assetID == BipID {
  2515  			return ethGases.Swap * uint64(numSwaps)
  2516  		} else {
  2517  			return tokenGases.Swap * uint64(numSwaps)
  2518  		}
  2519  
  2520  	}
  2521  
  2522  	testSwap := func(testName string, swaps asset.Swaps, expectError bool) {
  2523  		originalBalance, err := eth.Balance()
  2524  		if err != nil {
  2525  			t.Fatalf("%v: error getting balance: %v", testName, err)
  2526  		}
  2527  
  2528  		receipts, changeCoin, feeSpent, err := w.Swap(&swaps)
  2529  		if expectError {
  2530  			if err == nil {
  2531  				t.Fatalf("%v: expected error but did not get", testName)
  2532  			}
  2533  			return
  2534  		}
  2535  		if err != nil {
  2536  			t.Fatalf("%v: unexpected error doing Swap: %v", testName, err)
  2537  		}
  2538  
  2539  		if len(receipts) != len(swaps.Contracts) {
  2540  			t.Fatalf("%v: num receipts %d != num contracts %d",
  2541  				testName, len(receipts), len(swaps.Contracts))
  2542  		}
  2543  
  2544  		var totalCoinValue uint64
  2545  		for i, contract := range swaps.Contracts {
  2546  			// Check that receipts match the contract inputs
  2547  			receipt := receipts[i]
  2548  			if uint64(receipt.Expiration().Unix()) != contract.LockTime {
  2549  				t.Fatalf("%v: expected expiration %v != expiration %v",
  2550  					testName, time.Unix(int64(contract.LockTime), 0), receipts[0].Expiration())
  2551  			}
  2552  			if receipt.Coin().Value() != contract.Value {
  2553  				t.Fatalf("%v: receipt coin value: %v != expected: %v",
  2554  					testName, receipt.Coin().Value(), contract.Value)
  2555  			}
  2556  			contractData := receipt.Contract()
  2557  			ver, secretHash, err := dexeth.DecodeContractData(contractData)
  2558  			if err != nil {
  2559  				t.Fatalf("failed to decode contract data: %v", err)
  2560  			}
  2561  			if swaps.Version != ver {
  2562  				t.Fatal("wrong contract version")
  2563  			}
  2564  			if !bytes.Equal(contract.SecretHash, secretHash[:]) {
  2565  				t.Fatalf("%v, contract: %x != secret hash in input: %x",
  2566  					testName, receipt.Contract(), secretHash)
  2567  			}
  2568  
  2569  			totalCoinValue += receipt.Coin().Value()
  2570  		}
  2571  
  2572  		var totalInputValue uint64
  2573  		for _, coin := range swaps.Inputs {
  2574  			totalInputValue += coin.Value()
  2575  		}
  2576  
  2577  		// Check that the coins used in swaps are no longer locked
  2578  		postSwapBalance, err := eth.Balance()
  2579  		if err != nil {
  2580  			t.Fatalf("%v: error getting balance: %v", testName, err)
  2581  		}
  2582  		var expectedLocked uint64
  2583  		if swaps.LockChange {
  2584  			expectedLocked = originalBalance.Locked - totalCoinValue
  2585  			if assetID == BipID {
  2586  				expectedLocked -= gasNeededForSwaps(len(swaps.Contracts)) * swaps.FeeRate
  2587  			}
  2588  		} else {
  2589  			expectedLocked = originalBalance.Locked - totalInputValue
  2590  		}
  2591  		if expectedLocked != postSwapBalance.Locked {
  2592  			t.Fatalf("%v: funds locked after swap expected: %v != actual: %v",
  2593  				testName, expectedLocked, postSwapBalance.Locked)
  2594  		}
  2595  
  2596  		// Check that change coin is correctly returned
  2597  		expectedChangeValue := totalInputValue - totalCoinValue
  2598  		if assetID == BipID {
  2599  			expectedChangeValue -= gasNeededForSwaps(len(swaps.Contracts)) * swaps.FeeRate
  2600  		}
  2601  		if expectedChangeValue == 0 && changeCoin != nil {
  2602  			t.Fatalf("%v: change coin should be nil if change is 0", testName)
  2603  		} else if expectedChangeValue > 0 && changeCoin == nil && swaps.LockChange {
  2604  			t.Fatalf("%v: change coin should not be nil if there is expected change and change is locked",
  2605  				testName)
  2606  		} else if !swaps.LockChange && changeCoin != nil {
  2607  			t.Fatalf("%v: change should be nil if LockChange==False", testName)
  2608  		} else if changeCoin != nil && changeCoin.Value() != expectedChangeValue {
  2609  			t.Fatalf("%v: expected change value %v != change coin value: %v",
  2610  				testName, expectedChangeValue, changeCoin.Value())
  2611  		}
  2612  
  2613  		expectedFees := gasNeededForSwaps(len(swaps.Contracts)) * swaps.FeeRate
  2614  		if feeSpent != expectedFees {
  2615  			t.Fatalf("%v: expected fees: %v != actual fees %v", testName, expectedFees, feeSpent)
  2616  		}
  2617  	}
  2618  
  2619  	secret := encode.RandomBytes(32)
  2620  	secretHash := sha256.Sum256(secret)
  2621  	secret2 := encode.RandomBytes(32)
  2622  	secretHash2 := sha256.Sum256(secret2)
  2623  	expiration := uint64(time.Now().Add(time.Hour * 8).Unix())
  2624  
  2625  	// Ensure error when initializing swap errors
  2626  	node.tContractor.initErr = errors.New("test error")
  2627  	contracts := []*asset.Contract{
  2628  		{
  2629  			Address:    receivingAddress,
  2630  			Value:      ethToGwei(1),
  2631  			SecretHash: secretHash[:],
  2632  			LockTime:   expiration,
  2633  		},
  2634  	}
  2635  	inputs := refreshWalletAndFundCoins(5, []uint64{ethToGwei(2)}, 1)
  2636  	assetCfg := tETH
  2637  	if assetID != BipID {
  2638  		assetCfg = tToken
  2639  	}
  2640  	swaps := asset.Swaps{
  2641  		Version:    assetCfg.Version,
  2642  		Inputs:     inputs,
  2643  		Contracts:  contracts,
  2644  		FeeRate:    assetCfg.MaxFeeRate,
  2645  		LockChange: false,
  2646  	}
  2647  	testSwap("error initialize but no send", swaps, true)
  2648  	node.tContractor.initErr = nil
  2649  
  2650  	// Tests one contract without locking change
  2651  	contracts = []*asset.Contract{
  2652  		{
  2653  			Address:    receivingAddress,
  2654  			Value:      ethToGwei(1),
  2655  			SecretHash: secretHash[:],
  2656  			LockTime:   expiration,
  2657  		},
  2658  	}
  2659  
  2660  	inputs = refreshWalletAndFundCoins(5, []uint64{ethToGwei(2)}, 1)
  2661  	swaps = asset.Swaps{
  2662  		Version:    assetCfg.Version,
  2663  		Inputs:     inputs,
  2664  		Contracts:  contracts,
  2665  		FeeRate:    assetCfg.MaxFeeRate,
  2666  		LockChange: false,
  2667  	}
  2668  	testSwap("one contract, don't lock change", swaps, false)
  2669  
  2670  	// Test one contract with locking change
  2671  	inputs = refreshWalletAndFundCoins(5, []uint64{ethToGwei(2)}, 1)
  2672  	swaps = asset.Swaps{
  2673  		Version:    assetCfg.Version,
  2674  		Inputs:     inputs,
  2675  		Contracts:  contracts,
  2676  		FeeRate:    assetCfg.MaxFeeRate,
  2677  		LockChange: true,
  2678  	}
  2679  	testSwap("one contract, lock change", swaps, false)
  2680  
  2681  	// Test two contracts
  2682  	contracts = []*asset.Contract{
  2683  		{
  2684  			Address:    receivingAddress,
  2685  			Value:      ethToGwei(1),
  2686  			SecretHash: secretHash[:],
  2687  			LockTime:   expiration,
  2688  		},
  2689  		{
  2690  			Address:    receivingAddress,
  2691  			Value:      ethToGwei(1),
  2692  			SecretHash: secretHash2[:],
  2693  			LockTime:   expiration,
  2694  		},
  2695  	}
  2696  	inputs = refreshWalletAndFundCoins(5, []uint64{ethToGwei(3)}, 2)
  2697  	swaps = asset.Swaps{
  2698  		Version:    assetCfg.Version,
  2699  		Inputs:     inputs,
  2700  		Contracts:  contracts,
  2701  		FeeRate:    assetCfg.MaxFeeRate,
  2702  		LockChange: false,
  2703  	}
  2704  	testSwap("two contracts", swaps, false)
  2705  
  2706  	// Test error when funding coins are not enough to cover swaps
  2707  	inputs = refreshWalletAndFundCoins(5, []uint64{ethToGwei(1)}, 2)
  2708  	swaps = asset.Swaps{
  2709  		Version:    assetCfg.Version,
  2710  		Inputs:     inputs,
  2711  		Contracts:  contracts,
  2712  		FeeRate:    assetCfg.MaxFeeRate,
  2713  		LockChange: false,
  2714  	}
  2715  	testSwap("funding coins not enough balance", swaps, true)
  2716  
  2717  	// Ensure when funds are exactly the same as required works properly
  2718  	inputs = refreshWalletAndFundCoins(5, []uint64{ethToGwei(2) + (2 * 200 * dexeth.InitGas(1, 0))}, 2)
  2719  	swaps = asset.Swaps{
  2720  		Inputs:     inputs,
  2721  		Version:    assetCfg.Version,
  2722  		Contracts:  contracts,
  2723  		FeeRate:    assetCfg.MaxFeeRate,
  2724  		LockChange: false,
  2725  	}
  2726  	testSwap("exact change", swaps, false)
  2727  }
  2728  
  2729  func TestPreRedeem(t *testing.T) {
  2730  	w, _, _, shutdown := tassetWallet(BipID)
  2731  	defer shutdown()
  2732  
  2733  	form := &asset.PreRedeemForm{
  2734  		Version:       tETH.Version,
  2735  		Lots:          5,
  2736  		FeeSuggestion: 100,
  2737  	}
  2738  
  2739  	preRedeem, err := w.PreRedeem(form)
  2740  	if err != nil {
  2741  		t.Fatalf("unexpected PreRedeem error: %v", err)
  2742  	}
  2743  
  2744  	if preRedeem.Estimate.RealisticBestCase >= preRedeem.Estimate.RealisticWorstCase {
  2745  		t.Fatalf("best case > worst case")
  2746  	}
  2747  
  2748  	// Token
  2749  	w, _, _, shutdown2 := tassetWallet(usdcTokenID)
  2750  	defer shutdown2()
  2751  
  2752  	form.Version = tToken.Version
  2753  
  2754  	preRedeem, err = w.PreRedeem(form)
  2755  	if err != nil {
  2756  		t.Fatalf("unexpected token PreRedeem error: %v", err)
  2757  	}
  2758  
  2759  	if preRedeem.Estimate.RealisticBestCase >= preRedeem.Estimate.RealisticWorstCase {
  2760  		t.Fatalf("token best case > worst case")
  2761  	}
  2762  }
  2763  
  2764  func TestRedeem(t *testing.T) {
  2765  	t.Run("eth", func(t *testing.T) { testRedeem(t, BipID) })
  2766  	t.Run("token", func(t *testing.T) { testRedeem(t, usdcTokenID) })
  2767  }
  2768  
  2769  func testRedeem(t *testing.T, assetID uint32) {
  2770  	w, eth, node, shutdown := tassetWallet(assetID)
  2771  	defer shutdown()
  2772  
  2773  	// Test with a non-zero contract version to ensure it makes it into the receipt
  2774  	contractVer := uint32(1)
  2775  
  2776  	eth.versionedGases[1] = ethGases
  2777  	if assetID != BipID {
  2778  		eth.versionedGases[1] = &tokenGases
  2779  	}
  2780  
  2781  	tokenContracts := eth.tokens[usdcTokenID].NetTokens[dex.Simnet].SwapContracts
  2782  	tokenContracts[1] = tokenContracts[0]
  2783  	defer delete(tokenContracts, 1)
  2784  
  2785  	contractorV1 := &tContractor{
  2786  		swapMap:      make(map[[32]byte]*dexeth.SwapState, 1),
  2787  		gasEstimates: ethGases,
  2788  		redeemTx:     types.NewTx(&types.DynamicFeeTx{Data: []byte{1, 2, 3}}),
  2789  	}
  2790  	var c contractor = contractorV1
  2791  	if assetID != BipID {
  2792  		c = &tTokenContractor{
  2793  			tContractor: contractorV1,
  2794  		}
  2795  	}
  2796  	eth.contractors[1] = c
  2797  
  2798  	addSwapToSwapMap := func(secretHash [32]byte, value uint64, step dexeth.SwapStep) {
  2799  		swap := dexeth.SwapState{
  2800  			BlockHeight: 1,
  2801  			LockTime:    time.Now(),
  2802  			Initiator:   testAddressB,
  2803  			Participant: testAddressA,
  2804  			Value:       dexeth.GweiToWei(value),
  2805  			State:       step,
  2806  		}
  2807  		contractorV1.swapMap[secretHash] = &swap
  2808  	}
  2809  
  2810  	numSecrets := 3
  2811  	secrets := make([][32]byte, 0, numSecrets)
  2812  	secretHashes := make([][32]byte, 0, numSecrets)
  2813  	for i := 0; i < numSecrets; i++ {
  2814  		var secret [32]byte
  2815  		copy(secret[:], encode.RandomBytes(32))
  2816  		secretHash := sha256.Sum256(secret[:])
  2817  		secrets = append(secrets, secret)
  2818  		secretHashes = append(secretHashes, secretHash)
  2819  	}
  2820  
  2821  	addSwapToSwapMap(secretHashes[0], 1e9, dexeth.SSInitiated) // states will be reset by tests though
  2822  	addSwapToSwapMap(secretHashes[1], 1e9, dexeth.SSInitiated)
  2823  
  2824  	/* COMMENTED while estimateRedeemGas is on the $#!t list
  2825  	var redeemGas uint64
  2826  	if assetID == BipID {
  2827  		redeemGas = ethGases.Redeem
  2828  	} else {
  2829  		redeemGas = tokenGases.Redeem
  2830  	}
  2831  
  2832  	var higherGasEstimate uint64 = redeemGas * 2 * 12 / 10       // 120% of estimate
  2833  	var doubleGasEstimate uint64 = (redeemGas * 2 * 2) * 10 / 11 // 200% of estimate after 10% increase
  2834  	// var moreThanDoubleGasEstimate uint64 = (redeemGas * 2 * 21 / 10) * 10 / 11 // > 200% of estimate after 10% increase
  2835  	// additionalFundsNeeded calculates the amount of available funds that we be
  2836  	// needed to use a higher gas estimate than the original, and double the base
  2837  	// fee if it is higher than the server's max fee rate.
  2838  	additionalFundsNeeded := func(feeSuggestion, baseFee, gasEstimate, numRedeems uint64) uint64 {
  2839  		originalReserves := feeSuggestion * redeemGas * numRedeems
  2840  
  2841  		var gasFeeCap, gasLimit uint64
  2842  		if gasEstimate > redeemGas {
  2843  			gasLimit = gasEstimate * 11 / 10
  2844  		} else {
  2845  			gasLimit = redeemGas * numRedeems
  2846  		}
  2847  
  2848  		if baseFee > feeSuggestion {
  2849  			gasFeeCap = 2 * baseFee
  2850  		} else {
  2851  			gasFeeCap = feeSuggestion
  2852  		}
  2853  
  2854  		amountRequired := gasFeeCap * gasLimit
  2855  
  2856  		return amountRequired - originalReserves
  2857  	}
  2858  	*/
  2859  
  2860  	var bestBlock int64 = 123
  2861  	node.bestHdr = &types.Header{
  2862  		Number: big.NewInt(bestBlock),
  2863  	}
  2864  
  2865  	swappableSwapMap := map[[32]byte]dexeth.SwapStep{
  2866  		secretHashes[0]: dexeth.SSInitiated,
  2867  		secretHashes[1]: dexeth.SSInitiated,
  2868  	}
  2869  
  2870  	tests := []struct {
  2871  		name              string
  2872  		form              asset.RedeemForm
  2873  		redeemErr         error
  2874  		swapMap           map[[32]byte]dexeth.SwapStep
  2875  		swapErr           error
  2876  		ethBal            *big.Int
  2877  		baseFee           *big.Int
  2878  		redeemGasOverride *uint64
  2879  		expectedGasFeeCap *big.Int
  2880  		expectError       bool
  2881  	}{
  2882  		{
  2883  			name:              "ok",
  2884  			expectError:       false,
  2885  			swapMap:           swappableSwapMap,
  2886  			ethBal:            dexeth.GweiToWei(10e9),
  2887  			baseFee:           dexeth.GweiToWei(100),
  2888  			expectedGasFeeCap: dexeth.GweiToWei(100),
  2889  			form: asset.RedeemForm{
  2890  				Redemptions: []*asset.Redemption{
  2891  					{
  2892  						Spends: &asset.AuditInfo{
  2893  							Contract:   dexeth.EncodeContractData(contractVer, secretHashes[0]),
  2894  							SecretHash: secretHashes[0][:], // redundant for all current assets, unused with eth
  2895  							Coin: &coin{
  2896  								id: randomHash(),
  2897  							},
  2898  						},
  2899  						Secret: secrets[0][:],
  2900  					},
  2901  					{
  2902  						Spends: &asset.AuditInfo{
  2903  							Contract:   dexeth.EncodeContractData(contractVer, secretHashes[1]),
  2904  							SecretHash: secretHashes[1][:],
  2905  							Coin: &coin{
  2906  								id: randomHash(),
  2907  							},
  2908  						},
  2909  						Secret: secrets[1][:],
  2910  					},
  2911  				},
  2912  				FeeSuggestion: 100,
  2913  			},
  2914  		},
  2915  		/* COMMENTED while estimateRedeemGas is on the $#!t list
  2916  		{
  2917  			name:              "higher gas estimate than reserved",
  2918  			expectError:       false,
  2919  			swapMap:           swappableSwapMap,
  2920  			ethBal:            dexeth.GweiToWei(additionalFundsNeeded(100, 50, higherGasEstimate, 2)),
  2921  			baseFee:           dexeth.GweiToWei(100),
  2922  			expectedGasFeeCap: dexeth.GweiToWei(100),
  2923  			redeemGasOverride: &higherGasEstimate,
  2924  			form: asset.RedeemForm{
  2925  				Redemptions: []*asset.Redemption{
  2926  					{
  2927  						Spends: &asset.AuditInfo{
  2928  							Contract:   dexeth.EncodeContractData(contractVer, secretHashes[0]),
  2929  							SecretHash: secretHashes[0][:], // redundant for all current assets, unused with eth
  2930  							Coin: &coin{
  2931  								id: randomHash(),
  2932  							},
  2933  						},
  2934  						Secret: secrets[0][:],
  2935  					},
  2936  					{
  2937  						Spends: &asset.AuditInfo{
  2938  							Contract:   dexeth.EncodeContractData(contractVer, secretHashes[1]),
  2939  							SecretHash: secretHashes[1][:],
  2940  							Coin: &coin{
  2941  								id: randomHash(),
  2942  							},
  2943  						},
  2944  						Secret: secrets[1][:],
  2945  					},
  2946  				},
  2947  				FeeSuggestion: 100,
  2948  			},
  2949  		},
  2950  		{
  2951  			name:              "gas estimate double reserved",
  2952  			expectError:       false,
  2953  			swapMap:           swappableSwapMap,
  2954  			ethBal:            dexeth.GweiToWei(10e9),
  2955  			baseFee:           dexeth.GweiToWei(100),
  2956  			expectedGasFeeCap: dexeth.GweiToWei(100),
  2957  			redeemGasOverride: &doubleGasEstimate,
  2958  			form: asset.RedeemForm{
  2959  				Redemptions: []*asset.Redemption{
  2960  					{
  2961  						Spends: &asset.AuditInfo{
  2962  							Contract:   dexeth.EncodeContractData(contractVer, secretHashes[0]),
  2963  							SecretHash: secretHashes[0][:], // redundant for all current assets, unused with eth
  2964  							Coin: &coin{
  2965  								id: randomHash(),
  2966  							},
  2967  						},
  2968  						Secret: secrets[0][:],
  2969  					},
  2970  					{
  2971  						Spends: &asset.AuditInfo{
  2972  							Contract:   dexeth.EncodeContractData(contractVer, secretHashes[1]),
  2973  							SecretHash: secretHashes[1][:],
  2974  							Coin: &coin{
  2975  								id: randomHash(),
  2976  							},
  2977  						},
  2978  						Secret: secrets[1][:],
  2979  					},
  2980  				},
  2981  				FeeSuggestion: 100,
  2982  			},
  2983  		},
  2984  		{
  2985  			name:              "gas estimate more than double reserved",
  2986  			expectError:       true,
  2987  			swapMap:           swappableSwapMap,
  2988  			ethBal:            dexeth.GweiToWei(additionalFundsNeeded(100, 50, moreThanDoubleGasEstimate, 2)),
  2989  			baseFee:           dexeth.GweiToWei(100),
  2990  			redeemGasOverride: &moreThanDoubleGasEstimate,
  2991  			form: asset.RedeemForm{
  2992  				Redemptions: []*asset.Redemption{
  2993  					{
  2994  						Spends: &asset.AuditInfo{
  2995  							Contract:   dexeth.EncodeContractData(contractVer, secretHashes[0]),
  2996  							SecretHash: secretHashes[0][:], // redundant for all current assets, unused with eth
  2997  							Coin: &coin{
  2998  								id: randomHash(),
  2999  							},
  3000  						},
  3001  						Secret: secrets[0][:],
  3002  					},
  3003  					{
  3004  						Spends: &asset.AuditInfo{
  3005  							Contract:   dexeth.EncodeContractData(contractVer, secretHashes[1]),
  3006  							SecretHash: secretHashes[1][:],
  3007  							Coin: &coin{
  3008  								id: randomHash(),
  3009  							},
  3010  						},
  3011  						Secret: secrets[1][:],
  3012  					},
  3013  				},
  3014  				FeeSuggestion: 100,
  3015  			},
  3016  		},
  3017  		{
  3018  			name:              "higher gas estimate than reserved, balance too low",
  3019  			expectError:       true,
  3020  			swapMap:           swappableSwapMap,
  3021  			ethBal:            dexeth.GweiToWei(additionalFundsNeeded(100, 50, higherGasEstimate, 2) - 1),
  3022  			baseFee:           dexeth.GweiToWei(100),
  3023  			redeemGasOverride: &higherGasEstimate,
  3024  			form: asset.RedeemForm{
  3025  				Redemptions: []*asset.Redemption{
  3026  					{
  3027  						Spends: &asset.AuditInfo{
  3028  							Contract:   dexeth.EncodeContractData(contractVer, secretHashes[0]),
  3029  							SecretHash: secretHashes[0][:], // redundant for all current assets, unused with eth
  3030  							Coin: &coin{
  3031  								id: randomHash(),
  3032  							},
  3033  						},
  3034  						Secret: secrets[0][:],
  3035  					},
  3036  					{
  3037  						Spends: &asset.AuditInfo{
  3038  							Contract:   dexeth.EncodeContractData(contractVer, secretHashes[1]),
  3039  							SecretHash: secretHashes[1][:],
  3040  							Coin: &coin{
  3041  								id: randomHash(),
  3042  							},
  3043  						},
  3044  						Secret: secrets[1][:],
  3045  					},
  3046  				},
  3047  				FeeSuggestion: 100,
  3048  			},
  3049  		},
  3050  		{
  3051  			name:              "base fee > fee suggestion",
  3052  			expectError:       false,
  3053  			swapMap:           swappableSwapMap,
  3054  			ethBal:            dexeth.GweiToWei(additionalFundsNeeded(100, 200, higherGasEstimate, 2)),
  3055  			baseFee:           dexeth.GweiToWei(150),
  3056  			expectedGasFeeCap: dexeth.GweiToWei(300),
  3057  			redeemGasOverride: &higherGasEstimate,
  3058  			form: asset.RedeemForm{
  3059  				Redemptions: []*asset.Redemption{
  3060  					{
  3061  						Spends: &asset.AuditInfo{
  3062  							Contract:   dexeth.EncodeContractData(contractVer, secretHashes[0]),
  3063  							SecretHash: secretHashes[0][:], // redundant for all current assets, unused with eth
  3064  							Coin: &coin{
  3065  								id: randomHash(),
  3066  							},
  3067  						},
  3068  						Secret: secrets[0][:],
  3069  					},
  3070  					{
  3071  						Spends: &asset.AuditInfo{
  3072  							Contract:   dexeth.EncodeContractData(contractVer, secretHashes[1]),
  3073  							SecretHash: secretHashes[1][:],
  3074  							Coin: &coin{
  3075  								id: randomHash(),
  3076  							},
  3077  						},
  3078  						Secret: secrets[1][:],
  3079  					},
  3080  				},
  3081  				FeeSuggestion: 100,
  3082  			},
  3083  		},
  3084  		{
  3085  			name:              "base fee > fee suggestion, not enough for 2x base fee",
  3086  			expectError:       false,
  3087  			swapMap:           swappableSwapMap,
  3088  			ethBal:            dexeth.GweiToWei(additionalFundsNeeded(100, 149, higherGasEstimate, 2)),
  3089  			baseFee:           dexeth.GweiToWei(150),
  3090  			expectedGasFeeCap: dexeth.GweiToWei(298),
  3091  			redeemGasOverride: &higherGasEstimate,
  3092  			form: asset.RedeemForm{
  3093  				Redemptions: []*asset.Redemption{
  3094  					{
  3095  						Spends: &asset.AuditInfo{
  3096  							Contract:   dexeth.EncodeContractData(contractVer, secretHashes[0]),
  3097  							SecretHash: secretHashes[0][:], // redundant for all current assets, unused with eth
  3098  							Coin: &coin{
  3099  								id: randomHash(),
  3100  							},
  3101  						},
  3102  						Secret: secrets[0][:],
  3103  					},
  3104  					{
  3105  						Spends: &asset.AuditInfo{
  3106  							Contract:   dexeth.EncodeContractData(contractVer, secretHashes[1]),
  3107  							SecretHash: secretHashes[1][:],
  3108  							Coin: &coin{
  3109  								id: randomHash(),
  3110  							},
  3111  						},
  3112  						Secret: secrets[1][:],
  3113  					},
  3114  				},
  3115  				FeeSuggestion: 100,
  3116  			},
  3117  		},
  3118  		*/
  3119  		{
  3120  			name:        "not redeemable",
  3121  			expectError: true,
  3122  			swapMap: map[[32]byte]dexeth.SwapStep{
  3123  				secretHashes[0]: dexeth.SSNone,
  3124  				secretHashes[1]: dexeth.SSRedeemed,
  3125  			},
  3126  			ethBal:  dexeth.GweiToWei(10e9),
  3127  			baseFee: dexeth.GweiToWei(100),
  3128  
  3129  			form: asset.RedeemForm{
  3130  				Redemptions: []*asset.Redemption{
  3131  					{
  3132  						Spends: &asset.AuditInfo{
  3133  							Contract:   dexeth.EncodeContractData(contractVer, secretHashes[0]),
  3134  							SecretHash: secretHashes[0][:],
  3135  							Coin: &coin{
  3136  								id: randomHash(),
  3137  							},
  3138  						},
  3139  						Secret: secrets[0][:],
  3140  					},
  3141  					{
  3142  						Spends: &asset.AuditInfo{
  3143  							Contract:   dexeth.EncodeContractData(contractVer, secretHashes[1]),
  3144  							SecretHash: secretHashes[1][:],
  3145  							Coin: &coin{
  3146  								id: randomHash(),
  3147  							},
  3148  						},
  3149  						Secret: secrets[1][:],
  3150  					},
  3151  				},
  3152  				FeeSuggestion: 100,
  3153  			},
  3154  		},
  3155  		{
  3156  			name:        "isRedeemable error",
  3157  			expectError: true,
  3158  			ethBal:      dexeth.GweiToWei(10e9),
  3159  			baseFee:     dexeth.GweiToWei(100),
  3160  			swapErr:     errors.New("swap() error"),
  3161  			form: asset.RedeemForm{
  3162  				Redemptions: []*asset.Redemption{
  3163  					{
  3164  						Spends: &asset.AuditInfo{
  3165  							Contract:   dexeth.EncodeContractData(contractVer, secretHashes[0]),
  3166  							SecretHash: secretHashes[0][:],
  3167  							Coin: &coin{
  3168  								id: randomHash(),
  3169  							},
  3170  						},
  3171  						Secret: secrets[0][:],
  3172  					},
  3173  					{
  3174  						Spends: &asset.AuditInfo{
  3175  							Contract:   dexeth.EncodeContractData(contractVer, secretHashes[1]),
  3176  							SecretHash: secretHashes[1][:],
  3177  							Coin: &coin{
  3178  								id: randomHash(),
  3179  							},
  3180  						},
  3181  						Secret: secrets[1][:],
  3182  					},
  3183  				},
  3184  				FeeSuggestion: 100,
  3185  			},
  3186  		},
  3187  		{
  3188  			name:        "redeem error",
  3189  			redeemErr:   errors.New("test error"),
  3190  			swapMap:     swappableSwapMap,
  3191  			expectError: true,
  3192  			ethBal:      dexeth.GweiToWei(10e9),
  3193  			baseFee:     dexeth.GweiToWei(100),
  3194  			form: asset.RedeemForm{
  3195  				Redemptions: []*asset.Redemption{
  3196  					{
  3197  						Spends: &asset.AuditInfo{
  3198  							Contract:   dexeth.EncodeContractData(contractVer, secretHashes[0]),
  3199  							SecretHash: secretHashes[0][:],
  3200  							Coin: &coin{
  3201  								id: randomHash(),
  3202  							},
  3203  						},
  3204  						Secret: secrets[0][:],
  3205  					},
  3206  				},
  3207  				FeeSuggestion: 200,
  3208  			},
  3209  		},
  3210  		{
  3211  			name:        "swap not found in contract",
  3212  			swapMap:     swappableSwapMap,
  3213  			expectError: true,
  3214  			ethBal:      dexeth.GweiToWei(10e9),
  3215  			baseFee:     dexeth.GweiToWei(100),
  3216  			form: asset.RedeemForm{
  3217  				Redemptions: []*asset.Redemption{
  3218  					{
  3219  						Spends: &asset.AuditInfo{
  3220  							Contract:   dexeth.EncodeContractData(contractVer, secretHashes[2]),
  3221  							SecretHash: secretHashes[2][:],
  3222  							Coin: &coin{
  3223  								id: randomHash(),
  3224  							},
  3225  						},
  3226  						Secret: secrets[2][:],
  3227  					},
  3228  				},
  3229  				FeeSuggestion: 100,
  3230  			},
  3231  		},
  3232  		{
  3233  			name:        "empty redemptions slice error",
  3234  			ethBal:      dexeth.GweiToWei(10e9),
  3235  			baseFee:     dexeth.GweiToWei(100),
  3236  			swapMap:     swappableSwapMap,
  3237  			expectError: true,
  3238  			form: asset.RedeemForm{
  3239  				Redemptions:   []*asset.Redemption{},
  3240  				FeeSuggestion: 100,
  3241  			},
  3242  		},
  3243  	}
  3244  
  3245  	for _, test := range tests {
  3246  		contractorV1.redeemErr = test.redeemErr
  3247  		contractorV1.swapErr = test.swapErr
  3248  		contractorV1.redeemGasOverride = test.redeemGasOverride
  3249  		for secretHash, step := range test.swapMap {
  3250  			contractorV1.swapMap[secretHash].State = step
  3251  		}
  3252  		node.bal = test.ethBal
  3253  		node.baseFee = test.baseFee
  3254  
  3255  		txs, out, fees, err := w.Redeem(&test.form)
  3256  		if test.expectError {
  3257  			if err == nil {
  3258  				t.Fatalf("%v: expected error", test.name)
  3259  			}
  3260  			continue
  3261  		}
  3262  		if err != nil {
  3263  			t.Fatalf("%v: unexpected Redeem error: %v", test.name, err)
  3264  		}
  3265  
  3266  		if len(txs) != len(test.form.Redemptions) {
  3267  			t.Fatalf("%v: expected %d txn but got %d",
  3268  				test.name, len(test.form.Redemptions), len(txs))
  3269  		}
  3270  
  3271  		// Check fees returned from Redeem are as expected
  3272  		expectedGas := dexeth.RedeemGas(len(test.form.Redemptions), 0)
  3273  		if assetID != BipID {
  3274  			expectedGas = tokenGases.Redeem + (uint64(len(test.form.Redemptions))-1)*tokenGases.RedeemAdd
  3275  		}
  3276  		expectedFees := expectedGas * test.form.FeeSuggestion
  3277  		if fees != expectedFees {
  3278  			t.Fatalf("%v: expected fees %d, but got %d", test.name, expectedFees, fees)
  3279  		}
  3280  
  3281  		// Check that value of output coin is as axpected
  3282  		var totalSwapValue uint64
  3283  		for _, redemption := range test.form.Redemptions {
  3284  			_, secretHash, err := dexeth.DecodeContractData(redemption.Spends.Contract)
  3285  			if err != nil {
  3286  				t.Fatalf("DecodeContractData: %v", err)
  3287  			}
  3288  			// secretHash should equal redemption.Spends.SecretHash, but it's
  3289  			// not part of the Redeem code, just the test input consistency.
  3290  			swap := contractorV1.swapMap[secretHash]
  3291  			totalSwapValue += dexeth.WeiToGwei(swap.Value)
  3292  		}
  3293  		if out.Value() != totalSwapValue {
  3294  			t.Fatalf("expected coin value to be %d but got %d",
  3295  				totalSwapValue, out.Value())
  3296  		}
  3297  
  3298  		// Check that gas limit in the transaction is as expected
  3299  		var expectedGasLimit uint64
  3300  		// if test.redeemGasOverride == nil {
  3301  		if assetID == BipID {
  3302  			expectedGasLimit = ethGases.Redeem * uint64(len(test.form.Redemptions))
  3303  		} else {
  3304  			expectedGasLimit = tokenGases.Redeem * uint64(len(test.form.Redemptions))
  3305  		}
  3306  		// } else {
  3307  		// 	expectedGasLimit = *test.redeemGasOverride * 11 / 10
  3308  		// }
  3309  		if contractorV1.lastRedeemOpts.GasLimit != expectedGasLimit {
  3310  			t.Fatalf("%s: expected gas limit %d, but got %d", test.name, expectedGasLimit, contractorV1.lastRedeemOpts.GasLimit)
  3311  		}
  3312  
  3313  		// Check that the gas fee cap in the transaction is as expected
  3314  		if contractorV1.lastRedeemOpts.GasFeeCap.Cmp(test.expectedGasFeeCap) != 0 {
  3315  			t.Fatalf("%s: expected gas fee cap %v, but got %v", test.name, test.expectedGasFeeCap, contractorV1.lastRedeemOpts.GasFeeCap)
  3316  		}
  3317  	}
  3318  }
  3319  
  3320  func TestMaxOrder(t *testing.T) {
  3321  	const baseFee, tip = 42, 2
  3322  
  3323  	type testData struct {
  3324  		name          string
  3325  		bal           uint64
  3326  		balErr        error
  3327  		lotSize       uint64
  3328  		maxFeeRate    uint64
  3329  		feeSuggestion uint64
  3330  		token         bool
  3331  		parentBal     uint64
  3332  		wantErr       bool
  3333  		wantLots      uint64
  3334  		wantValue     uint64
  3335  		wantMaxFees   uint64
  3336  		wantWorstCase uint64
  3337  		wantBestCase  uint64
  3338  		wantLocked    uint64
  3339  	}
  3340  	tests := []testData{
  3341  		{
  3342  			name:          "no balance",
  3343  			bal:           0,
  3344  			lotSize:       10,
  3345  			feeSuggestion: 90,
  3346  			maxFeeRate:    100,
  3347  		},
  3348  		{
  3349  			name:          "no balance - token",
  3350  			bal:           0,
  3351  			lotSize:       10,
  3352  			feeSuggestion: 90,
  3353  			maxFeeRate:    100,
  3354  			token:         true,
  3355  			parentBal:     100,
  3356  		},
  3357  		{
  3358  			name:          "not enough for fees",
  3359  			bal:           10,
  3360  			lotSize:       10,
  3361  			feeSuggestion: 90,
  3362  			maxFeeRate:    100,
  3363  		},
  3364  		{
  3365  			name:          "not enough for fees - token",
  3366  			bal:           10,
  3367  			token:         true,
  3368  			parentBal:     0,
  3369  			lotSize:       10,
  3370  			feeSuggestion: 90,
  3371  			maxFeeRate:    100,
  3372  		},
  3373  		{
  3374  			name:          "one lot enough for fees",
  3375  			bal:           11,
  3376  			lotSize:       10,
  3377  			feeSuggestion: 90,
  3378  			maxFeeRate:    100,
  3379  			wantLots:      1,
  3380  			wantValue:     ethToGwei(10),
  3381  			wantMaxFees:   100 * ethGases.Swap,
  3382  			wantBestCase:  (baseFee + tip) * ethGases.Swap,
  3383  			wantWorstCase: (baseFee + tip) * ethGases.Swap,
  3384  			wantLocked:    ethToGwei(10) + (100 * ethGases.Swap),
  3385  		},
  3386  		{
  3387  			name:          "one lot enough for fees - token",
  3388  			bal:           11,
  3389  			lotSize:       10,
  3390  			feeSuggestion: 90,
  3391  			maxFeeRate:    100,
  3392  			token:         true,
  3393  			parentBal:     1,
  3394  			wantLots:      1,
  3395  			wantValue:     ethToGwei(10),
  3396  			wantMaxFees:   100 * tokenGases.Swap,
  3397  			wantBestCase:  (baseFee + tip) * tokenGases.Swap,
  3398  			wantWorstCase: (baseFee + tip) * tokenGases.Swap,
  3399  			wantLocked:    ethToGwei(10) + (100 * tokenGases.Swap),
  3400  		},
  3401  		{
  3402  			name:          "multiple lots",
  3403  			bal:           51,
  3404  			lotSize:       10,
  3405  			feeSuggestion: 90,
  3406  			maxFeeRate:    100,
  3407  			wantLots:      5,
  3408  			wantValue:     ethToGwei(50),
  3409  			wantMaxFees:   5 * 100 * ethGases.Swap,
  3410  			wantBestCase:  (baseFee + tip) * ethGases.Swap,
  3411  			wantWorstCase: 5 * (baseFee + tip) * ethGases.Swap,
  3412  			wantLocked:    ethToGwei(50) + (5 * 100 * ethGases.Swap),
  3413  		},
  3414  		{
  3415  			name:          "multiple lots - token",
  3416  			bal:           51,
  3417  			lotSize:       10,
  3418  			feeSuggestion: 90,
  3419  			maxFeeRate:    100,
  3420  			token:         true,
  3421  			parentBal:     1,
  3422  			wantLots:      5,
  3423  			wantValue:     ethToGwei(50),
  3424  			wantMaxFees:   5 * 100 * tokenGases.Swap,
  3425  			wantBestCase:  (baseFee + tip) * tokenGases.Swap,
  3426  			wantWorstCase: 5 * (baseFee + tip) * tokenGases.Swap,
  3427  			wantLocked:    ethToGwei(50) + (5 * 100 * tokenGases.Swap),
  3428  		},
  3429  		{
  3430  			name:          "balanceError",
  3431  			bal:           51,
  3432  			lotSize:       10,
  3433  			feeSuggestion: 90,
  3434  			maxFeeRate:    100,
  3435  			balErr:        errors.New("test error"),
  3436  			wantErr:       true,
  3437  		},
  3438  	}
  3439  
  3440  	runTest := func(t *testing.T, test testData) {
  3441  		var assetID uint32 = BipID
  3442  		assetCfg := tETH
  3443  		if test.token {
  3444  			assetID = usdcTokenID
  3445  			assetCfg = tToken
  3446  		}
  3447  
  3448  		w, _, node, shutdown := tassetWallet(assetID)
  3449  		defer shutdown()
  3450  		node.baseFee, node.tip = dexeth.GweiToWei(baseFee), dexeth.GweiToWei(tip)
  3451  
  3452  		if test.token {
  3453  			node.tContractor.gasEstimates = &tokenGases
  3454  			node.tokenContractor.bal = dexeth.GweiToWei(ethToGwei(test.bal))
  3455  			node.bal = dexeth.GweiToWei(ethToGwei(test.parentBal))
  3456  		} else {
  3457  			node.bal = dexeth.GweiToWei(ethToGwei(test.bal))
  3458  		}
  3459  
  3460  		node.balErr = test.balErr
  3461  
  3462  		maxOrder, err := w.MaxOrder(&asset.MaxOrderForm{
  3463  			LotSize:       ethToGwei(test.lotSize),
  3464  			FeeSuggestion: test.feeSuggestion, // ignored
  3465  			AssetVersion:  assetCfg.Version,
  3466  			MaxFeeRate:    test.maxFeeRate,
  3467  			RedeemVersion: tBTC.Version,
  3468  			RedeemAssetID: tBTC.ID,
  3469  		})
  3470  		if test.wantErr {
  3471  			if err == nil {
  3472  				t.Fatalf("expected error")
  3473  			}
  3474  			return
  3475  		}
  3476  		if err != nil {
  3477  			t.Fatalf("unexpected error: %v", err)
  3478  		}
  3479  
  3480  		if maxOrder.Lots != test.wantLots {
  3481  			t.Fatalf("want lots %v got %v", test.wantLots, maxOrder.Lots)
  3482  		}
  3483  		if maxOrder.Value != test.wantValue {
  3484  			t.Fatalf("want value %v got %v", test.wantValue, maxOrder.Value)
  3485  		}
  3486  		if maxOrder.MaxFees != test.wantMaxFees {
  3487  			t.Fatalf("want maxFees %v got %v", test.wantMaxFees, maxOrder.MaxFees)
  3488  		}
  3489  		if maxOrder.RealisticBestCase != test.wantBestCase {
  3490  			t.Fatalf("want best case %v got %v", test.wantBestCase, maxOrder.RealisticBestCase)
  3491  		}
  3492  		if maxOrder.RealisticWorstCase != test.wantWorstCase {
  3493  			t.Fatalf("want worst case %v got %v", test.wantWorstCase, maxOrder.RealisticWorstCase)
  3494  		}
  3495  	}
  3496  
  3497  	for _, test := range tests {
  3498  		t.Run(test.name, func(t *testing.T) {
  3499  			runTest(t, test)
  3500  		})
  3501  	}
  3502  }
  3503  
  3504  func overMaxWei() *big.Int {
  3505  	maxInt := ^uint64(0)
  3506  	maxWei := new(big.Int).SetUint64(maxInt)
  3507  	gweiFactorBig := big.NewInt(dexeth.GweiFactor)
  3508  	maxWei.Mul(maxWei, gweiFactorBig)
  3509  	overMaxWei := new(big.Int).Set(maxWei)
  3510  	return overMaxWei.Add(overMaxWei, gweiFactorBig)
  3511  }
  3512  
  3513  func packInitiateDataV0(initiations []*dexeth.Initiation) ([]byte, error) {
  3514  	abiInitiations := make([]swapv0.ETHSwapInitiation, 0, len(initiations))
  3515  	for _, init := range initiations {
  3516  		bigVal := new(big.Int).Set(init.Value)
  3517  		abiInitiations = append(abiInitiations, swapv0.ETHSwapInitiation{
  3518  			RefundTimestamp: big.NewInt(init.LockTime.Unix()),
  3519  			SecretHash:      init.SecretHash,
  3520  			Participant:     init.Participant,
  3521  			Value:           new(big.Int).Mul(bigVal, big.NewInt(dexeth.GweiFactor)),
  3522  		})
  3523  	}
  3524  	return (*dexeth.ABIs[0]).Pack("initiate", abiInitiations)
  3525  }
  3526  
  3527  func packRedeemDataV0(redemptions []*dexeth.Redemption) ([]byte, error) {
  3528  	abiRedemptions := make([]swapv0.ETHSwapRedemption, 0, len(redemptions))
  3529  	for _, redeem := range redemptions {
  3530  		abiRedemptions = append(abiRedemptions, swapv0.ETHSwapRedemption{
  3531  			Secret:     redeem.Secret,
  3532  			SecretHash: redeem.SecretHash,
  3533  		})
  3534  	}
  3535  	return (*dexeth.ABIs[0]).Pack("redeem", abiRedemptions)
  3536  }
  3537  
  3538  func TestAuditContract(t *testing.T) {
  3539  	t.Run("eth", func(t *testing.T) { testAuditContract(t, BipID) })
  3540  	t.Run("token", func(t *testing.T) { testAuditContract(t, usdcTokenID) })
  3541  }
  3542  
  3543  func testAuditContract(t *testing.T, assetID uint32) {
  3544  	_, eth, _, shutdown := tassetWallet(assetID)
  3545  	defer shutdown()
  3546  
  3547  	numSecretHashes := 3
  3548  	secretHashes := make([][32]byte, 0, numSecretHashes)
  3549  	for i := 0; i < numSecretHashes; i++ {
  3550  		var secretHash [32]byte
  3551  		copy(secretHash[:], encode.RandomBytes(32))
  3552  		secretHashes = append(secretHashes, secretHash)
  3553  	}
  3554  
  3555  	now := time.Now()
  3556  	laterThanNow := now.Add(time.Hour)
  3557  
  3558  	tests := []struct {
  3559  		name           string
  3560  		contract       dex.Bytes
  3561  		initiations    []*dexeth.Initiation
  3562  		differentHash  bool
  3563  		badTxData      bool
  3564  		badTxBinary    bool
  3565  		wantErr        bool
  3566  		wantRecipient  string
  3567  		wantExpiration time.Time
  3568  	}{
  3569  		{
  3570  			name:     "ok",
  3571  			contract: dexeth.EncodeContractData(0, secretHashes[1]),
  3572  			initiations: []*dexeth.Initiation{
  3573  				{
  3574  					LockTime:    now,
  3575  					SecretHash:  secretHashes[0],
  3576  					Participant: testAddressA,
  3577  					Value:       dexeth.GweiToWei(1),
  3578  				},
  3579  				{
  3580  					LockTime:    laterThanNow,
  3581  					SecretHash:  secretHashes[1],
  3582  					Participant: testAddressB,
  3583  					Value:       dexeth.GweiToWei(1),
  3584  				},
  3585  			},
  3586  			wantRecipient:  testAddressB.Hex(),
  3587  			wantExpiration: laterThanNow,
  3588  		},
  3589  		{
  3590  			name:     "coin id different than tx hash",
  3591  			contract: dexeth.EncodeContractData(0, secretHashes[0]),
  3592  			initiations: []*dexeth.Initiation{
  3593  				{
  3594  					LockTime:    now,
  3595  					SecretHash:  secretHashes[0],
  3596  					Participant: testAddressA,
  3597  					Value:       dexeth.GweiToWei(1),
  3598  				},
  3599  			},
  3600  			differentHash: true,
  3601  			wantErr:       true,
  3602  		},
  3603  		{
  3604  			name:     "contract is invalid versioned bytes",
  3605  			contract: []byte{},
  3606  			wantErr:  true,
  3607  		},
  3608  		{
  3609  			name:     "contract not part of transaction",
  3610  			contract: dexeth.EncodeContractData(0, secretHashes[2]),
  3611  			initiations: []*dexeth.Initiation{
  3612  				{
  3613  					LockTime:    now,
  3614  					SecretHash:  secretHashes[0],
  3615  					Participant: testAddressA,
  3616  					Value:       dexeth.GweiToWei(1),
  3617  				},
  3618  				{
  3619  					LockTime:    laterThanNow,
  3620  					SecretHash:  secretHashes[1],
  3621  					Participant: testAddressB,
  3622  					Value:       dexeth.GweiToWei(1),
  3623  				},
  3624  			},
  3625  			wantErr: true,
  3626  		},
  3627  		{
  3628  			name:      "cannot parse tx data",
  3629  			contract:  dexeth.EncodeContractData(0, secretHashes[2]),
  3630  			badTxData: true,
  3631  			wantErr:   true,
  3632  		},
  3633  		{
  3634  			name:     "cannot unmarshal tx binary",
  3635  			contract: dexeth.EncodeContractData(0, secretHashes[1]),
  3636  			initiations: []*dexeth.Initiation{
  3637  				{
  3638  					LockTime:    now,
  3639  					SecretHash:  secretHashes[0],
  3640  					Participant: testAddressA,
  3641  					Value:       dexeth.GweiToWei(1),
  3642  				},
  3643  				{
  3644  					LockTime:    laterThanNow,
  3645  					SecretHash:  secretHashes[1],
  3646  					Participant: testAddressB,
  3647  					Value:       dexeth.GweiToWei(1),
  3648  				},
  3649  			},
  3650  			badTxBinary: true,
  3651  			wantErr:     true,
  3652  		},
  3653  	}
  3654  
  3655  	for _, test := range tests {
  3656  		txData, err := packInitiateDataV0(test.initiations)
  3657  		if err != nil {
  3658  			t.Fatalf("unexpected error: %v", err)
  3659  		}
  3660  		if test.badTxData {
  3661  			txData = []byte{0}
  3662  		}
  3663  
  3664  		tx := tTx(2, 300, uint64(len(test.initiations)), &testAddressC, txData, 21000)
  3665  		txBinary, err := tx.MarshalBinary()
  3666  		if err != nil {
  3667  			t.Fatalf(`"%v": failed to marshal binary: %v`, test.name, err)
  3668  		}
  3669  		if test.badTxBinary {
  3670  			txBinary = []byte{0}
  3671  		}
  3672  
  3673  		txHash := tx.Hash()
  3674  		if test.differentHash {
  3675  			copy(txHash[:], encode.RandomBytes(20))
  3676  		}
  3677  
  3678  		auditInfo, err := eth.AuditContract(txHash[:], test.contract, txBinary, true)
  3679  		if test.wantErr {
  3680  			if err == nil {
  3681  				t.Fatalf(`"%v": expected error but did not get`, test.name)
  3682  			}
  3683  			continue
  3684  		}
  3685  		if err != nil {
  3686  			t.Fatalf(`"%v": unexpected error: %v`, test.name, err)
  3687  		}
  3688  
  3689  		if test.wantRecipient != auditInfo.Recipient {
  3690  			t.Fatalf(`"%v": expected recipient %v != actual %v`, test.name, test.wantRecipient, auditInfo.Recipient)
  3691  		}
  3692  		if test.wantExpiration.Unix() != auditInfo.Expiration.Unix() {
  3693  			t.Fatalf(`"%v": expected expiration %v != actual %v`, test.name, test.wantExpiration, auditInfo.Expiration)
  3694  		}
  3695  		if !bytes.Equal(txHash[:], auditInfo.Coin.ID()) {
  3696  			t.Fatalf(`"%v": tx hash %x != coin id %x`, test.name, txHash, auditInfo.Coin.ID())
  3697  		}
  3698  		if !bytes.Equal(test.contract, auditInfo.Contract) {
  3699  			t.Fatalf(`"%v": expected contract %x != actual %x`, test.name, test.contract, auditInfo.Contract)
  3700  		}
  3701  
  3702  		_, expectedSecretHash, err := dexeth.DecodeContractData(test.contract)
  3703  		if err != nil {
  3704  			t.Fatalf(`"%v": failed to decode versioned bytes: %v`, test.name, err)
  3705  		}
  3706  		if !bytes.Equal(expectedSecretHash[:], auditInfo.SecretHash) {
  3707  			t.Fatalf(`"%v": expected secret hash %x != actual %x`, test.name, expectedSecretHash, auditInfo.SecretHash)
  3708  		}
  3709  	}
  3710  }
  3711  
  3712  func TestOwnsAddress(t *testing.T) {
  3713  	address := "0b84C791b79Ee37De042AD2ffF1A253c3ce9bc27" // no "0x" prefix
  3714  	if !common.IsHexAddress(address) {
  3715  		t.Fatalf("bad test address")
  3716  	}
  3717  
  3718  	var otherAddress common.Address
  3719  	rand.Read(otherAddress[:])
  3720  
  3721  	eth := &baseWallet{
  3722  		addr:          common.HexToAddress(address),
  3723  		finalizeConfs: txConfsNeededToConfirm,
  3724  	}
  3725  
  3726  	tests := []struct {
  3727  		name     string
  3728  		address  string
  3729  		wantOwns bool
  3730  		wantErr  bool
  3731  	}{
  3732  		{
  3733  			name:     "same (exact)",
  3734  			address:  address,
  3735  			wantOwns: true,
  3736  			wantErr:  false,
  3737  		},
  3738  		{
  3739  			name:     "same (lower)",
  3740  			address:  strings.ToLower(address),
  3741  			wantOwns: true,
  3742  			wantErr:  false,
  3743  		},
  3744  		{
  3745  			name:     "same (upper)",
  3746  			address:  strings.ToUpper(address),
  3747  			wantOwns: true,
  3748  			wantErr:  false,
  3749  		},
  3750  		{
  3751  			name:     "same (0x prefix)",
  3752  			address:  "0x" + address,
  3753  			wantOwns: true,
  3754  			wantErr:  false,
  3755  		},
  3756  		{
  3757  			name:     "different (valid canonical)",
  3758  			address:  otherAddress.String(),
  3759  			wantOwns: false,
  3760  			wantErr:  false,
  3761  		},
  3762  		{
  3763  			name:     "different (valid hex)",
  3764  			address:  otherAddress.Hex(),
  3765  			wantOwns: false,
  3766  			wantErr:  false,
  3767  		},
  3768  		{
  3769  			name:     "error (bad hex char)",
  3770  			address:  strings.Replace(address, "b", "r", 1),
  3771  			wantOwns: false,
  3772  			wantErr:  true,
  3773  		},
  3774  		{
  3775  			name:     "error (bad length)",
  3776  			address:  "ababababababab",
  3777  			wantOwns: false,
  3778  			wantErr:  true,
  3779  		},
  3780  	}
  3781  
  3782  	for _, tt := range tests {
  3783  		t.Run(tt.name, func(t *testing.T) {
  3784  			owns, err := eth.OwnsDepositAddress(tt.address)
  3785  			if (err == nil) && tt.wantErr {
  3786  				t.Error("expected error")
  3787  			}
  3788  			if (err != nil) && !tt.wantErr {
  3789  				t.Errorf("unexpected error: %v", err)
  3790  			}
  3791  			if owns != tt.wantOwns {
  3792  				t.Errorf("got %v, want %v", owns, tt.wantOwns)
  3793  			}
  3794  		})
  3795  	}
  3796  }
  3797  
  3798  func TestSignMessage(t *testing.T) {
  3799  	ctx, cancel := context.WithCancel(context.Background())
  3800  	defer cancel()
  3801  
  3802  	node := newTestNode(BipID)
  3803  	eth := &assetWallet{
  3804  		baseWallet: &baseWallet{
  3805  			node:          node,
  3806  			addr:          node.address(),
  3807  			ctx:           ctx,
  3808  			log:           tLogger,
  3809  			finalizeConfs: txConfsNeededToConfirm,
  3810  		},
  3811  		assetID: BipID,
  3812  	}
  3813  
  3814  	msg := []byte("msg")
  3815  
  3816  	// SignData error
  3817  	node.signDataErr = errors.New("test error")
  3818  	_, _, err := eth.SignMessage(nil, msg)
  3819  	if err == nil {
  3820  		t.Fatalf("expected error due to error in rpcclient signData")
  3821  	}
  3822  	node.signDataErr = nil
  3823  
  3824  	// Test no error
  3825  	pubKeys, sigs, err := eth.SignMessage(nil, msg)
  3826  	if err != nil {
  3827  		t.Fatalf("unexpected error signing message: %v", err)
  3828  	}
  3829  	if len(pubKeys) != 1 {
  3830  		t.Fatalf("expected 1 pubKey but got %v", len(pubKeys))
  3831  	}
  3832  	if len(sigs) != 1 {
  3833  		t.Fatalf("expected 1 signature but got %v", len(sigs))
  3834  	}
  3835  	if !crypto.VerifySignature(pubKeys[0], crypto.Keccak256(msg), sigs[0][:len(sigs[0])-1]) {
  3836  		t.Fatalf("failed to verify signature")
  3837  	}
  3838  }
  3839  
  3840  func TestSwapConfirmation(t *testing.T) {
  3841  	_, eth, node, shutdown := tassetWallet(BipID)
  3842  	defer shutdown()
  3843  
  3844  	var secretHash [32]byte
  3845  	copy(secretHash[:], encode.RandomBytes(32))
  3846  	state := &dexeth.SwapState{}
  3847  	hdr := &types.Header{}
  3848  
  3849  	node.tContractor.swapMap[secretHash] = state
  3850  
  3851  	state.BlockHeight = 5
  3852  	state.State = dexeth.SSInitiated
  3853  	hdr.Number = big.NewInt(6)
  3854  	eth.currentTip = hdr
  3855  
  3856  	ver := uint32(0)
  3857  
  3858  	ctx, cancel := context.WithCancel(context.Background())
  3859  	defer cancel()
  3860  
  3861  	checkResult := func(expErr bool, expConfs uint32, expSpent bool) {
  3862  		t.Helper()
  3863  		confs, spent, err := eth.SwapConfirmations(ctx, nil, dexeth.EncodeContractData(ver, secretHash), time.Time{})
  3864  		if err != nil {
  3865  			if expErr {
  3866  				return
  3867  			}
  3868  			t.Fatalf("SwapConfirmations error: %v", err)
  3869  		}
  3870  		if confs != expConfs {
  3871  			t.Fatalf("expected %d confs, got %d", expConfs, confs)
  3872  		}
  3873  		if spent != expSpent {
  3874  			t.Fatalf("wrong spent. wanted %t, got %t", expSpent, spent)
  3875  		}
  3876  	}
  3877  
  3878  	checkResult(false, 2, false)
  3879  
  3880  	// unknown asset version
  3881  	ver = 12
  3882  	checkResult(true, 0, false)
  3883  	ver = 0
  3884  
  3885  	// swap error
  3886  	node.tContractor.swapErr = fmt.Errorf("test error")
  3887  	checkResult(true, 0, false)
  3888  	node.tContractor.swapErr = nil
  3889  
  3890  	// ErrSwapNotInitiated
  3891  	state.State = dexeth.SSNone
  3892  	_, _, err := eth.SwapConfirmations(ctx, nil, dexeth.EncodeContractData(0, secretHash), time.Time{})
  3893  	if !errors.Is(err, asset.ErrSwapNotInitiated) {
  3894  		t.Fatalf("expected ErrSwapNotInitiated, got %v", err)
  3895  	}
  3896  
  3897  	// 1 conf, spent
  3898  	state.BlockHeight = 6
  3899  	state.State = dexeth.SSRedeemed
  3900  	checkResult(false, 1, true)
  3901  }
  3902  
  3903  func TestDriverOpen(t *testing.T) {
  3904  	drv := &Driver{}
  3905  	logger := dex.StdOutLogger("ETHTEST", dex.LevelOff)
  3906  	tmpDir := t.TempDir()
  3907  
  3908  	settings := map[string]string{providersKey: "a.ipc"}
  3909  	err := CreateEVMWallet(dexeth.ChainIDs[dex.Testnet], &asset.CreateWalletParams{
  3910  		Type:     walletTypeRPC,
  3911  		Seed:     encode.RandomBytes(32),
  3912  		Pass:     encode.RandomBytes(32),
  3913  		Settings: settings,
  3914  		DataDir:  tmpDir,
  3915  		Net:      dex.Testnet,
  3916  		Logger:   logger,
  3917  	}, &testnetCompatibilityData, true)
  3918  	if err != nil {
  3919  		t.Fatalf("CreateWallet error: %v", err)
  3920  	}
  3921  
  3922  	// Make sure default gas fee limit is used when nothing is set
  3923  	cfg := &asset.WalletConfig{
  3924  		Type:     walletTypeRPC,
  3925  		Settings: settings,
  3926  		DataDir:  tmpDir,
  3927  	}
  3928  	wallet, err := drv.Open(cfg, logger, dex.Testnet)
  3929  	if err != nil {
  3930  		t.Fatalf("driver open error: %v", err)
  3931  	}
  3932  	eth, ok := wallet.(*ETHWallet)
  3933  	if !ok {
  3934  		t.Fatalf("failed to cast wallet as assetWallet")
  3935  	}
  3936  	if eth.gasFeeLimit() != defaultGasFeeLimit {
  3937  		t.Fatalf("expected gasFeeLimit to be default, but got %v", eth.gasFeeLimit())
  3938  	}
  3939  
  3940  	// Make sure gas fee limit is properly parsed from settings
  3941  	cfg.Settings["gasfeelimit"] = "150"
  3942  	wallet, err = drv.Open(cfg, logger, dex.Testnet)
  3943  	if err != nil {
  3944  		t.Fatalf("driver open error: %v", err)
  3945  	}
  3946  	eth, ok = wallet.(*ETHWallet)
  3947  	if !ok {
  3948  		t.Fatalf("failed to cast wallet as assetWallet")
  3949  	}
  3950  	if eth.gasFeeLimit() != 150 {
  3951  		t.Fatalf("expected gasFeeLimit to be 150, but got %v", eth.gasFeeLimit())
  3952  	}
  3953  }
  3954  
  3955  func TestDriverExists(t *testing.T) {
  3956  	drv := &Driver{}
  3957  	tmpDir := t.TempDir()
  3958  
  3959  	settings := map[string]string{providersKey: "a.ipc"}
  3960  
  3961  	// no wallet
  3962  	exists, err := drv.Exists(walletTypeRPC, tmpDir, settings, dex.Simnet)
  3963  	if err != nil {
  3964  		t.Fatalf("Exists error for no geth wallet: %v", err)
  3965  	}
  3966  	if exists {
  3967  		t.Fatalf("Uninitiated wallet exists")
  3968  	}
  3969  
  3970  	// Create the wallet.
  3971  	err = CreateEVMWallet(dexeth.ChainIDs[dex.Simnet], &asset.CreateWalletParams{
  3972  		Type:     walletTypeRPC,
  3973  		Seed:     encode.RandomBytes(32),
  3974  		Pass:     encode.RandomBytes(32),
  3975  		Settings: settings,
  3976  		DataDir:  tmpDir,
  3977  		Net:      dex.Simnet,
  3978  		Logger:   tLogger,
  3979  	}, &testnetCompatibilityData, true)
  3980  	if err != nil {
  3981  		t.Fatalf("CreateEVMWallet error: %v", err)
  3982  	}
  3983  
  3984  	// exists
  3985  	exists, err = drv.Exists(walletTypeRPC, tmpDir, settings, dex.Simnet)
  3986  	if err != nil {
  3987  		t.Fatalf("Exists error for existent geth wallet: %v", err)
  3988  	}
  3989  	if !exists {
  3990  		t.Fatalf("Initiated wallet doesn't exist")
  3991  	}
  3992  
  3993  	// Wrong wallet type
  3994  	if _, err := drv.Exists("not-geth", tmpDir, settings, dex.Simnet); err == nil {
  3995  		t.Fatalf("no error for unknown wallet type")
  3996  	}
  3997  }
  3998  
  3999  func TestDriverDecodeCoinID(t *testing.T) {
  4000  	drv := &Driver{}
  4001  	addressStr := "0xB6De8BB5ed28E6bE6d671975cad20C03931bE981"
  4002  	address := common.HexToAddress(addressStr)
  4003  
  4004  	// Test tx hash
  4005  	txHash := encode.RandomBytes(common.HashLength)
  4006  	coinID, err := drv.DecodeCoinID(txHash)
  4007  	if err != nil {
  4008  		t.Fatalf("error decoding coin id: %v", err)
  4009  	}
  4010  	var hash common.Hash
  4011  	hash.SetBytes(txHash)
  4012  	if coinID != hash.String() {
  4013  		t.Fatalf("expected coin id to be %s but got %s", hash.String(), coinID)
  4014  	}
  4015  
  4016  	// Test funding coin id
  4017  	fundingCoin := createFundingCoin(address, 1000)
  4018  	coinID, err = drv.DecodeCoinID(fundingCoin.RecoveryID())
  4019  	if err != nil {
  4020  		t.Fatalf("error decoding coin id: %v", err)
  4021  	}
  4022  	if coinID != fundingCoin.String() {
  4023  		t.Fatalf("expected coin id to be %s but got %s", fundingCoin.String(), coinID)
  4024  	}
  4025  
  4026  	// Token funding coin
  4027  	fc := createTokenFundingCoin(address, 1000, 1)
  4028  	coinID, err = drv.DecodeCoinID(fc.RecoveryID())
  4029  	if err != nil {
  4030  		t.Fatalf("error decoding token coin id: %v", err)
  4031  	}
  4032  	if coinID != fc.String() {
  4033  		t.Fatalf("expected coin id to be %s but got %s", fc.String(), coinID)
  4034  	}
  4035  
  4036  	// Test byte encoded address string
  4037  	coinID, err = drv.DecodeCoinID([]byte(addressStr))
  4038  	if err != nil {
  4039  		t.Fatalf("error decoding coin id: %v", err)
  4040  	}
  4041  	if coinID != addressStr {
  4042  		t.Fatalf("expected coin id to be %s but got %s", addressStr, coinID)
  4043  	}
  4044  
  4045  	// Test invalid coin id
  4046  	_, err = drv.DecodeCoinID(encode.RandomBytes(20))
  4047  	if err == nil {
  4048  		t.Fatal("expected error but did not get")
  4049  	}
  4050  }
  4051  
  4052  func TestLocktimeExpired(t *testing.T) {
  4053  	_, eth, node, shutdown := tassetWallet(BipID)
  4054  	defer shutdown()
  4055  
  4056  	var secretHash [32]byte
  4057  	copy(secretHash[:], encode.RandomBytes(32))
  4058  
  4059  	state := &dexeth.SwapState{
  4060  		LockTime: time.Now(),
  4061  		State:    dexeth.SSInitiated,
  4062  	}
  4063  
  4064  	header := &types.Header{
  4065  		Time: uint64(time.Now().Add(time.Second).Unix()),
  4066  	}
  4067  
  4068  	node.tContractor.swapMap[secretHash] = state
  4069  	node.bestHdr = header
  4070  
  4071  	contract := make([]byte, 36)
  4072  	copy(contract[4:], secretHash[:])
  4073  
  4074  	ensureResult := func(tag string, expErr, expExpired bool) {
  4075  		t.Helper()
  4076  		expired, _, err := eth.ContractLockTimeExpired(context.Background(), contract)
  4077  		switch {
  4078  		case err != nil:
  4079  			if !expErr {
  4080  				t.Fatalf("%s: ContractLockTimeExpired error existing expired swap: %v", tag, err)
  4081  			}
  4082  		case expErr:
  4083  			t.Fatalf("%s: expected error, got none", tag)
  4084  		case expExpired != expired:
  4085  			t.Fatalf("%s: expired wrong. %t != %t", tag, expired, expExpired)
  4086  		}
  4087  	}
  4088  
  4089  	// locktime expired
  4090  	ensureResult("locktime expired", false, true)
  4091  
  4092  	// header error
  4093  	node.bestHdrErr = errors.New("test error")
  4094  	ensureResult("header error", true, false)
  4095  	node.bestHdrErr = nil
  4096  
  4097  	// swap not initiated
  4098  	saveState := state.State
  4099  	state.State = dexeth.SSNone
  4100  	ensureResult("swap not initiated", true, false)
  4101  	state.State = saveState
  4102  
  4103  	// missing swap
  4104  	delete(node.tContractor.swapMap, secretHash)
  4105  	ensureResult("missing swap", true, false)
  4106  	node.tContractor.swapMap[secretHash] = state
  4107  
  4108  	// lock time not expired
  4109  	state.LockTime = time.Now().Add(time.Minute)
  4110  	ensureResult("lock time not expired", false, false)
  4111  
  4112  	// wrong contract version
  4113  	contract[3] = 1
  4114  	ensureResult("wrong contract version", true, false)
  4115  	contract[3] = 0
  4116  
  4117  	// bad contract
  4118  	contract = append(contract, 0) // nolint:makezero
  4119  	ensureResult("bad contract", true, false)
  4120  }
  4121  
  4122  func TestFindRedemption(t *testing.T) {
  4123  	t.Run("eth", func(t *testing.T) { testFindRedemption(t, BipID) })
  4124  	t.Run("token", func(t *testing.T) { testFindRedemption(t, usdcTokenID) })
  4125  }
  4126  
  4127  func testFindRedemption(t *testing.T, assetID uint32) {
  4128  	_, eth, node, shutdown := tassetWallet(assetID)
  4129  	defer shutdown()
  4130  
  4131  	var secret [32]byte
  4132  	copy(secret[:], encode.RandomBytes(32))
  4133  	secretHash := sha256.Sum256(secret[:])
  4134  
  4135  	contract := dexeth.EncodeContractData(0, secretHash)
  4136  	state := &dexeth.SwapState{
  4137  		Secret: secret,
  4138  		State:  dexeth.SSInitiated,
  4139  	}
  4140  
  4141  	node.tContractor.swapMap[secretHash] = state
  4142  
  4143  	baseCtx := context.Background()
  4144  
  4145  	runTest := func(tag string, wantErr bool, initStep dexeth.SwapStep) {
  4146  		// The queue should always be empty.
  4147  		eth.findRedemptionMtx.RLock()
  4148  		reqsPending := len(eth.findRedemptionReqs) > 0
  4149  		eth.findRedemptionMtx.RUnlock()
  4150  		if reqsPending {
  4151  			t.Fatalf("%s: requests pending at beginning of test", tag)
  4152  		}
  4153  
  4154  		state.State = initStep
  4155  		var err error
  4156  		ctx, cancel := context.WithTimeout(baseCtx, time.Second)
  4157  		defer cancel()
  4158  		_, secretB, err := eth.FindRedemption(ctx, nil, contract)
  4159  		if err != nil {
  4160  			if wantErr {
  4161  				return
  4162  			}
  4163  			t.Fatalf("%s: %v", tag, err)
  4164  		} else if wantErr {
  4165  			t.Fatalf("%s: didn't see expected error", tag)
  4166  		}
  4167  		if !bytes.Equal(secretB, secret[:]) {
  4168  			t.Fatalf("%s: wrong secret. %x != %x", tag, []byte(secretB), secret)
  4169  		}
  4170  	}
  4171  
  4172  	runWithUpdate := func(tag string, wantErr bool, initStep dexeth.SwapStep, updateFunc func()) {
  4173  		var wg sync.WaitGroup
  4174  		wg.Add(1)
  4175  		go func() {
  4176  			defer wg.Done()
  4177  			timeout := time.After(time.Second)
  4178  			for {
  4179  				select {
  4180  				case <-time.After(time.Millisecond):
  4181  					eth.findRedemptionMtx.RLock()
  4182  					pending := eth.findRedemptionReqs[secretHash] != nil
  4183  					eth.findRedemptionMtx.RUnlock()
  4184  					if !pending {
  4185  						continue
  4186  					}
  4187  					updateFunc()
  4188  					eth.checkFindRedemptions()
  4189  				case <-timeout:
  4190  					return
  4191  				}
  4192  			}
  4193  		}()
  4194  		runTest(tag, wantErr, initStep)
  4195  		wg.Wait()
  4196  	}
  4197  
  4198  	// Already redeemed.
  4199  	runTest("already redeemed", false, dexeth.SSRedeemed)
  4200  
  4201  	// Redeemed after queuing
  4202  	runWithUpdate("redeemed after queuing", false, dexeth.SSInitiated, func() {
  4203  		state.State = dexeth.SSRedeemed
  4204  	})
  4205  
  4206  	// Doesn't exist
  4207  	runTest("already refunded", true, dexeth.SSNone)
  4208  
  4209  	// Unknown swap state
  4210  	runTest("already refunded", true, dexeth.SwapStep(^uint8(0)))
  4211  
  4212  	// Already refunded
  4213  	runTest("already refunded", true, dexeth.SSRefunded)
  4214  
  4215  	// Refunded after queuing
  4216  	runWithUpdate("refunded after queuing", true, dexeth.SSInitiated, func() {
  4217  		state.State = dexeth.SSRefunded
  4218  	})
  4219  
  4220  	// swap error
  4221  	node.tContractor.swapErr = errors.New("test error")
  4222  	runTest("swap error", true, 0)
  4223  	node.tContractor.swapErr = nil
  4224  
  4225  	// swap error after queuing
  4226  	runWithUpdate("swap error after queuing", true, dexeth.SSInitiated, func() {
  4227  		node.tContractor.swapErr = errors.New("test error")
  4228  	})
  4229  	node.tContractor.swapErr = nil
  4230  
  4231  	// cancelled context error
  4232  	var cancel context.CancelFunc
  4233  	baseCtx, cancel = context.WithCancel(context.Background())
  4234  	cancel()
  4235  	runTest("context cancellation", true, dexeth.SSInitiated)
  4236  	baseCtx = context.Background()
  4237  
  4238  	// bad contract
  4239  	goodContract := contract
  4240  	contract = append(contract, 0)
  4241  	runTest("bad contract", true, dexeth.SSInitiated)
  4242  	contract = goodContract
  4243  
  4244  	// dupe
  4245  	eth.findRedemptionMtx.Lock()
  4246  	eth.findRedemptionReqs[secretHash] = &findRedemptionRequest{}
  4247  	eth.findRedemptionMtx.Unlock()
  4248  	res := make(chan error, 1)
  4249  	go func() {
  4250  		_, _, err := eth.FindRedemption(baseCtx, nil, contract)
  4251  		res <- err
  4252  	}()
  4253  
  4254  	select {
  4255  	case err := <-res:
  4256  		if err == nil {
  4257  			t.Fatalf("no error for dupe")
  4258  		}
  4259  	case <-time.After(time.Second):
  4260  		t.Fatalf("timed out on dupe test")
  4261  	}
  4262  }
  4263  
  4264  func TestRefundReserves(t *testing.T) {
  4265  	t.Run("eth", func(t *testing.T) { testRefundReserves(t, BipID) })
  4266  	t.Run("token", func(t *testing.T) { testRefundReserves(t, usdcTokenID) })
  4267  }
  4268  
  4269  func testRefundReserves(t *testing.T, assetID uint32) {
  4270  	wi, eth, node, shutdown := tassetWallet(assetID)
  4271  	defer shutdown()
  4272  
  4273  	w := wi.(asset.AccountLocker)
  4274  
  4275  	node.bal = dexeth.GweiToWei(1e9)
  4276  	node.refundable = true
  4277  	node.swapVers = map[uint32]struct{}{0: {}}
  4278  
  4279  	var secretHash [32]byte
  4280  	node.swapMap = map[[32]byte]*dexeth.SwapState{secretHash: {}}
  4281  
  4282  	feeWallet := eth
  4283  	gasesV0 := dexeth.VersionedGases[0]
  4284  	gasesV1 := &dexeth.Gases{Refund: 1e6}
  4285  	assetV0 := *tETH
  4286  
  4287  	assetV1 := *tETH
  4288  	if assetID == BipID {
  4289  		eth.versionedGases[1] = gasesV1
  4290  	} else {
  4291  		feeWallet = node.tokenParent
  4292  		assetV0 = *tToken
  4293  		assetV1 = *tToken
  4294  		tokenContracts := eth.tokens[usdcTokenID].NetTokens[dex.Simnet].SwapContracts
  4295  		tc := *tokenContracts[0]
  4296  		tc.Gas = *gasesV1
  4297  		tokenContracts[1] = &tc
  4298  		defer delete(tokenContracts, 1)
  4299  		gasesV0 = &tokenGases
  4300  		eth.versionedGases[0] = gasesV0
  4301  		eth.versionedGases[1] = gasesV1
  4302  		node.tokenContractor.bal = dexeth.GweiToWei(1e9)
  4303  	}
  4304  
  4305  	assetV0.MaxFeeRate = 45
  4306  	assetV1.Version = 1
  4307  	assetV1.MaxFeeRate = 50
  4308  
  4309  	// Lock for 3 refunds with contract version 0
  4310  	v0Val, err := w.ReserveNRefunds(3, assetV0.Version, assetV0.MaxFeeRate)
  4311  	if err != nil {
  4312  		t.Fatalf("ReserveNRefunds error: %v", err)
  4313  	}
  4314  	lockPerV0 := gasesV0.Refund * assetV0.MaxFeeRate
  4315  
  4316  	expLock := 3 * lockPerV0
  4317  	if feeWallet.lockedFunds.refundReserves != expLock {
  4318  		t.Fatalf("wrong v0 locked. wanted %d, got %d", expLock, feeWallet.lockedFunds.refundReserves)
  4319  	}
  4320  	if v0Val != expLock {
  4321  		t.Fatalf("expected locked %d, got %d", expLock, v0Val)
  4322  	}
  4323  
  4324  	// Lock for 2 refunds with contract version 1
  4325  	v1Val, err := w.ReserveNRefunds(2, assetV1.Version, assetV1.MaxFeeRate)
  4326  	if err != nil {
  4327  		t.Fatalf("ReserveNRefunds error: %v", err)
  4328  	}
  4329  	lockPerV1 := gasesV1.Refund * assetV1.MaxFeeRate
  4330  	v1Lock := 2 * lockPerV1
  4331  	expLock += v1Lock
  4332  	if feeWallet.lockedFunds.refundReserves != expLock {
  4333  		t.Fatalf("wrong v1 locked. wanted %d, got %d", expLock, feeWallet.lockedFunds.refundReserves)
  4334  	}
  4335  	if v1Val != v1Lock {
  4336  		t.Fatalf("expected locked %d, got %d", v1Lock, v1Val)
  4337  	}
  4338  
  4339  	w.UnlockRefundReserves(9e5)
  4340  	expLock -= 9e5
  4341  	if feeWallet.lockedFunds.refundReserves != expLock {
  4342  		t.Fatalf("incorrect amount locked. wanted %d, got %d", expLock, feeWallet.lockedFunds.refundReserves)
  4343  	}
  4344  
  4345  	// Reserve more than available should return an error
  4346  	err = w.ReReserveRefund(1e9 + 1)
  4347  	if err == nil {
  4348  		t.Fatalf("expected an error but did not get")
  4349  	}
  4350  
  4351  	err = w.ReReserveRefund(5e6)
  4352  	if err != nil {
  4353  		t.Fatalf("unexpected error: %v", err)
  4354  	}
  4355  	expLock += 5e6
  4356  	if feeWallet.lockedFunds.refundReserves != expLock {
  4357  		t.Fatalf("incorrect amount locked. wanted %d, got %d", expLock, feeWallet.lockedFunds.refundReserves)
  4358  	}
  4359  }
  4360  
  4361  func TestRedemptionReserves(t *testing.T) {
  4362  	t.Run("eth", func(t *testing.T) { testRedemptionReserves(t, BipID) })
  4363  	t.Run("token", func(t *testing.T) { testRedemptionReserves(t, usdcTokenID) })
  4364  }
  4365  
  4366  func testRedemptionReserves(t *testing.T, assetID uint32) {
  4367  	wi, eth, node, shutdown := tassetWallet(assetID)
  4368  	defer shutdown()
  4369  
  4370  	w := wi.(asset.AccountLocker)
  4371  
  4372  	node.bal = dexeth.GweiToWei(1e9)
  4373  	// node.tContractor.swapMap =  map[[32]byte]*dexeth.SwapState{
  4374  	// 	secretHashes[0]: {
  4375  	// 		State: dexeth.SSInitiated,
  4376  	// 	},
  4377  	// },
  4378  
  4379  	var secretHash [32]byte
  4380  	node.tContractor.swapMap[secretHash] = &dexeth.SwapState{}
  4381  
  4382  	gasesV1 := &dexeth.Gases{Redeem: 1e6, RedeemAdd: 85e5}
  4383  	gasesV0 := dexeth.VersionedGases[0]
  4384  	assetV0 := *tETH
  4385  	assetV1 := *tETH
  4386  	feeWallet := eth
  4387  	if assetID == BipID {
  4388  		eth.versionedGases[1] = gasesV1
  4389  	} else {
  4390  		node.tokenContractor.allow = unlimitedAllowanceReplenishThreshold
  4391  		feeWallet = node.tokenParent
  4392  		assetV0 = *tToken
  4393  		assetV1 = *tToken
  4394  		gasesV0 = &tokenGases
  4395  		eth.versionedGases[0] = gasesV0
  4396  		eth.versionedGases[1] = gasesV1
  4397  	}
  4398  
  4399  	assetV0.MaxFeeRate = 45
  4400  	assetV1.Version = 1
  4401  	assetV1.MaxFeeRate = 50
  4402  
  4403  	v0Val, err := w.ReserveNRedemptions(3, assetV0.Version, assetV0.MaxFeeRate)
  4404  	if err != nil {
  4405  		t.Fatalf("reservation error: %v", err)
  4406  	}
  4407  
  4408  	lockPerV0 := gasesV0.Redeem * assetV0.MaxFeeRate
  4409  	expLock := 3 * lockPerV0
  4410  
  4411  	if feeWallet.lockedFunds.redemptionReserves != expLock {
  4412  		t.Fatalf("wrong v0 locked. wanted %d, got %d", expLock, feeWallet.lockedFunds.redemptionReserves)
  4413  	}
  4414  
  4415  	if v0Val != expLock {
  4416  		t.Fatalf("expected value %d, got %d", lockPerV0, v0Val)
  4417  	}
  4418  
  4419  	v1Val, err := w.ReserveNRedemptions(2, assetV1.Version, assetV1.MaxFeeRate)
  4420  	if err != nil {
  4421  		t.Fatalf("reservation error: %v", err)
  4422  	}
  4423  
  4424  	lockPerV1 := gasesV1.Redeem * assetV1.MaxFeeRate
  4425  	v1Lock := 2 * lockPerV1
  4426  	if v1Val != v1Lock {
  4427  		t.Fatal("wrong reserved val", v1Val, v1Lock)
  4428  	}
  4429  
  4430  	expLock += v1Lock
  4431  	if feeWallet.lockedFunds.redemptionReserves != expLock {
  4432  		t.Fatalf("wrong v1 locked. wanted %d, got %d", expLock, feeWallet.lockedFunds.redemptionReserves)
  4433  	}
  4434  
  4435  	// Run some token tests
  4436  	if assetID == BipID {
  4437  		return
  4438  	}
  4439  }
  4440  
  4441  func ethToGwei(v uint64) uint64 {
  4442  	return v * dexeth.GweiFactor
  4443  }
  4444  
  4445  func ethToWei(v uint64) *big.Int {
  4446  	bigV := new(big.Int).SetUint64(ethToGwei(v))
  4447  	return new(big.Int).Mul(bigV, big.NewInt(dexeth.GweiFactor))
  4448  }
  4449  
  4450  func TestReconfigure(t *testing.T) {
  4451  	w, eth, _, shutdown := tassetWallet(BipID)
  4452  	defer shutdown()
  4453  
  4454  	reconfigurer, is := w.(asset.LiveReconfigurer)
  4455  	if !is {
  4456  		t.Fatal("wallet is not a reconfigurer")
  4457  	}
  4458  
  4459  	ethCfg := &WalletConfig{
  4460  		GasFeeLimit: 123,
  4461  	}
  4462  
  4463  	settings, err := config.Mapify(ethCfg)
  4464  	if err != nil {
  4465  		t.Fatal("failed to mapify")
  4466  	}
  4467  
  4468  	walletCfg := &asset.WalletConfig{
  4469  		Type:     walletTypeRPC,
  4470  		Settings: settings,
  4471  	}
  4472  
  4473  	restart, err := reconfigurer.Reconfigure(context.Background(), walletCfg, "")
  4474  	if err != nil {
  4475  		t.Fatalf("unexpected error: %v", err)
  4476  	}
  4477  	if restart {
  4478  		t.Fatalf("unexpected restart")
  4479  	}
  4480  
  4481  	if eth.baseWallet.gasFeeLimit() != ethCfg.GasFeeLimit {
  4482  		t.Fatal("gas fee limit was not updated properly")
  4483  	}
  4484  }
  4485  
  4486  func TestSend(t *testing.T) {
  4487  	t.Run("eth", func(t *testing.T) { testSend(t, BipID) })
  4488  	t.Run("token", func(t *testing.T) { testSend(t, usdcTokenID) })
  4489  }
  4490  
  4491  func testSend(t *testing.T, assetID uint32) {
  4492  	w, eth, node, shutdown := tassetWallet(assetID)
  4493  	defer shutdown()
  4494  
  4495  	tx := tTx(0, 0, 0, &testAddressA, nil, 21000)
  4496  	txHash := tx.Hash()
  4497  
  4498  	node.sendTxTx = tx
  4499  	node.tokenContractor.transferTx = tx
  4500  
  4501  	maxFeeRate, _, _ := eth.recommendedMaxFeeRate(eth.ctx)
  4502  	ethFees := dexeth.WeiToGwei(maxFeeRate) * defaultSendGasLimit
  4503  	tokenFees := dexeth.WeiToGwei(maxFeeRate) * tokenGases.Transfer
  4504  
  4505  	const val = 10e9
  4506  	const testAddr = "dd93b447f7eBCA361805eBe056259853F3912E04"
  4507  	tests := []struct {
  4508  		name              string
  4509  		sendAdj, feeAdj   uint64
  4510  		balErr, sendTxErr error
  4511  		addr              string
  4512  		wantErr           bool
  4513  	}{{
  4514  		name: "ok",
  4515  		addr: testAddr,
  4516  	}, {
  4517  		name:    "balance error",
  4518  		balErr:  errors.New("test error"),
  4519  		wantErr: true,
  4520  		addr:    testAddr,
  4521  	}, {
  4522  		name:    "not enough",
  4523  		sendAdj: 1,
  4524  		wantErr: true,
  4525  		addr:    testAddr,
  4526  	}, {
  4527  		name:    "low fees",
  4528  		feeAdj:  1,
  4529  		wantErr: true,
  4530  		addr:    testAddr,
  4531  	}, {
  4532  		name:      "sendToAddr error",
  4533  		sendTxErr: errors.New("test error"),
  4534  		wantErr:   true,
  4535  		addr:      testAddr,
  4536  	}, {
  4537  		name:      "Invalid address",
  4538  		sendTxErr: errors.New("invalid hex address error"),
  4539  		wantErr:   true,
  4540  		addr:      "",
  4541  	}}
  4542  
  4543  	for _, test := range tests {
  4544  		node.setBalanceError(eth, test.balErr)
  4545  		node.sendTxErr = test.sendTxErr
  4546  		node.tokenContractor.transferErr = test.sendTxErr
  4547  		if assetID == BipID {
  4548  			node.bal = dexeth.GweiToWei(val + ethFees - test.sendAdj - test.feeAdj)
  4549  		} else {
  4550  			node.tokenContractor.bal = dexeth.GweiToWei(val - test.sendAdj)
  4551  			node.bal = dexeth.GweiToWei(tokenFees - test.feeAdj)
  4552  		}
  4553  		coin, err := w.Send(test.addr, val, 0)
  4554  		if test.wantErr {
  4555  			if err == nil {
  4556  				t.Fatalf("expected error for test %v", test.name)
  4557  			}
  4558  			continue
  4559  		}
  4560  		if err != nil {
  4561  			t.Fatalf("unexpected error for test %v: %v", test.name, err)
  4562  		}
  4563  		if !bytes.Equal(txHash[:], coin.ID()) {
  4564  			t.Fatal("coin is not the tx hash")
  4565  		}
  4566  	}
  4567  }
  4568  
  4569  func TestConfirmRedemption(t *testing.T) {
  4570  	t.Run("eth", func(t *testing.T) { testConfirmRedemption(t, BipID) })
  4571  	t.Run("token", func(t *testing.T) { testConfirmRedemption(t, usdcTokenID) })
  4572  }
  4573  
  4574  func testConfirmRedemption(t *testing.T, assetID uint32) {
  4575  	wi, eth, node, shutdown := tassetWallet(assetID)
  4576  	defer shutdown()
  4577  
  4578  	db := eth.txDB.(*tTxDB)
  4579  
  4580  	const tip = 12
  4581  	const confBlock = tip - txConfsNeededToConfirm + 1
  4582  
  4583  	var secret, secretHash [32]byte
  4584  	copy(secret[:], encode.RandomBytes(32))
  4585  	copy(secretHash[:], encode.RandomBytes(32))
  4586  	var txHash common.Hash
  4587  	copy(txHash[:], encode.RandomBytes(32))
  4588  
  4589  	redemption := &asset.Redemption{
  4590  		Spends: &asset.AuditInfo{
  4591  			Contract: dexeth.EncodeContractData(0, secretHash),
  4592  		},
  4593  		Secret: secret[:],
  4594  	}
  4595  
  4596  	pendingTx := &extendedWalletTx{
  4597  		WalletTransaction: &asset.WalletTransaction{
  4598  			ID:          txHash.String(),
  4599  			BlockNumber: confBlock + 1,
  4600  		},
  4601  		txHash: txHash,
  4602  	}
  4603  	dbTx := &extendedWalletTx{
  4604  		WalletTransaction: &asset.WalletTransaction{
  4605  			ID:          txHash.String(),
  4606  			BlockNumber: confBlock,
  4607  		},
  4608  	}
  4609  
  4610  	type test struct {
  4611  		name                      string
  4612  		expectedConfs             uint64
  4613  		expectErr                 bool
  4614  		expectSwapRefundedErr     bool
  4615  		expectRedemptionFailedErr bool
  4616  		pendingTx                 *extendedWalletTx
  4617  		dbTx                      *extendedWalletTx
  4618  		dbErr                     error
  4619  		step                      dexeth.SwapStep
  4620  		receipt                   *types.Receipt
  4621  		receiptErr                error
  4622  	}
  4623  
  4624  	tests := []*test{
  4625  		{
  4626  			name: "found on-chain. not yet confirmed",
  4627  			receipt: &types.Receipt{
  4628  				Status:      types.ReceiptStatusSuccessful,
  4629  				BlockNumber: big.NewInt(confBlock + 1),
  4630  			},
  4631  			expectedConfs: txConfsNeededToConfirm - 1,
  4632  		},
  4633  		{
  4634  			name:          "found on-chain. confirmed",
  4635  			step:          dexeth.SSRedeemed,
  4636  			expectedConfs: txConfsNeededToConfirm,
  4637  			receipt: &types.Receipt{
  4638  				Status:      types.ReceiptStatusSuccessful,
  4639  				BlockNumber: big.NewInt(confBlock),
  4640  			},
  4641  		},
  4642  		{
  4643  			name:          "found in pending txs",
  4644  			step:          dexeth.SSRedeemed,
  4645  			pendingTx:     pendingTx,
  4646  			expectedConfs: txConfsNeededToConfirm - 1,
  4647  		},
  4648  		{
  4649  			name:          "found in db",
  4650  			step:          dexeth.SSRedeemed,
  4651  			dbTx:          dbTx,
  4652  			expectedConfs: txConfsNeededToConfirm,
  4653  			receipt: &types.Receipt{
  4654  				Status:      types.ReceiptStatusSuccessful,
  4655  				BlockNumber: big.NewInt(confBlock),
  4656  			},
  4657  		},
  4658  		{
  4659  			name:          "db error not propagated. unconfirmed",
  4660  			step:          dexeth.SSRedeemed,
  4661  			dbErr:         errors.New("test error"),
  4662  			expectedConfs: txConfsNeededToConfirm - 1,
  4663  			receipt: &types.Receipt{
  4664  				Status:      types.ReceiptStatusSuccessful,
  4665  				BlockNumber: big.NewInt(confBlock + 1),
  4666  			},
  4667  		},
  4668  		{
  4669  			name:                      "found on-chain. tx failed",
  4670  			step:                      dexeth.SSInitiated,
  4671  			expectErr:                 true,
  4672  			expectRedemptionFailedErr: true,
  4673  			receipt: &types.Receipt{
  4674  				Status:      types.ReceiptStatusFailed,
  4675  				BlockNumber: big.NewInt(confBlock),
  4676  			},
  4677  		},
  4678  		{
  4679  			name: "found on-chain. redeemed by another unknown transaction",
  4680  			step: dexeth.SSRedeemed,
  4681  			receipt: &types.Receipt{
  4682  				Status:      types.ReceiptStatusFailed,
  4683  				BlockNumber: big.NewInt(confBlock),
  4684  			},
  4685  			expectedConfs: txConfsNeededToConfirm,
  4686  		},
  4687  	}
  4688  
  4689  	runTest := func(test *test) {
  4690  		fmt.Printf("###### %s ###### \n", test.name)
  4691  
  4692  		node.tContractor.swapMap = map[[32]byte]*dexeth.SwapState{
  4693  			secretHash: {State: test.step},
  4694  		}
  4695  		node.tContractor.lastRedeems = nil
  4696  		node.tokenContractor.bal = big.NewInt(1e9)
  4697  		node.bal = big.NewInt(1e9)
  4698  
  4699  		eth.pendingTxs = []*extendedWalletTx{}
  4700  		if test.pendingTx != nil {
  4701  			eth.pendingTxs = append(eth.pendingTxs, test.pendingTx)
  4702  		}
  4703  
  4704  		db.txToGet = test.dbTx
  4705  		db.getTxErr = test.dbErr
  4706  
  4707  		node.lastSignedTx = nil
  4708  		eth.currentTip = &types.Header{Number: big.NewInt(tip)}
  4709  		node.receipt = test.receipt
  4710  		node.receiptErr = test.receiptErr
  4711  
  4712  		result, err := wi.ConfirmRedemption(txHash[:], redemption, 0)
  4713  		if test.expectErr {
  4714  			if err == nil {
  4715  				t.Fatalf("%s: expected error but did not get", test.name)
  4716  			}
  4717  			if test.expectRedemptionFailedErr && !errors.Is(err, asset.ErrTxRejected) {
  4718  				t.Fatalf("%s: expected rejected tx error. got %v", test.name, err)
  4719  			}
  4720  			if test.expectSwapRefundedErr && !errors.Is(asset.ErrSwapRefunded, err) {
  4721  				t.Fatalf("%s: expected swap refunded error but got %v", test.name, err)
  4722  			}
  4723  			return
  4724  		}
  4725  		if err != nil {
  4726  			t.Fatalf("%s: unexpected error: %v", test.name, err)
  4727  		}
  4728  
  4729  		// Check that the resulting status is as expected
  4730  		if test.expectedConfs != result.Confs ||
  4731  			txConfsNeededToConfirm != result.Req {
  4732  			t.Fatalf("%s: expected confs %d != result %d", test.name, test.expectedConfs, result.Confs)
  4733  		}
  4734  	}
  4735  
  4736  	for _, test := range tests {
  4737  		runTest(test)
  4738  	}
  4739  }
  4740  
  4741  // Ensures that a small rise in the base fee between estimation
  4742  // and sending will not cause a failure.
  4743  func TestEstimateVsActualSendFees(t *testing.T) {
  4744  	t.Run("eth", func(t *testing.T) { testEstimateVsActualSendFees(t, BipID) })
  4745  	t.Run("token", func(t *testing.T) { testEstimateVsActualSendFees(t, usdcTokenID) })
  4746  }
  4747  
  4748  func testEstimateVsActualSendFees(t *testing.T, assetID uint32) {
  4749  	w, _, node, shutdown := tassetWallet(assetID)
  4750  	defer shutdown()
  4751  
  4752  	tx := tTx(0, 0, 0, &testAddressA, nil, 21000)
  4753  	node.sendTxTx = tx
  4754  	node.tokenContractor.transferTx = tx
  4755  
  4756  	const testAddr = "dd93b447f7eBCA361805eBe056259853F3912E04"
  4757  
  4758  	txFeeEstimator := w.(asset.TxFeeEstimator)
  4759  	fee, _, err := txFeeEstimator.EstimateSendTxFee("", 0, 0, false, false)
  4760  	if err != nil {
  4761  		t.Fatalf("error estimating fee: %v", err)
  4762  	}
  4763  
  4764  	// Increase the base fee by 10%.
  4765  	node.baseFee = node.baseFee.Mul(node.baseFee, big.NewInt(11))
  4766  	node.baseFee = node.baseFee.Div(node.baseFee, big.NewInt(10))
  4767  
  4768  	if assetID == BipID {
  4769  		node.bal = dexeth.GweiToWei(11e9)
  4770  		canSend := new(big.Int).Sub(node.bal, dexeth.GweiToWei(fee))
  4771  		canSendGwei, err := dexeth.WeiToGweiSafe(canSend)
  4772  		if err != nil {
  4773  			t.Fatalf("error converting canSend to gwei: %v", err)
  4774  		}
  4775  		_, err = w.Send(testAddr, canSendGwei, 0)
  4776  		if err != nil {
  4777  			t.Fatalf("error sending: %v", err)
  4778  		}
  4779  	} else {
  4780  		tokenVal := uint64(10e9)
  4781  		node.tokenContractor.bal = dexeth.GweiToWei(tokenVal)
  4782  		node.bal = dexeth.GweiToWei(fee)
  4783  		_, err = w.Send(testAddr, tokenVal, 0)
  4784  		if err != nil {
  4785  			t.Fatalf("error sending: %v", err)
  4786  		}
  4787  	}
  4788  }
  4789  
  4790  func TestEstimateSendTxFee(t *testing.T) {
  4791  	t.Run("eth", func(t *testing.T) { testEstimateSendTxFee(t, BipID) })
  4792  	t.Run("token", func(t *testing.T) { testEstimateSendTxFee(t, usdcTokenID) })
  4793  }
  4794  
  4795  func testEstimateSendTxFee(t *testing.T, assetID uint32) {
  4796  	w, eth, node, shutdown := tassetWallet(assetID)
  4797  	defer shutdown()
  4798  
  4799  	maxFeeRate, _, _ := eth.recommendedMaxFeeRate(eth.ctx)
  4800  	ethFees := dexeth.WeiToGwei(maxFeeRate) * defaultSendGasLimit
  4801  	tokenFees := dexeth.WeiToGwei(maxFeeRate) * tokenGases.Transfer
  4802  
  4803  	ethFees = ethFees * 12 / 10
  4804  	tokenFees = tokenFees * 12 / 10
  4805  
  4806  	const testAddr = "dd93b447f7eBCA361805eBe056259853F3912E04"
  4807  
  4808  	const val = 10e9
  4809  	tests := []struct {
  4810  		name, addr      string
  4811  		sendAdj, feeAdj uint64
  4812  		balErr          error
  4813  		withdraw        bool
  4814  		wantErr         bool
  4815  	}{{
  4816  		name: "ok",
  4817  		addr: testAddr,
  4818  	}, {
  4819  		name: "ok: empty address",
  4820  		addr: "",
  4821  	}, {
  4822  		name:    "not enough",
  4823  		sendAdj: 1,
  4824  		wantErr: true,
  4825  		addr:    testAddr,
  4826  	}, {
  4827  		name:    "low fees",
  4828  		feeAdj:  1,
  4829  		wantErr: true,
  4830  		addr:    testAddr,
  4831  	}, {
  4832  		name:     "subtract",
  4833  		feeAdj:   1,
  4834  		withdraw: true,
  4835  		wantErr:  true,
  4836  		addr:     testAddr,
  4837  	}, {
  4838  		name:    "balance error",
  4839  		balErr:  errors.New("test error"),
  4840  		wantErr: true,
  4841  		addr:    testAddr,
  4842  	}}
  4843  
  4844  	for _, test := range tests {
  4845  		node.setBalanceError(eth, test.balErr)
  4846  		if assetID == BipID {
  4847  			node.bal = dexeth.GweiToWei(val + ethFees - test.sendAdj - test.feeAdj)
  4848  		} else {
  4849  			node.tokenContractor.bal = dexeth.GweiToWei(val - test.sendAdj)
  4850  			node.bal = dexeth.GweiToWei(tokenFees - test.feeAdj)
  4851  		}
  4852  		txFeeEstimator := w.(asset.TxFeeEstimator)
  4853  		estimate, _, err := txFeeEstimator.EstimateSendTxFee(test.addr, val, 0, false, false)
  4854  		if test.wantErr {
  4855  			if err == nil {
  4856  				t.Fatalf("expected error for test %v", test.name)
  4857  			}
  4858  			continue
  4859  		}
  4860  		if assetID == BipID {
  4861  			if estimate != ethFees {
  4862  				t.Fatalf("%s: expected fees to be %v, got %v", test.name, ethFees, estimate)
  4863  			}
  4864  		} else {
  4865  			if estimate != tokenFees {
  4866  				t.Fatalf("%s: expected fees to be %v, got %v", test.name, tokenFees, estimate)
  4867  			}
  4868  		}
  4869  		if err != nil {
  4870  			t.Fatalf("%s: unexpected error: %v", test.name, err)
  4871  		}
  4872  	}
  4873  }
  4874  
  4875  // This test will fail if new versions of the eth or the test token
  4876  // contract (that require more gas) are added.
  4877  func TestMaxSwapRedeemLots(t *testing.T) {
  4878  	t.Run("eth", func(t *testing.T) { testMaxSwapRedeemLots(t, BipID) })
  4879  	t.Run("token", func(t *testing.T) { testMaxSwapRedeemLots(t, usdcTokenID) })
  4880  }
  4881  
  4882  func testMaxSwapRedeemLots(t *testing.T, assetID uint32) {
  4883  	drv := &Driver{}
  4884  	logger := dex.StdOutLogger("ETHTEST", dex.LevelOff)
  4885  	tmpDir := t.TempDir()
  4886  
  4887  	settings := map[string]string{providersKey: "a.ipc"}
  4888  	err := CreateEVMWallet(dexeth.ChainIDs[dex.Testnet], &asset.CreateWalletParams{
  4889  		Type:     walletTypeRPC,
  4890  		Seed:     encode.RandomBytes(32),
  4891  		Pass:     encode.RandomBytes(32),
  4892  		Settings: settings,
  4893  		DataDir:  tmpDir,
  4894  		Net:      dex.Testnet,
  4895  		Logger:   logger,
  4896  	}, &testnetCompatibilityData, true)
  4897  	if err != nil {
  4898  		t.Fatalf("CreateEVMWallet error: %v", err)
  4899  	}
  4900  
  4901  	wallet, err := drv.Open(&asset.WalletConfig{
  4902  		Type:     walletTypeRPC,
  4903  		Settings: settings,
  4904  		DataDir:  tmpDir,
  4905  	}, logger, dex.Testnet)
  4906  	if err != nil {
  4907  		t.Fatalf("driver open error: %v", err)
  4908  	}
  4909  
  4910  	if assetID != BipID {
  4911  		eth, _ := wallet.(*ETHWallet)
  4912  		eth.net = dex.Simnet
  4913  		wallet, err = eth.OpenTokenWallet(&asset.TokenConfig{
  4914  			AssetID: assetID,
  4915  		})
  4916  		if err != nil {
  4917  			t.Fatal(err)
  4918  		}
  4919  	}
  4920  
  4921  	info := wallet.Info()
  4922  	if assetID == BipID {
  4923  		if info.MaxSwapsInTx != 28 {
  4924  			t.Fatalf("expected 28 for max swaps but got %d", info.MaxSwapsInTx)
  4925  		}
  4926  		if info.MaxRedeemsInTx != 63 {
  4927  			t.Fatalf("expected 63 for max redemptions but got %d", info.MaxRedeemsInTx)
  4928  		}
  4929  	} else {
  4930  		if info.MaxSwapsInTx != 20 {
  4931  			t.Fatalf("expected 20 for max swaps but got %d", info.MaxSwapsInTx)
  4932  		}
  4933  		if info.MaxRedeemsInTx != 45 {
  4934  			t.Fatalf("expected 45 for max redemptions but got %d", info.MaxRedeemsInTx)
  4935  		}
  4936  	}
  4937  }
  4938  
  4939  func TestSwapOrRedemptionFeesPaid(t *testing.T) {
  4940  	ctx, cancel := context.WithCancel(context.Background())
  4941  	defer cancel()
  4942  	_, bw, node, shutdown := tassetWallet(BipID)
  4943  	defer shutdown()
  4944  
  4945  	secretHA, secretHB := encode.RandomBytes(32), encode.RandomBytes(32)
  4946  	contractDataFn := func(ver uint32, secretH []byte) []byte {
  4947  		s := [32]byte{}
  4948  		copy(s[:], secretH)
  4949  		return dexeth.EncodeContractData(ver, s)
  4950  	}
  4951  	const tip = 100
  4952  	const feeRate = 2 // gwei / gas
  4953  	const gasUsed = 100
  4954  	const fees = feeRate * gasUsed
  4955  
  4956  	bw.currentTip = &types.Header{
  4957  		BaseFee: dexeth.GweiToWei(2),
  4958  		Number:  big.NewInt(tip),
  4959  	}
  4960  
  4961  	confirmedReceipt := &types.Receipt{
  4962  		GasUsed:           gasUsed,
  4963  		EffectiveGasPrice: dexeth.GweiToWei(feeRate),
  4964  		BlockNumber:       big.NewInt(tip - txConfsNeededToConfirm + 1),
  4965  	}
  4966  
  4967  	unconfirmedReceipt := &types.Receipt{
  4968  		BlockNumber: big.NewInt(tip - txConfsNeededToConfirm + 2),
  4969  	}
  4970  
  4971  	initFn := func(secretHs [][]byte) []byte {
  4972  		inits := make([]*dexeth.Initiation, 0, len(secretHs))
  4973  		for i := range secretHs {
  4974  			s := [32]byte{}
  4975  			copy(s[:], secretHs[i])
  4976  			init := &dexeth.Initiation{
  4977  				SecretHash: s,
  4978  				Value:      big.NewInt(0),
  4979  			}
  4980  			inits = append(inits, init)
  4981  		}
  4982  		data, err := packInitiateDataV0(inits)
  4983  		if err != nil {
  4984  			t.Fatalf("problem packing inits: %v", err)
  4985  		}
  4986  		return data
  4987  	}
  4988  	redeemFn := func(secretHs [][]byte) []byte {
  4989  		redeems := make([]*dexeth.Redemption, 0, len(secretHs))
  4990  		for i := range secretHs {
  4991  			s := [32]byte{}
  4992  			copy(s[:], secretHs[i])
  4993  			redeem := &dexeth.Redemption{
  4994  				SecretHash: s,
  4995  			}
  4996  			redeems = append(redeems, redeem)
  4997  		}
  4998  		data, err := packRedeemDataV0(redeems)
  4999  		if err != nil {
  5000  			t.Fatalf("problem packing redeems: %v", err)
  5001  		}
  5002  		return data
  5003  	}
  5004  	abFn := func() [][]byte {
  5005  		return [][]byte{secretHA, secretHB}
  5006  	}
  5007  	sortedFn := func() [][]byte {
  5008  		ab := abFn()
  5009  		sort.Slice(ab, func(i, j int) bool { return bytes.Compare(ab[i], ab[j]) < 0 })
  5010  		return ab
  5011  	}
  5012  	initTx := tTx(200, 2, 0, nil, initFn(abFn()), 200)
  5013  	redeemTx := tTx(200, 3, 0, nil, redeemFn(abFn()), 200)
  5014  	tests := []struct {
  5015  		name            string
  5016  		contractData    []byte
  5017  		isInit, wantErr bool
  5018  		receipt         *types.Receipt
  5019  		receiptTx       *types.Transaction
  5020  		receiptErr      error
  5021  		wantSecrets     [][]byte
  5022  		pendingTx       *types.Transaction
  5023  		pendingTxBlock  uint64
  5024  	}{{
  5025  		name:         "ok init",
  5026  		contractData: contractDataFn(0, secretHA),
  5027  		isInit:       true,
  5028  		receipt:      confirmedReceipt,
  5029  		receiptTx:    initTx,
  5030  		wantSecrets:  sortedFn(),
  5031  	}, {
  5032  		name:         "ok redeem",
  5033  		contractData: contractDataFn(0, secretHB),
  5034  		receipt:      confirmedReceipt,
  5035  		receiptTx:    redeemTx,
  5036  		wantSecrets:  sortedFn(),
  5037  	}, {
  5038  		name:           "ok init from pending txs",
  5039  		contractData:   contractDataFn(0, secretHA),
  5040  		isInit:         true,
  5041  		pendingTx:      initTx,
  5042  		pendingTxBlock: confirmedReceipt.BlockNumber.Uint64(),
  5043  		wantSecrets:    sortedFn(),
  5044  	}, {
  5045  		name:           "ok redeem from pending txs",
  5046  		contractData:   contractDataFn(0, secretHB),
  5047  		pendingTx:      redeemTx,
  5048  		pendingTxBlock: confirmedReceipt.BlockNumber.Uint64(),
  5049  		wantSecrets:    sortedFn(),
  5050  	}, {
  5051  		name:         "bad contract data",
  5052  		contractData: nil,
  5053  		wantErr:      true,
  5054  	}, {
  5055  		name:         "receipt error",
  5056  		contractData: contractDataFn(0, secretHA),
  5057  		receiptErr:   errors.New("test error"),
  5058  		wantErr:      true,
  5059  	}, {
  5060  		name:         "not enough confirms",
  5061  		contractData: contractDataFn(0, secretHA),
  5062  		receipt:      unconfirmedReceipt,
  5063  		wantErr:      true,
  5064  	}, {
  5065  		name:           "not enough confs, pending tx",
  5066  		contractData:   contractDataFn(0, secretHA),
  5067  		isInit:         true,
  5068  		pendingTx:      initTx,
  5069  		pendingTxBlock: confirmedReceipt.BlockNumber.Uint64() + 1,
  5070  		wantSecrets:    sortedFn(),
  5071  		wantErr:        true,
  5072  	}, {
  5073  		name:         "bad init data",
  5074  		contractData: contractDataFn(0, secretHA),
  5075  		isInit:       true,
  5076  		receipt:      confirmedReceipt,
  5077  		receiptTx:    tTx(200, 2, 0, nil, nil, 200),
  5078  		wantErr:      true,
  5079  	}, {
  5080  		name:         "bad redeem data",
  5081  		contractData: contractDataFn(0, secretHA),
  5082  		receipt:      confirmedReceipt,
  5083  		receiptTx:    tTx(200, 2, 0, nil, nil, 200),
  5084  		wantErr:      true,
  5085  	}, {
  5086  		name:         "secret hash not found",
  5087  		contractData: contractDataFn(0, secretHB),
  5088  		isInit:       true,
  5089  		receipt:      confirmedReceipt,
  5090  		receiptTx:    tTx(200, 2, 0, nil, initFn([][]byte{secretHA}), 200),
  5091  		wantErr:      true,
  5092  	}}
  5093  	for _, test := range tests {
  5094  		var txHash common.Hash
  5095  		if test.pendingTx != nil {
  5096  			wt := bw.extendedTx(test.pendingTx, asset.Unknown, 1, nil)
  5097  			wt.BlockNumber = test.pendingTxBlock
  5098  			wt.Fees = fees
  5099  			bw.pendingTxs = []*extendedWalletTx{wt}
  5100  			txHash = test.pendingTx.Hash()
  5101  		}
  5102  		node.receiptTx = test.receiptTx
  5103  		node.receipt = test.receipt
  5104  		node.receiptErr = test.receiptErr
  5105  		feesPaid, secretHs, err := bw.swapOrRedemptionFeesPaid(ctx, txHash[:], test.contractData, test.isInit)
  5106  		if test.wantErr {
  5107  			if err == nil {
  5108  				t.Fatalf("%q: expected error", test.name)
  5109  			}
  5110  			continue
  5111  		}
  5112  		if err != nil {
  5113  			t.Fatalf("%q: unexpected error: %v", test.name, err)
  5114  		}
  5115  		if feesPaid != fees {
  5116  			t.Fatalf("%q: wanted fee %d but got %d", test.name, fees, feesPaid)
  5117  		}
  5118  		if len(test.wantSecrets) != len(secretHs) {
  5119  			t.Fatalf("%q: wanted %d secrets but got %d", test.name, len(test.wantSecrets), len(secretHs))
  5120  		}
  5121  		for i := range test.wantSecrets {
  5122  			sGot := secretHs[i]
  5123  			sWant := test.wantSecrets[i]
  5124  			if !bytes.Equal(sGot, sWant) {
  5125  				t.Fatalf("%q: wanted secret %x but got %x at position %d", test.name, sWant, sGot, i)
  5126  			}
  5127  		}
  5128  	}
  5129  }
  5130  
  5131  func TestReceiptCache(t *testing.T) {
  5132  	m := &multiRPCClient{
  5133  		finalizeConfs: 3,
  5134  	}
  5135  	c := make(map[common.Hash]*receiptRecord)
  5136  	m.receipts.cache = c
  5137  
  5138  	r := &receiptRecord{
  5139  		r: &types.Receipt{
  5140  			Type: 50,
  5141  		},
  5142  		lastAccess: time.Now(),
  5143  	}
  5144  
  5145  	var txHash common.Hash
  5146  	copy(txHash[:], encode.RandomBytes(32))
  5147  	c[txHash] = r
  5148  
  5149  	if r := m.cachedReceipt(txHash); r == nil {
  5150  		t.Fatalf("cached receipt not returned")
  5151  	}
  5152  
  5153  	r.lastAccess = time.Now().Add(-(unconfirmedReceiptExpiration + 1))
  5154  	if r := m.cachedReceipt(txHash); r != nil {
  5155  		t.Fatalf("expired receipt returned")
  5156  	}
  5157  
  5158  	// The receipt still hasn't been pruned.
  5159  	if len(c) != 1 {
  5160  		t.Fatalf("receipt was pruned?")
  5161  	}
  5162  
  5163  	// An if it was confirmed, it would be returned.
  5164  	r.confirmed = true
  5165  	if r := m.cachedReceipt(txHash); r == nil {
  5166  		t.Fatalf("confirmed receipt not returned")
  5167  	}
  5168  
  5169  	r.lastAccess = time.Now().Add(-(receiptCacheExpiration + 1))
  5170  	m.receipts.lastClean = time.Time{}
  5171  	m.cachedReceipt(common.Hash{})
  5172  	// The receipt still hasn't been pruned.
  5173  	if len(c) != 0 {
  5174  		t.Fatalf("receipt wasn't pruned")
  5175  	}
  5176  
  5177  }
  5178  
  5179  func TestFreshProviderList(t *testing.T) {
  5180  
  5181  	tests := []struct {
  5182  		times    []int64
  5183  		expOrder []int
  5184  	}{
  5185  		{
  5186  			times:    []int64{1, 2, 3},
  5187  			expOrder: []int{0, 1, 2},
  5188  		},
  5189  		{
  5190  			times:    []int64{3, 2, 1},
  5191  			expOrder: []int{2, 1, 0},
  5192  		},
  5193  		{
  5194  			times:    []int64{1, 5, 4, 2, 3},
  5195  			expOrder: []int{0, 3, 4, 2, 1},
  5196  		},
  5197  	}
  5198  
  5199  	for i, tt := range tests {
  5200  		t.Run(fmt.Sprintf("test#%d", i), func(t *testing.T) {
  5201  			node := &multiRPCClient{
  5202  				finalizeConfs: 3,
  5203  				providers:     make([]*provider, len(tt.times)),
  5204  			}
  5205  			for i, stamp := range tt.times {
  5206  				p := &provider{}
  5207  				p.tip.headerStamp = time.Unix(stamp, 0)
  5208  				p.tip.failCount = i // hi-jacking field for initial sort order
  5209  				node.providers[i] = p
  5210  			}
  5211  			providers := node.freshnessSortedProviders()
  5212  			for i, p := range providers {
  5213  				if p.tip.failCount != tt.expOrder[i] {
  5214  					t.Fatalf("%d'th provider in sorted list is unexpected", i)
  5215  				}
  5216  			}
  5217  		})
  5218  	}
  5219  }
  5220  
  5221  func TestDomain(t *testing.T) {
  5222  	tests := []struct {
  5223  		addr       string
  5224  		wantDomain string
  5225  		wantErr    bool
  5226  	}{
  5227  		{
  5228  			addr:       "http://www.place.io/v3/234234wfsefe",
  5229  			wantDomain: "place.io",
  5230  		},
  5231  		{
  5232  			addr:       "wss://www.place.nz/stuff?=token",
  5233  			wantDomain: "place.nz",
  5234  		},
  5235  		{
  5236  			addr:       "http://en.us.lotssubdomains.place.io/v3/234234wfsefe",
  5237  			wantDomain: "place.io",
  5238  		},
  5239  		{
  5240  			addr:       "https://www.place.co.uk:443/blog/article/search?docid=720&hl=en#dayone",
  5241  			wantDomain: "place.co.uk:443",
  5242  		},
  5243  		{
  5244  			addr:       "wmba://www.place.com",
  5245  			wantDomain: "place.com",
  5246  		},
  5247  		{
  5248  			addr:       "ws://127.0.0.1:3000",
  5249  			wantDomain: "127.0.0.1:3000",
  5250  		},
  5251  		{
  5252  			addr:       "ws://localhost:3000",
  5253  			wantDomain: "localhost:3000",
  5254  		},
  5255  		{
  5256  			addr:       "http://123.123.123.123:3000",
  5257  			wantDomain: "123.123.123.123:3000",
  5258  		},
  5259  		{
  5260  			addr:       "https://123.123.123.123",
  5261  			wantDomain: "123.123.123.123",
  5262  		},
  5263  		{
  5264  			addr:       "ws://[abab:fde3:0:0:0:0:0:1]:8080",
  5265  			wantDomain: "[abab:fde3:0:0:0:0:0:1]:8080",
  5266  		},
  5267  		{
  5268  			addr:       "ws://[::1]:8000",
  5269  			wantDomain: "[::1]:8000",
  5270  		},
  5271  		{
  5272  			addr:       "[::1]:8000",
  5273  			wantDomain: "[::1]:8000",
  5274  		},
  5275  		{
  5276  			addr:       "[::1]",
  5277  			wantDomain: "[::1]",
  5278  		},
  5279  		{
  5280  			addr:       "127.0.0.1",
  5281  			wantDomain: "127.0.0.1",
  5282  		},
  5283  		{
  5284  			addr:       "/home/john/.geth/geth.ipc",
  5285  			wantDomain: "/home/john/.geth/geth.ipc",
  5286  		},
  5287  		{
  5288  			addr:       "/home/john/.geth/geth",
  5289  			wantDomain: "/home/john/.geth/geth",
  5290  		},
  5291  		{
  5292  			addr:    "https://\n:1234",
  5293  			wantErr: true,
  5294  		},
  5295  		{
  5296  			addr:    ":asdf",
  5297  			wantErr: true,
  5298  		},
  5299  		{
  5300  			wantErr: true,
  5301  		},
  5302  	}
  5303  
  5304  	for i, test := range tests {
  5305  		t.Run(fmt.Sprintf("test#%d", i), func(t *testing.T) {
  5306  			d, err := domain(test.addr)
  5307  			if test.wantErr {
  5308  				if err == nil {
  5309  					t.Fatalf("expected error")
  5310  				}
  5311  				return
  5312  			}
  5313  			if err != nil {
  5314  				t.Fatalf("unexpected error: %v", err)
  5315  			}
  5316  			if test.wantDomain != d {
  5317  				t.Fatalf("wanted domain %s but got %s", test.wantDomain, d)
  5318  			}
  5319  		})
  5320  	}
  5321  }
  5322  
  5323  func parseRecoveryID(c asset.Coin) []byte {
  5324  	return c.(asset.RecoveryCoin).RecoveryID()
  5325  }
  5326  
  5327  func randomHash() common.Hash {
  5328  	return common.BytesToHash(encode.RandomBytes(20))
  5329  }