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
This commit is contained in:
Joe Tsang 2022-10-27 11:58:20 +01:00 committed by GitHub
parent 1febd76511
commit f42ead0561
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 281 additions and 6 deletions

View File

@ -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');
}
}
);

View File

@ -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)

View File

@ -54,7 +54,9 @@ const WithdrawPendingContainer = () => {
<>
<header className="flex items-start justify-between">
<h2>{t('withdrawalsPreparedWarningHeading')}</h2>
<Button onClick={() => setWithdrawDialog(true)}>Withdraw</Button>
<Button data-testid="withdraw" onClick={() => setWithdrawDialog(true)}>
Withdraw
</Button>
</header>
<p>{t('withdrawalsText')}</p>
<p className="mb-8">{t('withdrawalsPreparedWarningText')}</p>

View File

@ -34,7 +34,10 @@ export const WithdrawalsContainer = () => {
{completed && completed.length > 0 && (
<h4 className="pt-3 pb-1">{t('Withdrawal history')}</h4>
)}
<WithdrawalsTable rowData={completed} />
<WithdrawalsTable
data-testid="withdrawals-history"
rowData={completed}
/>
</>
)}
/>

View File

@ -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';

View File

@ -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<Subject> {
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');
});
}

View File

@ -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 {

View File

@ -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();
}
}

View File

@ -153,7 +153,11 @@ export type CompleteCellProps = {
complete: (withdrawal: WithdrawalFields) => void;
};
export const CompleteCell = ({ data, complete }: CompleteCellProps) => (
<Button size="xs" onClick={() => complete(data)}>
<Button
data-testid="complete-withdrawal"
size="xs"
onClick={() => complete(data)}
>
{t('Complete withdrawal')}
</Button>
);

View File

@ -114,6 +114,7 @@ export const WithdrawForm = ({
id="asset"
name="asset"
required
data-testid="select-asset"
>
<option value="">{t('Please select')}</option>
{assets.filter(isAssetTypeERC20).map((a) => (
@ -134,6 +135,7 @@ export const WithdrawForm = ({
>
<Input
id="ethereum-address"
data-testid="eth-address-input"
{...register('to', { validate: { required, ethereumAddress } })}
/>
{errors.to?.message && (
@ -153,6 +155,7 @@ export const WithdrawForm = ({
)}
<FormGroup label={t('Amount')} labelFor="amount">
<Input
data-testid="amount-input"
type="number"
autoComplete="off"
id="amount"

View File

@ -45,11 +45,13 @@ export const WithdrawalFeedback = ({
<KeyValueTable>
<KeyValueTableRow>
<span>{t('Asset')}</span>
<span>{withdrawal.asset.symbol}</span>
<span data-testid="withdrawal-asset-symbol">
{withdrawal.asset.symbol}
</span>
</KeyValueTableRow>
<KeyValueTableRow>
<span>{t('Amount')}</span>
<span>
<span data-testid="withdrawal-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)}
</a>
@ -74,6 +77,7 @@ export const WithdrawalFeedback = ({
{isAvailable ? (
<Button
disabled={withdrawal === null ? true : false}
data-testid="withdraw-funds"
onClick={() => {
if (withdrawal) {
submitWithdraw(withdrawal.id);