Skip to content

Commit

Permalink
Swap fixes (#3955)
Browse files Browse the repository at this point in the history
* Disable 0x validation of swaps

* Fix issue where account wouldn't be reset when too low base asset balance

* Fix bug with expiry countdown

* Potential solution with doing some of the gas estimation ourselves

* Simplify base asset detection

* Fix test

* Fix an issue with the latest version of Ethers

* Fix tests

* Remove commented code

* Remove console.log

* Move network selection to state

* Update copy

* Scan tokens when swap finished

* Move some logic from StoreProvider to a saga

* Add test for new saga

* Add test for overwriting existing tx

* Add basic e2e test

* Add test for reverse swap quotes

* Add missing test

* Move selectors to hooks

* Fix typing issues with selectors
  • Loading branch information
FrederikBolding authored May 17, 2021
1 parent 0b5ba7e commit 5e34ef4
Show file tree
Hide file tree
Showing 27 changed files with 387 additions and 162 deletions.
4 changes: 2 additions & 2 deletions .storybook/__snapshots__/storyshots.test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -23125,7 +23125,7 @@ exports[` Storyshots Organisms/SwapQuote SwapQuote 1`] = `
<div
className="Box-sc-1974eaf-0 exRYr"
>
Max TX Fee
Estimated Cost

<div
className="Tooltip__Override-sc-1ccnnqh-0 kUkHVt"
Expand Down Expand Up @@ -23215,7 +23215,7 @@ exports[` Storyshots Organisms/SwapQuote SwapQuote 1`] = `
<div
className="Box-sc-1974eaf-0 exRYr"
>
15 minutes
31 seconds
</div>
</div>
</div>
Expand Down
3 changes: 2 additions & 1 deletion __tests__/fixtures.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ const PAGES = {
ADD_ACCOUNT_WEB3: `${FIXTURES_CONST.BASE_URL}/add-account/web3`,
SEND: `${FIXTURES_CONST.BASE_URL}/send`,
ADD_ACCOUNT: `${FIXTURES_CONST.BASE_URL}/add-account`,
TX_STATUS: `${FIXTURES_CONST.BASE_URL}/tx-status`
TX_STATUS: `${FIXTURES_CONST.BASE_URL}/tx-status`,
SWAP: `${FIXTURES_CONST.BASE_URL}/swap`
};

const FIXTURE_ETHEREUM = 'Ethereum';
Expand Down
18 changes: 18 additions & 0 deletions __tests__/swap-page.po.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { Selector, t } from 'testcafe';

import BasePage from './base-page.po';
import { FIXTURE_SEND_AMOUNT, PAGES } from './fixtures';

export default class SwapPage extends BasePage {
async navigateToPage() {
this.navigateTo(PAGES.SWAP);
}

async waitPageLoaded(timeout) {
await this.waitForPage(PAGES.SWAP, timeout);
}

async fillForm() {
await t.typeText(Selector('input[name="swap-from"]').parent(), FIXTURE_SEND_AMOUNT);
}
}
24 changes: 24 additions & 0 deletions __tests__/swap.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { getByText } from '@testing-library/testcafe';

import { injectLS } from './clientScripts';
import { FIXTURE_LOCALSTORAGE_WITH_ONE_ACC, FIXTURES_CONST, PAGES } from './fixtures';
import SwapPage from './swap-page.po';
import { findByTKey } from './translation-utils';

const swapPage = new SwapPage();

fixture('Swap')
.clientScripts({ content: injectLS(FIXTURE_LOCALSTORAGE_WITH_ONE_ACC) })
.page(PAGES.SWAP);

test('Can get swap quote', async (t) => {
await swapPage.waitPageLoaded();

/* Fill out form */
await swapPage.fillForm();
await t.wait(FIXTURES_CONST.TIMEOUT);

// Has received swap quote
const quote = await getByText(findByTKey('YOUR_QUOTE'));
await t.expect(quote).ok();
});
2 changes: 1 addition & 1 deletion jest_config/__fixtures__/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,5 +75,5 @@ export { fUserActions, fActionTemplates } from './userActions';
export { membershipApiResponse, accountWithMembership } from './membership';
export { default as APP_STATE } from './appState';

export { fSwapQuote } from './swapQuote';
export * from './swapQuote';
export { fBalances } from './balances';
64 changes: 64 additions & 0 deletions jest_config/__fixtures__/swapQuote.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,3 +62,67 @@ export const fSwapQuote = {
sellTokenToEthRate: '1547.962186957828746096',
buyTokenToEthRate: '1'
};

export const fSwapQuoteReverse = {
price: '1.305257025416429946',
guaranteedPrice: '1.318309595670594246',
to: '0xdef1c0ded9bec7f1a1670819833240f027b25eff',
data:
'0x415565b0000000000000000000000000eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee0000000000000000000000009f8f72aa9304c8b593d555f12ef6589cc3a579a2000000000000000000000000000000000000000000000000124b8c194d5703b80000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000040000000000000000000000000eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee000000000000000000000000000000000000000000000000124b8c194d5703b800000000000000000000000000000000000000000000000000000000000000110000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000028000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000009f8f72aa9304c8b593d555f12ef6589cc3a579a200000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000240000000000000000000000000000000000000000000000000000000000000024000000000000000000000000000000000000000000000000000000000000002200000000000000000000000000000000000000000000000000de99870435440000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000442616c616e6365720000000000000000000000000000000000000000000000000000000000000000124b8c194d5703b80000000000000000000000000000000000000000000000000de998704354400000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000020000000000000000000000000987d7cc04652710b74fff380403f5c02f82e290a000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000010000000000000000000000009f8f72aa9304c8b593d555f12ef6589cc3a579a20000000000000000000000000000000000000000000000000008e1bc9bf04000000000000000000000000000d8d46494e200fa585fc98f86e6a5ea0dc1f18ad00000000000000000000000000000000000000000000000000000000000000007000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000003000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000009f8f72aa9304c8b593d555f12ef6589cc3a579a2000000000000000000000000eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee0000000000000000000000000000000000000000000000000000000000000000869584cd000000000000000000000000100000000000000000000000000000000000001100000000000000000000000000000000000000000000008c9ad1818260a23bf6',
value: '1318301356235621304',
gas: '310000',
estimatedGas: '310000',
gasPrice: '66000000000',
protocolFee: '0',
minimumProtocolFee: '0',
buyTokenAddress: '0x9f8f72aa9304c8b593d555f12ef6589cc3a579a2',
sellTokenAddress: '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee',
buyAmount: '1000000000000000000',
sellAmount: '1305248867560021093',
sources: [
{ name: '0x', proportion: '0' },
{ name: 'Uniswap', proportion: '0' },
{ name: 'Uniswap_V2', proportion: '0' },
{ name: 'Eth2Dai', proportion: '0' },
{ name: 'Kyber', proportion: '0' },
{ name: 'Curve', proportion: '0' },
{ name: 'Balancer', proportion: '1' },
{ name: 'Balancer_V2', proportion: '0' },
{ name: 'Bancor', proportion: '0' },
{ name: 'mStable', proportion: '0' },
{ name: 'Mooniswap', proportion: '0' },
{ name: 'Swerve', proportion: '0' },
{ name: 'SnowSwap', proportion: '0' },
{ name: 'SushiSwap', proportion: '0' },
{ name: 'Shell', proportion: '0' },
{ name: 'MultiHop', proportion: '0' },
{ name: 'DODO', proportion: '0' },
{ name: 'DODO_V2', proportion: '0' },
{ name: 'CREAM', proportion: '0' },
{ name: 'LiquidityProvider', proportion: '0' },
{ name: 'CryptoCom', proportion: '0' },
{ name: 'Linkswap', proportion: '0' },
{ name: 'MakerPsm', proportion: '0' },
{ name: 'KyberDMM', proportion: '0' },
{ name: 'Smoothy', proportion: '0' },
{ name: 'Component', proportion: '0' },
{ name: 'Saddle', proportion: '0' },
{ name: 'xSigma', proportion: '0' },
{ name: 'Uniswap_V3', proportion: '0' }
],
orders: [
{
makerToken: '0x9f8f72aa9304c8b593d555f12ef6589cc3a579a2',
takerToken: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2',
makerAmount: '1002500000000000000',
takerAmount: '1305248867560021093',
fillData: { poolAddress: '0x987d7cc04652710b74fff380403f5c02f82e290a' },
source: 'Balancer',
sourcePathId: '0x646bfe1bbac7acdc36fb60378450d6f9c56d26c7b6c1e03f142777ee6194a987',
type: 0
}
],
allowanceTarget: '0x0000000000000000000000000000000000000000',
sellTokenToEthRate: '1',
buyTokenToEthRate: '0.76609049757173667'
};
2 changes: 1 addition & 1 deletion src/components/TimeCountdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ const TimeCountdown = ({
value: number;
format?: string[];
}) => {
const timeSince = (v: number) => formatTimeDuration(v, undefined, undefined, { format });
const timeSince = (v: number) => formatTimeDuration(v, Date.now() / 1000, false, { format });
const [countdown, setCountdown] = useState(timeSince(value));

useInterval(
Expand Down
48 changes: 44 additions & 4 deletions src/features/SwapAssets/SwapAssetsFlow.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@ import React from 'react';
import mockAxios from 'jest-mock-axios';
import { fireEvent, simpleRender, waitFor } from 'test-utils';

import { fAccounts, fAssets, fSwapQuote } from '@fixtures';
import { fAccounts, fAssets, fSwapQuote, fSwapQuoteReverse } from '@fixtures';
import { StoreContext } from '@services/Store';
import { truncate } from '@utils';

import SwapAssetsFlow from './SwapAssetsFlow';

Expand All @@ -16,7 +17,7 @@ function getComponent() {
assets: () => fAssets,
accounts: fAccounts,
userAssets: fAccounts.flatMap((a) => a.assets),
getDefaultAccount: () => undefined
getDefaultAccount: () => fAccounts[0]
} as any) as any
}
>
Expand All @@ -25,22 +26,41 @@ function getComponent() {
);
}

