Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -14,6 +14,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- `convertMnemonicToWordlistIndices` converts a mnemonic string into little-endian `Uint16` wordlist indices encoded as a `Uint8Array`.
- Add `@metamask/scure-bip39` as a dependency ([#300](https://github.com/MetaMask/utils/pull/300))

### Fixed

- `toWei` now rejects fractional wei values (e.g. `toWei('0.5', 'wei')`), which previously returned an incorrect whole-wei value instead of throwing ([#303](https://github.com/MetaMask/utils/pull/303))
- This is a caller-visible change: `toWei('0.0', 'wei')` now throws instead of returning `0n`, consistent with how the library already rejects an over-precision fraction for every other unit.

## [11.11.0]

### Added
Expand Down
20 changes: 19 additions & 1 deletion src/unitsConversion.test.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -151,7 +151,10 @@ describe('getValueOfUnit', () => {
describe('toWei', () => {
it('should handle edge cases', () => {
expect(toWei(0, 'wei').toString(10)).toBe('0');
expect(toWei('0.0', 'wei').toString(10)).toBe('0');
// wei has zero decimal places, so an explicit fractional digit (even a
// literal zero) is "too many decimal places", same as it would be for
// any other unit given more fractional digits than it supports.
expect(() => toWei('0.0', 'wei')).toThrow(Error);
expect(toWei('.3', 'ether').toString(10)).toBe('300000000000000000');
expect(() => toWei('.', 'wei')).toThrow(Error);
expect(() => toWei('1.243842387924387924897423897423', 'ether')).toThrow(
Expand All@@ -160,6 +163,21 @@ describe('toWei', () => {
expect(() => toWei('8723.98234.98234', 'ether')).toThrow(Error);
});

it('should reject fractional wei (wei has zero decimal places)', () => {
// Regression test: unitLengths used to compute `value.length - 1 || 1`,
// so wei's baseLength was wrongly coerced from 0 to 1, letting a single
// fractional digit silently pass through as whole wei.
expect(() => toWei('0.5', 'wei')).toThrow('too many decimal places');
expect(() => toWei('0.9', 'wei')).toThrow('too many decimal places');

// Whole wei values are unaffected by the fix.
expect(toWei('5', 'wei')).toBe(BigInt(5));
expect(toWei('0', 'wei')).toBe(BigInt(0));

// Sanity-check another unit (baseLength 9) still works after the fix.
expect(toWei('1.5', 'gwei')).toBe(BigInt('1500000000'));
});

it('should handle BigInt inputs with fast path optimizations', () => {
// Fast path: BigInt + 'wei' unit (should return input directly)
expect(toWei(BigInt(123), 'wei')).toBe(BigInt(123));
Expand Down
19 changes: 10 additions & 9 deletions src/unitsConversion.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -68,7 +68,7 @@ const unitMapBigInt = Object.fromEntries(
) as Record<EthereumUnit, bigint>;

const unitLengths = Object.fromEntries(
Object.entries(unitMap).map(([key, value]) => [key, value.length - 1 || 1]),
Object.entries(unitMap).map(([key, value]) => [key, value.length - 1]),
) as Record<EthereumUnit, number>;

const NUMBER_REGEX = /^-?[0-9.]+$/u;
Expand DownExpand Up@@ -266,16 +266,17 @@ export function toWei(
if (!whole) {
whole = '0';
}
if (!fraction) {

if (fraction) {
if (fraction.length > baseLength) {
throw new Error(
`While converting number ${etherInput} to wei, too many decimal places`,
);
}
fraction = fraction.padEnd(baseLength, '0');
} else {
fraction = '0';
}
if (fraction.length > baseLength) {
throw new Error(
`While converting number ${etherInput} to wei, too many decimal places`,
);
}

fraction = fraction.padEnd(baseLength, '0');

const wholeBigInt = BigInt(whole);
const fractionBigInt = BigInt(fraction);
Expand Down