diff --git a/packages/web3-errors/src/error_codes.ts b/packages/web3-errors/src/error_codes.ts index 1db99b30ea7..7239d2d1fa7 100644 --- a/packages/web3-errors/src/error_codes.ts +++ b/packages/web3-errors/src/error_codes.ts @@ -155,6 +155,7 @@ export const ERR_INVALID_LARGE_VALUE = 1011; export const ERR_INVALID_BLOCK = 1012; export const ERR_INVALID_TYPE_ABI = 1013; export const ERR_INVALID_NIBBLE_WIDTH = 1014; +export const ERR_INVALID_NUMBER_DECIMAL_PRECISION_LOSS = 1015; // Validation error codes export const ERR_VALIDATION = 1100; diff --git a/packages/web3-errors/src/errors/utils_errors.ts b/packages/web3-errors/src/errors/utils_errors.ts index b93ff964f38..9363b8a86cd 100644 --- a/packages/web3-errors/src/errors/utils_errors.ts +++ b/packages/web3-errors/src/errors/utils_errors.ts @@ -32,6 +32,7 @@ import { ERR_INVALID_TYPE_ABI, ERR_INVALID_UNIT, ERR_INVALID_UNSIGNED_INTEGER, + ERR_INVALID_NUMBER_DECIMAL_PRECISION_LOSS } from '../error_codes.js'; import { InvalidValueError } from '../web3_error_base.js'; @@ -146,3 +147,11 @@ export class InvalidTypeAbiInputError extends InvalidValueError { super(value, 'components found but type is not tuple'); } } + +export class InvalidNumberDecimalPrecisionLossError extends InvalidValueError { + public code = ERR_INVALID_NUMBER_DECIMAL_PRECISION_LOSS; + + public constructor(value: number) { + super(value, 'value is too small to be represented accurately, use bigInt or string instead.'); + } +} diff --git a/packages/web3-utils/src/converters.ts b/packages/web3-utils/src/converters.ts index acd696d5a59..c6cd8edb892 100644 --- a/packages/web3-utils/src/converters.ts +++ b/packages/web3-utils/src/converters.ts @@ -41,6 +41,7 @@ import { InvalidBytesError, InvalidNumberError, InvalidUnitError, + InvalidNumberDecimalPrecisionLossError } from 'web3-errors'; import { isUint8Array } from './uint8array.js'; @@ -411,6 +412,7 @@ export const toHex = ( */ export const toNumber = (value: Numbers): number | bigint => { if (typeof value === 'number') { + console.warn('Warning: Using type `number` with values that are large or contain many decimals may cause loss of precision, it is recommended to use type `string` or `BigInt` when using conversion methods') if (value > 1e+20) { // JavaScript converts numbers >= 10^21 to scientific notation when coerced to strings, // leading to potential parsing errors and incorrect representations. @@ -551,10 +553,15 @@ export const toWei = (number: Numbers, unit: EtherUnits): string => { if (!denomination) { throw new InvalidUnitError(unit); } + if (typeof number === 'number'){ + console.warn('Warning: The type `numbers` that are large or contain many decimals may cause loss of precision, it is recommended to use type `string` or `BigInt` when using conversion methods') + if (number < 1e-15){ + throw new InvalidNumberDecimalPrecisionLossError(number); + } + } // create error if decimal place is over 20 digits const parsedNumber = typeof number === 'number' ? number.toLocaleString('fullwide', {useGrouping: false, maximumFractionDigits: 20}) : number; - // console.log(parsedNumber) // if value is decimal e.g. 24.56 extract `integer` and `fraction` part // to avoid `fraction` to be null use `concat` with empty string const [integer, fraction] = String( diff --git a/packages/web3-utils/test/fixtures/converters.ts b/packages/web3-utils/test/fixtures/converters.ts index 54b62d9b9fd..393c19be950 100644 --- a/packages/web3-utils/test/fixtures/converters.ts +++ b/packages/web3-utils/test/fixtures/converters.ts @@ -290,6 +290,9 @@ export const toWeiValidData: [[Numbers, EtherUnits], Numbers][] = [ [['255', 'wei'], '0xFF'], [['255', 'wei'], '0xFF'], [['100000000000', 'ether'], 0.0000001], + [['1000000000', 'ether'], 0.000000001], + [['1000000', 'ether'], 0.000000000001] + ]; export const fromWeiInvalidData: [[any, any], string][] = [ @@ -312,6 +315,7 @@ export const toWeiInvalidData: [[any, any], string][] = [ [[{}, 'kwei'], 'value "{}" at "/0" must pass "number" validation'], [['data', 'kwei'], 'value "data" at "/0" must pass "number" validation'], [['1234', 'uwei'], 'Invalid value given "uwei". Error: invalid unit.'], + [[0.0000000000000000000001, 'ether'], 'value is too small to be represented accurately, use bigInt or string instead.'], ]; export const toCheckSumValidData: [string, string][] = [ ['0x0089d53f703f7e0843953d48133f74ce247184c2', '0x0089d53F703f7E0843953D48133f74cE247184c2'],