diff --git a/shared/modules/transaction.utils.test.js b/shared/modules/transaction.utils.test.js index ad43a8aa227b..133f2de9141e 100644 --- a/shared/modules/transaction.utils.test.js +++ b/shared/modules/transaction.utils.test.js @@ -395,6 +395,21 @@ describe('Transaction.utils', function () { const result = parseTypedDataMessage('{"test": "dummy"}'); expect(result.test).toBe('dummy'); }); + + it('parses message.value as a string', () => { + const result = parseTypedDataMessage( + '{"test": "dummy", "message": { "value": 3000123} }', + ); + expect(result.message.value).toBe('3000123'); + }); + + it('parses message.value such that it does not lose precision', () => { + const result = parseTypedDataMessage( + '{"test": "dummy", "message": { "value": 30001231231212312138768} }', + ); + expect(result.message.value).toBe('30001231231212312138768'); + }); + it('throw error for invalid typedDataMessage', () => { expect(() => { parseTypedDataMessage(''); diff --git a/shared/modules/transaction.utils.ts b/shared/modules/transaction.utils.ts index edf24d20be18..e09680c6c4bd 100644 --- a/shared/modules/transaction.utils.ts +++ b/shared/modules/transaction.utils.ts @@ -282,5 +282,40 @@ export async function determineTransactionAssetType( return { assetType: AssetType.native, tokenStandard: TokenStandard.none }; } -export const parseTypedDataMessage = (dataToParse: string) => - JSON.parse(dataToParse); +const REGEX_MESSAGE_VALUE_LARGE = + /"message"\s*:\s*\{[^}]*"value"\s*:\s*(\d{15,})/u; + +function extractLargeMessageValue(dataToParse: string): string | undefined { + if (typeof dataToParse !== 'string') { + return undefined; + } + return dataToParse.match(REGEX_MESSAGE_VALUE_LARGE)?.[1]; +} + +/** + * JSON.parse has a limitation which coerces values to scientific notation if numbers are greator than + * Number.MAX_SAFE_INTEGER. This can cause a loss in precision. + * + * Aside from precision concerns, if the value returned was a large number greator than 15 digits, + * e.g. 3.000123123123121e+26, passing the value to BigNumber will throw the error: + * Error: new BigNumber() number type has more than 15 significant digits + * + * Note that using JSON.parse reviver cannot help since the value will be coerced by the time it + * reaches the reviver function. + * + * This function has a workaround to extract the large value from the message and replace + * the message value with the string value. + * + * @param dataToParse + * @returns + */ +export const parseTypedDataMessage = (dataToParse: string) => { + const result = JSON.parse(dataToParse); + + const messageValue = extractLargeMessageValue(dataToParse); + if (result.message?.value) { + result.message.value = messageValue || String(result.message.value); + } + + return result; +}; diff --git a/ui/components/app/confirm/info/row/text.tsx b/ui/components/app/confirm/info/row/text.tsx index dacedf310457..a93b053dd80e 100644 --- a/ui/components/app/confirm/info/row/text.tsx +++ b/ui/components/app/confirm/info/row/text.tsx @@ -2,6 +2,7 @@ import React, { useContext } from 'react'; import { I18nContext } from '../../../../../contexts/i18n'; import { AlignItems, + BlockSize, Display, FlexWrap, IconColor, @@ -10,8 +11,18 @@ import { import { Box, ButtonIcon, IconName, Text } from '../../../../component-library'; import Tooltip from '../../../../ui/tooltip'; -const InfoText = ({ text }: { text: string }) => ( - +const InfoText = ({ + isEllipsis, + text, +}: { + isEllipsis: boolean; + text: string; +}) => ( + {text} ); @@ -20,12 +31,14 @@ export type ConfirmInfoRowTextProps = { text: string; onEditClick?: () => void; editIconClassName?: string; + isEllipsis?: boolean; tooltip?: string; }; export const ConfirmInfoRowText: React.FC = ({ text, onEditClick, + isEllipsis = false, editIconClassName, tooltip, }) => { @@ -39,6 +52,7 @@ export const ConfirmInfoRowText: React.FC = ({ alignItems={AlignItems.center} flexWrap={FlexWrap.Wrap} gap={2} + minWidth={BlockSize.Zero} > {tooltip ? ( = ({ wrapperStyle={{ minWidth: 0 }} interactive > - + ) : ( - + )} {isEditable ? (

does not render component for advanced transaction

renders component for advanced transaction details

3,000

@@ -432,7 +431,7 @@ exports[`TypedSignInfo correctly renders permit sign type 1`] = `

30

diff --git a/ui/pages/confirmations/components/confirm/info/typed-sign/permit-simulation/permit-simulation.tsx b/ui/pages/confirmations/components/confirm/info/typed-sign/permit-simulation/permit-simulation.tsx index 418b61a1eb24..97b212d08256 100644 --- a/ui/pages/confirmations/components/confirm/info/typed-sign/permit-simulation/permit-simulation.tsx +++ b/ui/pages/confirmations/components/confirm/info/typed-sign/permit-simulation/permit-simulation.tsx @@ -16,6 +16,7 @@ import { Box, Text } from '../../../../../../../components/component-library'; import Tooltip from '../../../../../../../components/ui/tooltip'; import { BackgroundColor, + BlockSize, BorderRadius, Display, TextAlign, @@ -52,10 +53,15 @@ const PermitSimulation: React.FC<{ }, [exchangeRate, value]); const { tokenValue, tokenValueMaxPrecision } = useMemo(() => { - const valueBN = new BigNumber(value / Math.pow(10, tokenDecimals)); + const valueBN = new BigNumber(value); + const diviserBN = new BigNumber(10).pow(tokenDecimals); + const resultBn = valueBN.div(diviserBN); + + // FIXME - Precision may be lost for large values when using formatAmount + /** @see {@link https://github.com/MetaMask/metamask-extension/issues/25755} */ return { - tokenValue: formatAmount('en-US', valueBN), - tokenValueMaxPrecision: formatAmountMaxPrecision('en-US', valueBN), + tokenValue: formatAmount('en-US', resultBn), + tokenValueMaxPrecision: formatAmountMaxPrecision('en-US', resultBn), }; }, [tokenDecimals, value]); @@ -68,9 +74,13 @@ const PermitSimulation: React.FC<{ - + - + {tokenValue} diff --git a/ui/pages/confirmations/components/confirm/row/__snapshots__/dataTree.test.tsx.snap b/ui/pages/confirmations/components/confirm/row/__snapshots__/dataTree.test.tsx.snap index 841f032ef091..b8d470123220 100644 --- a/ui/pages/confirmations/components/confirm/row/__snapshots__/dataTree.test.tsx.snap +++ b/ui/pages/confirmations/components/confirm/row/__snapshots__/dataTree.test.tsx.snap @@ -19,7 +19,7 @@ exports[`DataTree correctly renders reverse strings 1`] = `

diff --git a/ui/pages/confirmations/components/confirm/row/typed-sign-data-v1/__snapshots__/typedSignDataV1.test.tsx.snap b/ui/pages/confirmations/components/confirm/row/typed-sign-data-v1/__snapshots__/typedSignDataV1.test.tsx.snap index 760f38e61738..21d11d1453ec 100644 --- a/ui/pages/confirmations/components/confirm/row/typed-sign-data-v1/__snapshots__/typedSignDataV1.test.tsx.snap +++ b/ui/pages/confirmations/components/confirm/row/typed-sign-data-v1/__snapshots__/typedSignDataV1.test.tsx.snap @@ -26,7 +26,7 @@ exports[`ConfirmInfoRowTypedSignData should match snapshot 1`] = `

{ [47361034.006, '47,361,034'], ['12130982923409.5', '12,130,982,923,410'], ['1213098292340944.5', '1,213,098,292,340,945'], + // Precision is lost after the value is greator than Number.MAX_SAFE_INTEGER. The digits after + // the 15th digit become 0's. + // TODO fix the precision + /** @see {@link https://github.com/MetaMask/metamask-extension/issues/25755} */ + ['30001231231212312138768', '30,001,231,231,212,312,000,000'], + [ + '115792089237316195423570985008687907853269984665640564039457584007913129639935', + '115,792,089,237,316,200,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000', + ], ])( 'formats amount greater than or equal to 1 with appropriate decimal precision (%s => %s)', (amount: number, expected: string) => { diff --git a/ui/pages/confirmations/confirm/__snapshots__/confirm.test.tsx.snap b/ui/pages/confirmations/confirm/__snapshots__/confirm.test.tsx.snap index ae91c4e2b23f..5fa798380d8a 100644 --- a/ui/pages/confirmations/confirm/__snapshots__/confirm.test.tsx.snap +++ b/ui/pages/confirmations/confirm/__snapshots__/confirm.test.tsx.snap @@ -191,7 +191,7 @@ exports[`Confirm matches snapshot for personal signature type 1`] = `