harden: decimals || "18" collapses absent and zero if a backend ever sends numeric 0 #246

Open
opened 2026-08-12 10:26:40 +02:00 by clawbot · 0 comments
Collaborator

decimals || "18" appears in src/shared/transactions.js and src/shared/balances.js. Zero decimals is legal ERC-20.

Not a live bug: Blockscout sends decimals as a string, and "0" is truthy, so absent and zero stay distinct today. The defect is latent — a backend that sends numeric 0, or any change that coerces the field before it reaches this line, silently turns a 0-decimal token into an 18-decimal one. That misreports every balance and every amount for that token by a factor of 10^18, in a wallet, with no error.

Found during the coercion sweep in #244 and deliberately left out of its scope as a different field.

Implementation requirements

  • Distinguish absent from zero explicitly rather than relying on "0" being truthy — the current correctness depends on an upstream serialisation detail nobody controls.
  • Apply it at both call sites.
  • Check whether any other consumer of decimals makes the same assumption.

Definition of done

  • An explicitly-zero decimals, as both the string "0" and the number 0, yields 0 rather than 18.
  • An absent or null decimals still falls back to 18.
  • Tests cover both spellings of zero and the absent case.
  • TODO.md updated in the same commit.
  • make check passes.
`decimals || "18"` appears in `src/shared/transactions.js` and `src/shared/balances.js`. Zero decimals is legal ERC-20. Not a live bug: Blockscout sends `decimals` as a string, and `"0"` is truthy, so absent and zero stay distinct today. The defect is latent — a backend that sends numeric `0`, or any change that coerces the field before it reaches this line, silently turns a 0-decimal token into an 18-decimal one. That misreports every balance and every amount for that token by a factor of 10^18, in a wallet, with no error. Found during the coercion sweep in https://git.eeqj.de/sneak/AutistMask/pulls/244 and deliberately left out of its scope as a different field. ## Implementation requirements - Distinguish absent from zero explicitly rather than relying on `"0"` being truthy — the current correctness depends on an upstream serialisation detail nobody controls. - Apply it at both call sites. - Check whether any other consumer of `decimals` makes the same assumption. ## Definition of done - [ ] An explicitly-zero `decimals`, as both the string `"0"` and the number `0`, yields 0 rather than 18. - [ ] An absent or null `decimals` still falls back to 18. - [ ] Tests cover both spellings of zero and the absent case. - [ ] `TODO.md` updated in the same commit. - [ ] `make check` passes.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: sneak/AutistMask#246