From f42ead056110a74fa1301765da29d36273597775 Mon Sep 17 00:00:00 2001 From: Joe Tsang <30622993+jtsang586@users.noreply.github.com> Date: Thu, 27 Oct 2022 11:58:20 +0100 Subject: [PATCH] Test/766 withdrawals with wallet (#1869) * test: withdrawal flow passing * chore: functions for depositing assets * test: withdrawal full flow passing * test: unhappy withdrawal paths * chore: add variable --- .../integration/flow/withdrawal-flow.cy.js | 177 ++++++++++++++++++ .../src/support/wallet-teardown.functions.js | 50 +++++ apps/token/src/routes/withdrawals/index.tsx | 4 +- .../pages/portfolio/withdrawals-container.tsx | 5 +- libs/cypress/src/index.ts | 2 + .../commands/add-validators-to-multisig.ts | 21 +++ .../vega-wallet-receive-fauceted-asset.ts | 3 +- .../src/contracts/collateral-bridge.ts | 8 + .../src/lib/pending-withdrawals-table.tsx | 6 +- libs/withdraws/src/lib/withdraw-form.tsx | 3 + .../withdraws/src/lib/withdrawal-feedback.tsx | 8 +- 11 files changed, 281 insertions(+), 6 deletions(-) create mode 100644 apps/token-e2e/src/integration/flow/withdrawal-flow.cy.js create mode 100644 libs/cypress/src/lib/commands/add-validators-to-multisig.ts diff --git a/apps/token-e2e/src/integration/flow/withdrawal-flow.cy.js b/apps/token-e2e/src/integration/flow/withdrawal-flow.cy.js new file mode 100644 index 000000000..2c276a37a --- /dev/null +++ b/apps/token-e2e/src/integration/flow/withdrawal-flow.cy.js @@ -0,0 +1,177 @@ +const withdraw = 'withdraw'; +const selectAsset = 'select-asset'; +const ethAddressInput = 'eth-address-input'; +const amountInput = 'amount-input'; +const balanceAvailable = 'BALANCE_AVAILABLE_value'; +const withdrawalThreshold = 'WITHDRAWAL_THRESHOLD_value'; +const delayTime = 'DELAY_TIME_value'; +const useMaximum = 'use-maximum'; +const submitWithdrawalButton = 'submit-withdrawal'; +const dialogTitle = 'dialog-title'; +const dialogClose = 'dialog-close'; +const txExplorerLink = 'tx-block-explorer'; +const withdrawalAssetSymbol = 'withdrawal-asset-symbol'; +const withdrawalAmount = 'withdrawal-amount'; +const withdrawalRecipient = 'withdrawal-recipient'; +const withdrawFundsButton = 'withdraw-funds'; +const completeWithdrawalButton = 'complete-withdrawal'; +const usdtName = 'USDC (local)'; +const usdcEthAddress = '0x1b8a1B6CBE5c93609b46D1829Cc7f3Cb8eeE23a0'; +const usdcSymbol = 'tUSDC'; +const truncatedWithdrawalEthAddress = '0xEe7D…22d94F'; +const formValidationError = 'input-error-text'; +const txTimeout = Cypress.env('txTimeout'); + +context( + 'Withdrawals - with eth and vega wallet connected', + { tags: '@slow' }, + function () { + before('visit withdrawals and connect vega wallet', function () { + cy.updateCapsuleMultiSig(); // When running tests locally, will fail if run without restarting capsule + cy.vega_wallet_import(); + cy.deposit_asset(usdcEthAddress); + }); + + beforeEach('Navigate to withdrawal page', function () { + cy.visit('/'); + cy.navigate_to('withdrawals'); + cy.vega_wallet_connect(); + cy.ethereum_wallet_connect(); + waitForAssetsDisplayed(usdtName); + }); + + it('Able to open withdrawal form with vega wallet connected', function () { + cy.getByTestId(withdraw).should('be.visible').click(); + cy.getByTestId(selectAsset) + .find('option') + .should('have.length.at.least', 5); + cy.getByTestId(ethAddressInput).should('be.visible'); + cy.getByTestId(amountInput).should('be.visible'); + }); + + it('Unable to submit withdrawal with invalid fields', function () { + cy.getByTestId(withdraw).should('be.visible').click(); + cy.getByTestId(selectAsset).select('BTC (local)'); + cy.getByTestId(balanceAvailable).should('have.text', '0.00000'); + cy.getByTestId(submitWithdrawalButton).click(); + cy.getByTestId(formValidationError).should('have.length', 1); + cy.getByTestId(useMaximum).click(); + cy.getByTestId(submitWithdrawalButton).click(); + cy.getByTestId(formValidationError).should( + 'have.text', + 'Value is below minimum' + ); + cy.getByTestId(selectAsset).select(usdtName); + cy.getByTestId(amountInput).clear().click().type('10'); + cy.getByTestId(ethAddressInput).click().type('123'); + cy.getByTestId(submitWithdrawalButton).click(); + cy.getByTestId(formValidationError).should( + 'have.text', + 'Invalid Ethereum address' + ); + }); + + it('Able to withdraw asset: -eth wallet connected -withdraw funds button', function () { + // fill in withdrawal form + cy.getByTestId(withdraw).should('be.visible').click(); + cy.getByTestId(selectAsset).select(usdtName); + cy.getByTestId(balanceAvailable, txTimeout).should('exist'); + cy.getByTestId(withdrawalThreshold).should('have.text', '100,000.00000T'); + cy.getByTestId(delayTime).should('have.text', 'None'); + cy.getByTestId(amountInput).click().type('100'); + cy.getByTestId(submitWithdrawalButton).click(); + + cy.contains('Awaiting network confirmation').should('be.visible'); + // assert withdrawal request + cy.getByTestId(dialogTitle, txTimeout).should( + 'have.text', + 'Transaction complete' + ); + cy.getByTestId(txExplorerLink) + .should('have.attr', 'href') + .and('contain', '/txs/'); + cy.getByTestId(withdrawalAssetSymbol).should('have.text', usdcSymbol); + cy.getByTestId(withdrawalAmount).should('have.text', '100.00000'); + cy.getByTestId(withdrawalRecipient) + .should('have.text', truncatedWithdrawalEthAddress) + .and('have.attr', 'href') + .and('contain', `/address/${Cypress.env('ethWalletPublicKey')}`); + cy.getByTestId(withdrawFundsButton).click(); + // withdrawal complete + cy.getByTestId(dialogTitle, txTimeout).should( + 'have.text', + 'Withdraw asset complete' + ); + cy.getByTestId(dialogClose).click(); + + // need to reload page to see withdrawal history complete + cy.reload(); + waitForAssetsDisplayed(usdtName); + + // withdrawal history for complete withdrawal displayed + cy.get('[col-id="txHash"]', txTimeout) + .should('have.length.above', 1) + .eq(1) + .parent() + .within(() => { + cy.get('[col-id="asset.symbol"]').should('have.text', usdcSymbol); + cy.get('[col-id="amount"]').should('have.text', '100.00000'); + cy.get('[col-id="details.receiverAddress"]') + .find('a') + .should('have.attr', 'href') + .and('contain', 'https://sepolia.etherscan.io/address/'); + cy.get('[col-id="withdrawnTimestamp"]').should('not.be.empty'); + cy.get('[col-id="status"]').should('have.text', 'Completed'); + cy.get('[col-id="txHash"]') + .find('a') + .should('have.attr', 'href') + .and('contain', 'https://sepolia.etherscan.io/tx/'); + }); + }); + + // Skipping because of bug #1857 + it.skip('Able to withdraw asset: -eth wallet not connected', function () { + const ethWalletAddress = Cypress.env('ethWalletPublicKey'); + cy.reload(); + waitForAssetsDisplayed(usdtName); + // fill in withdrawal form + cy.getByTestId(withdraw).should('be.visible').click(); + cy.getByTestId(selectAsset).select(usdtName); + cy.getByTestId(ethAddressInput).should('be.empty'); + cy.getByTestId(amountInput).click().type('100'); + cy.getByTestId(submitWithdrawalButton).click(); + + // Need eth address to submit withdrawal + cy.getByTestId(formValidationError).should('have.length', 1); + cy.getByTestId(ethAddressInput).click().type(ethWalletAddress); + cy.getByTestId(submitWithdrawalButton).click(); + + cy.contains('Awaiting network confirmation').should('be.visible'); + // assert withdrawal request + cy.getByTestId(dialogTitle, txTimeout).should( + 'have.text', + 'Transaction complete' + ); + cy.getByTestId(dialogClose).click(); + + cy.getByTestId(completeWithdrawalButton) + .eq(0) + .parent() + .parent() + .within(() => { + cy.get('[col-id="asset.symbol"]').should('have.text', usdcSymbol); + cy.get('[col-id="amount"]').should('have.text', '100.00000'); + cy.get('[col-id="details.receiverAddress"]') + .find('a') + .should('have.attr', 'href') + .and('contain', 'https://sepolia.etherscan.io/address/'); + cy.get('[col-id="createdTimestamp"]').should('not.be.empty'); + cy.getByTestId(completeWithdrawalButton).click(); + }); + }); + + function waitForAssetsDisplayed(expectedAsset) { + cy.contains(expectedAsset, txTimeout).should('be.visible'); + } + } +); diff --git a/apps/token-e2e/src/support/wallet-teardown.functions.js b/apps/token-e2e/src/support/wallet-teardown.functions.js index f08793e93..9b94bb725 100644 --- a/apps/token-e2e/src/support/wallet-teardown.functions.js +++ b/apps/token-e2e/src/support/wallet-teardown.functions.js @@ -2,6 +2,8 @@ import { StakingBridge, Token, TokenVesting, + TokenFaucetable, + CollateralBridge, } from '@vegaprotocol/smart-contracts'; import { ethers, Wallet } from 'ethers'; @@ -18,6 +20,7 @@ const ethStakingBridgeContractAddress = Cypress.env( const ethProviderUrl = Cypress.env('ethProviderUrl'); const getAccount = (number = 0) => `m/44'/60'/0'/0/${number}`; const transactionTimeout = '90000'; +const Erc20BridgeAddress = '0x9708FF7510D4A7B9541e1699d15b53Ecb1AFDc54'; before('Vega wallet teardown prep', function () { cy.wrap(new ethers.providers.JsonRpcProvider({ url: ethProviderUrl }), { @@ -40,6 +43,53 @@ before('Vega wallet teardown prep', function () { }); }); +Cypress.Commands.add('deposit_asset', function (assetEthAddress) { + cy.get('@signer', { log: false }).then((signer) => { + // Approve asset + cy.wrap( + new TokenFaucetable(assetEthAddress, signer).approve( + Erc20BridgeAddress, + '10000000000' + ) + ) + .then((tx) => { + cy.wait_for_transaction(tx); + }) + .then(() => { + cy.wrap(new CollateralBridge(Erc20BridgeAddress, signer), { + log: false, + }).then((bridge) => { + // Deposit asset into vega wallet + cy.wrap( + bridge.deposit_asset( + assetEthAddress, + '1000000000', + '0x' + vegaWalletPubKey + ), + { timeout: transactionTimeout, log: false } + ).then((tx) => { + cy.wait_for_transaction(tx); + }); + }); + }); + }); +}); + +Cypress.Commands.add('faucet_asset', function (assetEthAddress) { + cy.get('@signer', { log: false }).then((signer) => { + cy.wrap(new TokenFaucetable(assetEthAddress, signer), { log: false }).then( + (token) => { + cy.wrap(token.faucet(), { + timeout: transactionTimeout, + log: false, + }).then((tx) => { + cy.wait_for_transaction(tx); + }); + } + ); + }); +}); + Cypress.Commands.add('vega_wallet_teardown', function () { cy.get(vegaWalletContainer).within(() => { cy.get(vegaWalletAssociatedBalance) diff --git a/apps/token/src/routes/withdrawals/index.tsx b/apps/token/src/routes/withdrawals/index.tsx index 9fe4e9ae9..f11622b19 100644 --- a/apps/token/src/routes/withdrawals/index.tsx +++ b/apps/token/src/routes/withdrawals/index.tsx @@ -54,7 +54,9 @@ const WithdrawPendingContainer = () => { <>

{t('withdrawalsPreparedWarningHeading')}

- +

{t('withdrawalsText')}

{t('withdrawalsPreparedWarningText')}

diff --git a/apps/trading/pages/portfolio/withdrawals-container.tsx b/apps/trading/pages/portfolio/withdrawals-container.tsx index 14b9eba56..8fcd75f37 100644 --- a/apps/trading/pages/portfolio/withdrawals-container.tsx +++ b/apps/trading/pages/portfolio/withdrawals-container.tsx @@ -34,7 +34,10 @@ export const WithdrawalsContainer = () => { {completed && completed.length > 0 && (

{t('Withdrawal history')}

)} - + )} /> diff --git a/libs/cypress/src/index.ts b/libs/cypress/src/index.ts index 465f2826e..bb7335904 100644 --- a/libs/cypress/src/index.ts +++ b/libs/cypress/src/index.ts @@ -13,6 +13,7 @@ import { addVegaWalletImport } from './lib/commands/vega-wallet-import'; import { addContainsExactly } from './lib/commands/contains-exactly'; import { addRestartVegacapsuleNetwork } from './lib/commands/restart-vegacapsule-network'; import { addGetNetworkParameters } from './lib/commands/get-network-parameters'; +import { addUpdateCapsuleMultiSig } from './lib/commands/add-validators-to-multisig'; addGetTestIdcommand(); addSlackCommand(); @@ -27,6 +28,7 @@ addVegaWalletImport(); addContainsExactly(); addRestartVegacapsuleNetwork(); addGetNetworkParameters(); +addUpdateCapsuleMultiSig(); export * from './lib/graphql-test-utils'; export type { onMessage } from './lib/commands/mock-gql'; diff --git a/libs/cypress/src/lib/commands/add-validators-to-multisig.ts b/libs/cypress/src/lib/commands/add-validators-to-multisig.ts new file mode 100644 index 000000000..4bad73e22 --- /dev/null +++ b/libs/cypress/src/lib/commands/add-validators-to-multisig.ts @@ -0,0 +1,21 @@ +declare global { + // eslint-disable-next-line @typescript-eslint/no-namespace + namespace Cypress { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + interface Chainable { + updateCapsuleMultiSig(): void; + } + } +} + +export function addUpdateCapsuleMultiSig() { + Cypress.Commands.add('updateCapsuleMultiSig', () => { + Cypress.on('uncaught:exception', () => { + return false; + }); + cy.log('Adding validators to multisig'); + cy.exec('vegacapsule ethereum multisig init', { failOnNonZeroExit: false }) + .its('stderr') + .should('contain', 'Updated multisig threshold'); + }); +} diff --git a/libs/cypress/src/lib/commands/vega-wallet-receive-fauceted-asset.ts b/libs/cypress/src/lib/commands/vega-wallet-receive-fauceted-asset.ts index ec0a587a7..e4ac857fe 100644 --- a/libs/cypress/src/lib/commands/vega-wallet-receive-fauceted-asset.ts +++ b/libs/cypress/src/lib/commands/vega-wallet-receive-fauceted-asset.ts @@ -22,6 +22,7 @@ export function addVegaWalletReceiveFaucetedAsset() { ); // @ts-ignore - ignoring Cypress type error which gets resolved when Cypress uses the command cy.get_asset_information().then((assets) => { + console.log(assets); const asset = assets[assetName]; if (assets[assetName] !== undefined) { for (let i = 0; i < asset.decimals; i++) amount += '0'; @@ -33,7 +34,7 @@ export function addVegaWalletReceiveFaucetedAsset() { assert.include( response, `"success":true`, - 'Ensuring curl command was succesfully undertaken' + 'Ensuring curl command was successfully undertaken' ); }); } else { diff --git a/libs/smart-contracts/src/contracts/collateral-bridge.ts b/libs/smart-contracts/src/contracts/collateral-bridge.ts index 50b4705ee..46dead63b 100644 --- a/libs/smart-contracts/src/contracts/collateral-bridge.ts +++ b/libs/smart-contracts/src/contracts/collateral-bridge.ts @@ -54,4 +54,12 @@ export class CollateralBridge { signatures ); } + + is_stopped() { + return this.contract.is_stopped(); + } + + get_erc20_asset_pool_address() { + return this.contract.erc20_asset_pool_address(); + } } diff --git a/libs/withdraws/src/lib/pending-withdrawals-table.tsx b/libs/withdraws/src/lib/pending-withdrawals-table.tsx index 84cfb7312..9955531e2 100644 --- a/libs/withdraws/src/lib/pending-withdrawals-table.tsx +++ b/libs/withdraws/src/lib/pending-withdrawals-table.tsx @@ -153,7 +153,11 @@ export type CompleteCellProps = { complete: (withdrawal: WithdrawalFields) => void; }; export const CompleteCell = ({ data, complete }: CompleteCellProps) => ( - ); diff --git a/libs/withdraws/src/lib/withdraw-form.tsx b/libs/withdraws/src/lib/withdraw-form.tsx index 60eac34e9..579800960 100644 --- a/libs/withdraws/src/lib/withdraw-form.tsx +++ b/libs/withdraws/src/lib/withdraw-form.tsx @@ -114,6 +114,7 @@ export const WithdrawForm = ({ id="asset" name="asset" required + data-testid="select-asset" > {assets.filter(isAssetTypeERC20).map((a) => ( @@ -134,6 +135,7 @@ export const WithdrawForm = ({ > {errors.to?.message && ( @@ -153,6 +155,7 @@ export const WithdrawForm = ({ )} {t('Asset')} - {withdrawal.asset.symbol} + + {withdrawal.asset.symbol} + {t('Amount')} - + {addDecimalsFormatNumber( withdrawal.amount, withdrawal.asset.decimals @@ -64,6 +66,7 @@ export const WithdrawalFeedback = ({ href={`${VEGA_EXPLORER_URL}/address/${withdrawal.details.receiverAddress}`} rel="noreferrer" className="underline" + data-testid="withdrawal-recipient" > {truncateByChars(withdrawal.details.receiverAddress)} @@ -74,6 +77,7 @@ export const WithdrawalFeedback = ({ {isAvailable ? (