Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
090a5e785c |
@@ -899,14 +899,15 @@ on the confirmation screen for the wallet's own send
|
|||||||
(`src/popup/views/confirmTx.js`). Both screens render a network fee through
|
(`src/popup/views/confirmTx.js`). Both screens render a network fee through
|
||||||
`formatFee()` in `src/popup/views/helpers.js`, which prices the exact fee in USD
|
`formatFee()` in `src/popup/views/helpers.js`, which prices the exact fee in USD
|
||||||
rather than its truncated figure, so the same fee reads the same on both, USD
|
rather than its truncated figure, so the same fee reads the same on both, USD
|
||||||
value included. Balances are stored exactly (`src/shared/balances.js`), whatever
|
value included. Balances are stored exactly (`src/shared/balances.js`), so a
|
||||||
decimals a token declares, so a balance below the floor reaches these screens as
|
balance below the floor reaches these screens as it is; the one cut is that a
|
||||||
it is. The history list (`src/shared/transactions.js`) uses the unfloored one:
|
token declaring more than 18 decimals is stored to 18, the most the confirmation
|
||||||
the transaction detail view is the authoritative record and already shows exact
|
screen's balance check reads. The history list (`src/shared/transactions.js`)
|
||||||
precision. The balance lists use neither: they round to four places with
|
uses the unfloored one: the transaction detail view is the authoritative record
|
||||||
`toFixed(4)` (`balanceLine()` in `src/popup/views/helpers.js`). The 4-decimal
|
and already shows exact precision. The balance lists use neither: they round to
|
||||||
rule is unchanged everywhere else, including for amounts at or above the floor
|
four places with `toFixed(4)` (`balanceLine()` in `src/popup/views/helpers.js`).
|
||||||
on the approval screens.
|
The 4-decimal rule is unchanged everywhere else, including for amounts at or
|
||||||
|
above the floor on the approval screens.
|
||||||
|
|
||||||
The floor applies only where the token's scale is known. Where it is not, the
|
The floor applies only where the token's scale is known. Where it is not, the
|
||||||
approval screen states base units instead of a quantity — see Unknown token
|
approval screen states base units instead of a quantity — see Unknown token
|
||||||
@@ -1061,15 +1062,13 @@ tokens do appear without the user adding them. An ERC-20 is shown when its
|
|||||||
balance is nonzero and it is in the bundled known-token list, is tracked by the
|
balance is nonzero and it is in the bundled known-token list, is tracked by the
|
||||||
user, or has 1,000 or more holders. A holding below 0.000001 of a token the user
|
user, or has 1,000 or more holders. A holding below 0.000001 of a token the user
|
||||||
does not track is dust and is not shown; a tracked token's holding is shown
|
does not track is dust and is not shown; a tracked token's holding is shown
|
||||||
whatever its size, also while "Show tracked tokens with zero balance" is off,
|
whatever its size. A token claiming a symbol from the bundled list from any
|
||||||
and one too small for the list's four decimal places reads `0.0000`. A token
|
other contract address is always dropped, and so is any token claiming a symbol
|
||||||
claiming a symbol from the bundled list from any other contract address is
|
that belongs to the native asset and therefore has no legitimate contract at all
|
||||||
always dropped, and so is any token claiming a symbol that belongs to the native
|
(`"ETH"`). That filter is unconditional — the "Hide tokens with fewer than 1,000
|
||||||
asset and therefore has no legitimate contract at all (`"ETH"`). That filter is
|
holders" setting governs the transaction history and the send-screen token
|
||||||
unconditional — the "Hide tokens with fewer than 1,000 holders" setting governs
|
selector, not this list. Tracked tokens with a zero balance are listed as well
|
||||||
the transaction history and the send-screen token selector, not this list.
|
while "Show tracked tokens with zero balance" is on.
|
||||||
Tracked tokens with a zero balance are listed as well while "Show tracked tokens
|
|
||||||
with zero balance" is on.
|
|
||||||
|
|
||||||
#### Stored state and its version
|
#### Stored state and its version
|
||||||
|
|
||||||
|
|||||||
@@ -50,17 +50,16 @@ but the review is broader than any of them.
|
|||||||
([#343](https://git.eeqj.de/sneak/AutistMask/issues/343)). The stored balances
|
([#343](https://git.eeqj.de/sneak/AutistMask/issues/343)). The stored balances
|
||||||
(`src/shared/balances.js`) and the confirmation screen's fee were each cut to
|
(`src/shared/balances.js`) and the confirmation screen's fee were each cut to
|
||||||
six decimal places by a rule of their own. Balances are now stored exactly,
|
six decimal places by a rule of their own. Balances are now stored exactly,
|
||||||
whatever decimals a token declares; the balance check reads a token balance to
|
except that a token declaring more than 18 decimals is stored to 18, the most
|
||||||
its first 18 places, the most an amount can have. A token holding below
|
the balance check reads. A token holding below 0.000001 is still not listed,
|
||||||
0.000001 is still not listed, but only for a token the user does not track, so
|
but only for a token the user does not track, so a tracked token's holding
|
||||||
a tracked token's holding reaches the Send screen and the send is checked
|
reaches the Send screen and the send is checked against it. The Send screen's
|
||||||
against it. The Send screen's `Current balance`, and the confirmation screen's
|
`Current balance`, and the confirmation screen's balance, fee, reserve and
|
||||||
balance, fee, reserve and insufficient-balance messages, go through
|
insufficient-balance messages, go through `truncateAmountNeverZero()` in
|
||||||
`truncateAmountNeverZero()` in `src/shared/amountDisplay.js`, the helper the
|
`src/shared/amountDisplay.js`, the helper the approval screen already used.
|
||||||
approval screen already used. The confirmation and approval screens both
|
The confirmation and approval screens both render the fee through
|
||||||
render the fee through `formatFee()` in `src/popup/views/helpers.js`, which
|
`formatFee()` in `src/popup/views/helpers.js`, which prices the exact fee in
|
||||||
prices the exact fee in USD, so the same fee reads the same on both, USD value
|
USD, so the same fee reads the same on both, USD value included.
|
||||||
included.
|
|
||||||
- 2026-10-04: Settings lists the sites connected without "Remember", and
|
- 2026-10-04: Settings lists the sites connected without "Remember", and
|
||||||
removing a site there disconnects it
|
removing a site there disconnects it
|
||||||
([#406](https://git.eeqj.de/sneak/AutistMask/issues/406)). Such a connection
|
([#406](https://git.eeqj.de/sneak/AutistMask/issues/406)). Such a connection
|
||||||
|
|||||||
@@ -361,7 +361,7 @@ async function estimateGas(txInfo) {
|
|||||||
// flight; a stale fee must not reach the screen or the balance check.
|
// flight; a stale fee must not reach the screen or the balance check.
|
||||||
if (pendingTx !== txInfo) return;
|
if (pendingTx !== txInfo) return;
|
||||||
|
|
||||||
// The fee line goes through formatFee(), as the approval screen's
|
// The fee lines go through formatFee(), as the approval screen's
|
||||||
// does, so the same fee reads the same on both.
|
// does, so the same fee reads the same on both.
|
||||||
if (estimateWei !== null && estimateWei < gasCostWei) {
|
if (estimateWei !== null && estimateWei < gasCostWei) {
|
||||||
$("confirm-fee-amount").textContent = "~" + formatFee(estimateWei);
|
$("confirm-fee-amount").textContent = "~" + formatFee(estimateWei);
|
||||||
|
|||||||
@@ -248,7 +248,7 @@ function unknownableAmount(balance) {
|
|||||||
// A network fee in wei as the confirmation and approval screens both show it:
|
// A network fee in wei as the confirmation and approval screens both show it:
|
||||||
// the ETH figure through truncateAmountNeverZero(), then its USD value when the
|
// the ETH figure through truncateAmountNeverZero(), then its USD value when the
|
||||||
// ETH price is known. The USD value is of the exact fee, not of the truncated
|
// ETH price is known. The USD value is of the exact fee, not of the truncated
|
||||||
// figure.
|
// figure, so it never understates what the fee costs.
|
||||||
function formatFee(wei) {
|
function formatFee(wei) {
|
||||||
const eth = formatEther(wei);
|
const eth = formatEther(wei);
|
||||||
const ethPrice = getPrice("ETH");
|
const ethPrice = getPrice("ETH");
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ const { LOW_HOLDER_THRESHOLD, parseHoldersCount } = require("./holders");
|
|||||||
const { isSpoofedSymbol } = require("./symbolSpoof");
|
const { isSpoofedSymbol } = require("./symbolSpoof");
|
||||||
const { toDecimals } = require("./transferAmount");
|
const { toDecimals } = require("./transferAmount");
|
||||||
const { resolveTokenDecimals } = require("./approvalAmount");
|
const { resolveTokenDecimals } = require("./approvalAmount");
|
||||||
|
const { SCALE_DECIMALS } = require("./txValidation");
|
||||||
|
|
||||||
// Use a static network to skip auto-detection (which can fail and cause
|
// Use a static network to skip auto-detection (which can fail and cause
|
||||||
// "could not coalesce error" on some RPC endpoints like Cloudflare).
|
// "could not coalesce error" on some RPC endpoints like Cloudflare).
|
||||||
@@ -52,13 +53,14 @@ function requireNetworkId(networkId) {
|
|||||||
return net;
|
return net;
|
||||||
}
|
}
|
||||||
|
|
||||||
// A token balance as an exact decimal string, never cut: a cut stores a small
|
// A token balance as a decimal string, exact except for a token that declares
|
||||||
// nonzero holding as zero.
|
// more than 18 decimals: that one is cut to 18 places, the most the balance
|
||||||
|
// check on the confirmation screen reads (SCALE_DECIMALS in txValidation.js).
|
||||||
function formatTokenBalance(raw, decimals) {
|
function formatTokenBalance(raw, decimals) {
|
||||||
const val = formatUnits(raw, decimals);
|
const val = formatUnits(raw, decimals);
|
||||||
const parts = val.split(".");
|
const parts = val.split(".");
|
||||||
if (parts.length === 1) return val + ".0";
|
if (parts.length === 1) return val + ".0";
|
||||||
const dec = parts[1].replace(/0+$/, "") || "0";
|
const dec = parts[1].slice(0, SCALE_DECIMALS).replace(/0+$/, "") || "0";
|
||||||
return parts[0] + "." + dec;
|
return parts[0] + "." + dec;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -139,15 +139,7 @@ function validateTransfer({
|
|||||||
const feeFp = known ? feeWei : null;
|
const feeFp = known ? feeWei : null;
|
||||||
|
|
||||||
if (isErc20) {
|
if (isErc20) {
|
||||||
// A token can declare more than 18 decimals, and its balance is
|
const tokenFp = toFixedPoint(tokenBalance) ?? 0n;
|
||||||
// stored with all of them. Only the first 18 places (SCALE_DECIMALS)
|
|
||||||
// are read: an amount with more was refused above, so the places
|
|
||||||
// after them cannot decide whether the amount fits.
|
|
||||||
const tokenText =
|
|
||||||
typeof tokenBalance === "string"
|
|
||||||
? tokenBalance.replace(/(\.\d{18})\d+$/, "$1")
|
|
||||||
: tokenBalance;
|
|
||||||
const tokenFp = toFixedPoint(tokenText) ?? 0n;
|
|
||||||
if (amountFp > tokenFp) codes.push(CODES.INSUFFICIENT_TOKEN);
|
if (amountFp > tokenFp) codes.push(CODES.INSUFFICIENT_TOKEN);
|
||||||
if (feeFp !== null && feeFp > ethFp) {
|
if (feeFp !== null && feeFp > ethFp) {
|
||||||
codes.push(CODES.INSUFFICIENT_ETH_FOR_FEE);
|
codes.push(CODES.INSUFFICIENT_ETH_FOR_FEE);
|
||||||
|
|||||||
@@ -147,10 +147,6 @@ const { prices, clearPrices } = require("../src/shared/prices");
|
|||||||
const send = require("../src/popup/views/send");
|
const send = require("../src/popup/views/send");
|
||||||
const confirmTx = require("../src/popup/views/confirmTx");
|
const confirmTx = require("../src/popup/views/confirmTx");
|
||||||
const approval = require("../src/popup/views/approval");
|
const approval = require("../src/popup/views/approval");
|
||||||
const {
|
|
||||||
addressHoldsFunds,
|
|
||||||
balanceLinesForAddress,
|
|
||||||
} = require("../src/popup/views/helpers");
|
|
||||||
|
|
||||||
const HOLDER = "0x" + "a".repeat(40);
|
const HOLDER = "0x" + "a".repeat(40);
|
||||||
const RECIPIENT = "0xC0FfEE0000000000000000000000000000c0fFEe";
|
const RECIPIENT = "0xC0FfEE0000000000000000000000000000c0fFEe";
|
||||||
@@ -297,8 +293,8 @@ describe("a tracked token holding below 0.000001 never renders as zero", () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
// The stored balance keeps all 24 places, and the balance check reads the
|
// Past 18 places the stored balance is cut: the balance check reads 18,
|
||||||
// first 18 of them rather than refusing it as no balance at all.
|
// and refuses a balance string longer than that as no balance at all.
|
||||||
test("with more than 18 decimals, the send is checked against 18 of them", async () => {
|
test("with more than 18 decimals, the send is checked against 18 of them", async () => {
|
||||||
state.trackedTokens[0].decimals = 24;
|
state.trackedTokens[0].decimals = 24;
|
||||||
// 1.5 plus one base unit.
|
// 1.5 plus one base unit.
|
||||||
@@ -309,30 +305,6 @@ describe("a tracked token holding below 0.000001 never renders as zero", () => {
|
|||||||
expect(errors()).toBe("");
|
expect(errors()).toBe("");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("with more than 18 decimals and a holding below 10^-18", async () => {
|
|
||||||
state.trackedTokens[0].decimals = 24;
|
|
||||||
// One base unit, 0.000000000000000000000001 TOK, and no ETH, so the
|
|
||||||
// token is all the address holds.
|
|
||||||
await refreshWith(0n, [tokenRow(1n, { decimals: "24" })]);
|
|
||||||
state.selectedToken = TOKEN;
|
|
||||||
send.updateSendBalance();
|
|
||||||
expect(text("send-balance")).toBe(
|
|
||||||
"Current balance: 0.000000000000000000000001 TOK",
|
|
||||||
);
|
|
||||||
await confirmSend("0.000000000000000001", TOKEN);
|
|
||||||
expect(text("confirm-balance")).toBe("0.000000000000000000000001 TOK");
|
|
||||||
expect(errors()).toContain(
|
|
||||||
"You have 0.000000000000000000000001 TOK but are trying to send" +
|
|
||||||
" 0.000000000000000001 TOK.",
|
|
||||||
);
|
|
||||||
// Listed with zero balances hidden, and counted as funds.
|
|
||||||
const addr = state.wallets[0].addresses[0];
|
|
||||||
expect(
|
|
||||||
balanceLinesForAddress(addr, state.trackedTokens, false),
|
|
||||||
).toContain(`data-token="${TOKEN}"`);
|
|
||||||
expect(addressHoldsFunds(addr)).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("while an untracked holding below 0.000001 is still not listed", async () => {
|
test("while an untracked holding below 0.000001 is still not listed", async () => {
|
||||||
state.trackedTokens = [];
|
state.trackedTokens = [];
|
||||||
await refreshWith(10n ** 18n, [
|
await refreshWith(10n ** 18n, [
|
||||||
|
|||||||
Reference in New Issue
Block a user