fix: every screen cuts amounts the same way (closes #757)
The balance lists and Home's ETH line rounded with toFixed(4), so 0.99996 ETH read 1.0000 there and 0.9999 on Send, and 0.00004 USDC read 0.0000. They now show the stored decimal string through truncateAmountNeverZero(), as Send and the approval screens do. History rows take the same floor. truncateAmount() is no longer exported. balanceLine() now takes the stored balance string instead of a parsed number. Model: opus-5-5
This commit is contained in:
1 parent
c150b83322
commit
2c5364f7bf
12 files changed
+164
-79
No files matched your search
@@ -25,10 +25,7 @@ const { ERC20_ABI } = require("../src/shared/constants");
|
||||
const { state } = require("../src/shared/state");
|
||||
const { decodeCalldata } = require("../src/popup/views/approval");
|
||||
const uniswap = require("../src/shared/uniswap");
|
||||
const {
|
||||
truncateAmount,
|
||||
truncateAmountNeverZero,
|
||||
} = require("../src/shared/amountDisplay");
|
||||
const { truncateAmountNeverZero } = require("../src/shared/amountDisplay");
|
||||
|
||||
const iface = new Interface(ERC20_ABI);
|
||||
|
||||
@@ -184,12 +181,7 @@ describe("the four-decimal rule is otherwise unchanged", () => {
|
||||
expect(amount(transferData(0n), DAI).rawValue).toBe("0.0000");
|
||||
});
|
||||
|
||||
// The three truncators now share one module. The floor is a policy of the
|
||||
// approval and confirmation screens only: the history and balance lists
|
||||
// keep plain truncation, because the transaction detail view is the
|
||||
// authoritative record and already shows exact precision.
|
||||
test("the list rule stays unfloored", () => {
|
||||
expect(truncateAmount("0.000000000000000001")).toBe("0.0000");
|
||||
test("one base unit of an 18-decimal token reads as itself", () => {
|
||||
expect(truncateAmountNeverZero("0.000000000000000001")).toBe(
|
||||
"0.000000000000000001",
|
||||
);
|
||||
|
||||
@@ -41,13 +41,13 @@ describe("balanceLine", () => {
|
||||
// this payload before its id attribute, so an assertion about the
|
||||
// rest of the payload would pass on the cap and say nothing about
|
||||
// the escape.
|
||||
const html = balanceLine(HOSTILE_SYMBOL, 1, null, null);
|
||||
const html = balanceLine(HOSTILE_SYMBOL, "1", null, null);
|
||||
expect(html).not.toContain("<iframe");
|
||||
expect(html).toContain("<iframe");
|
||||
});
|
||||
|
||||
test("caps the symbol before rendering it", () => {
|
||||
const html = balanceLine("A".repeat(4096), 1, null, null);
|
||||
const html = balanceLine("A".repeat(4096), "1", null, null);
|
||||
expect(html).toContain("A".repeat(MAX_SYMBOL_LENGTH - 1) + "…");
|
||||
expect(html).not.toContain("A".repeat(MAX_SYMBOL_LENGTH + 1));
|
||||
});
|
||||
@@ -55,13 +55,13 @@ describe("balanceLine", () => {
|
||||
// The token id lands inside data-token="...", so a quote in it is a
|
||||
// way out of the attribute and into a new one.
|
||||
test("keeps a quote-bearing token id inside its attribute", () => {
|
||||
const html = balanceLine("TKN", 1, null, '" onclick="alert(1)');
|
||||
const html = balanceLine("TKN", "1", null, '" onclick="alert(1)');
|
||||
expect(html).not.toContain('onclick="');
|
||||
expect(html).toContain('data-token="" onclick="alert(1)"');
|
||||
});
|
||||
|
||||
test("renders an ordinary holding unchanged", () => {
|
||||
const html = balanceLine("USDC", 1.5, null, "0xabc");
|
||||
const html = balanceLine("USDC", "1.5", null, "0xabc");
|
||||
expect(html).toContain("<span>USDC</span>");
|
||||
expect(html).toContain("<span>1.5000</span>");
|
||||
expect(html).toContain('data-token="0xabc"');
|
||||
@@ -69,7 +69,7 @@ describe("balanceLine", () => {
|
||||
|
||||
// formatUsd() writes a value under a cent as "< $0.01".
|
||||
test("escapes the USD value along with the symbol", () => {
|
||||
const html = balanceLine("USDC", 0.001, 1, null);
|
||||
const html = balanceLine("USDC", "0.001", 1, null);
|
||||
expect(html).toContain("< $0.01");
|
||||
expect(html).not.toContain("< $0.01");
|
||||
});
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
// The balance lists and Home's ETH line show a balance as Send does (README.md,
|
||||
// Amounts): cut to four decimal places, never rounded up, and a nonzero amount
|
||||
// never shown as zero
|
||||
// (https://git.eeqj.de/sneak/AutistMask/issues/757).
|
||||
//
|
||||
// Driven through the real popup over a stored profile on Ethereum Mainnet.
|
||||
|
||||
const {
|
||||
ADDRESS,
|
||||
bootPopup,
|
||||
cleanupPopup,
|
||||
unversionedValidProfile,
|
||||
} = require("./support/popupBoot");
|
||||
const { balanceLine } = require("../src/popup/views/helpers");
|
||||
|
||||
const USDC = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48";
|
||||
|
||||
afterEach(() => {
|
||||
cleanupPopup();
|
||||
});
|
||||
|
||||
// The quantity cell of a balance row.
|
||||
function quantity(row) {
|
||||
return row.match(/<span>([^<]*)<\/span><\/span>/)[1];
|
||||
}
|
||||
|
||||
// The popup on Home over one address holding `eth` ETH and `usdc` USDC.
|
||||
function bootHolding(eth, usdc) {
|
||||
const profile = unversionedValidProfile({
|
||||
trackedTokens: [{ address: USDC, symbol: "USDC", decimals: 6 }],
|
||||
});
|
||||
profile.wallets[0].addresses[0] = {
|
||||
address: ADDRESS,
|
||||
balance: eth,
|
||||
tokenBalances: [
|
||||
{ address: USDC, symbol: "USDC", decimals: 6, balance: usdc },
|
||||
],
|
||||
};
|
||||
return bootPopup(profile);
|
||||
}
|
||||
|
||||
// The balance row for `token` in Home's wallet list.
|
||||
function homeRow(page, token) {
|
||||
return page
|
||||
.node("wallet-list")
|
||||
.innerHTML.split("<div ")
|
||||
.find((row) => row.includes('data-token="' + token + '"'));
|
||||
}
|
||||
|
||||
describe("balanceLine", () => {
|
||||
test("cuts 0.99996 to 0.9999, never rounding it up to 1.0000", () => {
|
||||
expect(quantity(balanceLine("ETH", "0.99996", null, "ETH"))).toBe(
|
||||
"0.9999",
|
||||
);
|
||||
});
|
||||
|
||||
test("shows 0.00004 as 0.00004, not 0.0000", () => {
|
||||
expect(quantity(balanceLine("USDC", "0.00004", null, USDC))).toBe(
|
||||
"0.00004",
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Home", () => {
|
||||
test("the ETH line and the balance list read 0.99996 ETH as 0.9999", async () => {
|
||||
const page = await bootHolding("0.99996", "0.00004");
|
||||
|
||||
expect(page.visibleViews()).toEqual(["main"]);
|
||||
expect(page.text("total-value")).toBe("0.9999 ETH");
|
||||
expect(quantity(homeRow(page, "ETH"))).toBe("0.9999");
|
||||
});
|
||||
|
||||
test("the balance list reads 0.00004 USDC as 0.00004", async () => {
|
||||
const page = await bootHolding("0.99996", "0.00004");
|
||||
|
||||
expect(quantity(homeRow(page, USDC.toLowerCase()))).toBe("0.00004");
|
||||
});
|
||||
});
|
||||
@@ -4,8 +4,8 @@
|
||||
// the copy is the substance and is tested as such. Two things it must not
|
||||
// get wrong: what it takes to get the address back — the app refuses both
|
||||
// obvious routes — and what counts as holding something, which is any
|
||||
// ERC-20 as well as ETH, at any size, including a balance that rounds to
|
||||
// zero at the four decimals the balance lines render. The DOM behaviour
|
||||
// ERC-20 as well as ETH, at any size, including a balance below the four
|
||||
// decimals the balance lines render. The DOM behaviour
|
||||
// around them is driven against the real popup by tests/e2e/run.js.
|
||||
|
||||
// helpers.js pulls in state.js, which reads chrome.storage.local at load.
|
||||
@@ -102,7 +102,7 @@ describe("whether an address holds anything", () => {
|
||||
expect(addressHoldsFunds(TOKEN_ONLY)).toBe(true);
|
||||
});
|
||||
|
||||
// 0.00001 ETH renders as "0.0000" at four decimals. It is still money.
|
||||
// 0.00001 ETH is below four decimals. It is still money.
|
||||
test("an ETH balance below the displayed precision counts", () => {
|
||||
expect(addressHoldsFunds(DUST)).toBe(true);
|
||||
});
|
||||
@@ -141,13 +141,12 @@ describe("the balance warning on the removal confirmation", () => {
|
||||
expect(html).toContain("2500.0000");
|
||||
});
|
||||
|
||||
// The rendered line says 0.0000 for this address — that is the display
|
||||
// format, shared with Home and AddressDetail — and the warning is shown
|
||||
// all the same, because the balance is not zero.
|
||||
test("an ETH balance that renders as 0.0000 is warned about", () => {
|
||||
// The rendered line shows the balance to its first nonzero digit, as Home
|
||||
// and AddressDetail do (README.md, Amounts).
|
||||
test("an ETH balance below 0.0001 is warned about, and reads as itself", () => {
|
||||
const html = balanceWarningHtml(DUST);
|
||||
expect(html).toContain("This address holds a balance.");
|
||||
expect(html).toContain("<span>0.0000</span>");
|
||||
expect(html).toContain("<span>0.00001</span>");
|
||||
});
|
||||
|
||||
test("an ETH balance no refresh has read is warned about, and reads as unknown", () => {
|
||||
|
||||
@@ -1361,6 +1361,31 @@ describe("fetchRecentTransactions merge and dedup", () => {
|
||||
expect(txs[0].holders).toBe(3500000);
|
||||
});
|
||||
|
||||
// README.md, Amounts: a nonzero amount never reads as zero.
|
||||
test("a token transfer below 0.0001 shows its first nonzero digit", async () => {
|
||||
respondWith(
|
||||
[],
|
||||
[
|
||||
{
|
||||
transaction_hash: "0x" + "4".repeat(64),
|
||||
block_number: 21000040,
|
||||
timestamp: TS,
|
||||
from: { hash: ORDINARY_PEER },
|
||||
to: { hash: VICTIM },
|
||||
total: { value: "40", decimals: "6" },
|
||||
token: {
|
||||
symbol: "USDC",
|
||||
address_hash: USDC_CONTRACT,
|
||||
holders_count: "3500000",
|
||||
},
|
||||
},
|
||||
],
|
||||
);
|
||||
|
||||
const txs = await fetchRecentTransactions(VICTIM, BLOCKSCOUT, "0x1");
|
||||
expect(txs[0].value).toBe("0.00004");
|
||||
});
|
||||
|
||||
test("two transfers of the same token in one transaction collapse to one entry", async () => {
|
||||
const hash = "0x" + "3".repeat(64);
|
||||
const leg = (value) => ({
|
||||
|
||||
@@ -96,7 +96,7 @@ afterEach(() => {
|
||||
describe("balanceLine", () => {
|
||||
test("an unknown quantity and a zero one render differently", () => {
|
||||
const unknown = balanceLine("NOVEL", null, null, NOVEL);
|
||||
const zero = balanceLine("NOVEL", 0, null, NOVEL);
|
||||
const zero = balanceLine("NOVEL", "0", null, NOVEL);
|
||||
expect(unknown).not.toBe(zero);
|
||||
expect(quantities(unknown)).toEqual(["NOVEL", "quantity unknown"]);
|
||||
expect(quantities(zero)).toEqual(["NOVEL", "0.0000"]);
|
||||
@@ -105,7 +105,7 @@ describe("balanceLine", () => {
|
||||
test("an unknown quantity produces no fiat figure, a zero one does", () => {
|
||||
prices[NOVEL] = 3;
|
||||
const unknown = balanceLine("NOVEL", null, 3, NOVEL);
|
||||
const zero = balanceLine("NOVEL", 0, 3, NOVEL);
|
||||
const zero = balanceLine("NOVEL", "0", 3, NOVEL);
|
||||
// A price times an unknown quantity is not $0.00: that is the same
|
||||
// claim of "nothing here" the quantity cell just refused to make.
|
||||
expect(unknown).toContain(
|
||||
|
||||
Reference in new issue
Block a user