harden: a token scale above 80 decimal places is refused as unknown (closes #350)
toDecimals() accepted any uint8 scale, but formatUnits() and parseUnits() refuse more than 80 decimal places. A token reporting 81 to 255 made the formatter throw, and the catch in the swap decoder and in the ERC-20 decoder turned that into an undecoded approval screen with nothing saying why. MAX_DECIMALS is now 80, the formatter's own limit, so such a scale is treated exactly like an unknown one: both approval paths show the base-unit amount with the scale stated as unknown. The balance list, the history list and the Send screen use the same check. Model: opus-5-5
This commit was merged in pull request #438.
This commit is contained in:
@@ -184,6 +184,22 @@ describe("decodeCalldata amount", () => {
|
||||
expect(line).not.toMatch(/0\.0000/);
|
||||
});
|
||||
|
||||
// A token added by hand carries whatever its decimals() returned, and a
|
||||
// uint8 reaches 255, but formatUnits() throws above 80. The throw left the
|
||||
// call undecoded rather than refused
|
||||
// (https://git.eeqj.de/sneak/AutistMask/issues/350).
|
||||
test("a token reporting more than 80 decimals shows base units", () => {
|
||||
state.trackedTokens = [
|
||||
{ address: NOVEL_TOKEN, symbol: "NOVEL", decimals: 81 },
|
||||
];
|
||||
expect(
|
||||
amountLine(transferData(FIVE_THOUSAND_AT_SIX), NOVEL_TOKEN),
|
||||
).toBe("5000000000 base units (decimals unknown)");
|
||||
expect(amountLine(approveData(FIVE_THOUSAND_AT_SIX), NOVEL_TOKEN)).toBe(
|
||||
"5000000000 base units (decimals unknown)",
|
||||
);
|
||||
});
|
||||
|
||||
test("an unbounded allowance is still named, with or without a scale", () => {
|
||||
expect(amountLine(approveData(MAX_UINT256), NOVEL_TOKEN)).toBe(
|
||||
"Unlimited",
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// contract at signing time, with nothing comparing the two, so a token whose
|
||||
// on-chain scale differed signed an amount that was never displayed.
|
||||
|
||||
const { parseUnits } = require("ethers");
|
||||
const { formatUnits, parseUnits } = require("ethers");
|
||||
const {
|
||||
displayedDecimals,
|
||||
transferAmountUnits,
|
||||
@@ -25,7 +25,17 @@ describe("displayedDecimals", () => {
|
||||
expect(displayedDecimals(MAX_DECIMALS)).toBe(MAX_DECIMALS);
|
||||
});
|
||||
|
||||
test("refuses anything that is not a uint8", () => {
|
||||
// decimals() is a uint8, but formatUnits() and parseUnits() stop at 80
|
||||
// places, so a larger scale cannot be shown or encoded
|
||||
// (https://git.eeqj.de/sneak/AutistMask/issues/350).
|
||||
test("accepts exactly the scales the formatter accepts", () => {
|
||||
expect(() => formatUnits(1n, MAX_DECIMALS)).not.toThrow();
|
||||
expect(() => parseUnits("1", MAX_DECIMALS)).not.toThrow();
|
||||
expect(() => formatUnits(1n, MAX_DECIMALS + 1)).toThrow();
|
||||
expect(() => parseUnits("1", MAX_DECIMALS + 1)).toThrow();
|
||||
});
|
||||
|
||||
test("refuses anything that is not a uint8 the formatter accepts", () => {
|
||||
for (const bad of [
|
||||
null,
|
||||
undefined,
|
||||
|
||||
@@ -126,6 +126,19 @@ describe("a swap of a token outside the bundled list", () => {
|
||||
test("a bundled token on the other side still formats", () => {
|
||||
expect(swapDetail(data(), "Min. received").value).toBe("0.5000 WETH");
|
||||
});
|
||||
|
||||
// A token added by hand carries whatever its decimals() returned, and a
|
||||
// uint8 reaches 255, but formatUnits() throws above 80. The throw left the
|
||||
// whole swap undecoded rather than refused
|
||||
// (https://git.eeqj.de/sneak/AutistMask/issues/350).
|
||||
test("refuses to format when the token reports more than 80 decimals", () => {
|
||||
state.trackedTokens = [
|
||||
{ address: NOVEL, symbol: "NOVEL", decimals: 81 },
|
||||
];
|
||||
expect(swapDetail(data(), "Amount").value).toBe(
|
||||
"1000000000 base units (decimals unknown)",
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("the Min. received line takes the same rule", () => {
|
||||
|
||||
Reference in New Issue
Block a user