harden: a token scale above ~87 makes the whole swap silently undecodable #350

Closed
opened 2026-08-23 16:04:49 +02:00 by clawbot · 1 comment
Collaborator

Found by the review of #345 (#345 (comment)). Pre-existing on the ERC-20 path as well as the swap path.

MAX_DECIMALS is 255, but formatUnits() throws above roughly 87 (invalid FixedNumber decimals (too large)). A scale in 90..255 therefore throws inside decode(), where a blanket catch { return null } swallows it, and the entire swap becomes undecodable.

It fails safe — no wrong number is shown — but it fails silently, and the user is left with an approval screen that simply does not describe the transaction rather than one that says why.

Not in milestone 1.0.0: it cannot produce a wrong amount, and the accepted-scale bound is only reachable from a hostile or broken token.

Definition of done

  • A scale the formatter cannot handle is refused explicitly, the same way an unknown scale already is, rather than collapsing the whole decode to null.
  • MAX_DECIMALS and the formatter's real bound agree, or the mismatch is handled where it is introduced rather than by a blanket catch.
  • The blanket catch { return null } in decode() does not hide a class of failure that has a specific, useful message.
  • Test: a token reporting a scale above the formatter's bound reaches an explicit refusal. Fails against current head — state the mutation and observed result.
  • make check green.
Found by the review of https://git.eeqj.de/sneak/AutistMask/pulls/345 (https://git.eeqj.de/sneak/AutistMask/pulls/345#issuecomment-69081). Pre-existing on the ERC-20 path as well as the swap path. `MAX_DECIMALS` is 255, but `formatUnits()` throws above roughly 87 (`invalid FixedNumber decimals (too large)`). A scale in 90..255 therefore throws inside `decode()`, where a blanket `catch { return null }` swallows it, and the entire swap becomes undecodable. It **fails safe** — no wrong number is shown — but it fails silently, and the user is left with an approval screen that simply does not describe the transaction rather than one that says why. Not in milestone 1.0.0: it cannot produce a wrong amount, and the accepted-scale bound is only reachable from a hostile or broken token. ## Definition of done - [ ] A scale the formatter cannot handle is refused explicitly, the same way an unknown scale already is, rather than collapsing the whole decode to `null`. - [ ] `MAX_DECIMALS` and the formatter's real bound agree, or the mismatch is handled where it is introduced rather than by a blanket catch. - [ ] The blanket `catch { return null }` in `decode()` does not hide a class of failure that has a specific, useful message. - [ ] Test: a token reporting a scale above the formatter's bound reaches an explicit refusal. Fails against current head — state the mutation and observed result. - [ ] `make check` green.
Author
Collaborator

#438 sets MAX_DECIMALS to 80, the most formatUnits() accepts, so a token reporting 81 to 255 is shown as one whose scale is unknown on both approval paths instead of leaving the call undecoded. The deadline throw the same catch hides is #437.

Model: opus-5-5

https://git.eeqj.de/sneak/AutistMask/pulls/438 sets `MAX_DECIMALS` to 80, the most `formatUnits()` accepts, so a token reporting 81 to 255 is shown as one whose scale is unknown on both approval paths instead of leaving the call undecoded. The deadline throw the same catch hides is https://git.eeqj.de/sneak/AutistMask/issues/437. Model: opus-5-5
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: sneak/AutistMask#350