fix: never render a nonzero approval amount as zero (closes #322)
Amounts are truncated to four decimal places per README.md's Display Consistency rule. With the token's true scale resolved, an amount below 0.0001 still printed as 0.0000 — 1 base unit of an 18-decimal token, 500 base units of an 8-decimal one. On the dApp approval screen, and on the wait/success/error screens that carry the same string forward as txInfo.amount, a real transfer, allowance or swap was therefore stated as nothing. A swap's "Min. received" is the sharper case: a slippage floor shown as 0.0000 states that the swap may return nothing. That truncation existed in three separate copies — src/popup/views/approval.js (the ERC-20 amount, the ETH value, the max fee), src/shared/uniswap.js (the swap's Amount and Min. received lines, on that same screen) and src/shared/transactions.js (history and balance lists). They now share src/shared/amountDisplay.js, which holds the rule and its one exception side by side: truncateAmount() truncates, truncateAmountNeverZero() truncates with the nonzero floor. approval.js and uniswap.js take the floored function, so every amount those screens display or hand to the confirmation screens obeys the invariant. When the truncated string would carry no digit from 1 to 9 and the value does, the amount is extended to its first significant digit: 0.000000000000000001 DAI, not 0.0000 DAI. It stays in token units, the same unit as the symbol beside it; the base-unit rendering already on this screen means "the scale is unknown", and reusing it for a known scale would blur the two. A genuine zero still renders 0.0000, an amount at or above the floor is untouched, and truncation stays truncation — 0.99999 shows as 0.9999, never rounded up. transactions.js takes the unfloored function, keeping its current behaviour exactly: balance lists and history are out of scope by the issue's definition of done, and the transaction detail view already shows exact precision there. The code path is shared; the policy is not. tests/approvalDisplayFloor.test.js drives decodeCalldata() and uniswap.decode() and asserts both the displayed line and the rawValue the confirmation screens carry. Against this tree with src/shared/uniswap.js reverted to its previous formatAmount(), its two new swap cases fail: a 50-base-unit USDT input gives "0.0000 USDT" where "0.00005 USDT" is expected, and a 1-wei amountOutMin gives "0.0000 WETH" where "0.000000000000000001 WETH" is expected. A case pinning the list rule as unfloored is included so the shared module cannot drift into one policy. make check green: 42 suites, 848 tests; verify-build 39 cases; check-censored 153 files; eslint and prettier clean in the pinned container.
This commit is contained in:
26
README.md
26
README.md
@@ -708,6 +708,32 @@ Both are click-copyable. Truncating to 4 decimals in summary views is acceptable
|
||||
for scannability, but the detail view must never discard precision — it is the
|
||||
one place the user can always use to verify exact details.
|
||||
|
||||
**Specific Exception — nonzero floor on the approval screens:** A nonzero amount
|
||||
must never render as zero. Truncating to 4 decimals does exactly that to an
|
||||
amount below 0.0001 — 1 base unit of an 18-decimal token, 500 base units of an
|
||||
8-decimal one — and on the dApp approval screen and the wait/success/error
|
||||
screens that carry its amount forward, a real transfer or allowance then reads
|
||||
as "nothing is being moved". A swap's `Min. received` is the sharper case: a
|
||||
slippage floor shown as `0.0000` states that the swap may return nothing.
|
||||
|
||||
On those screens, when the truncated string would contain no digit from 1 to 9
|
||||
and the value does, the amount is extended to its first significant digit
|
||||
instead: `0.000000000000000001 DAI`, not `0.0000 DAI`. The test is on the whole
|
||||
truncated string, integer part included, so `1.00005` still shows as `1.0000` —
|
||||
the exception only fires where the entire displayed figure would read as zero. A
|
||||
genuine zero still renders `0.0000`, and truncation stays truncation: `0.99999`
|
||||
shows as `0.9999`, never rounded up.
|
||||
|
||||
The rule and its exception live in `src/shared/amountDisplay.js` as
|
||||
`truncateAmount()` and `truncateAmountNeverZero()`. Everything the approval and
|
||||
confirmation screens display goes through the floored one — the ERC-20 amount,
|
||||
the ETH value and max fee (`src/popup/views/approval.js`), and the swap's
|
||||
`Amount` and `Min. received` lines (`src/shared/uniswap.js`). The history and
|
||||
balance lists (`src/shared/transactions.js`) use the unfloored one: the
|
||||
transaction detail view is the authoritative record and already shows exact
|
||||
precision. The 4-decimal rule is unchanged everywhere else, including for
|
||||
amounts at or above the floor on the approval screens.
|
||||
|
||||
#### Partial USD totals
|
||||
|
||||
Prices are fetched for the top 25 tokens only, so an address can hold assets the
|
||||
|
||||
Reference in New Issue
Block a user