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 }