fix: store an absent explorer decimals as unknown instead of fabricating 18 (closes #349)
parseInt(decimals || "18") ran before writing stored tokenBalances[].decimals, so an explorer reporting no decimals produced a fabricated 18 indistinguishable from a real one at read time. That defeated the resolve-or-refuse guarantees of #306 and #340: their refusal paths were intact but never fired, because the guess was laundered upstream of them. An absent scale is now stored as unknown, and a holding whose scale nothing knows carries a null balance -- unknown, never zero -- with six reader sites saying so rather than printing 0.0000. The Send screen resolves the display scale rather than reading the stored one, so a bundled token whose explorer row omits decimals still sends; when the scale cannot be resolved the stored quantity is withdrawn too, so the user is told the balance is unknown rather than only that the fee failed. Existing fabricated 18s cannot be told apart retroactively and are replaced wholesale on the next balance refresh. An explorer-sourced scale stays trusted -- only fabrication is removed; the reasoning is recorded on the issue.
This commit was merged in pull request #367.
This commit is contained in:
@@ -52,7 +52,7 @@ function mismatchMessage(displayed, onChain) {
|
||||
);
|
||||
}
|
||||
|
||||
// A decimals value from either source as a number, or null if it is not one.
|
||||
// A decimals value from any source as a number, or null if it is not one.
|
||||
// decimals() comes back from ethers as a bigint and the explorer's copy arrives
|
||||
// as a string, so both of those are accepted alongside a plain number; anything
|
||||
// fractional, negative, out of uint8 range, or of any other type at all is not.
|
||||
@@ -60,7 +60,16 @@ function mismatchMessage(displayed, onChain) {
|
||||
// The types are enumerated rather than coerced because Number() is far too
|
||||
// willing: Number([]) is 0 and Number(true) is 1, so a coercing check would
|
||||
// admit an empty array as a scale of zero and encode a whole-token transfer
|
||||
// against it.
|
||||
// against it. Absence answers null and never a default, and a real scale of
|
||||
// ZERO answers 0 — the two are different answers, which is the whole point:
|
||||
// a falsy-collapsing `value || 18` cannot tell them apart, and neither can a
|
||||
// reader of what it wrote (https://git.eeqj.de/sneak/AutistMask/issues/246).
|
||||
//
|
||||
// Exported because every module that has to decide whether it knows a token's
|
||||
// scale needs exactly this test, and three separate copies of it is three
|
||||
// places for the answer to drift: approvalAmount.js resolves the scale the
|
||||
// approval screens display at, and balances.js decides what the explorer
|
||||
// actually reported before it is stored.
|
||||
function toDecimals(value) {
|
||||
let n;
|
||||
if (typeof value === "number") {
|
||||
@@ -110,6 +119,7 @@ module.exports = {
|
||||
displayedDecimals,
|
||||
transferAmountUnits,
|
||||
mismatchMessage,
|
||||
toDecimals,
|
||||
MAX_DECIMALS,
|
||||
UNKNOWN_DISPLAYED_DECIMALS_MESSAGE,
|
||||
UNREADABLE_CONTRACT_DECIMALS_MESSAGE,
|
||||
|
||||
Reference in New Issue
Block a user