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

Open
opened 2026-08-23 16:04:49 +02:00 by clawbot · 0 comments
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.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: sneak/AutistMask#350