decred.org/dcrdex@v1.0.5/server/asset/eth/coiner_test.go (about)

     1  //go:build !harness
     2  
     3  // These tests will not be run if the harness build tag is set.
     4  
     5  package eth
     6  
     7  import (
     8  	"context"
     9  	"errors"
    10  	"math/big"
    11  	"testing"
    12  
    13  	"decred.org/dcrdex/dex/encode"
    14  	dexeth "decred.org/dcrdex/dex/networks/eth"
    15  	"github.com/ethereum/go-ethereum"
    16  	"github.com/ethereum/go-ethereum/common"
    17  	"github.com/ethereum/go-ethereum/core/types"
    18  )
    19  
    20  func randomAddress() *common.Address {
    21  	var addr common.Address
    22  	copy(addr[:], encode.RandomBytes(20))
    23  	return &addr
    24  }
    25  
    26  func TestNewRedeemCoin(t *testing.T) {
    27  	contractAddr := randomAddress()
    28  	var secret, secretHash, txHash [32]byte
    29  	copy(txHash[:], encode.RandomBytes(32))
    30  	copy(secret[:], redeemSecretB)
    31  	copy(secretHash[:], redeemSecretHashB)
    32  	contract := dexeth.EncodeContractData(0, secretHash)
    33  	const gasPrice = 30
    34  	const gasTipCap = 2
    35  	const value = 5e9
    36  	const wantGas = 30
    37  	const wantGasTipCap = 2
    38  	tests := []struct {
    39  		name          string
    40  		contract      []byte
    41  		tx            *types.Transaction
    42  		swpErr, txErr error
    43  		swap          *dexeth.SwapState
    44  		wantErr       bool
    45  	}{{
    46  		name:     "ok redeem",
    47  		tx:       tTx(gasPrice, gasTipCap, 0, contractAddr, redeemCalldata),
    48  		contract: contract,
    49  	}, {
    50  		name:     "non zero value with redeem",
    51  		tx:       tTx(gasPrice, gasTipCap, value, contractAddr, redeemCalldata),
    52  		contract: contract,
    53  		wantErr:  true,
    54  	}, {
    55  		name:     "unable to decode redeem data, must be redeem for redeem coin type",
    56  		tx:       tTx(gasPrice, gasTipCap, 0, contractAddr, initCalldata),
    57  		contract: contract,
    58  		wantErr:  true,
    59  	}, {
    60  		name:     "tx coin id for redeem - contract not in tx",
    61  		tx:       tTx(gasPrice, gasTipCap, value, contractAddr, redeemCalldata),
    62  		contract: encode.RandomBytes(32),
    63  		wantErr:  true,
    64  	}, {
    65  		name:     "tx not found, redeemed",
    66  		txErr:    ethereum.NotFound,
    67  		swap:     tSwap(97, initLocktime, 1000, secret, dexeth.SSRedeemed, &initParticipantAddr),
    68  		contract: contract,
    69  	}, {
    70  		name:     "tx not found, not redeemed",
    71  		txErr:    ethereum.NotFound,
    72  		swap:     tSwap(97, initLocktime, 1000, secret, dexeth.SSInitiated, &initParticipantAddr),
    73  		contract: contract,
    74  		wantErr:  true,
    75  	}, {
    76  		name:     "tx not found, swap err",
    77  		txErr:    ethereum.NotFound,
    78  		swpErr:   errors.New("swap not found"),
    79  		contract: contract,
    80  		wantErr:  true,
    81  	}}
    82  	for _, test := range tests {
    83  		node := &testNode{
    84  			tx:     test.tx,
    85  			txErr:  test.txErr,
    86  			swp:    test.swap,
    87  			swpErr: test.swpErr,
    88  		}
    89  		eth := &AssetBackend{
    90  			baseBackend: &baseBackend{
    91  				node:       node,
    92  				baseLogger: tLogger,
    93  			},
    94  			contractAddr: *contractAddr,
    95  			assetID:      BipID,
    96  			log:          tLogger,
    97  		}
    98  		rc, err := eth.newRedeemCoin(txHash[:], test.contract)
    99  		if test.wantErr {
   100  			if err == nil {
   101  				t.Fatalf("expected error for test %q", test.name)
   102  			}
   103  			continue
   104  		}
   105  		if err != nil {
   106  			t.Fatalf("unexpected error for test %q: %v", test.name, err)
   107  		}
   108  
   109  		if test.txErr == nil && (rc.secretHash != secretHash ||
   110  			rc.secret != secret ||
   111  			rc.value.Uint64() != 0 ||
   112  			dexeth.WeiToGwei(rc.gasFeeCap) != wantGas ||
   113  			dexeth.WeiToGwei(rc.gasTipCap) != wantGasTipCap) {
   114  			t.Fatalf("returns do not match expected for test %q / %v", test.name, rc)
   115  		}
   116  	}
   117  }
   118  
   119  func TestNewSwapCoin(t *testing.T) {
   120  	contractAddr, randomAddr := randomAddress(), randomAddress()
   121  	var secret, secretHash, txHash [32]byte
   122  	copy(txHash[:], encode.RandomBytes(32))
   123  	copy(secret[:], redeemSecretB)
   124  	copy(secretHash[:], redeemSecretHashB)
   125  	txCoinIDBytes := txHash[:]
   126  	badCoinIDBytes := encode.RandomBytes(39)
   127  	const gasPrice = 30
   128  	const value = 5e9
   129  	const gasTipCap = 2
   130  	wantGas := dexeth.WeiToGwei(big.NewInt(3e10))
   131  	wantVal := dexeth.WeiToGwei(big.NewInt(5e18))
   132  	wantGasTipCap := dexeth.WeiToGweiCeil(big.NewInt(2e9))
   133  	tests := []struct {
   134  		name          string
   135  		coinID        []byte
   136  		contract      []byte
   137  		tx            *types.Transaction
   138  		swpErr, txErr error
   139  		wantErr       bool
   140  	}{{
   141  		name:     "ok init",
   142  		tx:       tTx(gasPrice, gasTipCap, value, contractAddr, initCalldata),
   143  		coinID:   txCoinIDBytes,
   144  		contract: dexeth.EncodeContractData(0, secretHash),
   145  	}, {
   146  		name:     "contract incorrect length",
   147  		tx:       tTx(gasPrice, gasTipCap, value, contractAddr, initCalldata),
   148  		coinID:   txCoinIDBytes,
   149  		contract: initSecretHashA[:31],
   150  		wantErr:  true,
   151  	}, {
   152  		name:     "tx has no data",
   153  		tx:       tTx(gasPrice, gasTipCap, value, contractAddr, nil),
   154  		coinID:   txCoinIDBytes,
   155  		contract: initSecretHashA,
   156  		wantErr:  true,
   157  	}, {
   158  		name:     "unable to decode init data, must be init for init coin type",
   159  		tx:       tTx(gasPrice, gasTipCap, value, contractAddr, redeemCalldata),
   160  		coinID:   txCoinIDBytes,
   161  		contract: initSecretHashA,
   162  		wantErr:  true,
   163  	}, {
   164  		name:     "unable to decode CoinID",
   165  		tx:       tTx(gasPrice, gasTipCap, value, contractAddr, initCalldata),
   166  		contract: initSecretHashA,
   167  		wantErr:  true,
   168  	}, {
   169  		name:     "invalid coinID",
   170  		tx:       tTx(gasPrice, gasTipCap, value, contractAddr, initCalldata),
   171  		coinID:   badCoinIDBytes,
   172  		contract: initSecretHashA,
   173  		wantErr:  true,
   174  	}, {
   175  		name:     "transaction error",
   176  		tx:       tTx(gasPrice, gasTipCap, value, contractAddr, initCalldata),
   177  		coinID:   txCoinIDBytes,
   178  		contract: initSecretHashA,
   179  		txErr:    errors.New(""),
   180  		wantErr:  true,
   181  	}, {
   182  		name:     "transaction not found error",
   183  		tx:       tTx(gasPrice, gasTipCap, value, contractAddr, initCalldata),
   184  		coinID:   txCoinIDBytes,
   185  		contract: initSecretHashA,
   186  		txErr:    ethereum.NotFound,
   187  		wantErr:  true,
   188  	}, {
   189  		name:     "wrong contract",
   190  		tx:       tTx(gasPrice, gasTipCap, value, randomAddr, initCalldata),
   191  		coinID:   txCoinIDBytes,
   192  		contract: initSecretHashA,
   193  		wantErr:  true,
   194  	}, {
   195  		name:     "tx coin id for swap - contract not in tx",
   196  		tx:       tTx(gasPrice, gasTipCap, value, contractAddr, initCalldata),
   197  		coinID:   txCoinIDBytes,
   198  		contract: encode.RandomBytes(32),
   199  		wantErr:  true,
   200  	}}
   201  	for _, test := range tests {
   202  		node := &testNode{
   203  			tx:    test.tx,
   204  			txErr: test.txErr,
   205  		}
   206  		eth := &AssetBackend{
   207  			baseBackend: &baseBackend{
   208  				node:       node,
   209  				baseLogger: tLogger,
   210  			},
   211  			contractAddr: *contractAddr,
   212  			atomize:      dexeth.WeiToGwei,
   213  		}
   214  		sc, err := eth.newSwapCoin(test.coinID, test.contract)
   215  		if test.wantErr {
   216  			if err == nil {
   217  				t.Fatalf("expected error for test %q", test.name)
   218  			}
   219  			continue
   220  		}
   221  		if err != nil {
   222  			t.Fatalf("unexpected error for test %q: %v", test.name, err)
   223  		}
   224  
   225  		if sc.init.Participant != initParticipantAddr ||
   226  			sc.secretHash != secretHash ||
   227  			dexeth.WeiToGwei(sc.value) != wantVal ||
   228  			dexeth.WeiToGwei(sc.gasFeeCap) != wantGas ||
   229  			dexeth.WeiToGwei(sc.gasTipCap) != wantGasTipCap ||
   230  			sc.init.LockTime.Unix() != initLocktime {
   231  			t.Fatalf("returns do not match expected for test %q / %v", test.name, sc)
   232  		}
   233  	}
   234  }
   235  
   236  type Confirmer interface {
   237  	Confirmations(context.Context) (int64, error)
   238  	String() string
   239  }
   240  
   241  func TestConfirmations(t *testing.T) {
   242  	contractAddr, nullAddr := new(common.Address), new(common.Address)
   243  	copy(contractAddr[:], encode.RandomBytes(20))
   244  	var secret, secretHash, txHash [32]byte
   245  	copy(txHash[:], encode.RandomBytes(32))
   246  	copy(secret[:], redeemSecretB)
   247  	copy(secretHash[:], redeemSecretHashB)
   248  	const gasPrice = 30
   249  	const gasTipCap = 2
   250  	const swapVal = 25e8
   251  	const txVal = swapVal * 2
   252  	const oneGweiMore = swapVal + 1
   253  	tests := []struct {
   254  		name            string
   255  		swap            *dexeth.SwapState
   256  		bn              uint64
   257  		value           uint64
   258  		wantConfs       int64
   259  		swapErr, bnErr  error
   260  		wantErr, redeem bool
   261  	}{{
   262  		name:      "ok has confs value not verified",
   263  		bn:        100,
   264  		swap:      tSwap(97, initLocktime, swapVal, secret, dexeth.SSInitiated, &initParticipantAddr),
   265  		value:     txVal,
   266  		wantConfs: 4,
   267  	}, {
   268  		name:  "ok no confs",
   269  		swap:  tSwap(0, 0, 0, secret, dexeth.SSNone, nullAddr),
   270  		value: txVal,
   271  	}, {
   272  		name:      "ok redeem swap status redeemed",
   273  		bn:        97,
   274  		swap:      tSwap(97, initLocktime, swapVal, secret, dexeth.SSRedeemed, &initParticipantAddr),
   275  		value:     0,
   276  		wantConfs: 1,
   277  		redeem:    true,
   278  	}, {
   279  		name:   "ok redeem swap status initiated",
   280  		swap:   tSwap(97, initLocktime, swapVal, secret, dexeth.SSInitiated, &initParticipantAddr),
   281  		value:  0,
   282  		redeem: true,
   283  	}, {
   284  		name:    "redeem bad swap state None",
   285  		swap:    tSwap(0, 0, 0, secret, dexeth.SSNone, nullAddr),
   286  		value:   0,
   287  		wantErr: true,
   288  		redeem:  true,
   289  	}, {
   290  		name:    "error getting swap",
   291  		swapErr: errors.New(""),
   292  		value:   txVal,
   293  		wantErr: true,
   294  	}, {
   295  		name:    "value differs from initial transaction",
   296  		swap:    tSwap(99, initLocktime, oneGweiMore, secret, dexeth.SSInitiated, &initParticipantAddr),
   297  		value:   txVal,
   298  		wantErr: true,
   299  	}, {
   300  		name:    "participant differs from initial transaction",
   301  		swap:    tSwap(99, initLocktime, swapVal, secret, dexeth.SSInitiated, nullAddr),
   302  		value:   txVal,
   303  		wantErr: true,
   304  		// }, {
   305  		// 	name:    "locktime not an int64",
   306  		// 	swap:    tSwap(99, new(big.Int).SetUint64(^uint64(0)), value, secret, dexeth.SSInitiated, &initParticipantAddr),
   307  		// 	value:   value,
   308  		// 	ct:      sctInit,
   309  		// 	wantErr: true,
   310  	}, {
   311  		name:    "locktime differs from initial transaction",
   312  		swap:    tSwap(99, 0, swapVal, secret, dexeth.SSInitiated, &initParticipantAddr),
   313  		value:   txVal,
   314  		wantErr: true,
   315  	}, {
   316  		name:    "block number error",
   317  		swap:    tSwap(97, initLocktime, swapVal, secret, dexeth.SSInitiated, &initParticipantAddr),
   318  		value:   txVal,
   319  		bnErr:   errors.New(""),
   320  		wantErr: true,
   321  	}}
   322  	for _, test := range tests {
   323  		node := &testNode{
   324  			swp:       test.swap,
   325  			swpErr:    test.swapErr,
   326  			blkNum:    test.bn,
   327  			blkNumErr: test.bnErr,
   328  		}
   329  		eth := &AssetBackend{
   330  			baseBackend: &baseBackend{
   331  				node:       node,
   332  				baseLogger: tLogger,
   333  			},
   334  			contractAddr: *contractAddr,
   335  			atomize:      dexeth.WeiToGwei,
   336  		}
   337  
   338  		swapData := dexeth.EncodeContractData(0, secretHash)
   339  
   340  		var confirmer Confirmer
   341  		var err error
   342  		if test.redeem {
   343  			node.tx = tTx(gasPrice, gasTipCap, test.value, contractAddr, redeemCalldata)
   344  			confirmer, err = eth.newRedeemCoin(txHash[:], swapData)
   345  		} else {
   346  			node.tx = tTx(gasPrice, gasTipCap, test.value, contractAddr, initCalldata)
   347  			confirmer, err = eth.newSwapCoin(txHash[:], swapData)
   348  		}
   349  		if err != nil {
   350  			t.Fatalf("unexpected error for test %q: %v", test.name, err)
   351  		}
   352  
   353  		_ = confirmer.String() // unrelated panic test
   354  
   355  		confs, err := confirmer.Confirmations(nil)
   356  		if test.wantErr {
   357  			if err == nil {
   358  				t.Fatalf("expected error for test %q", test.name)
   359  			}
   360  			continue
   361  		}
   362  		if err != nil {
   363  			t.Fatalf("unexpected error for test %q: %v", test.name, err)
   364  		}
   365  		if confs != test.wantConfs {
   366  			t.Fatalf("want %d but got %d confs for test: %v", test.wantConfs, confs, test.name)
   367  		}
   368  	}
   369  }
   370  
   371  // func TestGeneratePackedInits(t *testing.T) {
   372  // 	hexToHash := func(s string) (h [32]byte) {
   373  // 		b, _ := hex.DecodeString(s)
   374  // 		copy(h[:], b)
   375  // 		return
   376  // 	}
   377  // 	inits := []swapv0.ETHSwapInitiation{
   378  // 		{
   379  // 			RefundTimestamp: big.NewInt(1632112916),
   380  // 			SecretHash:      hexToHash("8b3e4acc53b664f9cf6fcac0adcd328e95d62ba1f4379650ae3e1460a0f9d1a1"),
   381  // 			Value:           dexeth.GweiToWei(25e8),
   382  // 			Participant:     common.HexToAddress("0x345853e21b1d475582e71cc269124ed5e2dd3422"),
   383  // 		},
   384  // 		{
   385  // 			RefundTimestamp: big.NewInt(1632112916),
   386  // 			SecretHash:      hexToHash("ebdc4c31b88d0c8f4d644591a8e00e92b607f920ad8050deb7c7469767d9c561"),
   387  // 			Value:           dexeth.GweiToWei(25e8),
   388  // 			Participant:     common.HexToAddress("0x345853e21b1d475582e71cc269124ed5e2dd3422"),
   389  // 		},
   390  // 	}
   391  // 	data, err := dexeth.ABIs[0].Pack("initiate", inits)
   392  // 	if err != nil {
   393  // 		t.Fatalf("Pack error: %v", err)
   394  // 	}
   395  
   396  // 	fmt.Printf("tx data: %x \n", data)
   397  // }