fix: withdrawal max threshold (#830)

* chore: fix remaning text for large number

* feat: make use max only use account balance, add custom max messages

* fix: withdraw threshold limit display

* fix: assertions in deposit withdrawy e2e tests
This commit is contained in:
Matthew Russell 2022-07-21 09:18:57 +01:00 committed by GitHub
parent 0964d6dee5
commit 065b48535b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 129 additions and 83 deletions

View File

@ -53,6 +53,6 @@ describe('deposit form validation', () => {
.clear()
.type('100')
.next(`[data-testid="${formFieldError}"]`)
.should('have.text', 'Amount is above approved amount');
.should('have.text', 'Insufficient amount in Ethereum wallet');
});
});

View File

@ -58,7 +58,7 @@ describe('withdraw', () => {
.clear()
.type('1') // Will be above maximum because the vega wallet doesnt have any collateral
.next('[data-testid="input-error-text"]')
.should('contain.text', 'Value is above maximum');
.should('contain.text', 'Insufficient amount in account');
});
it('can set amount using use maximum button', () => {

View File

@ -33,7 +33,7 @@ beforeEach(() => {
assets: [asset],
selectedAsset: undefined,
onSelectAsset: jest.fn(),
available: new BigNumber(5),
balance: new BigNumber(5),
submitApprove: jest.fn(),
submitDeposit: jest.fn(),
requestFaucet: jest.fn(),
@ -125,7 +125,7 @@ describe('Deposit form', () => {
fireEvent.submit(screen.getByTestId('deposit-form'));
expect(
await screen.findByText('Amount is above permitted maximum')
await screen.findByText('Insufficient amount in Ethereum wallet')
).toBeInTheDocument();
});
@ -133,6 +133,7 @@ describe('Deposit form', () => {
render(
<DepositForm
{...props}
balance={new BigNumber(100)}
limits={{ max: new BigNumber(100), deposited: new BigNumber(10) }}
/>
);
@ -216,12 +217,13 @@ describe('Deposit form', () => {
max: new BigNumber(20),
deposited: new BigNumber(10),
};
const balance = new BigNumber(50);
render(
<DepositForm
{...props}
allowance={new BigNumber(100)}
available={new BigNumber(50)}
balance={balance}
limits={limits}
selectedAsset={asset}
/>
@ -229,12 +231,18 @@ describe('Deposit form', () => {
// Check deposit limit is displayed
expect(
screen.getByText('Max deposit total', { selector: 'th' })
screen.getByText('Balance available', { selector: 'th' })
.nextElementSibling
).toHaveTextContent(balance.toString());
expect(
screen.getByText('Maximum total deposit amount', { selector: 'th' })
.nextElementSibling
).toHaveTextContent(limits.max.toString());
expect(
screen.getByText('Remaining available', { selector: 'th' })
.nextElementSibling
screen.getByText('Deposited', { selector: 'th' }).nextElementSibling
).toHaveTextContent(limits.deposited.toString());
expect(
screen.getByText('Remaining', { selector: 'th' }).nextElementSibling
).toHaveTextContent(limits.max.minus(limits.deposited).toString());
fireEvent.change(screen.getByLabelText('Amount'), {

View File

@ -37,7 +37,7 @@ export interface DepositFormProps {
assets: Asset[];
selectedAsset?: Asset;
onSelectAsset: (assetId: string) => void;
available: BigNumber | undefined;
balance: BigNumber | undefined;
submitApprove: () => Promise<void>;
submitDeposit: (args: {
assetSource: string;
@ -57,7 +57,7 @@ export const DepositForm = ({
assets,
selectedAsset,
onSelectAsset,
available,
balance,
submitApprove,
submitDeposit,
requestFaucet,
@ -99,7 +99,7 @@ export const DepositForm = ({
const max = useMemo(() => {
const maxApproved = allowance ? allowance : new BigNumber(0);
const maxAvailable = available ? available : new BigNumber(0);
const maxAvailable = balance ? balance : new BigNumber(0);
// limits.max is a lifetime deposit limit, so the actual max value for form
// input is the max minus whats already been deposited
@ -116,7 +116,7 @@ export const DepositForm = ({
limit: maxLimit,
amount: BigNumber.minimum(maxLimit, maxApproved, maxAvailable),
};
}, [limits, allowance, available]);
}, [limits, allowance, balance]);
const min = useMemo(() => {
// Min viable amount given asset decimals EG for WEI 0.000000000000000001
@ -198,7 +198,7 @@ export const DepositForm = ({
</FormGroup>
{selectedAsset && limits && (
<div className="mb-20">
<DepositLimits limits={limits} />
<DepositLimits limits={limits} balance={balance} />
</div>
)}
<FormGroup label={t('Amount')} labelFor="amount" className="relative">
@ -212,12 +212,12 @@ export const DepositForm = ({
minSafe: (value) => minSafe(new BigNumber(min))(value),
maxSafe: (v) => {
const value = new BigNumber(v);
if (value.isGreaterThan(max.approved)) {
return t('Amount is above approved amount');
} else if (value.isGreaterThan(max.limit)) {
return t('Amount is above permitted maximum');
} else if (value.isGreaterThan(max.available)) {
if (value.isGreaterThan(max.available)) {
return t('Insufficient amount in Ethereum wallet');
} else if (value.isGreaterThan(max.limit)) {
return t('Amount is above temporary deposit limit');
} else if (value.isGreaterThan(max.approved)) {
return t('Amount is above approved amount');
}
return maxSafe(max.amount)(v);
},
@ -229,10 +229,10 @@ export const DepositForm = ({
{errors.amount.message}
</InputError>
)}
{account && selectedAsset && available && (
{selectedAsset && balance && (
<UseButton
onClick={() => {
setValue('amount', max.amount.toFixed(selectedAsset.decimals));
setValue('amount', balance.toFixed(selectedAsset.decimals));
clearErrors('amount');
}}
>

View File

@ -6,11 +6,11 @@ interface DepositLimitsProps {
max: BigNumber;
deposited: BigNumber;
};
balance?: BigNumber;
}
export const DepositLimits = ({ limits }: DepositLimitsProps) => {
export const DepositLimits = ({ limits, balance }: DepositLimitsProps) => {
let maxLimit = '';
if (limits.max.isEqualTo(Infinity)) {
maxLimit = t('No limit');
} else if (limits.max.isGreaterThan(1_000_000)) {
@ -19,13 +19,24 @@ export const DepositLimits = ({ limits }: DepositLimitsProps) => {
maxLimit = limits.max.toString();
}
let remaining = '';
if (limits.deposited.isEqualTo(0)) {
remaining = maxLimit;
} else {
remaining = limits.max.minus(limits.deposited).toString();
}
return (
<>
<p className="text-ui font-bold">{t('Deposit limits')}</p>
<table className="w-full text-ui">
<tbody>
<tr>
<th className="text-left font-normal">{t('Max deposit total')}</th>
<th className="text-left font-normal">{t('Balance available')}</th>
<td className="text-right">{balance ? balance.toString() : 0}</td>
</tr>
<tr>
<th className="text-left font-normal">
{t('Maximum total deposit amount')}
</th>
<td className="text-right">{maxLimit}</td>
</tr>
<tr>
@ -33,15 +44,10 @@ export const DepositLimits = ({ limits }: DepositLimitsProps) => {
<td className="text-right">{limits.deposited.toString()}</td>
</tr>
<tr>
<th className="text-left font-normal">
{t('Remaining available')}
</th>
<td className="text-right">
{limits.max.minus(limits.deposited).toString()}
</td>
<th className="text-left font-normal">{t('Remaining')}</th>
<td className="text-right">{remaining}</td>
</tr>
</tbody>
</table>
</>
);
};

View File

@ -105,7 +105,7 @@ export const DepositManager = ({
return (
<>
<DepositForm
available={balance}
balance={balance}
selectedAsset={asset}
onSelectAsset={(id) => setAssetId(id)}
assets={sortBy(assets, 'name')}

View File

@ -20,9 +20,11 @@ export const useGetWithdrawLimits = (asset?: Asset) => {
if (!data || !asset) return null;
const max = new BigNumber(addDecimal(data.toString(), asset.decimals));
const value = new BigNumber(addDecimal(data.toString(), asset.decimals));
const max = value.isEqualTo(0)
? new BigNumber(Infinity)
: value.minus(new BigNumber(addDecimal('1', asset.decimals)));
return {
max: max.isEqualTo(0) ? new BigNumber(Infinity) : max,
max,
};
};

View File

@ -26,7 +26,10 @@ beforeEach(() => {
props = {
assets,
min: new BigNumber(0.00001),
max: new BigNumber(100),
max: {
balance: new BigNumber(100),
threshold: new BigNumber(200),
},
limits: {
max: new BigNumber(200),
},
@ -75,7 +78,9 @@ describe('Withdrawal form', () => {
expect(
await screen.findByText('Invalid Ethereum address')
).toBeInTheDocument();
expect(screen.getByText('Value is above maximum')).toBeInTheDocument();
expect(
screen.getByText('Insufficient amount in account')
).toBeInTheDocument();
});
it('fails when submitted amount is less than the minimum limit', async () => {
@ -111,14 +116,14 @@ describe('Withdrawal form', () => {
});
});
it('populates amount field with maximum value when clicking the "use maximum" button', () => {
it('populates amount field with balance value when clicking the "use maximum" button', () => {
const asset = props.assets[0];
render(<WithdrawForm {...props} selectedAsset={asset} />);
fireEvent.click(screen.getByText('Use maximum'));
expect(screen.getByLabelText('Amount')).toHaveValue(
Number(props.max.toFixed(asset.decimals))
Number(props.max.balance.toFixed(asset.decimals))
);
});
});

View File

@ -1,10 +1,10 @@
import {
ethereumAddress,
maxSafe,
minSafe,
t,
removeDecimal,
required,
maxSafe,
} from '@vegaprotocol/react-helpers';
import {
Button,
@ -15,7 +15,7 @@ import {
} from '@vegaprotocol/ui-toolkit';
import { Web3WalletInput } from '@vegaprotocol/web3';
import { useWeb3React } from '@web3-react/core';
import type BigNumber from 'bignumber.js';
import BigNumber from 'bignumber.js';
import type { ButtonHTMLAttributes, ReactNode } from 'react';
import { useForm, Controller } from 'react-hook-form';
import type { WithdrawalFields } from './use-withdraw';
@ -30,7 +30,10 @@ interface FormFields {
export interface WithdrawFormProps {
assets: Asset[];
max: BigNumber;
max: {
balance: BigNumber;
threshold: BigNumber;
};
min: BigNumber;
selectedAsset?: Asset;
limits: {
@ -49,7 +52,7 @@ export const WithdrawForm = ({
onSelectAsset,
submitWithdraw,
}: WithdrawFormProps) => {
const { account } = useWeb3React();
const { account: address } = useWeb3React();
const {
register,
handleSubmit,
@ -60,7 +63,7 @@ export const WithdrawForm = ({
} = useForm<FormFields>({
defaultValues: {
asset: selectedAsset?.id,
to: account,
to: address,
},
});
const onSubmit = async (fields: FormFields) => {
@ -106,7 +109,6 @@ export const WithdrawForm = ({
</Select>
)}
/>
{errors.asset?.message && (
<InputError intent="danger" className="mt-4">
{errors.asset.message}
@ -132,7 +134,7 @@ export const WithdrawForm = ({
</FormGroup>
{selectedAsset && limits && (
<div className="mb-20">
<WithdrawLimits limits={limits} />
<WithdrawLimits limits={limits} balance={max.balance} />
</div>
)}
<FormGroup label={t('Amount')} labelFor="amount" className="relative">
@ -143,7 +145,17 @@ export const WithdrawForm = ({
{...register('amount', {
validate: {
required,
maxSafe: (value) => maxSafe(max)(value),
maxSafe: (v) => {
const value = new BigNumber(v);
if (value.isGreaterThan(max.balance)) {
return t('Insufficient amount in account');
} else if (value.isGreaterThan(max.threshold)) {
return t('Amount is above temporary withdrawal limit');
}
return maxSafe(BigNumber.minimum(max.balance, max.threshold))(
v
);
},
minSafe: (value) => minSafe(min)(value),
},
})}
@ -157,7 +169,7 @@ export const WithdrawForm = ({
<UseButton
data-testid="use-maximum"
onClick={() => {
setValue('amount', max.toFixed(selectedAsset.decimals));
setValue('amount', max.balance.toFixed(selectedAsset.decimals));
clearErrors('amount');
}}
>

View File

@ -5,9 +5,10 @@ interface WithdrawLimitsProps {
limits: {
max: BigNumber;
};
balance: BigNumber;
}
export const WithdrawLimits = ({ limits }: WithdrawLimitsProps) => {
export const WithdrawLimits = ({ limits, balance }: WithdrawLimitsProps) => {
let maxLimit = '';
if (limits.max.isEqualTo(Infinity)) {
@ -19,16 +20,17 @@ export const WithdrawLimits = ({ limits }: WithdrawLimitsProps) => {
}
return (
<>
<p className="text-ui font-bold">{t('Withdraw limits')}</p>
<table className="w-full text-ui">
<tbody>
<tr>
<th className="text-left font-normal">{t('Maximum')}</th>
<th className="text-left font-normal">{t('Balance available')}</th>
<td className="text-right">{balance.toString()}</td>
</tr>
<tr>
<th className="text-left font-normal">{t('Maximum withdrawal')}</th>
<td className="text-right">{maxLimit}</td>
</tr>
</tbody>
</table>
</>
);
};

View File

@ -111,7 +111,9 @@ it('Correct min max values provided to form', async () => {
target: { value: '2' },
});
fireEvent.submit(screen.getByTestId('withdraw-form'));
expect(await screen.findByText('Value is above maximum')).toBeInTheDocument();
expect(
await screen.findByText('Insufficient amount in account')
).toBeInTheDocument();
expect(mockSubmit).not.toBeCalled();
});

View File

@ -38,22 +38,31 @@ export const WithdrawManager = ({
return assets?.find((a) => a.id === assetId);
}, [assets, assetId]);
const account = useMemo(() => {
return accounts.find(
(a) => a.type === AccountType.General && a.asset.id === asset?.id
);
}, [asset, accounts]);
const limits = useGetWithdrawLimits(asset);
const max = useMemo(() => {
if (!asset) {
return new BigNumber(0);
return {
balance: new BigNumber(0),
threshold: new BigNumber(0),
};
}
const account = accounts.find(
(a) => a.type === AccountType.General && a.asset.id === asset.id
);
const v = account
const balance = account
? new BigNumber(addDecimal(account.balance, asset.decimals))
: new BigNumber(0);
return BigNumber.minimum(v, limits ? limits.max : new BigNumber(Infinity));
}, [asset, accounts, limits]);
return {
balance,
threshold: limits ? limits.max : new BigNumber(Infinity),
};
}, [asset, account, limits]);
const min = useMemo(() => {
return asset