Skip to content

Frontend: toBaseUnits is floating point, so high-decimal tokens mint the wrong supply (regression of #320) #395

Description

@zachyo

Files: frontend/lib/utils.ts lines 14 to 18; frontend/lib/__tests__/utils.test.ts; frontend/app/hooks/useDeployToken.ts line 284; frontend/app/deploy/DeployForm.tsx line 65

Issue: The plumbing #320 asked for is correct. The schema types supply as a string (initialSupply: integerString), the value stays a string through the form, and useDeployToken routes through one shared helper rather than scaling inline. Then the helper throws the guarantee away on its first line:

export function toBaseUnits(display: number | string, decimals: number): bigint {
  const amount = typeof display === "string" ? Number(display) : display;
  if (isNaN(amount)) return 0n;
  return BigInt(Math.round(amount * (10 ** decimals)));
}

Number(display) puts the value back into an IEEE-754 double before the multiply, which is precisely what #253 and then #320 were raised to stop. Wrapping the result in BigInt does not recover precision that is already gone.

The multiply is exact only while supply * 5^decimals < 2^53, so the safe ceiling collapses as decimals rise:

decimals largest exactly-scaled supply
7 115,292,150,460
9 4,611,686,018
12 36,893,488
18 2,361

The contract permits decimal <= 18 and the deploy wizard lets the user choose it. Measured against the current helper:

  • 1,000,000 supply at 18 decimals mints 999999999999999983222784 instead of 100000000000000000000000016,777,216 base units short.
  • 123,456,789,012,345 supply at 7 decimals is 41,600 base units short.

The reason this passed review is the test. lib/__tests__/utils.test.ts covers 1,000,000 at 7 decimals and 0.1 at 7 decimals — small, round values that survive the float exactly — including the "assert the ScVal built for a 1,000,000 / 7-decimal token" case that #319 specifically asked for. Every assertion passes against the buggy helper, so the test gives false confidence rather than coverage.

Fix: Do the scaling with string arithmetic and never touch Number: split on ., reject more than decimals fraction digits, right-pad the fraction to decimals, concatenate, and BigInt the result. Add the two failing cases above as regression tests, because anything smaller passes either way.

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions