diff --git a/packages/web3-utils/CHANGELOG.md b/packages/web3-utils/CHANGELOG.md index 1ae402166d7..d0f162613d8 100644 --- a/packages/web3-utils/CHANGELOG.md +++ b/packages/web3-utils/CHANGELOG.md @@ -213,7 +213,15 @@ Documentation: ### Fixed -- fixed toHex incorrectly hexing Uint8Arrays and Buffer (#6957) -- fixed isUint8Array not returning true for Buffer (#6957) +- fixed toHex incorrectly hexing Uint8Arrays and Buffer (#6957) +- fixed isUint8Array not returning true for Buffer (#6957) ## [Unreleased] + +### Added + +- `toWei` add warning when using large numbers or large decimals that may cause precision loss (#6908) + +### Fixed + +- `toWei` support numbers in scientific notation (#6908) \ No newline at end of file diff --git a/packages/web3-utils/src/converters.ts b/packages/web3-utils/src/converters.ts index a8aef7699e7..55143a4277c 100644 --- a/packages/web3-utils/src/converters.ts +++ b/packages/web3-utils/src/converters.ts @@ -77,6 +77,8 @@ export const ethUnitMap = { tether: BigInt('1000000000000000000000000000000'), }; +const PrecisionLossWarning = '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'; + export type EtherUnits = keyof typeof ethUnitMap; /** * Convert a value from bytes to Uint8Array @@ -415,7 +417,8 @@ export const toHex = ( */ export const toNumber = (value: Numbers): number | bigint => { if (typeof value === 'number') { - if (value > 1e+20) { + if (value > 1e+20) { + console.warn(PrecisionLossWarning) // JavaScript converts numbers >= 10^21 to scientific notation when coerced to strings, // leading to potential parsing errors and incorrect representations. // For instance, String(10000000000000000000000) yields '1e+22'. @@ -555,17 +558,32 @@ export const toWei = (number: Numbers, unit: EtherUnits): string => { if (!denomination) { throw new InvalidUnitError(unit); } + let parsedNumber = number; + if (typeof parsedNumber === 'number'){ + if (parsedNumber < 1e-15){ + console.warn(PrecisionLossWarning) + } + if (parsedNumber > 1e+20) { + console.warn(PrecisionLossWarning) + parsedNumber = BigInt(parsedNumber); + } else { + // in case there is a decimal point, we need to convert it to string + parsedNumber = parsedNumber.toLocaleString('fullwide', {useGrouping: false, maximumFractionDigits: 20}) + } + } + // 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( - typeof number === 'string' && !isHexStrict(number) ? number : toNumber(number), + typeof parsedNumber === 'string' && !isHexStrict(parsedNumber) ? parsedNumber : toNumber(parsedNumber), ) .split('.') .concat(''); // join the value removing `.` from // 24.56 -> 2456 + const value = BigInt(`${integer}${fraction}`); // multiply value with denomination diff --git a/packages/web3-utils/test/fixtures/converters.ts b/packages/web3-utils/test/fixtures/converters.ts index 8cf2c3d9e8d..91f3a76f738 100644 --- a/packages/web3-utils/test/fixtures/converters.ts +++ b/packages/web3-utils/test/fixtures/converters.ts @@ -300,9 +300,20 @@ export const fromWeiValidData: [[Numbers, EtherUnits], string][] = [ [[1.9999999999999991611392e+22, 'ether'], '19999.999999999991611392'], ]; -export const toWeiValidData: [[Numbers, EtherUnits], string][] = [ +export const toWeiValidData: [[Numbers, EtherUnits], Numbers][] = [ ...conversionBaseData, [['255', 'wei'], '0xFF'], + [['100000000000', 'ether'], 0.0000001], + [['1000000000', 'ether'], 0.000000001], + [['1000000', 'ether'], 0.000000000001] + +]; + +export const toWeiValidDataWarnings: [[Numbers, EtherUnits], string][] = [ + [[0.0000000000000000000001, 'ether'], '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'], + [[0.0000000000000000000001, 'ether'], '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'], + [[1999999000000009900000, 'kwei'], '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'], + ]; export const fromWeiInvalidData: [[any, any], string][] = [ diff --git a/packages/web3-utils/test/unit/converters.test.ts b/packages/web3-utils/test/unit/converters.test.ts index cb6fa58caa0..940590f06e7 100644 --- a/packages/web3-utils/test/unit/converters.test.ts +++ b/packages/web3-utils/test/unit/converters.test.ts @@ -63,6 +63,7 @@ import { toHexInvalidData, toWeiInvalidData, toWeiValidData, + toWeiValidDataWarnings, utf8ToHexInvalidData, utf8ToHexValidData, toCheckSumValidData, @@ -365,6 +366,19 @@ describe('converters', () => { expect(() => toWei(input[0], input[1])).toThrow(output); }); }); + describe('test console warnings', () => { + beforeEach(() => { + jest.spyOn(console, 'warn').mockImplementation(() => { + // do nothing + }); + }); + it.each(toWeiValidDataWarnings)('%s', (input, output) => { + toWei(input[0], input[1]); + // expect(() => toWei(input[0], input[1])).toThrow(output); + expect(console.warn).toHaveBeenCalledWith(output) + }); + + }) }); describe('toChecksumAddress', () => { describe('valid cases', () => {