decred.org/dcrdex@v1.0.5/server/asset/eth/eth_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 "bytes" 9 "context" 10 "crypto/sha256" 11 "encoding/binary" 12 "encoding/hex" 13 "errors" 14 "math/big" 15 "net/url" 16 "os" 17 "strings" 18 "testing" 19 "time" 20 21 "decred.org/dcrdex/dex" 22 "decred.org/dcrdex/dex/calc" 23 "decred.org/dcrdex/dex/encode" 24 dexeth "decred.org/dcrdex/dex/networks/eth" 25 "decred.org/dcrdex/server/asset" 26 "github.com/ethereum/go-ethereum" 27 "github.com/ethereum/go-ethereum/common" 28 "github.com/ethereum/go-ethereum/core/types" 29 ) 30 31 const initLocktime = 1632112916 32 33 var ( 34 _ ethFetcher = (*testNode)(nil) 35 tLogger = dex.StdOutLogger("ETHTEST", dex.LevelTrace) 36 tCtx context.Context 37 initCalldata = mustParseHex("a8793f94000000000000000000000000000" + 38 "0000000000000000000000000000000000020000000000000000000000000000000000" + 39 "0000000000000000000000000000002000000000000000000000000000000000000000" + 40 "00000000000000000614811148b3e4acc53b664f9cf6fcac0adcd328e95d62ba1f4379" + 41 "650ae3e1460a0f9d1a1000000000000000000000000345853e21b1d475582e71cc2691" + 42 "24ed5e2dd342200000000000000000000000000000000000000000000000022b1c8c12" + 43 "27a0000000000000000000000000000000000000000000000000000000000006148111" + 44 "4ebdc4c31b88d0c8f4d644591a8e00e92b607f920ad8050deb7c7469767d9c56100000" + 45 "0000000000000000000345853e21b1d475582e71cc269124ed5e2dd342200000000000" + 46 "000000000000000000000000000000000000022b1c8c1227a0000") 47 /* initCallData parses to: 48 [ETHSwapInitiation { 49 RefundTimestamp: 1632112916 50 SecretHash: 8b3e4acc53b664f9cf6fcac0adcd328e95d62ba1f4379650ae3e1460a0f9d1a1 51 Value: 5e9 gwei 52 Participant: 0x345853e21b1d475582e71cc269124ed5e2dd3422 53 }, 54 ETHSwapInitiation { 55 RefundTimestamp: 1632112916 56 SecretHash: ebdc4c31b88d0c8f4d644591a8e00e92b607f920ad8050deb7c7469767d9c561 57 Value: 5e9 gwei 58 Participant: 0x345853e21b1d475582e71cc269124ed5e2dd3422 59 }] 60 */ 61 initSecretHashA = mustParseHex("8b3e4acc53b664f9cf6fcac0adcd328e95d62ba1f4379650ae3e1460a0f9d1a1") 62 initSecretHashB = mustParseHex("ebdc4c31b88d0c8f4d644591a8e00e92b607f920ad8050deb7c7469767d9c561") 63 initParticipantAddr = common.HexToAddress("345853e21b1d475582E71cC269124eD5e2dD3422") 64 redeemCalldata = mustParseHex("f4fd17f90000000000000000000000000000000000000" + 65 "000000000000000000000000020000000000000000000000000000000000000000000000000" + 66 "00000000000000022c0a304c9321402dc11cbb5898b9f2af3029ce1c76ec6702c4cd5bb965f" + 67 "d3e7399d971975c09331eb00f5e0dc1eaeca9bf4ee2d086d3fe1de489f920007d654687eac0" + 68 "9638c0c38b4e735b79f053cb869167ee770640ac5df5c4ab030813122aebdc4c31b88d0c8f4" + 69 "d644591a8e00e92b607f920ad8050deb7c7469767d9c561") 70 /* 71 redeemCallData parses to: 72 [ETHSwapRedemption { 73 SecretHash: 99d971975c09331eb00f5e0dc1eaeca9bf4ee2d086d3fe1de489f920007d6546 74 Secret: 2c0a304c9321402dc11cbb5898b9f2af3029ce1c76ec6702c4cd5bb965fd3e73 75 } 76 ETHSwapRedemption { 77 SecretHash: ebdc4c31b88d0c8f4d644591a8e00e92b607f920ad8050deb7c7469767d9c561 78 Secret: 87eac09638c0c38b4e735b79f053cb869167ee770640ac5df5c4ab030813122a 79 }] 80 */ 81 redeemSecretHashA = mustParseHex("99d971975c09331eb00f5e0dc1eaeca9bf4ee2d086d3fe1de489f920007d6546") 82 redeemSecretHashB = mustParseHex("ebdc4c31b88d0c8f4d644591a8e00e92b607f920ad8050deb7c7469767d9c561") 83 redeemSecretB = mustParseHex("87eac09638c0c38b4e735b79f053cb869167ee770640ac5df5c4ab030813122a") 84 ) 85 86 func mustParseHex(s string) []byte { 87 b, err := hex.DecodeString(s) 88 if err != nil { 89 panic(err) 90 } 91 return b 92 } 93 94 type testNode struct { 95 connectErr error 96 bestHdr *types.Header 97 bestHdrErr error 98 hdrByHeight *types.Header 99 hdrByHeightErr error 100 blkNum uint64 101 blkNumErr error 102 syncProg *ethereum.SyncProgress 103 syncProgErr error 104 suggGasTipCap *big.Int 105 suggGasTipCapErr error 106 swp *dexeth.SwapState 107 swpErr error 108 tx *types.Transaction 109 txIsMempool bool 110 txErr error 111 acctBal *big.Int 112 acctBalErr error 113 } 114 115 func (n *testNode) connect(ctx context.Context) error { 116 return n.connectErr 117 } 118 119 func (n *testNode) shutdown() {} 120 121 func (n *testNode) loadToken(context.Context, uint32, *VersionedToken) error { 122 return nil 123 } 124 125 func (n *testNode) bestHeader(ctx context.Context) (*types.Header, error) { 126 return n.bestHdr, n.bestHdrErr 127 } 128 129 func (n *testNode) headerByHeight(ctx context.Context, height uint64) (*types.Header, error) { 130 return n.hdrByHeight, n.hdrByHeightErr 131 } 132 133 func (n *testNode) blockNumber(ctx context.Context) (uint64, error) { 134 return n.blkNum, n.blkNumErr 135 } 136 137 func (n *testNode) syncProgress(ctx context.Context) (*ethereum.SyncProgress, error) { 138 return n.syncProg, n.syncProgErr 139 } 140 141 func (n *testNode) suggestGasTipCap(ctx context.Context) (*big.Int, error) { 142 return n.suggGasTipCap, n.suggGasTipCapErr 143 } 144 145 func (n *testNode) swap(ctx context.Context, assetID uint32, secretHash [32]byte) (*dexeth.SwapState, error) { 146 return n.swp, n.swpErr 147 } 148 149 func (n *testNode) transaction(ctx context.Context, hash common.Hash) (tx *types.Transaction, isMempool bool, err error) { 150 return n.tx, n.txIsMempool, n.txErr 151 } 152 153 func (n *testNode) accountBalance(ctx context.Context, assetID uint32, addr common.Address) (*big.Int, error) { 154 return n.acctBal, n.acctBalErr 155 } 156 157 func tSwap(bn, locktime int64, value uint64, secret [32]byte, state dexeth.SwapStep, participantAddr *common.Address) *dexeth.SwapState { 158 return &dexeth.SwapState{ 159 Secret: secret, 160 BlockHeight: uint64(bn), 161 LockTime: time.Unix(locktime, 0), 162 Participant: *participantAddr, 163 State: state, 164 Value: dexeth.GweiToWei(value), 165 } 166 } 167 168 func tNewBackend(assetID uint32) (*AssetBackend, *testNode) { 169 node := &testNode{} 170 return &AssetBackend{ 171 baseBackend: &baseBackend{ 172 net: dex.Simnet, 173 node: node, 174 baseLogger: tLogger, 175 }, 176 log: tLogger.SubLogger(strings.ToUpper(dex.BipIDSymbol(assetID))), 177 assetID: assetID, 178 blockChans: make(map[chan *asset.BlockUpdate]struct{}), 179 atomize: dexeth.WeiToGwei, 180 }, node 181 } 182 183 func TestMain(m *testing.M) { 184 tLogger = dex.StdOutLogger("TEST", dex.LevelTrace) 185 var shutdown func() 186 tCtx, shutdown = context.WithCancel(context.Background()) 187 doIt := func() int { 188 defer shutdown() 189 dexeth.Tokens[usdcID].NetTokens[dex.Simnet].SwapContracts[0].Address = common.BytesToAddress(encode.RandomBytes(20)) 190 return m.Run() 191 } 192 os.Exit(doIt()) 193 } 194 195 func TestDecodeCoinID(t *testing.T) { 196 drv := &Driver{} 197 txid := "0x1b86600b740d58ecc06eda8eba1c941c7ba3d285c78be89b56678da146ed53d1" 198 txHashB := mustParseHex("1b86600b740d58ecc06eda8eba1c941c7ba3d285c78be89b56678da146ed53d1") 199 200 type test struct { 201 name string 202 input []byte 203 wantErr bool 204 expRes string 205 } 206 207 tests := []test{{ 208 name: "ok", 209 input: txHashB, 210 expRes: txid, 211 }, { 212 name: "too short", 213 input: txHashB[:len(txHashB)/2], 214 wantErr: true, 215 }, { 216 name: "too long", 217 input: append(txHashB, txHashB...), 218 wantErr: true, 219 }} 220 221 for _, tt := range tests { 222 res, err := drv.DecodeCoinID(tt.input) 223 if err != nil { 224 if !tt.wantErr { 225 t.Fatalf("%s: error: %v", tt.name, err) 226 } 227 continue 228 } 229 230 if tt.wantErr { 231 t.Fatalf("%s: no error", tt.name) 232 } 233 if res != tt.expRes { 234 t.Fatalf("%s: wrong result. wanted %s, got %s", tt.name, tt.expRes, res) 235 } 236 } 237 } 238 239 func TestRun(t *testing.T) { 240 ctx, cancel := context.WithCancel(context.Background()) 241 backend, err := unconnectedETH(BipID, dexeth.ContractAddresses[0][dex.Simnet], registeredTokens, tLogger, dex.Simnet) 242 if err != nil { 243 t.Fatalf("unconnectedETH error: %v", err) 244 } 245 backend.node = &testNode{ 246 blkNum: backend.bestHeight + 1, 247 } 248 ch := backend.BlockChannel(1) 249 go func() { 250 select { 251 case <-ch: 252 cancel() 253 case <-time.After(time.Second * 2): 254 } 255 }() 256 backend.run(ctx) 257 // Ok if ctx was canceled above. Linters complain about calling t.Fatal 258 // in the goroutine above. 259 select { 260 case <-ctx.Done(): 261 return 262 default: 263 t.Fatal("test timeout") 264 } 265 } 266 267 func TestFeeRate(t *testing.T) { 268 maxInt := ^uint64(0) 269 maxWei := new(big.Int).SetUint64(maxInt) 270 gweiFactorBig := big.NewInt(dexeth.GweiFactor) 271 maxWei.Mul(maxWei, gweiFactorBig) 272 overMaxWei := new(big.Int).Set(maxWei) 273 overMaxWei.Add(overMaxWei, gweiFactorBig) 274 tests := []struct { 275 name string 276 hdrBaseFee *big.Int 277 hdrErr error 278 suggGasTipCap *big.Int 279 suggGasTipCapErr error 280 wantFee uint64 281 wantErr bool 282 }{{ 283 name: "ok zero", 284 hdrBaseFee: new(big.Int), 285 suggGasTipCap: new(big.Int), 286 wantFee: 0, 287 }, { 288 name: "ok rounded up", 289 hdrBaseFee: big.NewInt(dexeth.GweiFactor - 1), 290 suggGasTipCap: new(big.Int), 291 wantFee: 2, 292 }, { 293 name: "ok 100, 2", 294 hdrBaseFee: big.NewInt(dexeth.GweiFactor * 100), 295 suggGasTipCap: big.NewInt(dexeth.GweiFactor * 2), 296 wantFee: 202, 297 }, { 298 name: "over max int", 299 hdrBaseFee: overMaxWei, 300 suggGasTipCap: big.NewInt(dexeth.GweiFactor * 2), 301 wantErr: true, 302 }, { 303 name: "node header err", 304 hdrBaseFee: new(big.Int), 305 hdrErr: errors.New(""), 306 suggGasTipCap: new(big.Int), 307 wantErr: true, 308 }, { 309 name: "nil base fee error", 310 hdrBaseFee: nil, 311 suggGasTipCap: new(big.Int), 312 wantErr: true, 313 }, { 314 name: "node suggest gas tip cap err", 315 hdrBaseFee: new(big.Int), 316 suggGasTipCapErr: errors.New(""), 317 wantErr: true, 318 }} 319 320 for _, test := range tests { 321 eth, node := tNewBackend(BipID) 322 node.bestHdr = &types.Header{ 323 BaseFee: test.hdrBaseFee, 324 } 325 node.bestHdrErr = test.hdrErr 326 node.suggGasTipCap = test.suggGasTipCap 327 node.suggGasTipCapErr = test.suggGasTipCapErr 328 329 fee, err := eth.FeeRate(tCtx) 330 if test.wantErr { 331 if err == nil { 332 t.Fatalf("expected error for test %q", test.name) 333 } 334 continue 335 } 336 if err != nil { 337 t.Fatalf("unexpected error for test %q: %v", test.name, err) 338 } 339 if fee != test.wantFee { 340 t.Fatalf("want fee %v got %v for test %q", test.wantFee, fee, test.name) 341 } 342 } 343 } 344 345 func TestSynced(t *testing.T) { 346 tests := []struct { 347 name string 348 syncProg *ethereum.SyncProgress 349 subSecs uint64 350 bestHdrErr, syncProgErr error 351 wantErr, wantSynced bool 352 }{{ 353 name: "ok synced", 354 subSecs: dexeth.MaxBlockInterval - 1, 355 wantSynced: true, 356 }, { 357 name: "ok header too old", 358 subSecs: dexeth.MaxBlockInterval, 359 }, { 360 name: "best header error", 361 bestHdrErr: errors.New(""), 362 wantErr: true, 363 }} 364 365 for _, test := range tests { 366 nowInSecs := uint64(time.Now().Unix()) 367 eth, node := tNewBackend(BipID) 368 node.syncProg = test.syncProg 369 node.syncProgErr = test.syncProgErr 370 node.bestHdr = &types.Header{Time: nowInSecs - test.subSecs} 371 node.bestHdrErr = test.bestHdrErr 372 373 synced, err := eth.Synced() 374 if test.wantErr { 375 if err == nil { 376 t.Fatalf("expected error for test %q", test.name) 377 } 378 continue 379 } 380 if err != nil { 381 t.Fatalf("unexpected error for test %q: %v", test.name, err) 382 } 383 if synced != test.wantSynced { 384 t.Fatalf("want synced %v got %v for test %q", test.wantSynced, synced, test.name) 385 } 386 } 387 } 388 389 // TestRequiredOrderFunds ensures that a fee calculation in the calc package 390 // will come up with the correct required funds. 391 func TestRequiredOrderFunds(t *testing.T) { 392 393 initTxSize := dexeth.InitGas(1, ethContractVersion) 394 swapVal := uint64(1000000000) // gwei 395 numSwaps := uint64(17) // swaps 396 feeRate := uint64(30) // gwei / gas 397 398 // We want the fee calculation to simply be the cost of the gas used 399 // for each swap plus the initial value. 400 want := swapVal + (numSwaps * initTxSize * feeRate) 401 // Second argument called inputsSize same as another initSize. 402 got := calc.RequiredOrderFunds(swapVal, 0, numSwaps, initTxSize, initTxSize, feeRate) 403 if got != want { 404 t.Fatalf("want %v got %v for fees", want, got) 405 } 406 } 407 408 func tTx(gasFeeCap, gasTipCap, value uint64, to *common.Address, data []byte) *types.Transaction { 409 return types.NewTx(&types.DynamicFeeTx{ 410 GasFeeCap: dexeth.GweiToWei(gasFeeCap), 411 GasTipCap: dexeth.GweiToWei(gasTipCap), 412 To: to, 413 Value: dexeth.GweiToWei(value), 414 Data: data, 415 }) 416 } 417 418 func TestContract(t *testing.T) { 419 receiverAddr, contractAddr := new(common.Address), new(common.Address) 420 copy(receiverAddr[:], encode.RandomBytes(20)) 421 copy(contractAddr[:], encode.RandomBytes(20)) 422 var txHash [32]byte 423 copy(txHash[:], encode.RandomBytes(32)) 424 const gasPrice = 30 425 const gasTipCap = 2 426 const swapVal = 25e8 427 const txVal = 5e9 428 var secret, secretHash [32]byte 429 copy(secret[:], redeemSecretB) 430 copy(secretHash[:], redeemSecretHashB) 431 tests := []struct { 432 name string 433 coinID []byte 434 contract []byte 435 tx *types.Transaction 436 swap *dexeth.SwapState 437 swapErr, txErr error 438 wantErr bool 439 }{{ 440 name: "ok", 441 tx: tTx(gasPrice, gasTipCap, txVal, contractAddr, initCalldata), 442 contract: dexeth.EncodeContractData(0, secretHash), 443 swap: tSwap(97, initLocktime, swapVal, secret, dexeth.SSInitiated, &initParticipantAddr), 444 coinID: txHash[:], 445 }, { 446 name: "new coiner error, wrong tx type", 447 tx: tTx(gasPrice, gasTipCap, txVal, contractAddr, initCalldata), 448 contract: dexeth.EncodeContractData(0, secretHash), 449 swap: tSwap(97, initLocktime, swapVal, secret, dexeth.SSInitiated, &initParticipantAddr), 450 coinID: txHash[1:], 451 wantErr: true, 452 }, { 453 name: "confirmations error, swap error", 454 tx: tTx(gasPrice, gasTipCap, txVal, contractAddr, initCalldata), 455 contract: dexeth.EncodeContractData(0, secretHash), 456 coinID: txHash[:], 457 swapErr: errors.New(""), 458 wantErr: true, 459 }} 460 for _, test := range tests { 461 eth, node := tNewBackend(BipID) 462 node.tx = test.tx 463 node.txErr = test.txErr 464 node.swp = test.swap 465 node.swpErr = test.swapErr 466 eth.contractAddr = *contractAddr 467 468 contractData := dexeth.EncodeContractData(0, secretHash) // matches initCalldata 469 contract, err := eth.Contract(test.coinID, contractData) 470 if test.wantErr { 471 if err == nil { 472 t.Fatalf("expected error for test %q", test.name) 473 } 474 continue 475 } 476 if err != nil { 477 t.Fatalf("unexpected error for test %q: %v", test.name, err) 478 } 479 if contract.SwapAddress != initParticipantAddr.String() || 480 contract.LockTime.Unix() != initLocktime { 481 t.Fatalf("returns do not match expected for test %q", test.name) 482 } 483 } 484 } 485 486 func TestValidateFeeRate(t *testing.T) { 487 swapCoin := swapCoin{ 488 baseCoin: &baseCoin{ 489 backend: &AssetBackend{log: tLogger}, 490 gasFeeCap: dexeth.GweiToWei(100), 491 gasTipCap: dexeth.GweiToWei(2), 492 }, 493 } 494 495 contract := &asset.Contract{ 496 Coin: &swapCoin, 497 } 498 499 eth, _ := tNewBackend(BipID) 500 501 if !eth.ValidateFeeRate(contract.Coin, 100) { 502 t.Fatalf("expected valid fee rate, but was not valid") 503 } 504 505 if eth.ValidateFeeRate(contract.Coin, 101) { 506 t.Fatalf("expected invalid fee rate, but was valid") 507 } 508 509 swapCoin.gasTipCap = dexeth.GweiToWei(dexeth.MinGasTipCap - 1) 510 if eth.ValidateFeeRate(contract.Coin, 100) { 511 t.Fatalf("expected invalid fee rate, but was valid") 512 } 513 } 514 515 func TestValidateSecret(t *testing.T) { 516 secret, blankHash := [32]byte{}, [32]byte{} 517 copy(secret[:], encode.RandomBytes(32)) 518 secretHash := sha256.Sum256(secret[:]) 519 tests := []struct { 520 name string 521 contractData []byte 522 want bool 523 }{{ 524 name: "ok", 525 contractData: dexeth.EncodeContractData(0, secretHash), 526 want: true, 527 }, { 528 name: "not the right hash", 529 contractData: dexeth.EncodeContractData(0, blankHash), 530 }, { 531 name: "bad contract data", 532 }} 533 for _, test := range tests { 534 eth, _ := tNewBackend(BipID) 535 got := eth.ValidateSecret(secret[:], test.contractData) 536 if test.want != got { 537 t.Fatalf("expected %v but got %v for test %q", test.want, got, test.name) 538 } 539 } 540 } 541 542 func TestRedemption(t *testing.T) { 543 receiverAddr, contractAddr := new(common.Address), new(common.Address) 544 copy(receiverAddr[:], encode.RandomBytes(20)) 545 copy(contractAddr[:], encode.RandomBytes(20)) 546 var secret, secretHash, txHash [32]byte 547 copy(secret[:], redeemSecretB) 548 copy(secretHash[:], redeemSecretHashB) 549 copy(txHash[:], encode.RandomBytes(32)) 550 const gasPrice = 30 551 const gasTipCap = 2 552 tests := []struct { 553 name string 554 coinID, contractID []byte 555 swp *dexeth.SwapState 556 tx *types.Transaction 557 txIsMempool bool 558 swpErr, txErr error 559 wantErr bool 560 }{{ 561 name: "ok", 562 tx: tTx(gasPrice, gasTipCap, 0, contractAddr, redeemCalldata), 563 contractID: dexeth.EncodeContractData(0, secretHash), 564 coinID: txHash[:], 565 swp: tSwap(0, 0, 0, secret, dexeth.SSRedeemed, receiverAddr), 566 }, { 567 name: "new coiner error, wrong tx type", 568 tx: tTx(gasPrice, gasTipCap, 0, contractAddr, redeemCalldata), 569 contractID: dexeth.EncodeContractData(0, secretHash), 570 coinID: txHash[1:], 571 wantErr: true, 572 }, { 573 name: "confirmations error, swap wrong state", 574 tx: tTx(gasPrice, gasTipCap, 0, contractAddr, redeemCalldata), 575 contractID: dexeth.EncodeContractData(0, secretHash), 576 swp: tSwap(0, 0, 0, secret, dexeth.SSRefunded, receiverAddr), 577 coinID: txHash[:], 578 wantErr: true, 579 }, { 580 name: "validate redeem error", 581 tx: tTx(gasPrice, gasTipCap, 0, contractAddr, redeemCalldata), 582 contractID: secretHash[:31], 583 coinID: txHash[:], 584 swp: tSwap(0, 0, 0, secret, dexeth.SSRedeemed, receiverAddr), 585 wantErr: true, 586 }} 587 for _, test := range tests { 588 eth, node := tNewBackend(BipID) 589 node.tx = test.tx 590 node.txIsMempool = test.txIsMempool 591 node.txErr = test.txErr 592 node.swp = test.swp 593 node.swpErr = test.swpErr 594 eth.contractAddr = *contractAddr 595 596 _, err := eth.Redemption(test.coinID, nil, test.contractID) 597 if test.wantErr { 598 if err == nil { 599 t.Fatalf("expected error for test %q", test.name) 600 } 601 continue 602 } 603 if err != nil { 604 t.Fatalf("unexpected error for test %q: %v", test.name, err) 605 } 606 } 607 } 608 609 func TestTxData(t *testing.T) { 610 eth, node := tNewBackend(BipID) 611 612 const gasPrice = 30 613 const gasTipCap = 2 614 const value = 5e9 615 addr := randomAddress() 616 data := encode.RandomBytes(5) 617 tx := tTx(gasPrice, gasTipCap, value, addr, data) 618 goodCoinID, _ := hex.DecodeString("09c3bed75b35c6cf0549b0636c9511161b18765c019ef371e2a9f01e4b4a1487") 619 node.tx = tx 620 621 // initial success 622 txData, err := eth.TxData(goodCoinID) 623 if err != nil { 624 t.Fatalf("TxData error: %v", err) 625 } 626 checkB, _ := tx.MarshalBinary() 627 if !bytes.Equal(txData, checkB) { 628 t.Fatalf("tx data not transmitted") 629 } 630 631 // bad coin ID 632 coinID := encode.RandomBytes(2) 633 _, err = eth.TxData(coinID) 634 if err == nil { 635 t.Fatalf("no error for bad coin ID") 636 } 637 638 // Wrong type of coin ID 639 _, err = eth.TxData(goodCoinID[2:]) 640 if err == nil { 641 t.Fatalf("no error for wrong coin type") 642 } 643 644 // No transaction 645 node.tx = nil 646 _, err = eth.TxData(goodCoinID) 647 if err == nil { 648 t.Fatalf("no error for missing tx") 649 } 650 651 // Success again 652 node.tx = tx 653 _, err = eth.TxData(goodCoinID) 654 if err != nil { 655 t.Fatalf("TxData error: %v", err) 656 } 657 } 658 659 func TestValidateContract(t *testing.T) { 660 t.Run("eth", func(t *testing.T) { testValidateContract(t, BipID) }) 661 t.Run("token", func(t *testing.T) { testValidateContract(t, usdcID) }) 662 } 663 664 func testValidateContract(t *testing.T, assetID uint32) { 665 tests := []struct { 666 name string 667 ver uint32 668 secretHash []byte 669 wantErr bool 670 }{{ 671 name: "ok", 672 secretHash: make([]byte, dexeth.SecretHashSize), 673 }, { 674 name: "wrong size", 675 secretHash: make([]byte, dexeth.SecretHashSize-1), 676 wantErr: true, 677 }, { 678 name: "wrong version", 679 ver: 1, 680 secretHash: make([]byte, dexeth.SecretHashSize), 681 wantErr: true, 682 }} 683 684 type contractValidator interface { 685 ValidateContract([]byte) error 686 } 687 688 for _, test := range tests { 689 eth, _ := tNewBackend(assetID) 690 var cv contractValidator 691 if assetID == BipID { 692 cv = ÐBackend{eth} 693 } else { 694 cv = &TokenBackend{ 695 AssetBackend: eth, 696 VersionedToken: &VersionedToken{ 697 Token: dexeth.Tokens[usdcID], 698 Ver: 0, 699 }, 700 } 701 } 702 703 swapData := make([]byte, 4+len(test.secretHash)) 704 binary.BigEndian.PutUint32(swapData[:4], test.ver) 705 copy(swapData[4:], test.secretHash) 706 707 err := cv.ValidateContract(swapData) 708 if test.wantErr { 709 if err == nil { 710 t.Fatalf("expected error for test %q", test.name) 711 } 712 continue 713 } 714 if err != nil { 715 t.Fatalf("unexpected error for test %q: %v", test.name, err) 716 } 717 } 718 } 719 720 func TestAccountBalance(t *testing.T) { 721 eth, node := tNewBackend(BipID) 722 723 const gweiBal = 1e9 724 bigBal := big.NewInt(gweiBal) 725 node.acctBal = bigBal.Mul(bigBal, big.NewInt(dexeth.GweiFactor)) 726 727 // Initial success 728 bal, err := eth.AccountBalance("") 729 if err != nil { 730 t.Fatalf("AccountBalance error: %v", err) 731 } 732 733 if bal != gweiBal { 734 t.Fatalf("wrong balance. expected %f, got %d", gweiBal, bal) 735 } 736 737 // Only error path. 738 node.acctBalErr = errors.New("test error") 739 _, err = eth.AccountBalance("") 740 if err == nil { 741 t.Fatalf("no AccountBalance error when expected") 742 } 743 node.acctBalErr = nil 744 745 // Success again 746 _, err = eth.AccountBalance("") 747 if err != nil { 748 t.Fatalf("AccountBalance error: %v", err) 749 } 750 } 751 752 func TestPoll(t *testing.T) { 753 tests := []struct { 754 name string 755 addBlock bool 756 blockNumErr error 757 }{{ 758 name: "ok nothing to do", 759 }, { 760 name: "ok new", 761 addBlock: true, 762 }, { 763 name: "blockNumber error", 764 blockNumErr: errors.New(""), 765 }} 766 767 for _, test := range tests { 768 be, node := tNewBackend(BipID) 769 eth := ÐBackend{be} 770 node.blkNumErr = test.blockNumErr 771 if test.addBlock { 772 node.blkNum = be.bestHeight + 1 773 } else { 774 node.blkNum = be.bestHeight 775 } 776 ch := make(chan *asset.BlockUpdate, 1) 777 eth.blockChans[ch] = struct{}{} 778 bu := new(asset.BlockUpdate) 779 wait := make(chan struct{}) 780 go func() { 781 select { 782 case bu = <-ch: 783 case <-time.After(time.Second * 2): 784 } 785 close(wait) 786 }() 787 eth.poll(nil) 788 <-wait 789 if test.blockNumErr != nil { 790 if bu.Err == nil { 791 t.Fatalf("expected error for test %q", test.name) 792 } 793 continue 794 } 795 if bu.Err != nil { 796 t.Fatalf("unexpected error for test %q: %v", test.name, bu.Err) 797 } 798 } 799 } 800 801 func TestValidateSignature(t *testing.T) { 802 // "ok" values used are the same as tests in client/assets/eth. 803 pkBytes := mustParseHex("04b911d1f39f7792e165767e35aa134083e2f70ac7de6945d7641a3015d09a54561b71112b8d60f63831f0e62c23c6921ec627820afedf8236155b9e9bd82b6523") 804 msg := []byte("msg") 805 sigBytes := mustParseHex("ffd26911d3fdaf11ac44801744f2df015a16539b6e688aff4cabc092b747466e7bc8036a03d1479a1570dd11bf042120301c34a65b237267720ef8a9e56f2eb1") 806 max32Bytes := mustParseHex("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff") 807 addr := "0x2b84C791b79Ee37De042AD2ffF1A253c3ce9bc27" 808 eth := new(AssetBackend) 809 810 tests := []struct { 811 name string 812 wantErr bool 813 pkBytes, sigBytes, msg []byte 814 addr string 815 }{{ 816 name: "ok", 817 pkBytes: pkBytes, 818 msg: msg, 819 addr: addr, 820 sigBytes: sigBytes, 821 }, { 822 name: "sig wrong size", 823 pkBytes: pkBytes, 824 msg: msg, 825 addr: addr, 826 wantErr: true, 827 }, { 828 name: "pubkey doesn't match address", 829 pkBytes: pkBytes, 830 msg: msg, 831 addr: addr[:21] + "a", 832 sigBytes: sigBytes, 833 wantErr: true, 834 }, { 835 name: "bad pubkey", 836 pkBytes: pkBytes[1:], 837 msg: msg, 838 sigBytes: sigBytes, 839 addr: addr, 840 wantErr: true, 841 }, { 842 name: "r too big", 843 pkBytes: pkBytes, 844 msg: msg, 845 sigBytes: append(append([]byte{}, max32Bytes...), sigBytes[32:]...), 846 addr: addr, 847 wantErr: true, 848 }, { 849 name: "s too big", 850 pkBytes: pkBytes, 851 msg: msg, 852 sigBytes: append(append(append([]byte{}, sigBytes[:32]...), max32Bytes...), byte(1)), 853 addr: addr, 854 wantErr: true, 855 }, { 856 name: "cannot verify signature, bad msg", 857 pkBytes: pkBytes, 858 sigBytes: sigBytes, 859 addr: addr, 860 wantErr: true, 861 }} 862 863 for _, test := range tests { 864 err := eth.ValidateSignature(test.addr, test.pkBytes, test.msg, test.sigBytes) 865 if test.wantErr { 866 if err == nil { 867 t.Fatalf("expected error for test %q", test.name) 868 } 869 continue 870 } 871 if err != nil { 872 t.Fatalf("unexpected error for test %q: %v", test.name, err) 873 } 874 } 875 } 876 877 func TestIsRemoteURL(t *testing.T) { 878 for _, tt := range []struct { 879 url string 880 wantRemote bool 881 }{ 882 {"http://localhost:1234", false}, 883 {"http://127.0.0.1", false}, 884 {"http://127.0.0.1:1234", false}, 885 {"https://127.0.0.1:1234", false}, 886 {"https://decred.org", true}, 887 {"https://241.45.173.171", true}, 888 {"http://[::1]:8080", false}, 889 {"https://[2001:db8::1]:8080", true}, 890 {"https://241.45.173.171:1234", true}, 891 } { 892 uri, _ := url.Parse(tt.url) 893 if is := isRemoteURL(uri); is != tt.wantRemote { 894 t.Fatalf("%s: wanted %t, got %t", tt.url, tt.wantRemote, is) 895 } 896 } 897 } 898 899 func TestParseEndpoints(t *testing.T) { 900 type test struct { 901 name string 902 fileContents string 903 relayAddr string 904 expectedEndpoints []string 905 wantErr bool 906 } 907 908 url1 := "http://127.0.0.1:1234" 909 url2 := "https://example.com" 910 relayAddr := "123.111.4.8:1111" 911 relayURL := "http://" + relayAddr 912 913 tests := []*test{ 914 { 915 name: "single localhost in file", 916 fileContents: url1, 917 expectedEndpoints: []string{"http://127.0.0.1:1234"}, 918 }, 919 { 920 name: "no path provided error", 921 wantErr: true, 922 }, 923 { 924 name: "two from file and a noderelay", 925 fileContents: url1 + "\n" + url2, 926 relayAddr: relayAddr, 927 expectedEndpoints: []string{relayURL, url1, url2}, 928 }, 929 { 930 name: "just a relay adddress", 931 relayAddr: relayAddr, 932 expectedEndpoints: []string{relayURL}, 933 }, 934 } 935 936 runTest := func(t *testing.T, tt *test) { 937 var configPath string 938 if tt.fileContents != "" { 939 f, err := os.CreateTemp("", "") 940 if err != nil { 941 t.Fatalf("error getting temporary file") 942 } 943 configPath = f.Name() 944 defer os.Remove(configPath) 945 defer f.Close() 946 f.WriteString(tt.fileContents) 947 } 948 endpoints, err := parseEndpoints(&asset.BackendConfig{ 949 ConfigPath: configPath, 950 RelayAddr: tt.relayAddr, 951 }) 952 if err != nil { 953 if tt.wantErr { 954 return 955 } 956 t.Fatalf("parseEndpoints error: %v", err) 957 } 958 if len(endpoints) != len(tt.expectedEndpoints) { 959 t.Fatalf("wrong number of endpoints. wanted %d, got %d", len(tt.expectedEndpoints), len(endpoints)) 960 } 961 for i, pt := range endpoints { 962 if expURL := tt.expectedEndpoints[i]; pt.url != expURL { 963 t.Fatalf("wrong endpoint at index %d: wanted %s, got %s", i, expURL, pt.url) 964 } 965 } 966 } 967 for _, tt := range tests { 968 t.Run(tt.name, func(t *testing.T) { 969 runTest(t, tt) 970 }) 971 } 972 }