Skip to content

Commit

Permalink
Fix towei scientific notation (#6908)
Browse files Browse the repository at this point in the history
* update utils

* replace web3 eventemitter for eventemitter3

* update tests

* update snapshot

* remove connection error test

* fix scientific notation

* add error

* add warnings and address feedback

* update

* update

* add testcases

* add more testcases

* uses parsednumber

* update test case

* refactor

* update changelog

---------

Co-authored-by: Muhammad Altabba <[email protected]>
  • Loading branch information
Alex and Muhammad-Altabba authored Apr 19, 2024
1 parent dd172c7 commit d4e937d
Show file tree
Hide file tree
Showing 4 changed files with 56 additions and 5 deletions.
12 changes: 10 additions & 2 deletions packages/web3-utils/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
22 changes: 20 additions & 2 deletions packages/web3-utils/src/converters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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'.
Expand Down Expand Up @@ -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
Expand Down
13 changes: 12 additions & 1 deletion packages/web3-utils/test/fixtures/converters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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][] = [
Expand Down
14 changes: 14 additions & 0 deletions packages/web3-utils/test/unit/converters.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ import {
toHexInvalidData,
toWeiInvalidData,
toWeiValidData,
toWeiValidDataWarnings,
utf8ToHexInvalidData,
utf8ToHexValidData,
toCheckSumValidData,
Expand Down Expand Up @@ -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', () => {
Expand Down

1 comment on commit d4e937d

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Benchmark

Benchmark suite Current: d4e937d Previous: 6c075db Ratio
processingTx 8940 ops/sec (±4.46%) 9301 ops/sec (±4.81%) 1.04
processingContractDeploy 37084 ops/sec (±5.64%) 39129 ops/sec (±7.62%) 1.06
processingContractMethodSend 19122 ops/sec (±8.06%) 19443 ops/sec (±5.19%) 1.02
processingContractMethodCall 38545 ops/sec (±6.03%) 38971 ops/sec (±6.34%) 1.01
abiEncode 42765 ops/sec (±7.08%) 44252 ops/sec (±6.92%) 1.03
abiDecode 30658 ops/sec (±8.09%) 30419 ops/sec (±8.89%) 0.99
sign 1518 ops/sec (±3.35%) 1656 ops/sec (±4.08%) 1.09
verify 371 ops/sec (±0.58%) 373 ops/sec (±0.78%) 1.01

This comment was automatically generated by workflow using github-action-benchmark.

Please sign in to comment.