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:
parent
1febd76511
commit
f42ead0561
177
apps/token-e2e/src/integration/flow/withdrawal-flow.cy.js
Normal file
177
apps/token-e2e/src/integration/flow/withdrawal-flow.cy.js
Normal 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');
|
||||
}
|
||||
}
|
||||
);
|
@ -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)
|
||||
|
@ -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>
|
||||
|
@ -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}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
/>
|
||||
|
@ -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';
|
||||
|
21
libs/cypress/src/lib/commands/add-validators-to-multisig.ts
Normal file
21
libs/cypress/src/lib/commands/add-validators-to-multisig.ts
Normal 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');
|
||||
});
|
||||
}
|
@ -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 {
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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>
|
||||
);
|
||||
|
@ -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"
|
||||
|
@ -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);
|
||||
|
Loading…
Reference in New Issue
Block a user