1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-12-23 09:52:26 +01:00

Add token decimals validation in Swaps (#11587)

* Add token decimals validation in Swaps
* Use camelCase
This commit is contained in:
Daniel 2021-07-22 19:29:31 +02:00 committed by GitHub
parent c1d96676b5
commit a17c4462b4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 81 additions and 15 deletions

View File

@ -2302,6 +2302,10 @@
"message": "Verified on $1 sources.",
"description": "Indicates the number of token information sources that recognize the symbol + address. $1 is a decimal number."
},
"swapTooManyDecimalsError": {
"message": "$1 allows up to $2 decimals",
"description": "$1 is a token symbol and $2 is the max. number of decimals allowed for the token"
},
"swapTransactionComplete": {
"message": "Transaction complete"
},

View File

@ -74,6 +74,7 @@ export default function BuildQuote({
maxSlippage,
selectedAccountAddress,
isFeatureFlagLoaded,
tokenFromError,
}) {
const t = useContext(I18nContext);
const dispatch = useDispatch();
@ -367,6 +368,11 @@ export default function BuildQuote({
}
}
const swapYourTokenBalance = t('swapYourTokenBalance', [
fromTokenString || '0',
fromTokenSymbol,
]);
return (
<div className="build-quote">
<div className="build-quote__content">
@ -406,28 +412,35 @@ export default function BuildQuote({
/>
<div
className={classnames('build-quote__balance-message', {
'build-quote__balance-message--error': balanceError,
'build-quote__balance-message--error':
balanceError || tokenFromError,
})}
>
{!balanceError &&
{!tokenFromError &&
!balanceError &&
fromTokenSymbol &&
t('swapYourTokenBalance', [
fromTokenString || '0',
fromTokenSymbol,
])}
{balanceError && fromTokenSymbol && (
swapYourTokenBalance}
{!tokenFromError && balanceError && fromTokenSymbol && (
<div className="build-quite__insufficient-funds">
<div className="build-quite__insufficient-funds-first">
{t('swapsNotEnoughForTx', [fromTokenSymbol])}
</div>
<div className="build-quite__insufficient-funds-second">
{t('swapYourTokenBalance', [
fromTokenString || '0',
fromTokenSymbol,
])}
{swapYourTokenBalance}
</div>
</div>
)}
{tokenFromError && (
<>
<div className="build-quote__form-error">
{t('swapTooManyDecimalsError', [
fromTokenSymbol,
fromTokenDecimals,
])}
</div>
<div>{swapYourTokenBalance}</div>
</>
)}
</div>
<div className="build-quote__swap-arrows-row">
<button
@ -560,6 +573,7 @@ export default function BuildQuote({
}}
submitText={t('swapReviewSwap')}
disabled={
tokenFromError ||
!isFeatureFlagLoaded ||
!Number(inputValue) ||
!selectedToToken?.address ||
@ -582,4 +596,5 @@ BuildQuote.propTypes = {
setMaxSlippage: PropTypes.func,
selectedAccountAddress: PropTypes.string,
isFeatureFlagLoaded: PropTypes.bool.isRequired,
tokenFromError: PropTypes.string,
};

View File

@ -87,6 +87,11 @@
color: $Black-100;
}
.build-quote__form-error:first-of-type {
font-weight: bold;
color: $Red-500;
}
div:last-of-type {
font-weight: normal;
color: $Grey-500;

View File

@ -34,6 +34,7 @@ import {
fetchAndSetSwapsGasPriceInfo,
fetchSwapsLiveness,
getUseNewSwapsApi,
getFromToken,
} from '../../ducks/swaps/swaps';
import {
AWAITING_SIGNATURES_ROUTE,
@ -70,6 +71,7 @@ import {
fetchTopAssets,
getSwapsTokensReceivedFromTxMeta,
fetchAggregatorMetadata,
countDecimals,
} from './swaps.util';
import AwaitingSignatures from './awaiting-signatures';
import AwaitingSwap from './awaiting-swap';
@ -94,6 +96,7 @@ export default function Swap() {
const [inputValue, setInputValue] = useState(fetchParams?.value || '');
const [maxSlippage, setMaxSlippage] = useState(fetchParams?.slippage || 3);
const [isFeatureFlagLoaded, setIsFeatureFlagLoaded] = useState(false);
const [tokenFromError, setTokenFromError] = useState(null);
const routeState = useSelector(getBackgroundSwapRouteState);
const selectedAccount = useSelector(getSelectedAccount);
@ -108,6 +111,7 @@ export default function Swap() {
const chainId = useSelector(getCurrentChainId);
const isSwapsChain = useSelector(getIsSwapsChain);
const useNewSwapsApi = useSelector(getUseNewSwapsApi);
const fromToken = useSelector(getFromToken);
const {
balance: ethBalance,
@ -288,10 +292,15 @@ export default function Swap() {
const onInputChange = (newInputValue, balance) => {
setInputValue(newInputValue);
dispatch(
setBalanceError(
new BigNumber(newInputValue || 0).gt(balance || 0),
),
const balanceError = new BigNumber(newInputValue || 0).gt(
balance || 0,
);
// "setBalanceError" is just a warning, a user can still click on the "Review Swap" button.
dispatch(setBalanceError(balanceError));
setTokenFromError(
countDecimals(newInputValue) > fromToken.decimals
? 'tooManyDecimals'
: null,
);
};
@ -304,6 +313,7 @@ export default function Swap() {
selectedAccountAddress={selectedAccountAddress}
maxSlippage={maxSlippage}
isFeatureFlagLoaded={isFeatureFlagLoaded}
tokenFromError={tokenFromError}
/>
);
}}

View File

@ -826,3 +826,12 @@ export const getSwapsLivenessForNetwork = (swapsFeatureFlags = {}, chainId) => {
useNewSwapsApi: false,
};
};
/**
* @param {number} value
* @returns number
*/
export const countDecimals = (value) => {
if (!value || Math.floor(value) === value) return 0;
return value.toString().split('.')[1]?.length || 0;
};

View File

@ -31,6 +31,7 @@ import {
isContractAddressValid,
getNetworkNameByChainId,
getSwapsLivenessForNetwork,
countDecimals,
} from './swaps.util';
jest.mock('../../helpers/utils/storage-helpers.js', () => ({
@ -450,4 +451,26 @@ describe('Swaps Util', () => {
).toMatchObject(expectedSwapsLiveness);
});
});
describe('countDecimals', () => {
it('returns 0 decimals for an undefined value', () => {
expect(countDecimals()).toBe(0);
});
it('returns 0 decimals for number: 1', () => {
expect(countDecimals(1)).toBe(0);
});
it('returns 1 decimals for number: 1.1', () => {
expect(countDecimals(1.1)).toBe(1);
});
it('returns 3 decimals for number: 1.123', () => {
expect(countDecimals(1.123)).toBe(3);
});
it('returns 9 decimals for number: 1.123456789', () => {
expect(countDecimals(1.123456789)).toBe(9);
});
});
});