import chai , { expect } from 'chai' ; import chaiAsPromised from 'chai-as-promised' ; import _ from 'lodash' ; import * as crypto from '../../../packages/crypto/lib' ; import * as BitcoinUtils from '../../../packages/bitcoin-utils/lib' ; import { Chain , chains , initiateAndVerify , claimAndVerify , refundAndVerify , getSwapParams , expectBalance , deployERC20Token , connectMetaMask , fundWallet , importBitcoinAddresses , clearEthMiner , mineUntilTimestamp , CONSTANTS , describeExternal , mineBlock , expectFee , TEST_TIMEOUT } from '../common' ; import { BigNumber , Transaction } from '../../../packages/types/lib' ; process . env [ 'NODE_TLS_REJECT_UNAUTHORIZED' ] = '0' ; chai . use ( chaiAsPromised ) ; const mockSecret = _ . repeat ( 'ff' , 32 ) ; function testSwap ( chain ) { it ( 'Generated secrets are different' , async ( ) => { const secret1 = await chain . client . swap . generateSecret ( 'secret1' ) ; const secret2 = await chain . client . swap . generateSecret ( 'secret2' ) ; expect ( secret1 ) . to . not . equal ( secret2 ) ; } ) ; it ( 'Initiate and claim - happy route' , async ( ) => { if ( process . env . RUN_EXTERNAL ) console . log ( '\x1b[33m' , `Generating secret: Watch for prompt` , '\x1b[0m' ) ; const secret = await chain . client . swap . generateSecret ( 'secret' ) ; const secretHash = crypto . sha256 ( secret ) ; const swapParams = await getSwapParams ( chain , secretHash ) ; const initiationTxId = await initiateAndVerify ( chain , swapParams ) ; let claimTx ; await expectBalance ( chain , swapParams . recipientAddress , async ( ) => { claimTx = await claimAndVerify ( chain , initiationTxId , secret , swapParams ) ; } , ( before , after ) => expect ( after . gt ( before ) ) . to . be . true ) ; const revealedSecret = claimTx . secret ; expect ( revealedSecret ) . to . equal ( secret ) ; } ) ; it ( 'Claim only works using correct secret' , async ( ) => { const secretHash = crypto . sha256 ( mockSecret ) ; const swapParams = await getSwapParams ( chain , secretHash ) ; const initiationTxId = await initiateAndVerify ( chain , swapParams ) ; const wrongSecret = crypto . sha256 ( '0' ) ; const emptySecret = '' ; await expect ( claimAndVerify ( chain , initiationTxId , wrongSecret , swapParams ) ) . to . be . rejected ; await expect ( claimAndVerify ( chain , initiationTxId , emptySecret , swapParams ) ) . to . be . rejected ; const claimTx = await claimAndVerify ( chain , initiationTxId , mockSecret , swapParams ) ; const revealedSecret = claimTx . secret ; expect ( revealedSecret ) . to . equal ( mockSecret ) ; } ) ; it ( 'Claim fails with secret longer than 32 bytes' , async ( ) => { const secret = _ . repeat ( 'ff' , 33 ) ; const secretHash = crypto . sha256 ( secret ) ; const swapParams = await getSwapParams ( chain , secretHash ) ; const initiationTxId = await initiateAndVerify ( chain , swapParams ) ; await expect ( claimAndVerify ( chain , initiationTxId , secret , swapParams ) ) . to . be . rejected ; } ) ; it ( 'Claim fails with secret shorter than 32 bytes' , async ( ) => { const secret = _ . repeat ( 'ff' , 31 ) ; const secretHash = crypto . sha256 ( secret ) ; const swapParams = await getSwapParams ( chain , secretHash ) ; const initiationTxId = await initiateAndVerify ( chain , swapParams ) ; await expect ( claimAndVerify ( chain , initiationTxId , secret , swapParams ) ) . to . be . rejected ; } ) ; it ( 'Initiate and Refund' , async ( ) => { const secretHash = crypto . sha256 ( mockSecret ) ; const swapParams = await getSwapParams ( chain , secretHash ) ; swapParams . expiration = Math . floor ( Date . now ( ) / 1000 ) ; const initiationTxId = await initiateAndVerify ( chain , swapParams ) ; await expectBalance ( chain , swapParams . refundAddress , async ( ) => { await mineUntilTimestamp ( chain , swapParams . expiration ) ; await refundAndVerify ( chain , initiationTxId , swapParams ) ; } , ( before , after ) => expect ( after . gt ( before ) ) . to . be . true ) ; } ) ; if ( chain . name !== 'near' && chain . name !== 'solana' && chain . name !== 'terra' ) { it ( 'Refund fails after claim' , async ( ) => { const secretHash = crypto . sha256 ( mockSecret ) ; const swapParams = await getSwapParams ( chain , secretHash ) ; swapParams . expiration = Math . floor ( Date . now ( ) / 1000 ) ; const initiationTxId = await initiateAndVerify ( chain , swapParams ) ; await expectBalance ( chain , swapParams . recipientAddress , async ( ) => claimAndVerify ( chain , initiationTxId , mockSecret , swapParams ) , ( before , after ) => expect ( after . gt ( before ) ) . to . be . true ) ; await expectBalance ( chain , swapParams . refundAddress , async ( ) => { try { await refundAndVerify ( chain , initiationTxId , swapParams ) ; } catch ( e ) { } } , ( before , after ) => expect ( after . eq ( before ) ) . to . be . true ) ; await mineUntilTimestamp ( chain , swapParams . expiration ) ; await expectBalance ( chain , swapParams . refundAddress , async ( ) => { try { await refundAndVerify ( chain , initiationTxId , swapParams ) ; } catch ( __e ) { } } , ( before , after ) => expect ( after . eq ( before ) ) . to . be . true ) ; } ) ; it ( 'Refund available after expiration' , async ( ) => { const secretHash = crypto . sha256 ( mockSecret ) ; const swapParams = await getSwapParams ( chain , secretHash ) ; const latestBlock = await chain . client . chain . getBlockByNumber ( await chain . client . chain . getBlockHeight ( ) ) ; swapParams . expiration = latestBlock . timestamp + 10 ; const initiationTxId = await initiateAndVerify ( chain , swapParams ) ; await expect ( refundAndVerify ( chain , initiationTxId , swapParams ) ) . to . be . rejected ; await mineUntilTimestamp ( chain , swapParams . expiration ) ; await refundAndVerify ( chain , initiationTxId , swapParams ) ; } ) ; } } function testClaimSucceedAfterExpiration ( chain ) { it ( `Claim works after expiration - ${ chain . name }` , async ( ) => { const secret = await chain . client . swap . generateSecret ( 'secret' ) ; const secretHash = crypto . sha256 ( secret ) ; const swapParams = await getSwapParams ( chain , secretHash ) ; swapParams . expiration = Math . floor ( Date . now ( ) / 1000 ) + 1 ; const initiationTxId = await initiateAndVerify ( chain , swapParams ) ; let claimTx ; await mineUntilTimestamp ( chain , swapParams . expiration ) ; await expectBalance ( chain , swapParams . recipientAddress , async ( ) => { claimTx = await claimAndVerify ( chain , initiationTxId , secret , swapParams ) ; } , ( before , after ) => expect ( after . gt ( before ) ) . to . be . true ) ; const revealedSecret = claimTx . secret ; expect ( revealedSecret ) . to . equal ( secret ) ; } ) ; } function testEthereumBalance ( chain ) { it ( 'Balance - Claim' , async ( ) => { const secretHash = crypto . sha256 ( mockSecret ) ; const swapParams = await getSwapParams ( chain , secretHash ) ; const initiationTxId = await initiateAndVerify ( chain , swapParams ) ; await expectBalance ( chain , swapParams . recipientAddress , async ( ) => { await claimAndVerify ( chain , initiationTxId , mockSecret , swapParams ) ; } , ( before , after ) => { const expectedBalance = before . plus ( swapParams . value ) ; expect ( after . eq ( expectedBalance ) ) . to . be . true ; } ) ; } ) ; it ( 'Balance - Refund' , async ( ) => { const secretHash = crypto . sha256 ( mockSecret ) ; const swapParams = await getSwapParams ( chain , secretHash ) ; swapParams . expiration = Math . floor ( Date . now ( ) / 1000 ) + 20 ; const initiationTxId = await initiateAndVerify ( chain , swapParams ) ; await mineUntilTimestamp ( chain , swapParams . expiration ) ; await expectBalance ( chain , swapParams . refundAddress , async ( ) => refundAndVerify ( chain , initiationTxId , swapParams ) , ( before , after ) => { const expectedBalance = before . plus ( swapParams . value ) ; expect ( after . eq ( expectedBalance ) ) . to . be . true ; } ) ; } ) ; } function testBitcoinBalance ( chain ) { it ( 'Balance - Claim' , async ( ) => { const secretHash = crypto . sha256 ( mockSecret ) ; const swapParams = await getSwapParams ( chain , secretHash ) ; const initiationTxId = await initiateAndVerify ( chain , swapParams ) ; const fee = BitcoinUtils . calculateFee ( 1 , 1 , CONSTANTS . BITCOIN_FEE_PER_BYTE ) ; await expectBalance ( chain , swapParams . recipientAddress , async ( ) => { await claimAndVerify ( chain , initiationTxId , mockSecret , swapParams ) ; } , ( before , after ) => { const expectedBalance = before . plus ( swapParams . value ) . minus ( new BigNumber ( fee ) ) ; expect ( after . eq ( expectedBalance ) ) . to . be . true ; } ) ; } ) ; it ( 'Balance - Refund' , async ( ) => { const secretHash = crypto . sha256 ( mockSecret ) ; const swapParams = await getSwapParams ( chain , secretHash ) ; swapParams . expiration = Math . floor ( Date . now ( ) / 1000 ) + 20 ; const initiationTxId = await initiateAndVerify ( chain , swapParams ) ; const fee = BitcoinUtils . calculateFee ( 1 , 1 , CONSTANTS . BITCOIN_FEE_PER_BYTE ) ; await mineUntilTimestamp ( chain , swapParams . expiration ) ; await expectBalance ( chain , swapParams . refundAddress , async ( ) => refundAndVerify ( chain , initiationTxId , swapParams ) , ( before , after ) => { const expectedBalance = before . plus ( swapParams . value ) . minus ( new BigNumber ( fee ) ) ; expect ( after . eq ( expectedBalance ) ) . to . be . true ; } ) ; } ) ; } function testSolanaRefund ( chain ) { it ( 'Refund fails after claim' , async ( ) => { if ( chain . name === 'solana' ) { const secretHash = crypto . sha256 ( mockSecret ) ; const swapParams = await getSwapParams ( chain , secretHash ) ; swapParams . expiration = Math . floor ( Date . now ( ) / 1000 ) + 60 ; const initiationTxId = await initiateAndVerify ( chain , swapParams ) ; await expectBalance ( chain , swapParams . recipientAddress , async ( ) => claimAndVerify ( chain , initiationTxId , mockSecret , swapParams ) , ( before , after ) => expect ( after . gt ( before ) ) . to . be . true ) ; let fee ; await expectBalance ( chain , swapParams . refundAddress , async ( ) => { try { await refundAndVerify ( chain , initiationTxId , swapParams ) ; } catch ( e ) { expect ( e . message ) . equal ( 'AccountDoesNotExist' ) ; fee = new BigNumber ( 0 ) ; } } , ( before , after ) => expect ( fee ? after . plus ( fee ) . eq ( before ) : after . eq ( before ) ) . to . be . true ) ; } } ) ; it ( 'Refund available after expiration' , async ( ) => { const secretHash = crypto . sha256 ( mockSecret ) ; const swapParams = await getSwapParams ( chain , secretHash ) ; swapParams . expiration = Math . floor ( Date . now ( ) / 1000 ) + 60 ; const initiationTxId = await initiateAndVerify ( chain , swapParams ) ; await expect ( refundAndVerify ( chain , initiationTxId , swapParams ) ) . to . be . rejected ; await mineBlock ( chain , 3 ) ; await refundAndVerify ( chain , initiationTxId , swapParams ) ; } ) ; } function testNearRefund ( chain ) { it ( 'Refund fails after claim' , async ( ) => { if ( chain . name === 'near' ) { const secretHash = crypto . sha256 ( mockSecret ) ; const swapParams = await getSwapParams ( chain , secretHash ) ; swapParams . expiration = Math . floor ( Date . now ( ) / 1000 ) + 60 ; const initiationTxId = await initiateAndVerify ( chain , swapParams ) ; await expectBalance ( chain , swapParams . recipientAddress , async ( ) => claimAndVerify ( chain , initiationTxId , mockSecret , swapParams ) , ( before , after ) => expect ( after . gt ( before ) ) . to . be . true ) ; let fee ; await expectBalance ( chain , swapParams . refundAddress , async ( ) => { try { await refundAndVerify ( chain , initiationTxId , swapParams ) ; } catch ( e ) { expect ( e . type ) . equal ( 'AccountDoesNotExist' ) ; fee = new BigNumber ( e . transaction_outcome . outcome . tokens_burnt ) . multipliedBy ( 2 ) ; } } , ( before , after ) => expect ( fee ? after . plus ( fee ) . eq ( before ) : after . eq ( before ) ) . to . be . true ) ; } } ) ; it ( 'Refund available after expiration' , async ( ) => { const secretHash = crypto . sha256 ( mockSecret ) ; const swapParams = await getSwapParams ( chain , secretHash ) ; swapParams . expiration = Math . floor ( Date . now ( ) / 1000 ) + 60 ; const initiationTxId = await initiateAndVerify ( chain , swapParams ) ; await expect ( refundAndVerify ( chain , initiationTxId , swapParams ) ) . to . be . rejected ; await mineBlock ( chain , 3 ) ; await refundAndVerify ( chain , initiationTxId , swapParams ) ; } ) ; } function testTerraRefund ( chain ) { it ( 'Refund fails after claim' , async ( ) => { const secretHash = crypto . sha256 ( mockSecret ) ; const swapParams = await getSwapParams ( chain , secretHash ) ; swapParams . expiration = Math . floor ( Date . now ( ) / 1000 ) + 20 ; const initiationTxId = await initiateAndVerify ( chain , swapParams ) ; await expectBalance ( chain , swapParams . recipientAddress , async ( ) => claimAndVerify ( chain , initiationTxId , mockSecret , swapParams ) , ( before , after ) => expect ( after . gt ( before ) ) . to . be . true ) ; let fee ; await expectBalance ( chain , swapParams . refundAddress , async ( ) => { try { await refundAndVerify ( chain , initiationTxId , swapParams ) ; } catch ( e ) { expect ( e . response . data . error ) . include ( 'Balance is 0' ) ; fee = new BigNumber ( 0 ) ; } } , ( before , after ) => expect ( fee ? after . plus ( fee ) . eq ( before ) : after . eq ( before ) ) . to . be . true ) ; } ) ; it ( 'Refund available after expiration' , async ( ) => { const secretHash = crypto . sha256 ( mockSecret ) ; const swapParams = await getSwapParams ( chain , secretHash ) ; swapParams . expiration = Math . floor ( Date . now ( ) / 1000 ) + 60 ; const initiationTxId = await initiateAndVerify ( chain , swapParams ) ; await expect ( refundAndVerify ( chain , initiationTxId , swapParams ) ) . to . be . rejected ; await mineBlock ( chain , 3 ) ; await refundAndVerify ( chain , initiationTxId , swapParams ) ; } ) ; } function testFee ( chain ) { describe ( 'Set Fee' , ( ) => { it ( 'Initiate & Claim' , async ( ) => { const secretHash = crypto . sha256 ( mockSecret ) ; const swapParams = await getSwapParams ( chain , secretHash ) ; const expectedFee = 25 ; const initiationTxId = await initiateAndVerify ( chain , swapParams , expectedFee ) ; await expectFee ( chain , initiationTxId , expectedFee , true ) ; const claimTx = await claimAndVerify ( chain , initiationTxId , mockSecret , swapParams , expectedFee ) ; await expectFee ( chain , claimTx . hash , expectedFee , false , true ) ; } ) ; it ( 'Initiate & Refund' , async ( ) => { const secretHash = crypto . sha256 ( mockSecret ) ; const swapParams = await getSwapParams ( chain , secretHash ) ; swapParams . expiration = Math . floor ( Date . now ( ) / 1000 ) ; const expectedFee = 25 ; const initiationTxId = await initiateAndVerify ( chain , swapParams , expectedFee ) ; await expectFee ( chain , initiationTxId , expectedFee , true ) ; await mineUntilTimestamp ( chain , swapParams . expiration ) ; const refundTx = await refundAndVerify ( chain , initiationTxId , swapParams , expectedFee ) ; await expectFee ( chain , refundTx . hash , expectedFee , false , true ) ; } ) ; } ) ; ( chain . client . wallet . canUpdateFee ? describe : describe . skip ) ( 'Update Fee' , ( ) => { if ( ! chain . id . includes ( 'ERC20' ) ) { it ( 'Initiate' , async ( ) => { const secretHash = crypto . sha256 ( mockSecret ) ; const swapParams = await getSwapParams ( chain , secretHash ) ; const initiationTx = await chain . client . swap . initiateSwap ( swapParams , 25 ) ; const expectedFee = 50 ; const newInitiationTx = await chain . client . chain . updateTransactionFee ( initiationTx . hash , expectedFee ) ; await expectFee ( chain , newInitiationTx . hash , expectedFee , true ) ; } ) ; } it ( 'Claim' , async ( ) => { const secretHash = crypto . sha256 ( mockSecret ) ; const swapParams = await getSwapParams ( chain , secretHash ) ; const initiationTxId = await initiateAndVerify ( chain , swapParams ) ; await mineBlock ( chain ) ; const claimTx = await chain . client . swap . claimSwap ( swapParams , initiationTxId , mockSecret , 25 ) ; const expectedFee = 50 ; const newClaimTx = await chain . client . chain . updateTransactionFee ( claimTx . hash , expectedFee ) ; await expectFee ( chain , newClaimTx . hash , expectedFee , false , true ) ; } ) ; it ( 'Refund' , async ( ) => { const secretHash = crypto . sha256 ( mockSecret ) ; const swapParams = await getSwapParams ( chain , secretHash ) ; swapParams . expiration = Math . floor ( Date . now ( ) / 1000 ) ; const initiationTxId = await initiateAndVerify ( chain , swapParams ) ; await mineBlock ( chain ) ; await mineUntilTimestamp ( chain , swapParams . expiration ) ; const refundTx = await chain . client . swap . refundSwap ( swapParams , initiationTxId , 25 ) ; const expectedFee = 50 ; const newRefundTx = await chain . client . chain . updateTransactionFee ( refundTx . hash , expectedFee ) ; await expectFee ( chain , newRefundTx . hash , expectedFee , false , true ) ; } ) ; } ) ; } describe ( 'Swap Single Chain Flow' , function ( ) { this . timeout ( TEST_TIMEOUT ) ; describeExternal ( 'Terra' , ( ) => { testSwap ( chains . terra ) ; testTerraRefund ( chains . terra ) ; testClaimSucceedAfterExpiration ( chains . terra ) ; } ) ; describeExternal ( 'Solana' , ( ) => { testSwap ( chains . solana ) ; testSolanaRefund ( chains . solana ) ; testClaimSucceedAfterExpiration ( chains . solana ) ; } ) ; describeExternal ( 'Near - JS' , ( ) => { testSwap ( chains . nearWithJs ) ; testNearRefund ( chains . nearWithJs ) ; testClaimSucceedAfterExpiration ( chains . nearWithJs ) ; } ) ; describeExternal ( 'Bitcoin - Ledger' , ( ) => { before ( async function ( ) { await importBitcoinAddresses ( chains . bitcoinWithLedger ) ; await fundWallet ( chains . bitcoinWithLedger ) ; } ) ; testSwap ( chains . bitcoinWithLedger ) ; testBitcoinBalance ( chains . bitcoinWithLedger ) ; testFee ( chains . bitcoinWithLedger ) ; testClaimSucceedAfterExpiration ( chains . bitcoinWithLedger ) ; } ) ; describe ( 'Bitcoin - Node' , ( ) => { testSwap ( chains . bitcoinWithNode ) ; testBitcoinBalance ( chains . bitcoinWithNode ) ; testFee ( chains . bitcoinWithNode ) ; testClaimSucceedAfterExpiration ( chains . bitcoinWithNode ) ; } ) ; describe ( 'Bitcoin - Js' , ( ) => { before ( async function ( ) { await importBitcoinAddresses ( chains . bitcoinWithJs ) ; await fundWallet ( chains . bitcoinWithJs ) ; } ) ; testSwap ( chains . bitcoinWithJs ) ; testBitcoinBalance ( chains . bitcoinWithJs ) ; testFee ( chains . bitcoinWithJs ) ; testClaimSucceedAfterExpiration ( chains . bitcoinWithJs ) ; } ) ; describe ( 'Ethereum' , ( ) => { clearEthMiner ( chains . ethereumWithNode ) ; describeExternal ( 'Ethereum - MetaMask' , ( ) => { connectMetaMask ( ) ; before ( async function ( ) { await fundWallet ( chains . ethereumWithMetaMask ) ; } ) ; testSwap ( chains . ethereumWithMetaMask ) ; testEthereumBalance ( chains . ethereumWithMetaMask ) ; testFee ( chains . ethereumWithMetaMask ) ; testClaimSucceedAfterExpiration ( chains . ethereumWithMetaMask ) ; } ) ; describe ( 'Ethereum - Node' , ( ) => { testSwap ( chains . ethereumWithNode ) ; testEthereumBalance ( chains . ethereumWithNode ) ; testFee ( chains . ethereumWithNode ) ; testClaimSucceedAfterExpiration ( chains . ethereumWithNode ) ; } ) ; describeExternal ( 'Ethereum - Ledger' , ( ) => { before ( async function ( ) { await fundWallet ( chains . ethereumWithLedger ) ; } ) ; testSwap ( chains . ethereumWithLedger ) ; testEthereumBalance ( chains . ethereumWithLedger ) ; testFee ( chains . ethereumWithLedger ) ; testClaimSucceedAfterExpiration ( chains . ethereumWithLedger ) ; } ) ; describe ( 'Ethereum - Js' , ( ) => { before ( async function ( ) { await fundWallet ( chains . ethereumWithJs ) ; } ) ; testSwap ( chains . ethereumWithJs ) ; testEthereumBalance ( chains . ethereumWithJs ) ; testFee ( chains . ethereumWithJs ) ; testClaimSucceedAfterExpiration ( chains . ethereumWithJs ) ; } ) ; describeExternal ( 'ERC20 - MetaMask' , ( ) => { connectMetaMask ( ) ; before ( async function ( ) { await fundWallet ( chains . erc20WithMetaMask ) ; await deployERC20Token ( chains . erc20WithMetaMask ) ; } ) ; testSwap ( chains . erc20WithMetaMask ) ; testEthereumBalance ( chains . erc20WithMetaMask ) ; testFee ( chains . erc20WithMetaMask ) ; testClaimSucceedAfterExpiration ( chains . erc20WithMetaMask ) ; } ) ; describe ( 'ERC20 - Node' , async ( ) => { before ( async function ( ) { await deployERC20Token ( chains . erc20WithNode ) ; } ) ; testSwap ( chains . erc20WithNode ) ; testEthereumBalance ( chains . erc20WithNode ) ; testFee ( chains . erc20WithNode ) ; testClaimSucceedAfterExpiration ( chains . erc20WithNode ) ; } ) ; describeExternal ( 'ERC20 - Ledger' , ( ) => { before ( async function ( ) { await fundWallet ( chains . erc20WithLedger ) ; await deployERC20Token ( chains . erc20WithLedger ) ; } ) ; testSwap ( chains . erc20WithLedger ) ; testEthereumBalance ( chains . erc20WithLedger ) ; testFee ( chains . erc20WithLedger ) ; testClaimSucceedAfterExpiration ( chains . erc20WithLedger ) ; } ) ; describe ( 'ERC20 - JS' , ( ) => { before ( async function ( ) { await fundWallet ( chains . erc20WithJs ) ; await deployERC20Token ( chains . erc20WithJs ) ; } ) ; testSwap ( chains . erc20WithJs ) ; testEthereumBalance ( chains . erc20WithJs ) ; testFee ( chains . erc20WithJs ) ; testClaimSucceedAfterExpiration ( chains . erc20WithJs ) ; } ) ; } ) ; } ) ;