const tokenResponse = {
data: {
records: [fAssets[0], fAssets[13]].map((a) => ({
...a,
symbol: a.ticker,
address: a.type === 'base' ? '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee' : a.contractAddress
}))
}
};

describe('SwapAssetsFlow', () => {
afterEach(() => {
mockAxios.reset();
});
it('selects default tokens', async () => {
const { getAllByText } = getComponent();
expect(mockAxios.get).toHaveBeenCalledWith('swap/v1/tokens');
mockAxios.mockResponse({ data: { records: fAssets.map((a) => ({ ...a, symbol: a.ticker })) } });
mockAxios.mockResponse(tokenResponse);
await waitFor(() => expect(getAllByText(fAssets[0].ticker, { exact: false })).toBeDefined());
await waitFor(() => expect(getAllByText(fAssets[13].ticker, { exact: false })).toBeDefined());
});

it('selects default account', async () => {
const { getByText } = getComponent();
expect(mockAxios.get).toHaveBeenCalledWith('swap/v1/tokens');
mockAxios.mockResponse(tokenResponse);
await waitFor(() =>
expect(getByText(truncate(fAccounts[0].address), { exact: false })).toBeDefined()
);
});

it('calculates and shows to amount', async () => {
const { getAllByText, getAllByDisplayValue, container } = getComponent();
expect(mockAxios.get).toHaveBeenCalledWith('swap/v1/tokens');
mockAxios.mockResponse({ data: { records: fAssets.map((a) => ({ ...a, symbol: a.ticker })) } });
mockAxios.mockResponse(tokenResponse);
await waitFor(() => expect(getAllByText(fAssets[0].ticker, { exact: false })).toBeDefined());
await waitFor(() => expect(getAllByText(fAssets[13].ticker, { exact: false })).toBeDefined());
mockAxios.reset();
Expand All @@ -56,4 +76,24 @@ describe('SwapAssetsFlow', () => {
expect(getAllByDisplayValue('0.000642566300455615', { exact: false })).toBeDefined()
);
});

it('calculates and shows from amount', async () => {
const { getAllByText, getAllByDisplayValue, container } = getComponent();
expect(mockAxios.get).toHaveBeenCalledWith('swap/v1/tokens');
mockAxios.mockResponse(tokenResponse);
await waitFor(() => expect(getAllByText(fAssets[0].ticker, { exact: false })).toBeDefined());
await waitFor(() => expect(getAllByText(fAssets[13].ticker, { exact: false })).toBeDefined());
mockAxios.reset();
fireEvent.change(container.querySelector('input[name="swap-to"]')!, {
target: { value: '1' }
});
await waitFor(() =>
expect(mockAxios.get).toHaveBeenCalledWith('swap/v1/quote', expect.anything())
);
mockAxios.mockResponse({ data: fSwapQuoteReverse });
await waitFor(() => expect(getAllByDisplayValue('1', { exact: false })).toBeDefined());
await waitFor(() =>
expect(getAllByDisplayValue('1.305248867560021093', { exact: false })).toBeDefined()
);
});
});
10 changes: 7 additions & 3 deletions src/features/SwapAssets/SwapAssetsFlow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,8 @@ const SwapAssetsFlow = (props: RouteComponentProps) => {
expiration,
approvalTx,
isEstimatingGas,
tradeTx
tradeTx,
selectedNetwork
}: SwapFormState = formState;

const [assetPair, setAssetPair] = useState({});
Expand Down Expand Up @@ -109,7 +110,8 @@ const SwapAssetsFlow = (props: RouteComponentProps) => {
expiration,
approvalTx,
isEstimatingGas,
isSubmitting
isSubmitting,
selectedNetwork
},
actions: {
handleFromAssetSelected,
Expand All @@ -133,7 +135,9 @@ const SwapAssetsFlow = (props: RouteComponentProps) => {
initWith(
() =>
Promise.resolve(
(approvalTx ? [approvalTx, tradeTx] : [tradeTx]).map(appendSender(account.address))
(approvalTx ? [approvalTx, tradeTx!] : [tradeTx!]).map(
appendSender(account.address)
)
),
account,
account.network
Expand Down
1 change: 1 addition & 0 deletions src/features/SwapAssets/components/SwapAssets.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { LAST_CHANGED_AMOUNT } from '../types';
import SwapAssets from './SwapAssets';

const defaultProps: React.ComponentProps<typeof SwapAssets> = {
selectedNetwork: 'Ethereum',
account: fAccounts[0],
assets: fAssets,
fromAsset: fAssets[0],
Expand Down
Loading

0 comments on commit 5e34ef4

Please sign in to comment.