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

     1  //go:build harness
     2  
     3  // This test requires that the simnet harness be running. Some tests will
     4  // alternatively work on testnet.
     5  //
     6  // NOTE: These test reuse a light node that lives in the dextest folders.
     7  // However, when recreating the test database for every test, the nonce used
     8  // for imported accounts is sometimes, randomly, off, which causes transactions
     9  // to not be mined and effectively makes the node unusable (at least before
    10  // restarting). It also seems to have caused getting balance of an account to
    11  // fail, and sometimes the redeem and refund functions to also fail. This could
    12  // be a problem in the future if a user restores from seed. Punting on this
    13  // particular problem for now.
    14  
    15  package eth
    16  
    17  import (
    18  	"bytes"
    19  	"context"
    20  	"crypto/ecdsa"
    21  	"crypto/elliptic"
    22  	"crypto/sha256"
    23  	"encoding/hex"
    24  	"encoding/json"
    25  	"errors"
    26  	"flag"
    27  	"fmt"
    28  	"math"
    29  	"math/big"
    30  	"os"
    31  	"os/exec"
    32  	"os/signal"
    33  	"path/filepath"
    34  	"strconv"
    35  	"strings"
    36  	"sync"
    37  	"testing"
    38  	"time"
    39  
    40  	"decred.org/dcrdex/client/asset"
    41  	"decred.org/dcrdex/dex"
    42  	"decred.org/dcrdex/dex/encode"
    43  	dexeth "decred.org/dcrdex/dex/networks/eth"
    44  	swapv0 "decred.org/dcrdex/dex/networks/eth/contracts/v0"
    45  	"github.com/davecgh/go-spew/spew"
    46  	"github.com/ethereum/go-ethereum/accounts"
    47  	"github.com/ethereum/go-ethereum/accounts/abi/bind"
    48  	"github.com/ethereum/go-ethereum/accounts/keystore"
    49  	"github.com/ethereum/go-ethereum/common"
    50  	"github.com/ethereum/go-ethereum/core/types"
    51  	"github.com/ethereum/go-ethereum/crypto"
    52  	"github.com/ethereum/go-ethereum/crypto/secp256k1"
    53  )
    54  
    55  const (
    56  	alphaNode         = "enode://897c84f6e4f18195413c1d02927e6a4093f5e7574b52bdec6f20844c4f1f6dd3f16036a9e600bd8681ab50fd8dd144df4a6ba9dd8722bb578a86aaa8222c964f@127.0.0.1:30304"
    57  	alphaAddr         = "18d65fb8d60c1199bb1ad381be47aa692b482605"
    58  	pw                = "bee75192465cef9f8ab1198093dfed594e93ae810ccbbf2b3b12e1771cc6cb19"
    59  	maxFeeRate uint64 = 200 // gwei per gas
    60  
    61  	// addPeer is optional and will be added if set. It should looks
    62  	// something like enode://1565e5bc2...2c3300e9@127.0.0.1:30303
    63  	// Be sure the full node is run with --light.serve ##
    64  	addPeer = ""
    65  )
    66  
    67  var (
    68  	homeDir                     = os.Getenv("HOME")
    69  	simnetWalletDir             = filepath.Join(homeDir, "dextest", "eth", "client_rpc_tests", "simnet")
    70  	participantWalletDir        = filepath.Join(homeDir, "dextest", "eth", "client_rpc_tests", "participant")
    71  	testnetWalletDir            string
    72  	testnetParticipantWalletDir string
    73  
    74  	alphaNodeDir               = filepath.Join(homeDir, "dextest", "eth", "alpha", "node")
    75  	alphaIPCFile               = filepath.Join(alphaNodeDir, "geth.ipc")
    76  	betaNodeDir                = filepath.Join(homeDir, "dextest", "eth", "beta", "node")
    77  	betaIPCFile                = filepath.Join(betaNodeDir, "geth.ipc")
    78  	ctx                        context.Context
    79  	tLogger                    = dex.StdOutLogger("ETHTEST", dex.LevelWarn)
    80  	simnetWalletSeed           = "0812f5244004217452059e2fd11603a511b5d0870ead753df76c966ce3c71531"
    81  	simnetAddr                 common.Address
    82  	simnetAcct                 *accounts.Account
    83  	ethClient                  ethFetcher
    84  	participantWalletSeed      = "a897afbdcba037c8c735cc63080558a30d72851eb5a3d05684400ec4123a2d00"
    85  	participantAddr            common.Address
    86  	participantAcct            *accounts.Account
    87  	participantEthClient       ethFetcher
    88  	ethSwapContractAddr        common.Address
    89  	simnetContractor           contractor
    90  	participantContractor      contractor
    91  	simnetTokenContractor      tokenContractor
    92  	participantTokenContractor tokenContractor
    93  	ethGases                   = dexeth.VersionedGases[0]
    94  	tokenGases                 *dexeth.Gases
    95  	secPerBlock                = 15 * time.Second
    96  	// If you are testing on testnet, you must specify the rpcNode. You can also
    97  	// specify it in the testnet-credentials.json file.
    98  	rpcProviders []string
    99  
   100  	// isTestnet can be set to true to perform tests on the sepolia testnet.
   101  	// May need some setup including sending testnet coins to the addresses
   102  	// and a lengthy sync. Wallet addresses are the same as simnet. Tests may
   103  	// need to be run with a high --timeout=2h for the initial sync.
   104  	//
   105  	// Only for non-token tests, so run with --run=TestGroupName.
   106  	//
   107  	// TODO: Make this also work for token tests.
   108  	isTestnet bool
   109  
   110  	// testnetWalletSeed and testnetParticipantWalletSeed are required for
   111  	// use on testnet and can be any 256 bit hex. If the wallets created by
   112  	// these seeds do not have enough funds to test, addresses that need
   113  	// funds will be printed.
   114  	testnetWalletSeed            string
   115  	testnetParticipantWalletSeed string
   116  	usdcID, _                    = dex.BipSymbolID("usdc.eth")
   117  	masterToken                  *dexeth.Token
   118  )
   119  
   120  func newContract(stamp uint64, secretHash [32]byte, val uint64) *asset.Contract {
   121  	return &asset.Contract{
   122  		LockTime:   stamp,
   123  		SecretHash: secretHash[:],
   124  		Address:    participantAddr.String(),
   125  		Value:      val,
   126  	}
   127  }
   128  
   129  func newRedeem(secret, secretHash [32]byte) *asset.Redemption {
   130  	return &asset.Redemption{
   131  		Spends: &asset.AuditInfo{
   132  			SecretHash: secretHash[:],
   133  		},
   134  		Secret: secret[:],
   135  	}
   136  }
   137  
   138  // waitForReceipt waits for a tx. This is useful on testnet when a tx may be "missing"
   139  // due to reorg. Wait for a few blocks to find the main chain and hopefully our tx.
   140  func waitForReceipt(nc ethFetcher, tx *types.Transaction) (*types.Receipt, error) {
   141  	hash := tx.Hash()
   142  	// Waiting as much as five blocks.
   143  	timesUp := time.After(5 * secPerBlock)
   144  	for {
   145  		select {
   146  		case <-ctx.Done():
   147  			return nil, ctx.Err()
   148  		case <-time.After(time.Second):
   149  			receipt, err := nc.transactionReceipt(ctx, hash)
   150  			if err != nil {
   151  				if errors.Is(err, asset.CoinNotFoundError) {
   152  					continue
   153  				}
   154  				return nil, err
   155  			}
   156  			// spew.Dump(receipt)
   157  			return receipt, nil
   158  		case <-timesUp:
   159  			spew.Dump(tx)
   160  			return nil, errors.New("wait for receipt timed out, txn might be missing due to reorg, " +
   161  				"check the preceding tx for a bad nonce or low gas cap")
   162  		}
   163  	}
   164  }
   165  
   166  func waitForMined() error {
   167  	hdr, err := ethClient.bestHeader(ctx)
   168  	if err != nil {
   169  		return err
   170  	}
   171  	const targetConfs = 1
   172  	currentHeight := hdr.Number
   173  	barrierHeight := new(big.Int).Add(currentHeight, big.NewInt(targetConfs))
   174  	fmt.Println("Waiting for RPC blocks")
   175  	for {
   176  		select {
   177  		case <-time.After(time.Second):
   178  			hdr, err = ethClient.bestHeader(ctx)
   179  			if err != nil {
   180  				return err
   181  			}
   182  			if hdr.Number.Cmp(barrierHeight) > 0 {
   183  				return nil
   184  			}
   185  			if hdr.Number.Cmp(currentHeight) > 0 {
   186  				currentHeight = hdr.Number
   187  				fmt.Println("Block mined!!!", new(big.Int).Sub(barrierHeight, currentHeight).Uint64()+1, "to go")
   188  			}
   189  		case <-ctx.Done():
   190  			return ctx.Err()
   191  		}
   192  	}
   193  }
   194  
   195  func prepareRPCClient(name, dataDir string, providers []string, net dex.Network) (*multiRPCClient, *accounts.Account, error) {
   196  	cfg, err := ChainConfig(net)
   197  	if err != nil {
   198  		return nil, nil, err
   199  	}
   200  
   201  	c, err := newMultiRPCClient(dataDir, providers, tLogger.SubLogger(name), cfg, 3, net)
   202  	if err != nil {
   203  		return nil, nil, fmt.Errorf("(%s) prepareRPCClient error: %v", name, err)
   204  	}
   205  	if err := c.connect(ctx); err != nil {
   206  		return nil, nil, fmt.Errorf("(%s) connect error: %v", name, err)
   207  	}
   208  	return c, c.creds.acct, nil
   209  }
   210  
   211  func rpcEndpoints(net dex.Network) ([]string, []string) {
   212  	if net == dex.Testnet {
   213  		return rpcProviders, rpcProviders
   214  	}
   215  	return []string{alphaIPCFile}, []string{betaIPCFile}
   216  }
   217  
   218  func prepareTestRPCClients(initiatorDir, participantDir string, net dex.Network) (err error) {
   219  	initiatorEndpoints, participantEndpoints := rpcEndpoints(net)
   220  
   221  	ethClient, simnetAcct, err = prepareRPCClient("initiator", initiatorDir, initiatorEndpoints, net)
   222  	if err != nil {
   223  		return err
   224  	}
   225  	fmt.Println("initiator address is", ethClient.address())
   226  
   227  	participantEthClient, participantAcct, err = prepareRPCClient("participant", participantDir, participantEndpoints, net)
   228  	if err != nil {
   229  		ethClient.shutdown()
   230  		return err
   231  	}
   232  	fmt.Println("participant address is", participantEthClient.address())
   233  	return nil
   234  }
   235  
   236  func runSimnet(m *testing.M) (int, error) {
   237  	// Create dir if none yet exists. This persists for the life of the
   238  	// testing harness.
   239  	err := os.MkdirAll(simnetWalletDir, 0755)
   240  	if err != nil {
   241  		return 1, fmt.Errorf("error creating simnet wallet dir dir: %v", err)
   242  	}
   243  	err = os.MkdirAll(participantWalletDir, 0755)
   244  	if err != nil {
   245  		return 1, fmt.Errorf("error creating participant wallet dir: %v", err)
   246  	}
   247  
   248  	const contractVer = 0
   249  
   250  	tokenGases = &dexeth.Tokens[usdcID].NetTokens[dex.Simnet].SwapContracts[contractVer].Gas
   251  
   252  	// ETH swap contract.
   253  	masterToken = dexeth.Tokens[usdcID]
   254  	token := masterToken.NetTokens[dex.Simnet]
   255  	fmt.Printf("ETH swap contract address is %v\n", dexeth.ContractAddresses[contractVer][dex.Simnet])
   256  	fmt.Printf("Token swap contract addr is %v\n", token.SwapContracts[0].Address)
   257  	fmt.Printf("Test token contract addr is %v\n", token.Address)
   258  
   259  	ethSwapContractAddr = dexeth.ContractAddresses[contractVer][dex.Simnet]
   260  
   261  	initiatorProviders, participantProviders := rpcEndpoints(dex.Simnet)
   262  
   263  	err = setupWallet(simnetWalletDir, simnetWalletSeed, "localhost:30355", initiatorProviders, dex.Simnet)
   264  	if err != nil {
   265  		return 1, err
   266  	}
   267  
   268  	err = setupWallet(participantWalletDir, participantWalletSeed, "localhost:30356", participantProviders, dex.Simnet)
   269  	if err != nil {
   270  		return 1, err
   271  	}
   272  
   273  	if err = prepareTestRPCClients(simnetWalletDir, participantWalletDir, dex.Simnet); err != nil {
   274  		return 1, err
   275  	}
   276  	defer ethClient.shutdown()
   277  	defer participantEthClient.shutdown()
   278  
   279  	if err := syncClient(ethClient); err != nil {
   280  		return 1, fmt.Errorf("error initializing initiator client: %v", err)
   281  	}
   282  	if err := syncClient(participantEthClient); err != nil {
   283  		return 1, fmt.Errorf("error initializing participant client: %v", err)
   284  	}
   285  
   286  	simnetAddr = simnetAcct.Address
   287  	participantAddr = participantAcct.Address
   288  
   289  	contractAddr, exists := dexeth.ContractAddresses[contractVer][dex.Simnet]
   290  	if !exists || contractAddr == (common.Address{}) {
   291  		return 1, fmt.Errorf("no contract address for version %d", contractVer)
   292  	}
   293  
   294  	if simnetContractor, err = newV0Contractor(contractAddr, simnetAddr, ethClient.contractBackend()); err != nil {
   295  		return 1, fmt.Errorf("newV0Contractor error: %w", err)
   296  	}
   297  	if participantContractor, err = newV0Contractor(contractAddr, participantAddr, participantEthClient.contractBackend()); err != nil {
   298  		return 1, fmt.Errorf("participant newV0Contractor error: %w", err)
   299  	}
   300  
   301  	if simnetTokenContractor, err = newV0TokenContractor(dex.Simnet, dexeth.Tokens[usdcID], simnetAddr, ethClient.contractBackend()); err != nil {
   302  		return 1, fmt.Errorf("newV0TokenContractor error: %w", err)
   303  	}
   304  
   305  	// I don't know why this is needed for the participant client but not
   306  	// the initiator. Without this, we'll get a bind.ErrNoCode from
   307  	// (*BoundContract).Call while calling (*ERC20Swap).TokenAddress.
   308  	time.Sleep(time.Second)
   309  
   310  	if participantTokenContractor, err = newV0TokenContractor(dex.Simnet, dexeth.Tokens[usdcID], participantAddr, participantEthClient.contractBackend()); err != nil {
   311  		return 1, fmt.Errorf("participant newV0TokenContractor error: %w", err)
   312  	}
   313  
   314  	if err := ethClient.unlock(pw); err != nil {
   315  		return 1, fmt.Errorf("error unlocking initiator client: %w", err)
   316  	}
   317  	if err := participantEthClient.unlock(pw); err != nil {
   318  		return 1, fmt.Errorf("error unlocking initiator client: %w", err)
   319  	}
   320  
   321  	// Fund the wallets.
   322  	homeDir, err := os.UserHomeDir()
   323  	if err != nil {
   324  		return 1, err
   325  	}
   326  	harnessCtlDir := filepath.Join(homeDir, "dextest", "eth", "harness-ctl")
   327  	send := func(exe, addr, amt string) error {
   328  		cmd := exec.CommandContext(ctx, exe, addr, amt)
   329  		cmd.Dir = harnessCtlDir
   330  		out, err := cmd.CombinedOutput()
   331  		if err != nil {
   332  			return fmt.Errorf("error running %q: %v", cmd, err)
   333  		}
   334  		fmt.Printf("result from %q: %s\n", cmd, out)
   335  		return nil
   336  	}
   337  	for _, s := range []*struct {
   338  		exe, addr, amt string
   339  	}{
   340  		{"./sendtoaddress", simnetAddr.String(), "10"},
   341  		{"./sendtoaddress", participantAddr.String(), "10"},
   342  		{"./sendUSDC", simnetAddr.String(), "10"},
   343  		{"./sendUSDC", participantAddr.String(), "10"},
   344  	} {
   345  		if err := send(s.exe, s.addr, s.amt); err != nil {
   346  			return 1, err
   347  		}
   348  	}
   349  
   350  	cmd := exec.CommandContext(ctx, "./mine-alpha", "1")
   351  	cmd.Dir = harnessCtlDir
   352  	if err := cmd.Run(); err != nil {
   353  		return 1, fmt.Errorf("error mining block after funding wallets")
   354  	}
   355  
   356  	code := m.Run()
   357  
   358  	if code != 0 {
   359  		return code, nil
   360  	}
   361  
   362  	if err := ethClient.lock(); err != nil {
   363  		return 1, fmt.Errorf("error locking initiator client: %w", err)
   364  	}
   365  	if err := participantEthClient.lock(); err != nil {
   366  		return 1, fmt.Errorf("error locking initiator client: %w", err)
   367  	}
   368  
   369  	return code, nil
   370  }
   371  
   372  func runTestnet(m *testing.M) (int, error) {
   373  	usdcID = usdcID
   374  	masterToken = dexeth.Tokens[usdcID]
   375  	tokenGases = &masterToken.NetTokens[dex.Testnet].SwapContracts[0].Gas
   376  	if testnetWalletSeed == "" || testnetParticipantWalletSeed == "" {
   377  		return 1, errors.New("testnet seeds not set")
   378  	}
   379  	// Create dir if none yet exists. This persists for the life of the
   380  	// testing harness.
   381  	err := os.MkdirAll(testnetWalletDir, 0755)
   382  	if err != nil {
   383  		return 1, fmt.Errorf("error creating testnet wallet dir dir: %v", err)
   384  	}
   385  	err = os.MkdirAll(testnetParticipantWalletDir, 0755)
   386  	if err != nil {
   387  		return 1, fmt.Errorf("error creating testnet participant wallet dir: %v", err)
   388  	}
   389  	const contractVer = 0
   390  	ethSwapContractAddr = dexeth.ContractAddresses[contractVer][dex.Testnet]
   391  	fmt.Printf("ETH swap contract address is %v\n", ethSwapContractAddr)
   392  
   393  	initiatorRPC, participantRPC := rpcEndpoints(dex.Testnet)
   394  
   395  	err = setupWallet(testnetWalletDir, testnetWalletSeed, "localhost:30355", initiatorRPC, dex.Testnet)
   396  	if err != nil {
   397  		return 1, err
   398  	}
   399  	err = setupWallet(testnetParticipantWalletDir, testnetParticipantWalletSeed, "localhost:30356", participantRPC, dex.Testnet)
   400  	if err != nil {
   401  		return 1, err
   402  	}
   403  	if err = prepareTestRPCClients(testnetWalletDir, testnetParticipantWalletDir, dex.Testnet); err != nil {
   404  		return 1, err
   405  	}
   406  	defer ethClient.shutdown()
   407  	defer participantEthClient.shutdown()
   408  
   409  	fmt.Println("Testnet nodes starting sync, this may take a while...")
   410  	wg := sync.WaitGroup{}
   411  	wg.Add(2)
   412  	var initerErr, participantErr error
   413  
   414  	go func() {
   415  		initerErr = syncClient(ethClient)
   416  		wg.Done()
   417  	}()
   418  	go func() {
   419  		participantErr = syncClient(participantEthClient)
   420  		wg.Done()
   421  	}()
   422  	wg.Wait()
   423  
   424  	if initerErr != nil {
   425  		return 1, fmt.Errorf("error initializing initiator client: %v", initerErr)
   426  	}
   427  	if participantErr != nil {
   428  		return 1, fmt.Errorf("error initializing participant client: %v", participantErr)
   429  	}
   430  	fmt.Println("Testnet nodes synced!!")
   431  
   432  	simnetAddr = simnetAcct.Address
   433  	participantAddr = participantAcct.Address
   434  
   435  	contractAddr, exists := dexeth.ContractAddresses[contractVer][dex.Testnet]
   436  	if !exists || contractAddr == (common.Address{}) {
   437  		return 1, fmt.Errorf("no contract address for version %d", contractVer)
   438  	}
   439  
   440  	if simnetContractor, err = newV0Contractor(contractAddr, simnetAddr, ethClient.contractBackend()); err != nil {
   441  		return 1, fmt.Errorf("newV0Contractor error: %w", err)
   442  	}
   443  	if participantContractor, err = newV0Contractor(contractAddr, participantAddr, participantEthClient.contractBackend()); err != nil {
   444  		return 1, fmt.Errorf("participant newV0Contractor error: %w", err)
   445  	}
   446  
   447  	if err := ethClient.unlock(pw); err != nil {
   448  		return 1, fmt.Errorf("error unlocking initiator client: %w", err)
   449  	}
   450  	if err := participantEthClient.unlock(pw); err != nil {
   451  		return 1, fmt.Errorf("error unlocking initiator client: %w", err)
   452  	}
   453  
   454  	if simnetTokenContractor, err = newV0TokenContractor(dex.Testnet, dexeth.Tokens[usdcID], simnetAddr, ethClient.contractBackend()); err != nil {
   455  		return 1, fmt.Errorf("newV0TokenContractor error: %w", err)
   456  	}
   457  
   458  	// I don't know why this is needed for the participant client but not
   459  	// the initiator. Without this, we'll get a bind.ErrNoCode from
   460  	// (*BoundContract).Call while calling (*ERC20Swap).TokenAddress.
   461  	time.Sleep(time.Second)
   462  
   463  	if participantTokenContractor, err = newV0TokenContractor(dex.Testnet, dexeth.Tokens[usdcID], participantAddr, participantEthClient.contractBackend()); err != nil {
   464  		return 1, fmt.Errorf("participant newV0TokenContractor error: %w", err)
   465  	}
   466  
   467  	code := m.Run()
   468  
   469  	if code != 0 {
   470  		return code, nil
   471  	}
   472  
   473  	if err := ethClient.lock(); err != nil {
   474  		return 1, fmt.Errorf("error locking initiator client: %w", err)
   475  	}
   476  	if err := participantEthClient.lock(); err != nil {
   477  		return 1, fmt.Errorf("error locking initiator client: %w", err)
   478  	}
   479  
   480  	return code, nil
   481  }
   482  
   483  func useTestnet() error {
   484  	isTestnet = true
   485  	b, err := os.ReadFile(filepath.Join(homeDir, "dextest", "credentials.json"))
   486  	if err != nil {
   487  		return fmt.Errorf("error reading credentials file: %v", err)
   488  	}
   489  	var creds providersFile
   490  	if err = json.Unmarshal(b, &creds); err != nil {
   491  		return fmt.Errorf("error decoding credential: %w", err)
   492  	}
   493  	if len(creds.Seed) == 0 {
   494  		return errors.New("no seed found in credentials file")
   495  	}
   496  	seed2 := sha256.Sum256(creds.Seed)
   497  	testnetWalletSeed = hex.EncodeToString(creds.Seed)
   498  	testnetParticipantWalletSeed = hex.EncodeToString(seed2[:])
   499  	rpcProviders = creds.Providers["eth"][dex.Testnet.String()]
   500  	return nil
   501  }
   502  
   503  func TestMain(m *testing.M) {
   504  	dexeth.MaybeReadSimnetAddrs()
   505  
   506  	flag.BoolVar(&isTestnet, "testnet", false, "use testnet")
   507  	flag.Parse()
   508  
   509  	if isTestnet {
   510  		tmpDir, err := os.MkdirTemp("", "")
   511  		if err != nil {
   512  			fmt.Fprintf(os.Stderr, "error creating temporary directory: %v", err)
   513  			os.Exit(1)
   514  		}
   515  		testnetWalletDir = filepath.Join(tmpDir, "initiator")
   516  		defer os.RemoveAll(testnetWalletDir)
   517  		testnetParticipantWalletDir = filepath.Join(tmpDir, "participant")
   518  		defer os.RemoveAll(testnetParticipantWalletDir)
   519  		if err := useTestnet(); err != nil {
   520  			fmt.Fprintf(os.Stderr, "error loading testnet: %v", err)
   521  			os.Exit(1)
   522  		}
   523  	}
   524  
   525  	var cancel context.CancelFunc
   526  	ctx, cancel = context.WithCancel(context.Background())
   527  	c := make(chan os.Signal, 1)
   528  	signal.Notify(c, os.Interrupt)
   529  	go func() {
   530  		select {
   531  		case <-c:
   532  			cancel()
   533  		case <-ctx.Done():
   534  		}
   535  	}()
   536  	// Run in function so that defers happen before os.Exit is called.
   537  	run := runSimnet
   538  	if isTestnet {
   539  		run = runTestnet
   540  	}
   541  	exitCode, err := run(m)
   542  	if err != nil {
   543  		fmt.Println(err)
   544  	}
   545  	signal.Stop(c)
   546  	cancel()
   547  	os.Exit(exitCode)
   548  }
   549  
   550  func setupWallet(walletDir, seed, listenAddress string, providers []string, net dex.Network) error {
   551  	walletType := walletTypeRPC
   552  	settings := map[string]string{
   553  		providersKey: strings.Join(providers, " "),
   554  	}
   555  	seedB, _ := hex.DecodeString(seed)
   556  	createWalletParams := asset.CreateWalletParams{
   557  		Type:     walletType,
   558  		Seed:     seedB,
   559  		Pass:     []byte(pw),
   560  		Settings: settings,
   561  		DataDir:  walletDir,
   562  		Net:      net,
   563  		Logger:   tLogger,
   564  	}
   565  	compat, err := NetworkCompatibilityData(net)
   566  	if err != nil {
   567  		return err
   568  	}
   569  	return CreateEVMWallet(dexeth.ChainIDs[net], &createWalletParams, &compat, true)
   570  }
   571  
   572  func prepareTokenClients(t *testing.T) {
   573  	err := ethClient.unlock(pw)
   574  	if err != nil {
   575  		t.Fatalf("initiator unlock error; %v", err)
   576  	}
   577  	txOpts, err := ethClient.txOpts(ctx, 0, tokenGases.Approve, nil, nil, nil)
   578  	if err != nil {
   579  		t.Fatalf("txOpts error: %v", err)
   580  	}
   581  	var tx1, tx2 *types.Transaction
   582  	if tx1, err = simnetTokenContractor.approve(txOpts, unlimitedAllowance); err != nil {
   583  		t.Fatalf("initiator approveToken error: %v", err)
   584  	}
   585  	err = participantEthClient.unlock(pw)
   586  	if err != nil {
   587  		t.Fatalf("participant unlock error; %v", err)
   588  	}
   589  
   590  	txOpts, err = participantEthClient.txOpts(ctx, 0, tokenGases.Approve, nil, nil, nil)
   591  	if err != nil {
   592  		t.Fatalf("txOpts error: %v", err)
   593  	}
   594  	if tx2, err = participantTokenContractor.approve(txOpts, unlimitedAllowance); err != nil {
   595  		t.Fatalf("participant approveToken error: %v", err)
   596  	}
   597  
   598  	if err := waitForMined(); err != nil {
   599  		t.Fatalf("unexpected error while waiting to mine approval block: %v", err)
   600  	}
   601  
   602  	_, err = waitForReceipt(ethClient, tx1)
   603  	if err != nil {
   604  		t.Fatal(err)
   605  	}
   606  	// spew.Dump(receipt1)
   607  
   608  	_, err = waitForReceipt(participantEthClient, tx2)
   609  	if err != nil {
   610  		t.Fatal(err)
   611  	}
   612  	// spew.Dump(receipt2)
   613  }
   614  
   615  func syncClient(cl ethFetcher) error {
   616  	giveUpAt := 60
   617  	if isTestnet {
   618  		giveUpAt = 10000
   619  	}
   620  	for i := 0; ; i++ {
   621  		if err := ctx.Err(); err != nil {
   622  			return err
   623  		}
   624  		prog, tipTime, err := cl.syncProgress(ctx)
   625  		if err != nil {
   626  			return err
   627  		}
   628  		if isTestnet {
   629  			timeDiff := time.Now().Unix() - int64(tipTime)
   630  			if timeDiff < dexeth.MaxBlockInterval {
   631  				return nil
   632  			}
   633  		} else {
   634  			// If client has ever synced, assume synced with
   635  			// harness. This avoids checking the header time which
   636  			// is probably old.
   637  			if prog.CurrentBlock > 20 {
   638  				return nil
   639  			}
   640  		}
   641  		if i == giveUpAt {
   642  			return fmt.Errorf("block count has not synced in %d seconds", giveUpAt)
   643  		}
   644  		time.Sleep(time.Second)
   645  	}
   646  }
   647  
   648  func TestBasicRetrieval(t *testing.T) {
   649  	if !t.Run("testAddressesHaveFunds", testAddressesHaveFundsFn(100_000 /* gwei */)) {
   650  		t.Fatal("not enough funds")
   651  	}
   652  	t.Run("testBestHeader", testBestHeader)
   653  	t.Run("testPendingTransactions", testPendingTransactions)
   654  	t.Run("testHeaderByHash", testHeaderByHash)
   655  	t.Run("testTransactionReceipt", testTransactionReceipt)
   656  }
   657  
   658  func TestPeering(t *testing.T) {
   659  	t.Run("testSyncProgress", testSyncProgress)
   660  }
   661  
   662  func TestAccount(t *testing.T) {
   663  	if !t.Run("testAddressesHaveFunds", testAddressesHaveFundsFn(10_000_000 /* gwei */)) {
   664  		t.Fatal("not enough funds")
   665  	}
   666  	t.Run("testAddressBalance", testAddressBalance)
   667  	t.Run("testSendTransaction", testSendTransaction)
   668  	t.Run("testSendSignedTransaction", testSendSignedTransaction)
   669  	t.Run("testSignMessage", testSignMessage)
   670  }
   671  
   672  // TestContract tests methods that interact with the contract.
   673  func TestContract(t *testing.T) {
   674  	if !t.Run("testAddressesHaveFunds", testAddressesHaveFundsFn(100_000_000 /* gwei */)) {
   675  		t.Fatal("not enough funds")
   676  	}
   677  	t.Run("testSwap", func(t *testing.T) { testSwap(t, BipID) })
   678  	t.Run("testInitiate", func(t *testing.T) { testInitiate(t, BipID) })
   679  	t.Run("testRedeem", func(t *testing.T) { testRedeem(t, BipID) })
   680  	t.Run("testRefund", func(t *testing.T) { testRefund(t, BipID) })
   681  }
   682  
   683  func TestGas(t *testing.T) {
   684  	t.Run("testInitiateGas", func(t *testing.T) { testInitiateGas(t, BipID) })
   685  	t.Run("testRedeemGas", func(t *testing.T) { testRedeemGas(t, BipID) })
   686  	t.Run("testRefundGas", func(t *testing.T) { testRefundGas(t, BipID) })
   687  }
   688  
   689  func TestTokenContract(t *testing.T) {
   690  	t.Run("testTokenSwap", func(t *testing.T) { testSwap(t, usdcID) })
   691  	t.Run("testInitiateToken", func(t *testing.T) { testInitiate(t, usdcID) })
   692  	t.Run("testRedeemToken", func(t *testing.T) { testRedeem(t, usdcID) })
   693  	t.Run("testRefundToken", func(t *testing.T) { testRefund(t, usdcID) })
   694  }
   695  
   696  func TestTokenGas(t *testing.T) {
   697  	t.Run("testTransferGas", testTransferGas)
   698  	t.Run("testApproveGas", testApproveGas)
   699  	t.Run("testInitiateTokenGas", func(t *testing.T) { testInitiateGas(t, usdcID) })
   700  	t.Run("testRedeemTokenGas", func(t *testing.T) { testRedeemGas(t, usdcID) })
   701  	t.Run("testRefundTokenGas", func(t *testing.T) { testRefundGas(t, usdcID) })
   702  }
   703  
   704  func TestTokenAccess(t *testing.T) {
   705  	t.Run("testTokenBalance", testTokenBalance)
   706  	t.Run("testApproveAllowance", testApproveAllowance)
   707  }
   708  
   709  func testBestHeader(t *testing.T) {
   710  	bh, err := ethClient.bestHeader(ctx)
   711  	if err != nil {
   712  		t.Fatal(err)
   713  	}
   714  	spew.Dump(bh)
   715  }
   716  
   717  func testAddressBalance(t *testing.T) {
   718  	bal, err := ethClient.addressBalance(ctx, simnetAddr)
   719  	if err != nil {
   720  		t.Fatalf("error getting initiator balance: %v", err)
   721  	}
   722  	if bal == nil {
   723  		t.Fatalf("empty balance")
   724  	}
   725  	fmt.Printf("Initiator balance: %.9f ETH \n", float64(dexeth.WeiToGwei(bal))/dexeth.GweiFactor)
   726  	bal, err = participantEthClient.addressBalance(ctx, participantAddr)
   727  	if err != nil {
   728  		t.Fatalf("error getting participant balance: %v", err)
   729  	}
   730  	fmt.Printf("Participant balance: %.9f ETH \n", float64(dexeth.WeiToGwei(bal))/dexeth.GweiFactor)
   731  }
   732  
   733  func testTokenBalance(t *testing.T) {
   734  	bal, err := simnetTokenContractor.balance(ctx)
   735  	if err != nil {
   736  		t.Fatal(err)
   737  	}
   738  	if bal == nil {
   739  		t.Fatalf("empty balance")
   740  	}
   741  
   742  	fmt.Println("### Balance:", simnetAddr, stringifyTokenBalance(t, bal))
   743  }
   744  
   745  func stringifyTokenBalance(t *testing.T, evmBal *big.Int) string {
   746  	t.Helper()
   747  	atomicBal := masterToken.EVMToAtomic(evmBal)
   748  	ui, err := asset.UnitInfo(usdcID)
   749  	if err != nil {
   750  		t.Fatalf("cannot get unit info: %v", err)
   751  	}
   752  	prec := math.Round(math.Log10(float64(ui.Conventional.ConversionFactor)))
   753  	return strconv.FormatFloat(float64(atomicBal)/float64(ui.Conventional.ConversionFactor), 'f', int(prec), 64)
   754  }
   755  
   756  // testAddressesHaveFundsFn returns a function that tests that addresses used
   757  // in tests have enough funds to complete those tests.
   758  func testAddressesHaveFundsFn(amt uint64) func(t *testing.T) {
   759  	return func(t *testing.T) {
   760  		checkAddr := func(addr common.Address) error {
   761  			bal, err := ethClient.addressBalance(ctx, addr)
   762  			if err != nil {
   763  				return err
   764  			}
   765  			if bal == nil {
   766  				return errors.New("empty balance")
   767  			}
   768  			gweiBal := dexeth.WeiToGwei(bal)
   769  			if gweiBal < amt {
   770  				fmt.Printf("Balance is too low to test. Send more than %v test eth to %v.\n", float64(amt-gweiBal)/1e9, addr)
   771  				return fmt.Errorf("balance too low")
   772  			}
   773  			return nil
   774  		}
   775  		var errs error
   776  		if err := checkAddr(simnetAddr); err != nil {
   777  			errs = fmt.Errorf("client one: %v", err)
   778  		}
   779  		if err := checkAddr(participantAddr); err != nil {
   780  			err = fmt.Errorf("client two: %v", err)
   781  			if errs != nil {
   782  				errs = fmt.Errorf("%v: %v", errs, err)
   783  			} else {
   784  				errs = err
   785  			}
   786  		}
   787  		if errs != nil {
   788  			t.Fatal(errs)
   789  		}
   790  	}
   791  }
   792  
   793  func testSendTransaction(t *testing.T) {
   794  	// Checking confirmations for a random hash should result in not found error.
   795  	var txHash common.Hash
   796  	copy(txHash[:], encode.RandomBytes(32))
   797  	_, err := ethClient.transactionConfirmations(ctx, txHash)
   798  	if !errors.Is(err, asset.CoinNotFoundError) {
   799  		t.Fatalf("no CoinNotFoundError")
   800  	}
   801  
   802  	txOpts, err := ethClient.txOpts(ctx, 1, defaultSendGasLimit, nil, nil, nil)
   803  	if err != nil {
   804  		t.Fatalf("txOpts error: %v", err)
   805  	}
   806  
   807  	tx, err := ethClient.sendTransaction(ctx, txOpts, participantAddr, nil)
   808  	if err != nil {
   809  		t.Fatal(err)
   810  	}
   811  
   812  	txHash = tx.Hash()
   813  
   814  	confs, err := ethClient.transactionConfirmations(ctx, txHash)
   815  	// CoinNotFoundError OK for RPC wallet until mined.
   816  	if err != nil && !errors.Is(err, asset.CoinNotFoundError) {
   817  		t.Fatalf("transactionConfirmations error: %v", err)
   818  	}
   819  	if confs != 0 {
   820  		t.Fatalf("%d confs reported for unmined transaction", confs)
   821  	}
   822  
   823  	spew.Dump(tx)
   824  	if err := waitForMined(); err != nil {
   825  		t.Fatal(err)
   826  	}
   827  
   828  	confs, err = ethClient.transactionConfirmations(ctx, txHash)
   829  	if err != nil {
   830  		t.Fatalf("transactionConfirmations error after mining: %v", err)
   831  	}
   832  	if confs == 0 {
   833  		t.Fatalf("zero confs after mining")
   834  	}
   835  }
   836  
   837  func testHeaderByHash(t *testing.T) {
   838  	// Checking a random hash should result in no header.
   839  	var txHash common.Hash
   840  	copy(txHash[:], encode.RandomBytes(32))
   841  	_, err := ethClient.headerByHash(ctx, txHash)
   842  	if err == nil {
   843  		t.Fatal("expected header not found error")
   844  	}
   845  
   846  	bestHdr, err := ethClient.bestHeader(ctx)
   847  	if err != nil {
   848  		t.Fatal(err)
   849  	}
   850  
   851  	txHash = bestHdr.Hash()
   852  
   853  	hdr, err := ethClient.headerByHash(ctx, txHash)
   854  	if err != nil {
   855  		t.Fatal(err)
   856  	}
   857  	hdrHash := hdr.Hash()
   858  	if !bytes.Equal(hdrHash[:], txHash[:]) {
   859  		t.Fatal("hashes not equal")
   860  	}
   861  }
   862  
   863  func testSendSignedTransaction(t *testing.T) {
   864  	// Checking confirmations for a random hash should result in not found error.
   865  	var txHash common.Hash
   866  	copy(txHash[:], encode.RandomBytes(32))
   867  	_, err := ethClient.transactionConfirmations(ctx, txHash)
   868  	if !errors.Is(err, asset.CoinNotFoundError) {
   869  		t.Fatalf("no CoinNotFoundError")
   870  	}
   871  	c := ethClient.(*multiRPCClient)
   872  	var nonce uint64
   873  	var chainID *big.Int
   874  	var ks *keystore.KeyStore
   875  	n, _, err := ethClient.nonce(ctx)
   876  	if err != nil {
   877  		t.Fatalf("error getting nonce: %v", err)
   878  	}
   879  	nonce = n.Uint64()
   880  	ks = c.creds.ks
   881  	chainID = c.chainID
   882  
   883  	tx := types.NewTx(&types.DynamicFeeTx{
   884  		To:        &simnetAddr,
   885  		ChainID:   chainID,
   886  		Nonce:     nonce,
   887  		Gas:       21000,
   888  		GasFeeCap: dexeth.GweiToWei(maxFeeRate),
   889  		GasTipCap: dexeth.GweiToWei(2),
   890  		Value:     dexeth.GweiToWei(1),
   891  		Data:      []byte{},
   892  	})
   893  	tx, err = ks.SignTx(*simnetAcct, tx, chainID)
   894  	if err != nil {
   895  		t.Fatal(err)
   896  	}
   897  
   898  	err = ethClient.sendSignedTransaction(ctx, tx)
   899  	if err != nil {
   900  		t.Fatal(err)
   901  	}
   902  
   903  	txHash = tx.Hash()
   904  
   905  	confs, err := ethClient.transactionConfirmations(ctx, txHash)
   906  	// CoinNotFoundError OK for RPC wallet until mined.
   907  	if err != nil && !errors.Is(err, asset.CoinNotFoundError) {
   908  		t.Fatalf("transactionConfirmations error: %v", err)
   909  	}
   910  	if confs != 0 {
   911  		t.Fatalf("%d confs reported for unmined transaction", confs)
   912  	}
   913  
   914  	spew.Dump(tx)
   915  	if err := waitForMined(); err != nil {
   916  		t.Fatal(err)
   917  	}
   918  
   919  	confs, err = ethClient.transactionConfirmations(ctx, txHash)
   920  	if err != nil {
   921  		t.Fatalf("transactionConfirmations error after mining: %v", err)
   922  	}
   923  	if confs == 0 {
   924  		t.Fatalf("zero confs after mining")
   925  	}
   926  }
   927  
   928  func testTransactionReceipt(t *testing.T) {
   929  	txOpts, err := ethClient.txOpts(ctx, 1, defaultSendGasLimit, nil, nil, nil)
   930  	if err != nil {
   931  		t.Fatalf("txOpts error: %v", err)
   932  	}
   933  	tx, err := ethClient.sendTransaction(ctx, txOpts, simnetAddr, nil)
   934  	if err != nil {
   935  		t.Fatal(err)
   936  	}
   937  	if err := waitForMined(); err != nil {
   938  		t.Fatal(err)
   939  	}
   940  	receipt, err := waitForReceipt(ethClient, tx)
   941  	if err != nil {
   942  		t.Fatal(err)
   943  	}
   944  	spew.Dump(receipt)
   945  }
   946  
   947  func testPendingTransactions(t *testing.T) {
   948  	mf, is := ethClient.(txPoolFetcher)
   949  	if !is {
   950  		return
   951  	}
   952  	txs, err := mf.pendingTransactions()
   953  	if err != nil {
   954  		t.Fatal(err)
   955  	}
   956  	// Should be empty.
   957  	spew.Dump(txs)
   958  }
   959  
   960  func testSwap(t *testing.T, assetID uint32) {
   961  	var secretHash [32]byte
   962  	copy(secretHash[:], encode.RandomBytes(32))
   963  	swap, err := simnetContractor.swap(ctx, secretHash)
   964  	if err != nil {
   965  		t.Fatal(err)
   966  	}
   967  	// Should be empty.
   968  	spew.Dump(swap)
   969  }
   970  
   971  func testSyncProgress(t *testing.T) {
   972  	p, _, err := ethClient.syncProgress(ctx)
   973  	if err != nil {
   974  		t.Fatal(err)
   975  	}
   976  	spew.Dump(p)
   977  }
   978  
   979  func testInitiateGas(t *testing.T, assetID uint32) {
   980  	if assetID != BipID {
   981  		prepareTokenClients(t)
   982  	}
   983  
   984  	net := dex.Simnet
   985  	if isTestnet {
   986  		net = dex.Testnet
   987  	}
   988  
   989  	c := simnetContractor
   990  	versionedGases := dexeth.VersionedGases
   991  	if assetID != BipID {
   992  		c = simnetTokenContractor
   993  		versionedGases = make(map[uint32]*dexeth.Gases)
   994  		for ver, c := range dexeth.Tokens[assetID].NetTokens[net].SwapContracts {
   995  			versionedGases[ver] = &c.Gas
   996  		}
   997  	}
   998  	gases := gases(0, versionedGases)
   999  
  1000  	var previousGas uint64
  1001  	maxSwaps := 50
  1002  	for i := 1; i <= maxSwaps; i++ {
  1003  		gas, err := c.estimateInitGas(ctx, i)
  1004  		if err != nil {
  1005  			t.Fatalf("unexpected error from estimateInitGas(%d): %v", i, err)
  1006  		}
  1007  
  1008  		var expectedGas uint64
  1009  		var actualGas uint64
  1010  		if i == 1 {
  1011  			expectedGas = gases.Swap
  1012  			actualGas = gas
  1013  		} else {
  1014  			expectedGas = gases.SwapAdd
  1015  			actualGas = gas - previousGas
  1016  		}
  1017  		if actualGas > expectedGas || actualGas < expectedGas*70/100 {
  1018  			t.Fatalf("Expected incremental gas for %d initiations to be close to %d but got %d",
  1019  				i, expectedGas, actualGas)
  1020  		}
  1021  
  1022  		fmt.Printf("Gas used for batch initiating %v swaps: %v. %v more than previous \n", i, gas, gas-previousGas)
  1023  		previousGas = gas
  1024  	}
  1025  }
  1026  
  1027  // feesAtBlk calculates the gas fee at blkNum. This adds the base fee at blkNum
  1028  // to a minimum gas tip cap.
  1029  func feesAtBlk(ctx context.Context, n ethFetcher, blkNum int64) (fees *big.Int, err error) {
  1030  	hdr, err := n.(*multiRPCClient).HeaderByNumber(ctx, big.NewInt(blkNum))
  1031  	if err != nil {
  1032  		return nil, err
  1033  	}
  1034  
  1035  	minGasTipCapWei := dexeth.GweiToWei(dexeth.MinGasTipCap)
  1036  	tip := new(big.Int).Set(minGasTipCapWei)
  1037  
  1038  	return tip.Add(tip, hdr.BaseFee), nil
  1039  }
  1040  
  1041  // initiateOverflow is just like *contractorV0.initiate but sets the first swap
  1042  // value to a max uint256 minus one emv unit.
  1043  func initiateOverflow(c *contractorV0, txOpts *bind.TransactOpts, contracts []*asset.Contract) (*types.Transaction, error) {
  1044  	inits := make([]swapv0.ETHSwapInitiation, 0, len(contracts))
  1045  	secrets := make(map[[32]byte]bool, len(contracts))
  1046  
  1047  	for i, contract := range contracts {
  1048  		if len(contract.SecretHash) != dexeth.SecretHashSize {
  1049  			return nil, fmt.Errorf("wrong secret hash length. wanted %d, got %d", dexeth.SecretHashSize, len(contract.SecretHash))
  1050  		}
  1051  
  1052  		var secretHash [32]byte
  1053  		copy(secretHash[:], contract.SecretHash)
  1054  
  1055  		if secrets[secretHash] {
  1056  			return nil, fmt.Errorf("secret hash %s is a duplicate", contract.SecretHash)
  1057  		}
  1058  		secrets[secretHash] = true
  1059  
  1060  		if !common.IsHexAddress(contract.Address) {
  1061  			return nil, fmt.Errorf("%q is not an address", contract.Address)
  1062  		}
  1063  
  1064  		val := c.evmify(contract.Value)
  1065  		if i == 0 {
  1066  			val = big.NewInt(2)
  1067  			val.Exp(val, big.NewInt(256), nil)
  1068  			val.Sub(val, c.evmify(1))
  1069  		}
  1070  		inits = append(inits, swapv0.ETHSwapInitiation{
  1071  			RefundTimestamp: big.NewInt(int64(contract.LockTime)),
  1072  			SecretHash:      secretHash,
  1073  			Participant:     common.HexToAddress(contract.Address),
  1074  			Value:           val,
  1075  		})
  1076  	}
  1077  
  1078  	return c.contractV0.Initiate(txOpts, inits)
  1079  }
  1080  
  1081  func testInitiate(t *testing.T, assetID uint32) {
  1082  	if assetID != BipID {
  1083  		prepareTokenClients(t)
  1084  	}
  1085  
  1086  	isETH := assetID == BipID
  1087  
  1088  	sc := simnetContractor
  1089  	balance := func() (*big.Int, error) {
  1090  		return ethClient.addressBalance(ctx, ethClient.address())
  1091  	}
  1092  	gases := ethGases
  1093  	evmify := dexeth.GweiToWei
  1094  	if !isETH {
  1095  		sc = simnetTokenContractor
  1096  		balance = func() (*big.Int, error) {
  1097  			return simnetTokenContractor.balance(ctx)
  1098  		}
  1099  		gases = tokenGases
  1100  		tc := sc.(*tokenContractorV0)
  1101  		evmify = tc.evmify
  1102  	}
  1103  
  1104  	// Create a slice of random secret hashes that can be used in the tests and
  1105  	// make sure none of them have been used yet.
  1106  	numSecretHashes := 10
  1107  	secretHashes := make([][32]byte, numSecretHashes)
  1108  	for i := 0; i < numSecretHashes; i++ {
  1109  		copy(secretHashes[i][:], encode.RandomBytes(32))
  1110  		swap, err := sc.swap(ctx, secretHashes[i])
  1111  		if err != nil {
  1112  			t.Fatal("unable to get swap state")
  1113  		}
  1114  		state := dexeth.SwapStep(swap.State)
  1115  		if state != dexeth.SSNone {
  1116  			t.Fatalf("unexpected swap state: want %s got %s", dexeth.SSNone, state)
  1117  		}
  1118  	}
  1119  
  1120  	now := uint64(time.Now().Unix())
  1121  
  1122  	tests := []struct {
  1123  		name              string
  1124  		swaps             []*asset.Contract
  1125  		success, overflow bool
  1126  		swapErr           bool
  1127  	}{
  1128  		{
  1129  			name:    "1 swap ok",
  1130  			success: true,
  1131  			swaps: []*asset.Contract{
  1132  				newContract(now, secretHashes[0], 2),
  1133  			},
  1134  		},
  1135  		{
  1136  			name:    "1 swap with existing hash",
  1137  			success: false,
  1138  			swaps: []*asset.Contract{
  1139  				newContract(now, secretHashes[0], 1),
  1140  			},
  1141  		},
  1142  		{
  1143  			name:    "2 swaps ok",
  1144  			success: true,
  1145  			swaps: []*asset.Contract{
  1146  				newContract(now, secretHashes[1], 1),
  1147  				newContract(now, secretHashes[2], 1),
  1148  			},
  1149  		},
  1150  		{
  1151  			name:    "2 swaps repeated hash",
  1152  			success: false,
  1153  			swaps: []*asset.Contract{
  1154  				newContract(now, secretHashes[3], 1),
  1155  				newContract(now, secretHashes[3], 1),
  1156  			},
  1157  			swapErr: true,
  1158  		},
  1159  		{
  1160  			name:    "1 swap nil refundtimestamp",
  1161  			success: false,
  1162  			swaps: []*asset.Contract{
  1163  				newContract(0, secretHashes[4], 1),
  1164  			},
  1165  		},
  1166  		{
  1167  			// Preventing this used to need explicit checks before solidity 0.8, but now the
  1168  			// compiler checks for integer overflows by default.
  1169  			name:     "value addition overflows",
  1170  			success:  false,
  1171  			overflow: true,
  1172  			swaps: []*asset.Contract{
  1173  				newContract(now, secretHashes[5], 0), // Will be set to max uint256 - 1 evm unit
  1174  				newContract(now, secretHashes[6], 3),
  1175  			},
  1176  		},
  1177  		{
  1178  			name:    "swap with 0 value",
  1179  			success: false,
  1180  			swaps: []*asset.Contract{
  1181  				newContract(now, secretHashes[7], 0),
  1182  				newContract(now, secretHashes[8], 1),
  1183  			},
  1184  		},
  1185  	}
  1186  
  1187  	for _, test := range tests {
  1188  		var originalParentBal *big.Int
  1189  
  1190  		originalBal, err := balance()
  1191  		if err != nil {
  1192  			t.Fatalf("balance error for asset %d, test %s: %v", assetID, test.name, err)
  1193  		}
  1194  
  1195  		var totalVal uint64
  1196  		originalStates := make(map[string]dexeth.SwapStep)
  1197  		for _, tSwap := range test.swaps {
  1198  			swap, err := sc.swap(ctx, bytesToArray(tSwap.SecretHash))
  1199  			if err != nil {
  1200  				t.Fatalf("%s: swap error: %v", test.name, err)
  1201  			}
  1202  			originalStates[tSwap.SecretHash.String()] = dexeth.SwapStep(swap.State)
  1203  			totalVal += tSwap.Value
  1204  		}
  1205  
  1206  		optsVal := totalVal
  1207  		if !isETH {
  1208  			optsVal = 0
  1209  			originalParentBal, err = ethClient.addressBalance(ctx, ethClient.address())
  1210  			if err != nil {
  1211  				t.Fatalf("balance error for eth, test %s: %v", test.name, err)
  1212  			}
  1213  		}
  1214  
  1215  		if test.overflow {
  1216  			optsVal = 2
  1217  		}
  1218  
  1219  		expGas := gases.SwapN(len(test.swaps))
  1220  		txOpts, err := ethClient.txOpts(ctx, optsVal, expGas, dexeth.GweiToWei(maxFeeRate), nil, nil)
  1221  		if err != nil {
  1222  			t.Fatalf("%s: txOpts error: %v", test.name, err)
  1223  		}
  1224  		var tx *types.Transaction
  1225  		if test.overflow {
  1226  			switch c := sc.(type) {
  1227  			case *contractorV0:
  1228  				tx, err = initiateOverflow(c, txOpts, test.swaps)
  1229  			case *tokenContractorV0:
  1230  				tx, err = initiateOverflow(c.contractorV0, txOpts, test.swaps)
  1231  			}
  1232  		} else {
  1233  			tx, err = sc.initiate(txOpts, test.swaps)
  1234  		}
  1235  		if err != nil {
  1236  			if test.swapErr {
  1237  				continue
  1238  			}
  1239  			t.Fatalf("%s: initiate error: %v", test.name, err)
  1240  		}
  1241  
  1242  		if err := waitForMined(); err != nil {
  1243  			t.Fatalf("%s: post-initiate mining error: %v", test.name, err)
  1244  		}
  1245  
  1246  		// It appears the receipt is only accessible after the tx is mined.
  1247  		receipt, err := waitForReceipt(ethClient, tx)
  1248  		if err != nil {
  1249  			t.Fatalf("%s: failed retrieving initiate receipt: %v", test.name, err)
  1250  		}
  1251  		spew.Dump(receipt)
  1252  
  1253  		err = checkTxStatus(receipt, txOpts.GasLimit)
  1254  		if err != nil && test.success {
  1255  			t.Fatalf("%s: failed init transaction status: %v", test.name, err)
  1256  		}
  1257  		fmt.Printf("Gas used for %d initiations, success = %t: %d (expected max %d) \n",
  1258  			len(test.swaps), test.success, receipt.GasUsed, expGas)
  1259  
  1260  		gasPrice, err := feesAtBlk(ctx, ethClient, receipt.BlockNumber.Int64())
  1261  		if err != nil {
  1262  			t.Fatalf("%s: feesAtBlk error: %v", test.name, err)
  1263  		}
  1264  		bigGasUsed := new(big.Int).SetUint64(receipt.GasUsed)
  1265  		txFee := new(big.Int).Mul(bigGasUsed, gasPrice)
  1266  
  1267  		wantBal := new(big.Int).Set(originalBal)
  1268  		if test.success {
  1269  			wantBal.Sub(wantBal, evmify(totalVal))
  1270  		}
  1271  		bal, err := balance()
  1272  		if err != nil {
  1273  			t.Fatalf("%s: balance error: %v", test.name, err)
  1274  		}
  1275  
  1276  		if isETH {
  1277  			wantBal = new(big.Int).Sub(wantBal, txFee)
  1278  		} else {
  1279  			parentBal, err := ethClient.addressBalance(ctx, ethClient.address())
  1280  			if err != nil {
  1281  				t.Fatalf("%s: eth balance error: %v", test.name, err)
  1282  			}
  1283  			wantParentBal := new(big.Int).Sub(originalParentBal, txFee)
  1284  			diff := new(big.Int).Sub(wantParentBal, parentBal)
  1285  			if diff.Cmp(big.NewInt(0)) != 0 {
  1286  				t.Fatalf("%s: unexpected parent chain balance change: want %d got %d, diff = %d",
  1287  					test.name, wantParentBal, parentBal, diff)
  1288  			}
  1289  		}
  1290  
  1291  		diff := new(big.Int).Sub(wantBal, bal)
  1292  		if diff.Cmp(big.NewInt(0)) != 0 {
  1293  			t.Fatalf("%s: unexpected balance change: want %d got %d units, diff = %d units",
  1294  				test.name, wantBal, bal, diff)
  1295  		}
  1296  
  1297  		for _, tSwap := range test.swaps {
  1298  			swap, err := sc.swap(ctx, bytesToArray(tSwap.SecretHash))
  1299  			if err != nil {
  1300  				t.Fatalf("%s: swap error post-init: %v", test.name, err)
  1301  			}
  1302  
  1303  			state := dexeth.SwapStep(swap.State)
  1304  			if test.success && state != dexeth.SSInitiated {
  1305  				t.Fatalf("%s: wrong success swap state: want %s got %s", test.name, dexeth.SSInitiated, state)
  1306  			}
  1307  
  1308  			originalState := originalStates[hex.EncodeToString(tSwap.SecretHash[:])]
  1309  			if !test.success && state != originalState {
  1310  				t.Fatalf("%s: wrong error swap state: want %s got %s", test.name, originalState, state)
  1311  			}
  1312  		}
  1313  	}
  1314  }
  1315  
  1316  func testRedeemGas(t *testing.T, assetID uint32) {
  1317  	if assetID != BipID {
  1318  		prepareTokenClients(t)
  1319  	}
  1320  
  1321  	// Create secrets and secret hashes
  1322  	const numSwaps = 9
  1323  	secrets := make([][32]byte, 0, numSwaps)
  1324  	secretHashes := make([][32]byte, 0, numSwaps)
  1325  	for i := 0; i < numSwaps; i++ {
  1326  		var secret [32]byte
  1327  		copy(secret[:], encode.RandomBytes(32))
  1328  		secretHash := sha256.Sum256(secret[:])
  1329  		secrets = append(secrets, secret)
  1330  		secretHashes = append(secretHashes, secretHash)
  1331  	}
  1332  
  1333  	// Initiate swaps
  1334  	now := uint64(time.Now().Unix())
  1335  
  1336  	swaps := make([]*asset.Contract, 0, numSwaps)
  1337  	for i := 0; i < numSwaps; i++ {
  1338  		swaps = append(swaps, newContract(now, secretHashes[i], 1))
  1339  	}
  1340  
  1341  	gases := ethGases
  1342  	c := simnetContractor
  1343  	pc := participantContractor
  1344  	optsVal := uint64(numSwaps)
  1345  	if assetID != BipID {
  1346  		optsVal = 0
  1347  		gases = tokenGases
  1348  		c = simnetTokenContractor
  1349  		pc = participantTokenContractor
  1350  	}
  1351  
  1352  	txOpts, err := ethClient.txOpts(ctx, optsVal, gases.SwapN(len(swaps)), dexeth.GweiToWei(maxFeeRate), nil, nil)
  1353  	if err != nil {
  1354  		t.Fatalf("txOpts error: %v", err)
  1355  	}
  1356  	tx, err := c.initiate(txOpts, swaps)
  1357  	if err != nil {
  1358  		t.Fatalf("Unable to initiate swap: %v ", err)
  1359  	}
  1360  	if err := waitForMined(); err != nil {
  1361  		t.Fatalf("unexpected error while waiting to mine: %v", err)
  1362  	}
  1363  	receipt, err := waitForReceipt(ethClient, tx)
  1364  	if err != nil {
  1365  		t.Fatalf("failed retrieving initiate receipt: %v", err)
  1366  	}
  1367  	spew.Dump(receipt)
  1368  
  1369  	err = checkTxStatus(receipt, txOpts.GasLimit)
  1370  	if err != nil {
  1371  		t.Fatalf("failed init transaction status: %v", err)
  1372  	}
  1373  
  1374  	// Make sure swaps were properly initiated
  1375  	for i := range swaps {
  1376  		swap, err := c.swap(ctx, bytesToArray(swaps[i].SecretHash))
  1377  		if err != nil {
  1378  			t.Fatal("unable to get swap state")
  1379  		}
  1380  		if swap.State != dexeth.SSInitiated {
  1381  			t.Fatalf("unexpected swap state: want %s got %s", dexeth.SSInitiated, swap.State)
  1382  		}
  1383  	}
  1384  
  1385  	// Test gas usage of redeem function
  1386  	var previous uint64
  1387  	for i := 0; i < numSwaps; i++ {
  1388  		gas, err := pc.estimateRedeemGas(ctx, secrets[:i+1])
  1389  		if err != nil {
  1390  			t.Fatalf("Error estimating gas for redeem function: %v", err)
  1391  		}
  1392  
  1393  		var expectedGas uint64
  1394  		var actualGas uint64
  1395  		if i == 0 {
  1396  			expectedGas = gases.Redeem
  1397  			actualGas = gas
  1398  		} else {
  1399  			expectedGas = gases.RedeemAdd
  1400  			actualGas = gas - previous
  1401  		}
  1402  		if actualGas > expectedGas || actualGas < (expectedGas*70/100) {
  1403  			t.Fatalf("Expected incremental gas for %d redemptions to be close to %d but got %d",
  1404  				i, expectedGas, actualGas)
  1405  		}
  1406  
  1407  		fmt.Printf("\n\nGas used to redeem %d swaps: %d -- %d more than previous \n\n", i+1, gas, gas-previous)
  1408  		previous = gas
  1409  	}
  1410  }
  1411  
  1412  func testRedeem(t *testing.T, assetID uint32) {
  1413  	if assetID != BipID {
  1414  		prepareTokenClients(t)
  1415  	}
  1416  	lockTime := uint64(time.Now().Add(12 * secPerBlock).Unix())
  1417  	numSecrets := 10
  1418  	secrets := make([][32]byte, 0, numSecrets)
  1419  	secretHashes := make([][32]byte, 0, numSecrets)
  1420  	for i := 0; i < numSecrets; i++ {
  1421  		var secret [32]byte
  1422  		copy(secret[:], encode.RandomBytes(32))
  1423  		secretHash := sha256.Sum256(secret[:])
  1424  		secrets = append(secrets, secret)
  1425  		secretHashes = append(secretHashes, secretHash)
  1426  	}
  1427  
  1428  	isETH := assetID == BipID
  1429  	gases := ethGases
  1430  	c, pc := simnetContractor, participantContractor
  1431  	evmify := dexeth.GweiToWei
  1432  	if !isETH {
  1433  		gases = tokenGases
  1434  		c, pc = simnetTokenContractor, participantTokenContractor
  1435  		tc := c.(*tokenContractorV0)
  1436  		evmify = tc.evmify
  1437  	}
  1438  
  1439  	tests := []struct {
  1440  		name               string
  1441  		sleepNBlocks       int
  1442  		redeemerClient     ethFetcher
  1443  		redeemer           *accounts.Account
  1444  		redeemerContractor contractor
  1445  		swaps              []*asset.Contract
  1446  		redemptions        []*asset.Redemption
  1447  		finalStates        []dexeth.SwapStep
  1448  		addAmt             bool
  1449  		expectRedeemErr    bool
  1450  	}{
  1451  		{
  1452  			name:               "ok before locktime",
  1453  			sleepNBlocks:       8,
  1454  			redeemerClient:     participantEthClient,
  1455  			redeemer:           participantAcct,
  1456  			redeemerContractor: pc,
  1457  			swaps:              []*asset.Contract{newContract(lockTime, secretHashes[0], 1)},
  1458  			redemptions:        []*asset.Redemption{newRedeem(secrets[0], secretHashes[0])},
  1459  			finalStates:        []dexeth.SwapStep{dexeth.SSRedeemed},
  1460  			addAmt:             true,
  1461  		},
  1462  		{
  1463  			name:               "ok two before locktime",
  1464  			sleepNBlocks:       8,
  1465  			redeemerClient:     participantEthClient,
  1466  			redeemer:           participantAcct,
  1467  			redeemerContractor: pc,
  1468  			swaps: []*asset.Contract{
  1469  				newContract(lockTime, secretHashes[1], 1),
  1470  				newContract(lockTime, secretHashes[2], 1),
  1471  			},
  1472  			redemptions: []*asset.Redemption{
  1473  				newRedeem(secrets[1], secretHashes[1]),
  1474  				newRedeem(secrets[2], secretHashes[2]),
  1475  			},
  1476  			finalStates: []dexeth.SwapStep{
  1477  				dexeth.SSRedeemed, dexeth.SSRedeemed,
  1478  			},
  1479  			addAmt: true,
  1480  		},
  1481  		{
  1482  			name:               "ok after locktime",
  1483  			sleepNBlocks:       16,
  1484  			redeemerClient:     participantEthClient,
  1485  			redeemer:           participantAcct,
  1486  			redeemerContractor: pc,
  1487  			swaps:              []*asset.Contract{newContract(lockTime, secretHashes[3], 1)},
  1488  			redemptions:        []*asset.Redemption{newRedeem(secrets[3], secretHashes[3])},
  1489  			finalStates:        []dexeth.SwapStep{dexeth.SSRedeemed},
  1490  			addAmt:             true,
  1491  		},
  1492  		{
  1493  			name:               "bad redeemer",
  1494  			sleepNBlocks:       8,
  1495  			redeemerClient:     ethClient,
  1496  			redeemer:           simnetAcct,
  1497  			redeemerContractor: c,
  1498  			swaps:              []*asset.Contract{newContract(lockTime, secretHashes[4], 1)},
  1499  			redemptions:        []*asset.Redemption{newRedeem(secrets[4], secretHashes[4])},
  1500  			finalStates:        []dexeth.SwapStep{dexeth.SSInitiated},
  1501  			addAmt:             false,
  1502  		},
  1503  		{
  1504  			name:               "bad secret",
  1505  			sleepNBlocks:       8,
  1506  			redeemerClient:     participantEthClient,
  1507  			redeemer:           participantAcct,
  1508  			redeemerContractor: pc,
  1509  			swaps:              []*asset.Contract{newContract(lockTime, secretHashes[5], 1)},
  1510  			redemptions:        []*asset.Redemption{newRedeem(secrets[6], secretHashes[5])},
  1511  			finalStates:        []dexeth.SwapStep{dexeth.SSInitiated},
  1512  			addAmt:             false,
  1513  		},
  1514  		{
  1515  			name:               "duplicate secret hashes",
  1516  			expectRedeemErr:    true,
  1517  			sleepNBlocks:       8,
  1518  			redeemerClient:     participantEthClient,
  1519  			redeemer:           participantAcct,
  1520  			redeemerContractor: pc,
  1521  			swaps: []*asset.Contract{
  1522  				newContract(lockTime, secretHashes[7], 1),
  1523  				newContract(lockTime, secretHashes[8], 1),
  1524  			},
  1525  			redemptions: []*asset.Redemption{
  1526  				newRedeem(secrets[7], secretHashes[7]),
  1527  				newRedeem(secrets[7], secretHashes[7]),
  1528  			},
  1529  			finalStates: []dexeth.SwapStep{
  1530  				dexeth.SSInitiated,
  1531  				dexeth.SSInitiated,
  1532  			},
  1533  			addAmt: false,
  1534  		},
  1535  	}
  1536  
  1537  	for _, test := range tests {
  1538  		var optsVal uint64
  1539  		for i, contract := range test.swaps {
  1540  			swap, err := c.swap(ctx, bytesToArray(test.swaps[i].SecretHash))
  1541  			if err != nil {
  1542  				t.Fatal("unable to get swap state")
  1543  			}
  1544  			state := dexeth.SwapStep(swap.State)
  1545  			if state != dexeth.SSNone {
  1546  				t.Fatalf("unexpected swap state for test %v: want %s got %s", test.name, dexeth.SSNone, state)
  1547  			}
  1548  			if isETH {
  1549  				optsVal += contract.Value
  1550  			}
  1551  		}
  1552  
  1553  		balance := func() (*big.Int, error) {
  1554  			return test.redeemerClient.addressBalance(ctx, test.redeemerClient.address())
  1555  		}
  1556  		if !isETH {
  1557  			balance = func() (*big.Int, error) {
  1558  				return test.redeemerContractor.(tokenContractor).balance(ctx)
  1559  			}
  1560  		}
  1561  
  1562  		txOpts, err := test.redeemerClient.txOpts(ctx, optsVal, gases.SwapN(len(test.swaps)), dexeth.GweiToWei(maxFeeRate), nil, nil)
  1563  		if err != nil {
  1564  			t.Fatalf("%s: txOpts error: %v", test.name, err)
  1565  		}
  1566  		tx, err := test.redeemerContractor.initiate(txOpts, test.swaps)
  1567  		if err != nil {
  1568  			t.Fatalf("%s: initiate error: %v ", test.name, err)
  1569  		}
  1570  
  1571  		// This waitForMined will always take test.sleepNBlocks to complete.
  1572  		if err := waitForMined(); err != nil {
  1573  			t.Fatalf("%s: post-init mining error: %v", test.name, err)
  1574  		}
  1575  
  1576  		receipt, err := waitForReceipt(test.redeemerClient, tx)
  1577  		if err != nil {
  1578  			t.Fatalf("%s: failed to get init receipt: %v", test.name, err)
  1579  		}
  1580  		spew.Dump(receipt)
  1581  
  1582  		err = checkTxStatus(receipt, txOpts.GasLimit)
  1583  		if err != nil {
  1584  			t.Fatalf("%s: failed init transaction status: %v", test.name, err)
  1585  		}
  1586  		fmt.Printf("Gas used for %d inits: %d \n", len(test.swaps), receipt.GasUsed)
  1587  
  1588  		for i := range test.swaps {
  1589  			swap, err := test.redeemerContractor.swap(ctx, bytesToArray(test.swaps[i].SecretHash))
  1590  			if err != nil {
  1591  				t.Fatal("unable to get swap state")
  1592  			}
  1593  			if swap.State != dexeth.SSInitiated {
  1594  				t.Fatalf("unexpected swap state for test %v: want %s got %s", test.name, dexeth.SSInitiated, swap.State)
  1595  			}
  1596  		}
  1597  
  1598  		var originalParentBal *big.Int
  1599  		if !isETH {
  1600  			originalParentBal, err = test.redeemerClient.addressBalance(ctx, test.redeemerClient.address())
  1601  			if err != nil {
  1602  				t.Fatalf("%s: eth balance error: %v", test.name, err)
  1603  			}
  1604  		}
  1605  
  1606  		originalBal, err := balance()
  1607  		if err != nil {
  1608  			t.Fatalf("%s: balance error: %v", test.name, err)
  1609  		}
  1610  
  1611  		expGas := gases.RedeemN(len(test.redemptions))
  1612  		txOpts, err = test.redeemerClient.txOpts(ctx, 0, expGas, dexeth.GweiToWei(maxFeeRate), nil, nil)
  1613  		if err != nil {
  1614  			t.Fatalf("%s: txOpts error: %v", test.name, err)
  1615  		}
  1616  		tx, err = test.redeemerContractor.redeem(txOpts, test.redemptions)
  1617  		if test.expectRedeemErr {
  1618  			if err == nil {
  1619  				t.Fatalf("%s: expected error but did not get", test.name)
  1620  			}
  1621  			continue
  1622  		}
  1623  		if err != nil {
  1624  			t.Fatalf("%s: redeem error: %v", test.name, err)
  1625  		}
  1626  		spew.Dump(tx)
  1627  
  1628  		if err := waitForMined(); err != nil {
  1629  			t.Fatalf("%s: post-redeem mining error: %v", test.name, err)
  1630  		}
  1631  
  1632  		receipt, err = waitForReceipt(test.redeemerClient, tx)
  1633  		if err != nil {
  1634  			t.Fatalf("%s: failed to get redeem receipt: %v", test.name, err)
  1635  		}
  1636  		spew.Dump(receipt)
  1637  
  1638  		expSuccess := !test.expectRedeemErr && test.addAmt
  1639  		err = checkTxStatus(receipt, txOpts.GasLimit)
  1640  		if err != nil && expSuccess {
  1641  			t.Fatalf("%s: failed redeem transaction status: %v", test.name, err)
  1642  		}
  1643  		fmt.Printf("Gas used for %d redeems, success = %t: %d \n", len(test.swaps), expSuccess, receipt.GasUsed)
  1644  
  1645  		bal, err := balance()
  1646  		if err != nil {
  1647  			t.Fatalf("%s: redeemer balance error: %v", test.name, err)
  1648  		}
  1649  
  1650  		// Check transaction parsing while we're here.
  1651  		if in, _, err := test.redeemerContractor.value(ctx, tx); err != nil {
  1652  			t.Fatalf("error parsing value from redemption: %v", err)
  1653  		} else if in != uint64(len(test.swaps)) {
  1654  			t.Fatalf("%s: unexpected pending in balance %d", test.name, in)
  1655  		}
  1656  
  1657  		// Balance should increase or decrease by a certain amount
  1658  		// depending on whether redeem completed successfully on-chain.
  1659  		// If unsuccessful the fee is subtracted. If successful, amt is
  1660  		// added.
  1661  		gasPrice, err := feesAtBlk(ctx, ethClient, receipt.BlockNumber.Int64())
  1662  		if err != nil {
  1663  			t.Fatalf("%s: feesAtBlk error: %v", test.name, err)
  1664  		}
  1665  		bigGasUsed := new(big.Int).SetUint64(receipt.GasUsed)
  1666  		txFee := new(big.Int).Mul(bigGasUsed, gasPrice)
  1667  		wantBal := new(big.Int).Set(originalBal)
  1668  
  1669  		if test.addAmt {
  1670  			wantBal.Add(wantBal, evmify(uint64(len(test.redemptions))))
  1671  		}
  1672  
  1673  		if isETH {
  1674  			wantBal.Sub(wantBal, txFee)
  1675  		} else {
  1676  			parentBal, err := test.redeemerClient.addressBalance(ctx, test.redeemerClient.address())
  1677  			if err != nil {
  1678  				t.Fatalf("%s: post-redeem eth balance error: %v", test.name, err)
  1679  			}
  1680  			wantParentBal := new(big.Int).Sub(originalParentBal, txFee)
  1681  			diff := new(big.Int).Sub(wantParentBal, parentBal)
  1682  			if diff.Cmp(big.NewInt(0)) != 0 {
  1683  				t.Fatalf("%s: unexpected parent chain balance change: want %d got %d, diff = %d",
  1684  					test.name, wantParentBal, parentBal, diff)
  1685  			}
  1686  		}
  1687  
  1688  		diff := new(big.Int).Sub(wantBal, bal)
  1689  		if diff.Cmp(big.NewInt(0)) != 0 {
  1690  			t.Fatalf("%s: unexpected balance change: want %d got %d, diff = %d",
  1691  				test.name, wantBal, bal, diff)
  1692  		}
  1693  
  1694  		for i, redemption := range test.redemptions {
  1695  			swap, err := c.swap(ctx, bytesToArray(redemption.Spends.SecretHash))
  1696  			if err != nil {
  1697  				t.Fatalf("unexpected error for test %v: %v", test.name, err)
  1698  			}
  1699  			state := dexeth.SwapStep(swap.State)
  1700  			if state != test.finalStates[i] {
  1701  				t.Fatalf("unexpected swap state for test %v [%d]: want %s got %s",
  1702  					test.name, i, test.finalStates[i], state)
  1703  			}
  1704  		}
  1705  	}
  1706  }
  1707  
  1708  func testRefundGas(t *testing.T, assetID uint32) {
  1709  	if assetID != BipID {
  1710  		prepareTokenClients(t)
  1711  	}
  1712  
  1713  	isETH := assetID == BipID
  1714  
  1715  	c := simnetContractor
  1716  	gases := ethGases
  1717  	var optsVal uint64 = 1
  1718  	if !isETH {
  1719  		c = simnetTokenContractor
  1720  		gases = tokenGases
  1721  		optsVal = 0
  1722  	}
  1723  
  1724  	var secret [32]byte
  1725  	copy(secret[:], encode.RandomBytes(32))
  1726  	secretHash := sha256.Sum256(secret[:])
  1727  
  1728  	lockTime := uint64(time.Now().Unix())
  1729  
  1730  	txOpts, err := ethClient.txOpts(ctx, optsVal, gases.SwapN(1), nil, nil, nil)
  1731  	if err != nil {
  1732  		t.Fatalf("txOpts error: %v", err)
  1733  	}
  1734  	_, err = c.initiate(txOpts, []*asset.Contract{newContract(lockTime, secretHash, 1)})
  1735  	if err != nil {
  1736  		t.Fatalf("Unable to initiate swap: %v ", err)
  1737  	}
  1738  	if err := waitForMined(); err != nil {
  1739  		t.Fatalf("unexpected error while waiting to mine: %v", err)
  1740  	}
  1741  
  1742  	swap, err := c.swap(ctx, secretHash)
  1743  	if err != nil {
  1744  		t.Fatal("unable to get swap state")
  1745  	}
  1746  	state := dexeth.SwapStep(swap.State)
  1747  	if state != dexeth.SSInitiated {
  1748  		t.Fatalf("unexpected swap state: want %s got %s", dexeth.SSInitiated, state)
  1749  	}
  1750  
  1751  	gas, err := c.estimateRefundGas(ctx, secretHash)
  1752  	if err != nil {
  1753  		t.Fatalf("Error estimating gas for refund function: %v", err)
  1754  	}
  1755  	if isETH {
  1756  		expGas := gases.Refund
  1757  		if gas > expGas || gas < expGas*70/100 {
  1758  			t.Fatalf("expected refund gas to be near %d, but got %d",
  1759  				expGas, gas)
  1760  		}
  1761  	}
  1762  	fmt.Printf("Gas used for refund: %v \n", gas)
  1763  }
  1764  
  1765  func testRefund(t *testing.T, assetID uint32) {
  1766  	if assetID != BipID {
  1767  		prepareTokenClients(t)
  1768  	}
  1769  
  1770  	const amt = 1
  1771  
  1772  	isETH := assetID == BipID
  1773  
  1774  	gases := ethGases
  1775  	c, pc := simnetContractor, participantContractor
  1776  	evmify := dexeth.GweiToWei
  1777  	if !isETH {
  1778  		gases = tokenGases
  1779  		c, pc = simnetTokenContractor, participantTokenContractor
  1780  		tc := c.(*tokenContractorV0)
  1781  		evmify = tc.evmify
  1782  	}
  1783  	tests := []struct {
  1784  		name                         string
  1785  		refunder                     *accounts.Account
  1786  		refunderClient               ethFetcher
  1787  		refunderContractor           contractor
  1788  		finalState                   dexeth.SwapStep
  1789  		addAmt, redeem, isRefundable bool
  1790  		addTime                      time.Duration
  1791  	}{{
  1792  		name:               "ok",
  1793  		isRefundable:       true,
  1794  		refunderClient:     ethClient,
  1795  		refunder:           simnetAcct,
  1796  		refunderContractor: c,
  1797  		addAmt:             true,
  1798  		finalState:         dexeth.SSRefunded,
  1799  	}, {
  1800  		name:               "before locktime",
  1801  		isRefundable:       false,
  1802  		refunderClient:     ethClient,
  1803  		refunder:           simnetAcct,
  1804  		refunderContractor: c,
  1805  		finalState:         dexeth.SSInitiated,
  1806  		addTime:            time.Hour,
  1807  
  1808  		// NOTE: Refunding to an account other than the sender takes more
  1809  		// gas. At present redeem gas must be set to around 46000 although
  1810  		// it will only use about 43100. Set in dex/networks/eth/params.go
  1811  		// to test.
  1812  		// }, {
  1813  		// 	name:           "ok non initiator refunder",
  1814  		// 	sleep:          time.Second * 16,
  1815  		// 	isRefundable:   true,
  1816  		// 	refunderClient: participantEthClient,
  1817  		// 	refunder:       participantAcct,
  1818  		// 	addAmt:         true,
  1819  		// 	finalState:     dexeth.SSRefunded,
  1820  	}, {
  1821  		name:               "already redeemed",
  1822  		isRefundable:       false,
  1823  		refunderClient:     ethClient,
  1824  		refunder:           simnetAcct,
  1825  		refunderContractor: c,
  1826  		redeem:             true,
  1827  		finalState:         dexeth.SSRedeemed,
  1828  	}}
  1829  
  1830  	for _, test := range tests {
  1831  		balance := func() (*big.Int, error) {
  1832  
  1833  			return test.refunderClient.addressBalance(ctx, test.refunder.Address)
  1834  		}
  1835  
  1836  		var optsVal uint64 = amt
  1837  		if !isETH {
  1838  			optsVal = 0
  1839  			balance = func() (*big.Int, error) {
  1840  				return test.refunderContractor.(tokenContractor).balance(ctx)
  1841  			}
  1842  		}
  1843  
  1844  		var secret [32]byte
  1845  		copy(secret[:], encode.RandomBytes(32))
  1846  		secretHash := sha256.Sum256(secret[:])
  1847  
  1848  		swap, err := test.refunderContractor.swap(ctx, secretHash)
  1849  		if err != nil {
  1850  			t.Fatalf("%s: unable to get swap state pre-init", test.name)
  1851  		}
  1852  		if swap.State != dexeth.SSNone {
  1853  			t.Fatalf("unexpected swap state for test %v: want %s got %s", test.name, dexeth.SSNone, swap.State)
  1854  		}
  1855  
  1856  		inLocktime := uint64(time.Now().Add(test.addTime).Unix())
  1857  
  1858  		txOpts, err := ethClient.txOpts(ctx, optsVal, gases.SwapN(1), nil, nil, nil)
  1859  		if err != nil {
  1860  			t.Fatalf("%s: txOpts error: %v", test.name, err)
  1861  		}
  1862  		_, err = c.initiate(txOpts, []*asset.Contract{newContract(inLocktime, secretHash, amt)})
  1863  		if err != nil {
  1864  			t.Fatalf("%s: initiate error: %v ", test.name, err)
  1865  		}
  1866  
  1867  		if test.redeem {
  1868  			if err := waitForMined(); err != nil {
  1869  				t.Fatalf("%s: pre-redeem mining error: %v", test.name, err)
  1870  			}
  1871  
  1872  			txOpts, err = participantEthClient.txOpts(ctx, 0, gases.RedeemN(1), nil, nil, nil)
  1873  			if err != nil {
  1874  				t.Fatalf("%s: txOpts error: %v", test.name, err)
  1875  			}
  1876  			_, err := pc.redeem(txOpts, []*asset.Redemption{newRedeem(secret, secretHash)})
  1877  			if err != nil {
  1878  				t.Fatalf("%s: redeem error: %v", test.name, err)
  1879  			}
  1880  		}
  1881  
  1882  		// This waitForMined will always take test.sleep to complete.
  1883  		if err := waitForMined(); err != nil {
  1884  			t.Fatalf("unexpected post-init mining error for test %v: %v", test.name, err)
  1885  		}
  1886  
  1887  		var originalParentBal *big.Int
  1888  		if !isETH {
  1889  			originalParentBal, err = test.refunderClient.addressBalance(ctx, test.refunderClient.address())
  1890  			if err != nil {
  1891  				t.Fatalf("%s: eth balance error: %v", test.name, err)
  1892  			}
  1893  		}
  1894  
  1895  		originalBal, err := balance()
  1896  		if err != nil {
  1897  			t.Fatalf("%s: balance error: %v", test.name, err)
  1898  		}
  1899  
  1900  		isRefundable, err := test.refunderContractor.isRefundable(secretHash)
  1901  		if err != nil {
  1902  			t.Fatalf("%s: isRefundable error %v", test.name, err)
  1903  		}
  1904  		if isRefundable != test.isRefundable {
  1905  			t.Fatalf("%s: expected isRefundable=%v, but got %v",
  1906  				test.name, test.isRefundable, isRefundable)
  1907  		}
  1908  
  1909  		txOpts, err = test.refunderClient.txOpts(ctx, 0, gases.Refund, dexeth.GweiToWei(maxFeeRate), nil, nil)
  1910  		if err != nil {
  1911  			t.Fatalf("%s: txOpts error: %v", test.name, err)
  1912  		}
  1913  		tx, err := test.refunderContractor.refund(txOpts, secretHash)
  1914  		if err != nil {
  1915  			t.Fatalf("%s: refund error: %v", test.name, err)
  1916  		}
  1917  		spew.Dump(tx)
  1918  
  1919  		in, _, err := test.refunderContractor.value(ctx, tx)
  1920  		if err != nil {
  1921  			t.Fatalf("%s: error finding in value: %v", test.name, err)
  1922  		}
  1923  
  1924  		if test.addAmt && in != amt {
  1925  			t.Fatalf("%s: unexpected pending in balance %d", test.name, in)
  1926  		}
  1927  
  1928  		if err := waitForMined(); err != nil {
  1929  			t.Fatalf("%s: post-refund mining error: %v", test.name, err)
  1930  		}
  1931  
  1932  		receipt, err := waitForReceipt(test.refunderClient, tx)
  1933  		if err != nil {
  1934  			t.Fatalf("%s: failed to get refund receipt: %v", test.name, err)
  1935  		}
  1936  		spew.Dump(receipt)
  1937  
  1938  		err = checkTxStatus(receipt, txOpts.GasLimit)
  1939  		// test.addAmt being true indicates the refund should succeed.
  1940  		if err != nil && test.addAmt {
  1941  			t.Fatalf("%s: failed refund transaction status: %v", test.name, err)
  1942  		}
  1943  		fmt.Printf("Gas used for refund, success = %t: %d\n", test.addAmt, receipt.GasUsed)
  1944  
  1945  		// Balance should increase or decrease by a certain amount
  1946  		// depending on whether refund completed successfully on-chain.
  1947  		// If unsuccessful the fee is subtracted. If successful, amt is
  1948  		// added.
  1949  		gasPrice, err := feesAtBlk(ctx, ethClient, receipt.BlockNumber.Int64())
  1950  		if err != nil {
  1951  			t.Fatalf("%s: feesAtBlk error: %v", test.name, err)
  1952  		}
  1953  		fmt.Printf("Gas price for refund: %d\n", gasPrice)
  1954  		bigGasUsed := new(big.Int).SetUint64(receipt.GasUsed)
  1955  		txFee := new(big.Int).Mul(bigGasUsed, gasPrice)
  1956  
  1957  		wantBal := new(big.Int).Set(originalBal)
  1958  		if test.addAmt {
  1959  			wantBal.Add(wantBal, evmify(amt))
  1960  		}
  1961  
  1962  		if isETH {
  1963  			wantBal.Sub(wantBal, txFee)
  1964  		} else {
  1965  			parentBal, err := test.refunderClient.addressBalance(ctx, test.refunderClient.address())
  1966  			if err != nil {
  1967  				t.Fatalf("%s: post-refund eth balance error: %v", test.name, err)
  1968  			}
  1969  			wantParentBal := new(big.Int).Sub(originalParentBal, txFee)
  1970  			diff := new(big.Int).Sub(wantParentBal, parentBal)
  1971  			if diff.Cmp(big.NewInt(0)) != 0 {
  1972  				t.Fatalf("%s: unexpected parent chain balance change: want %d got %d, diff = %d",
  1973  					test.name, wantParentBal, parentBal, diff)
  1974  			}
  1975  		}
  1976  
  1977  		bal, err := balance()
  1978  		if err != nil {
  1979  			t.Fatalf("%s: balance error: %v", test.name, err)
  1980  		}
  1981  
  1982  		diff := new(big.Int).Sub(wantBal, bal)
  1983  		if diff.Cmp(big.NewInt(0)) != 0 {
  1984  			t.Fatalf("%s: unexpected balance change: want %d got %d, diff = %d",
  1985  				test.name, wantBal, bal, diff)
  1986  		}
  1987  
  1988  		swap, err = test.refunderContractor.swap(ctx, secretHash)
  1989  		if err != nil {
  1990  			t.Fatalf("%s: post-refund swap error: %v", test.name, err)
  1991  		}
  1992  		if swap.State != test.finalState {
  1993  			t.Fatalf("%s: wrong swap state: want %s got %s", test.name, test.finalState, swap.State)
  1994  		}
  1995  	}
  1996  }
  1997  
  1998  func testApproveAllowance(t *testing.T) {
  1999  	err := ethClient.unlock(pw)
  2000  	if err != nil {
  2001  		t.Fatal(err)
  2002  	}
  2003  
  2004  	txOpts, err := ethClient.txOpts(ctx, 0, tokenGases.Approve, nil, nil, nil)
  2005  	if err != nil {
  2006  		t.Fatalf("txOpts error: %v", err)
  2007  	}
  2008  
  2009  	if _, err = simnetTokenContractor.approve(txOpts, unlimitedAllowance); err != nil {
  2010  		t.Fatalf("initiator approveToken error: %v", err)
  2011  	}
  2012  
  2013  	if err := waitForMined(); err != nil {
  2014  		t.Fatalf("post approve mining error: %v", err)
  2015  	}
  2016  
  2017  	allowance, err := simnetTokenContractor.allowance(ctx)
  2018  	if err != nil {
  2019  		t.Fatal(err)
  2020  	}
  2021  
  2022  	if allowance.Cmp(new(big.Int)) == 0 {
  2023  		t.Fatalf("expected allowance > 0")
  2024  	}
  2025  }
  2026  
  2027  func testTransferGas(t *testing.T) {
  2028  	err := ethClient.unlock(pw)
  2029  	if err != nil {
  2030  		t.Fatalf("unlock error: %v", err)
  2031  	}
  2032  	gas, err := simnetTokenContractor.estimateTransferGas(ctx, simnetTokenContractor.(*tokenContractorV0).contractorV0.evmify(1))
  2033  	if err != nil {
  2034  		t.Fatalf("estimateTransferGas error: %v", err)
  2035  	}
  2036  	fmt.Printf("=========== gas for transfer: %d ==============\n", gas)
  2037  }
  2038  
  2039  func testApproveGas(t *testing.T) {
  2040  	gas, err := simnetTokenContractor.estimateApproveGas(ctx, simnetTokenContractor.(*tokenContractorV0).contractorV0.evmify(1))
  2041  	if err != nil {
  2042  		t.Fatalf("")
  2043  	}
  2044  	fmt.Printf("=========== gas for approve: %d ==============\n", gas)
  2045  }
  2046  
  2047  // This test can be used to test that the resubmission of ETH redemptions after
  2048  // they have been overridden by another transaction works properly. Just replace
  2049  // the app seed and nonce. Also uncomment the lines in the function which will
  2050  // either create a replacement transaction that redeems the swap, or one that
  2051  // just sends ETH to another address. If the swap is redeemed by another tx,
  2052  // the DEX should not attempt to resubmit the redemption.
  2053  /*func TestReplaceWithRandomTransaction(t *testing.T) {
  2054  	appSeed := "36e6976207c4ae35d2942fc644fc845cba28c2d3832f93c94fd23e9857e19ad65c2752a041afbf3f1897f693d4abe3903d6374d53cb78b90002ecc6999d4b317"
  2055  	var gasFeeCap int64 = 300000000000
  2056  	var gasTipCap int64 = 300000000000
  2057  	var nonce int64 = 6
  2058  
  2059  	// uncomment these lines to override the tx with another one that redeems the swap
  2060  	// addressToCall := common.HexToAddress("0x2f68e723b8989ba1c6a9f03e42f33cb7dc9d606f")
  2061  	// data, _ := hex.DecodeString("f4fd17f9000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000011ad8ca2d3762cc2d1b8e6216944d90bb5d2b0885106dcadc7ad8f24af49b295d4914b04a3523fb24740bf72f58ca69549343e3f0428fd557f4a81bb6695f467e")
  2062  	// value := 0
  2063  
  2064  	// uncomment these lines to override the tx with another one that does not redeem the swap
  2065  	// addressToCall := simnetAddr
  2066  	// data := []byte{}
  2067  	// value := uint64(1)
  2068  
  2069  	walletDir := filepath.Join(dcrutil.AppDataDir("bisonw", false), "simnet", "assetdb", dex.BipIDSymbol(60))
  2070  	client, err := newNodeClient(getWalletDir(walletDir, dex.Simnet), dex.Simnet, tLogger.SubLogger("initiator"))
  2071  	if err != nil {
  2072  		t.Fatalf("unable to create node client %v", err)
  2073  	}
  2074  	if err := client.connect(ctx); err != nil {
  2075  		t.Fatalf("failed to connect to node: %v", err)
  2076  	}
  2077  	defer client.shutdown()
  2078  
  2079  	appSeedB, err := hex.DecodeString(appSeed)
  2080  	if err != nil {
  2081  		t.Fatal(err)
  2082  	}
  2083  
  2084  	appSeedAndPass := func(assetID uint32, appSeed []byte) ([]byte, []byte) {
  2085  		b := make([]byte, len(appSeed)+4)
  2086  		copy(b, appSeed)
  2087  		binary.BigEndian.PutUint32(b[len(appSeed):], assetID)
  2088  
  2089  		s := blake256.Sum256(b)
  2090  		p := blake256.Sum256(s[:])
  2091  		return s[:], p[:]
  2092  	}
  2093  
  2094  	_, pw := appSeedAndPass(60, appSeedB)
  2095  	err = client.unlock(string(pw))
  2096  	if err != nil {
  2097  		t.Fatal(err)
  2098  	}
  2099  
  2100  	txOpts := &bind.TransactOpts{
  2101  		Context:   ctx,
  2102  		From:      client.address(),
  2103  		Value:     dexeth.GweiToWei(value),
  2104  		GasFeeCap: big.NewInt(gasFeeCap),
  2105  		GasTipCap: big.NewInt(gasTipCap),
  2106  		GasLimit:  63000,
  2107  		Nonce:     big.NewInt(nonce),
  2108  	}
  2109  
  2110  	tx, err := client.sendTransaction(ctx, txOpts, addressToCall, data)
  2111  	if err != nil {
  2112  		t.Fatalf("failed to send transaction: %v", err)
  2113  	}
  2114  
  2115  	fmt.Printf("replacement tx hash: %s\n", tx.Hash())
  2116  }*/
  2117  
  2118  func testSignMessage(t *testing.T) {
  2119  	msg := []byte("test message")
  2120  	sig, pubKey, err := ethClient.signData(msg)
  2121  	if err != nil {
  2122  		t.Fatalf("error signing text: %v", err)
  2123  	}
  2124  	x, y := elliptic.Unmarshal(secp256k1.S256(), pubKey)
  2125  	recoveredAddress := crypto.PubkeyToAddress(ecdsa.PublicKey{
  2126  		Curve: secp256k1.S256(),
  2127  		X:     x,
  2128  		Y:     y,
  2129  	})
  2130  	if !bytes.Equal(recoveredAddress.Bytes(), simnetAcct.Address.Bytes()) {
  2131  		t.Fatalf("recovered address: %v != simnet account address: %v", recoveredAddress, simnetAcct.Address)
  2132  	}
  2133  	if !crypto.VerifySignature(pubKey, crypto.Keccak256(msg), sig) {
  2134  		t.Fatalf("failed to verify signature")
  2135  	}
  2136  }
  2137  
  2138  func TestTokenGasEstimates(t *testing.T) {
  2139  	ctx, cancel := context.WithCancel(ctx)
  2140  	defer cancel()
  2141  	runSimnetMiner(ctx, "eth", tLogger)
  2142  	prepareTokenClients(t)
  2143  	tLogger.SetLevel(dex.LevelInfo)
  2144  	if err := getGasEstimates(ctx, ethClient, participantEthClient, simnetTokenContractor, participantTokenContractor, 5, tokenGases, tLogger); err != nil {
  2145  		t.Fatalf("getGasEstimates error: %v", err)
  2146  	}
  2147  }
  2148  
  2149  func TestConfirmedNonce(t *testing.T) {
  2150  	_, err := ethClient.getConfirmedNonce(ctx)
  2151  	if err != nil {
  2152  		t.Fatalf("getConfirmedNonce error: %v", err)
  2153  	}
  2154  }
  2155  
  2156  func bytesToArray(b []byte) (a [32]byte) {
  2157  	copy(a[:], b)
  2158  	return
  2159  }
  2160  
  2161  func bigUint(v uint64) *big.Int {
  2162  	return new(big.Int).SetUint64(v)
  2163  }