fix: the balance list and the add-token screens apply the spoof rule to tracked tokens (closes #705)
A tracked token that the spoof rule refuses still got a zero row in the balance list while "Show tracked tokens with zero balance" was on. balanceLinesForAddress() now asks isSpoofedSymbol() before it adds that row. AddToken and SettingsAddToken refuse such a token after the contract lookup, with the flash "Not added: this token fakes a known symbol." The bundled-list buttons on SettingsAddToken need no check: the rule never refuses a bundled token. README.md and docs/README.md now say the balance list drops such a token whatever the settings. Model: opus-5-5
This commit was merged in pull request #716.
This commit is contained in:
@@ -1473,21 +1473,20 @@ tracked.
|
||||
|
||||
A tracked token is shown when its balance is nonzero, and any other ERC-20 when
|
||||
its balance is nonzero and it is in the bundled known-token list or has 1,000 or
|
||||
more holders. A token claiming a symbol from the bundled list from any other
|
||||
contract address is always dropped, tracked or not, and so is any token claiming
|
||||
a symbol that belongs to the native asset and therefore has no legitimate
|
||||
contract at all (`"ETH"`, and every network's `nativeCurrency`, such as
|
||||
`"SepoliaETH"`, on every network). That filter is unconditional — the "Hide
|
||||
tokens with fewer than 1,000 holders" setting governs the transaction history
|
||||
and the send-screen token selector, not this list. A token's holder count is
|
||||
unknown when the explorer reports none, or reports anything other than a whole
|
||||
number written in digits alone, such as `1,000` or `1e3` (`parseHoldersCount()`
|
||||
in `src/shared/holders.js`). This list does not take an unknown count as 1,000
|
||||
or more, so such a token is shown only when it is on the bundled list or
|
||||
tracked. Every nonzero holding of a token shown is stored exactly, however
|
||||
small, but a holding below 0.000001 is left out of the balance lists, the
|
||||
send-screen token selector, the address total and the remove-address warning
|
||||
(`isBelowOneMillionth()` in `src/shared/amountDisplay.js`). The Send and
|
||||
more holders. The balance list always drops a token that claims a bundled-list
|
||||
symbol from another contract address, whatever the settings. The list also drops
|
||||
every token that claims the native asset's symbol: `"ETH"`, or any network's
|
||||
`nativeCurrency`, such as `"SepoliaETH"`. That filter is unconditional — the
|
||||
"Hide tokens with fewer than 1,000 holders" setting governs the transaction
|
||||
history and the send-screen token selector, not this list. A token's holder
|
||||
count is unknown when the explorer reports none, or reports anything other than
|
||||
a whole number written in digits alone, such as `1,000` or `1e3`
|
||||
(`parseHoldersCount()` in `src/shared/holders.js`). This list does not take an
|
||||
unknown count as 1,000 or more, so such a token is shown only when it is on the
|
||||
bundled list or tracked. Every nonzero holding of a token shown is stored
|
||||
exactly, however small, but a holding below 0.000001 is left out of the balance
|
||||
lists, the send-screen token selector, the address total and the remove-address
|
||||
warning (`isBelowOneMillionth()` in `src/shared/amountDisplay.js`). The Send and
|
||||
confirmation screens show it when its token is the one being sent. Tracked
|
||||
tokens with a zero balance are listed as well while "Show tracked tokens with
|
||||
zero balance" is on.
|
||||
@@ -2239,6 +2238,8 @@ view would leave a wallet one click from deletion.
|
||||
**AddressDetail**
|
||||
- "Add" with a token already tracked, a scam-listed address, or a failed
|
||||
contract lookup → flash message, no screen change
|
||||
- "Add" with a token that the spoof rule refuses → nothing is tracked; "Not
|
||||
added: this token fakes a known symbol." flash message, no screen change
|
||||
- "Add", then the contract lookup answers while another screen shows, or
|
||||
after AddToken was opened again → tracks the token, no screen change
|
||||
- "Add", then a network switch in Settings before the contract lookup
|
||||
@@ -2547,6 +2548,9 @@ view would leave a wallet one click from deletion.
|
||||
"Added SYMBOL" flash message (no screen change)
|
||||
- A duplicate, a scam-listed address, or a failed contract lookup → flash
|
||||
message, no screen change
|
||||
- "Add" by contract address with a token that the spoof rule refuses →
|
||||
nothing is tracked; "Not added: this token fakes a known symbol." flash
|
||||
message, no screen change
|
||||
- "Add" by contract address, then a network switch in Settings before the
|
||||
lookup answers → nothing is tracked; "The active network changed before
|
||||
the lookup finished, so the token was not added." flash message, no screen
|
||||
|
||||
+2
-4
@@ -380,10 +380,8 @@ but comes from an unrecognized contract, it is identified as a spoof and hidden.
|
||||
In your transaction history this is the "Hide fake tokens impersonating a known
|
||||
symbol" setting, which you can switch off; doing so also stops new entries being
|
||||
added to the fraud contract blocklist below, since detecting a spoof is what
|
||||
fills it. The send token list always applies the check. Your balances apply it
|
||||
too, with one exception: a token claiming the symbol "ETH" is not filtered
|
||||
there, so a fake "ETH" token can still show up in your balance list even though
|
||||
it is hidden from your transaction history and from the send token list.
|
||||
fills it. The send token list always applies the check. Your balance list always
|
||||
applies the check too.
|
||||
|
||||
**Low-holder token filtering.** Tokens with fewer than 1,000 holders are hidden
|
||||
from transaction history and the send token list, and are left out of your
|
||||
|
||||
@@ -3,6 +3,7 @@ const { getTopTokens } = require("../../shared/tokenList");
|
||||
const { state, saveState, currentNetwork } = require("../../shared/state");
|
||||
const { lookupTokenInfo } = require("../../shared/balances");
|
||||
const { isScamAddress } = require("../../shared/scamlist");
|
||||
const { isSpoofedSymbol } = require("../../shared/symbolSpoof");
|
||||
const { log } = require("../../shared/log");
|
||||
|
||||
// Counts the times AddToken has opened. A contract lookup started before
|
||||
@@ -77,6 +78,15 @@ function init(ctx) {
|
||||
);
|
||||
return;
|
||||
}
|
||||
// The balance list never shows a token that the spoof rule
|
||||
// refuses, so such a token is not tracked.
|
||||
if (isSpoofedSymbol(info.symbol, contractAddr)) {
|
||||
showFlash("Not added: this token fakes a known symbol.");
|
||||
if (addTokenScreenOpenings !== opening) return;
|
||||
infoEl.textContent = "";
|
||||
infoEl.style.visibility = "hidden";
|
||||
return;
|
||||
}
|
||||
log.infof("Adding token", info.symbol, contractAddr);
|
||||
state.trackedTokens.push({
|
||||
address: contractAddr,
|
||||
|
||||
@@ -33,6 +33,7 @@ const { networkByChainId } = require("../../shared/networks");
|
||||
const { formatUsd, getPrice } = require("../../shared/prices");
|
||||
const { state, saveState, currentNetwork } = require("../../shared/state");
|
||||
const { displaySymbol } = require("../../shared/symbolDisplay");
|
||||
const { isSpoofedSymbol } = require("../../shared/symbolSpoof");
|
||||
const { walletHasRecoveryPhrase } = require("../../shared/wallet");
|
||||
const { markViewRendered } = require("../viewRouter");
|
||||
|
||||
@@ -447,6 +448,9 @@ function balanceLinesForAddress(addr, trackedTokens, showZero) {
|
||||
if (showZero && trackedTokens) {
|
||||
for (const t of trackedTokens) {
|
||||
if (seen.has(t.address.toLowerCase())) continue;
|
||||
// A token that the spoof rule refuses has no stored balance, and
|
||||
// gets no zero row either.
|
||||
if (isSpoofedSymbol(t.symbol, t.address)) continue;
|
||||
html += balanceLine(
|
||||
t.symbol,
|
||||
0,
|
||||
|
||||
@@ -3,6 +3,7 @@ const { getTopTokens } = require("../../shared/tokenList");
|
||||
const { state, saveState, currentNetwork } = require("../../shared/state");
|
||||
const { lookupTokenInfo } = require("../../shared/balances");
|
||||
const { isScamAddress } = require("../../shared/scamlist");
|
||||
const { isSpoofedSymbol } = require("../../shared/symbolSpoof");
|
||||
const { log } = require("../../shared/log");
|
||||
|
||||
let ctx;
|
||||
@@ -155,6 +156,16 @@ function init(_ctx) {
|
||||
);
|
||||
return;
|
||||
}
|
||||
// The balance list never shows a token that the spoof rule
|
||||
// refuses, so such a token is not tracked. The rule never refuses
|
||||
// a bundled token, so the "Top tokens:" buttons and the top 100
|
||||
// dropdown need no such check.
|
||||
if (isSpoofedSymbol(info.symbol, addr)) {
|
||||
showFlash("Not added: this token fakes a known symbol.");
|
||||
infoEl.textContent = "";
|
||||
infoEl.style.visibility = "hidden";
|
||||
return;
|
||||
}
|
||||
log.infof("Adding token", info.symbol, addr);
|
||||
state.trackedTokens.push({
|
||||
address: addr,
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
// A token that the spoof rule refuses never shows in the balance list, even
|
||||
// tracked at zero balance with "Show tracked tokens with zero balance" on, and
|
||||
// the two add-token screens refuse to track one
|
||||
// (https://git.eeqj.de/sneak/AutistMask/issues/705). Driven through the real
|
||||
// popup over a stored profile.
|
||||
|
||||
const {
|
||||
bootPopup,
|
||||
cleanupPopup,
|
||||
unversionedValidProfile,
|
||||
TOKEN_ADDRESS,
|
||||
} = require("./support/popupBoot");
|
||||
|
||||
// Claims the symbol of the bundled USDT from another contract.
|
||||
const FAKE_USDT = {
|
||||
address: TOKEN_ADDRESS,
|
||||
symbol: "USDT",
|
||||
decimals: 6,
|
||||
name: "Tether USD",
|
||||
};
|
||||
// A symbol the bundled list does not have, so the spoof rule passes it.
|
||||
const TEST_TOKEN = {
|
||||
address: "0xBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB",
|
||||
symbol: "TST",
|
||||
decimals: 18,
|
||||
name: "Test Token",
|
||||
};
|
||||
const REFUSED_FLASH = "Not added: this token fakes a known symbol.";
|
||||
|
||||
afterEach(() => {
|
||||
cleanupPopup();
|
||||
});
|
||||
|
||||
test("a fake token tracked at zero balance is not listed while zero balances are shown", async () => {
|
||||
const boot = await bootPopup(
|
||||
unversionedValidProfile({
|
||||
currentView: "address",
|
||||
selectedWallet: 0,
|
||||
selectedAddress: 0,
|
||||
trackedTokens: [FAKE_USDT, TEST_TOKEN],
|
||||
showZeroBalanceTokens: true,
|
||||
}),
|
||||
);
|
||||
const rows = boot.node("address-balances").innerHTML;
|
||||
expect(rows).toContain(`data-token="${TEST_TOKEN.address.toLowerCase()}"`);
|
||||
expect(rows).not.toContain(
|
||||
`data-token="${FAKE_USDT.address.toLowerCase()}"`,
|
||||
);
|
||||
});
|
||||
|
||||
// Each screen is opened from AddressDetail by the buttons named, in order.
|
||||
describe.each([
|
||||
[
|
||||
"AddToken",
|
||||
["btn-add-token"],
|
||||
"add-token",
|
||||
"add-token-address",
|
||||
"btn-add-token-confirm",
|
||||
],
|
||||
[
|
||||
"SettingsAddToken",
|
||||
["btn-settings", "btn-settings-add-token"],
|
||||
"settings-addtoken",
|
||||
"settings-addtoken-address",
|
||||
"btn-settings-addtoken-manual",
|
||||
],
|
||||
])("%s", (_, opening, view, field, button) => {
|
||||
test("refuses a fake token by contract address", async () => {
|
||||
const boot = await bootPopup(
|
||||
unversionedValidProfile({
|
||||
currentView: "address",
|
||||
selectedWallet: 0,
|
||||
selectedAddress: 0,
|
||||
}),
|
||||
);
|
||||
for (const id of opening) await boot.click(id);
|
||||
expect(boot.visibleViews()).toEqual([view]);
|
||||
const { lookupTokenInfo } = require("../src/shared/balances");
|
||||
lookupTokenInfo.mockResolvedValueOnce({
|
||||
symbol: FAKE_USDT.symbol,
|
||||
decimals: FAKE_USDT.decimals,
|
||||
name: FAKE_USDT.name,
|
||||
});
|
||||
boot.node(field).value = FAKE_USDT.address;
|
||||
|
||||
await boot.click(button);
|
||||
expect(boot.text("flash-msg")).toBe(REFUSED_FLASH);
|
||||
expect(boot.visibleViews()).toEqual([view]);
|
||||
expect(boot.storage.read("autistmask").trackedTokens).toEqual([]);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user