docs: code comments name the headings README has now (closes #724) #746
@@ -19,13 +19,10 @@ on: [push]
|
|||||||
# by the host daemon and mounts an empty directory, and the runner image's
|
# by the host daemon and mounts an empty directory, and the runner image's
|
||||||
# node is too old to install this repo's dependencies.
|
# node is too old to install this repo's dependencies.
|
||||||
#
|
#
|
||||||
# These jobs REPORT, they do not gate. Whether a check blocks a merge is
|
# These jobs REPORT, they do not gate. No branch requires these two jobs, so
|
||||||
# Gitea branch protection, which this repo does not configure, so a failure
|
# a failure is a red mark a reviewer has to account for. The Chrome suite has
|
||||||
# here is a red mark a reviewer has to account for rather than a hard
|
# failed under load before, and no such report is open. A gate that fails at
|
||||||
# block. Making e2e-chrome a required check is blocked while reports of the
|
# random teaches people to merge past red.
|
||||||
# Chrome suite failing under load are still open; the "In CI" section of
|
|
||||||
# README.md names them. A gate that fails at random teaches people to merge
|
|
||||||
# past red.
|
|
||||||
#
|
#
|
||||||
# Nothing here may pass vacuously. There is no continue-on-error and no
|
# Nothing here may pass vacuously. There is no continue-on-error and no
|
||||||
# `|| true`. Both scripts exit non-zero when docker is missing, when the
|
# `|| true`. Both scripts exit non-zero when docker is missing, when the
|
||||||
@@ -36,8 +33,7 @@ jobs:
|
|||||||
e2e-chrome:
|
e2e-chrome:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
# Bounds the image build, a cold cache included, and the three Chrome
|
# Bounds the image build, a cold cache included, and the three Chrome
|
||||||
# programs, so a hung browser frees the shared runner. README.md
|
# programs, so a hung browser frees the shared runner.
|
||||||
# "In CI" has the measured times.
|
|
||||||
timeout-minutes: 40
|
timeout-minutes: 40
|
||||||
steps:
|
steps:
|
||||||
# actions/checkout v4.2.2, 2026-02-22
|
# actions/checkout v4.2.2, 2026-02-22
|
||||||
@@ -47,8 +43,7 @@ jobs:
|
|||||||
e2e-firefox:
|
e2e-firefox:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
# Bounds the image build, a cold cache included, and both Firefox
|
# Bounds the image build, a cold cache included, and both Firefox
|
||||||
# programs, so a hung browser frees the shared runner. README.md
|
# programs, so a hung browser frees the shared runner.
|
||||||
# "In CI" has the measured times.
|
|
||||||
timeout-minutes: 15
|
timeout-minutes: 15
|
||||||
steps:
|
steps:
|
||||||
# actions/checkout v4.2.2, 2026-02-22
|
# actions/checkout v4.2.2, 2026-02-22
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// Turn a verified dist/ into the two distributable archives.
|
// Turn a verified dist/ into the two distributable archives.
|
||||||
//
|
//
|
||||||
// Invoked by script/package, which runs `make build` first so that dist/ has
|
// Invoked by script/package, which runs `make build` first so that dist/ has
|
||||||
// already been checked against the build's own receipt (see the Build Receipts
|
// already been checked against the build's own receipt (see the Builds
|
||||||
// section of README.md). This program does not build anything and does not
|
// section of README.md). This program does not build anything and does not
|
||||||
// write into dist/: it reads the emitted tree and writes release/.
|
// write into dist/: it reads the emitted tree and writes release/.
|
||||||
//
|
//
|
||||||
|
|||||||
@@ -60,12 +60,12 @@ main() {
|
|||||||
|
|
||||||
echo "Running the Firefox e2e suite..."
|
echo "Running the Firefox e2e suite..."
|
||||||
# --shm-size=1g: Firefox needs more than the default 64MB /dev/shm.
|
# --shm-size=1g: Firefox needs more than the default 64MB /dev/shm.
|
||||||
# --network none: the suite stubs nothing, so this is what keeps the
|
# --network none: the container has no network, and the suite reaches
|
||||||
# run offline and deterministic. The extension swallows its own
|
# only the test page and stub node on loopback (README.md, Browser
|
||||||
# fetch failures, so the popup flows work unchanged; see the
|
# tests). The extension swallows its own fetch failures, so the popup
|
||||||
# network note in README.md. Weaker than the Chrome suite's
|
# flows work unchanged. Weaker than the Chrome suite's fixture
|
||||||
# fixture interception, and honestly so — it proves no request
|
# interception, and honestly so — it proves no request escaped, but it
|
||||||
# escaped, but it cannot report which ones were attempted.
|
# cannot report which ones were attempted.
|
||||||
# HOME=/tmp: the image's root home is not a reliable place for the
|
# HOME=/tmp: the image's root home is not a reliable place for the
|
||||||
# browser profile.
|
# browser profile.
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -18,11 +18,11 @@
|
|||||||
// updateState(fn) read fresh, apply fn to that fresh record, write it
|
// updateState(fn) read fresh, apply fn to that fresh record, write it
|
||||||
// back — all holding the lock every write of the stored
|
// back — all holding the lock every write of the stored
|
||||||
// record takes, so no other write lands in between, by
|
// record takes, so no other write lands in between, by
|
||||||
// the background or by any page (README, "Stored state
|
// the background or by any page (README, "Stored data").
|
||||||
// and its version"). The read is one storage round trip
|
// The read is one storage round trip ahead of the write
|
||||||
// ahead of the write rather than a page lifetime ahead of
|
// rather than a page lifetime ahead of it (which is what
|
||||||
// it (which is what made the popup's saveState() need a
|
// made the popup's saveState() need a per-field merge
|
||||||
// per-field merge against a baseline at all).
|
// against a baseline at all).
|
||||||
//
|
//
|
||||||
// A handler that must both read and write therefore does its network work
|
// A handler that must both read and write therefore does its network work
|
||||||
// against a snapshot it owns, and applies the RESULT inside updateState().
|
// against a snapshot it owns, and applies the RESULT inside updateState().
|
||||||
@@ -72,7 +72,7 @@ async function updateStateOnce(mutate) {
|
|||||||
// src/shared/browserApi.js) is held from the read to the write: two
|
// src/shared/browserApi.js) is held from the read to the write: two
|
||||||
// background turns, or a turn and a page's save, never interleave
|
// background turns, or a turn and a page's save, never interleave
|
||||||
// (https://git.eeqj.de/sneak/AutistMask/issues/641). Every page's save waits
|
// (https://git.eeqj.de/sneak/AutistMask/issues/641). Every page's save waits
|
||||||
// while it is held (README, "Stored state and its version").
|
// while it is held (README, "Stored data").
|
||||||
//
|
//
|
||||||
// `mutate` must also not call updateState() itself, directly or through
|
// `mutate` must also not call updateState() itself, directly or through
|
||||||
// anything it awaits: the inner turn waits for the lock the outer one holds,
|
// anything it awaits: the inner turn waits for the lock the outer one holds,
|
||||||
|
|||||||
@@ -319,12 +319,12 @@ function showTxApproval(details) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
// A swap the decoder read no amount for, such as one whose V4 swap
|
// A swap the decoder read no amount for, such as one whose V4 swap
|
||||||
// action could not be read or a V4 exact-out swap. The wait and error
|
// action could not be read or a V4 exact-out swap. The wait, success
|
||||||
// screens say the amount could not be decoded, with no currency after
|
// and error screens say the amount could not be decoded, with no
|
||||||
// it, rather than show the transaction's ETH value as the amount. A
|
// currency after it, rather than show the transaction's ETH value as
|
||||||
// swap paid in native ETH keeps that value: it is the ETH the swap
|
// the amount. A swap paid in native ETH keeps that value: it is the
|
||||||
// pays (for an exact-out swap, the most it pays), not a figure that
|
// ETH the swap pays (for an exact-out swap, the most it pays), not a
|
||||||
// had to be read from the calldata.
|
// figure that had to be read from the calldata.
|
||||||
const tokenIn = decoded.details.find((d) => d.label === "Token In");
|
const tokenIn = decoded.details.find((d) => d.label === "Token In");
|
||||||
if (
|
if (
|
||||||
tokenIn &&
|
tokenIn &&
|
||||||
|
|||||||
@@ -83,10 +83,11 @@ function recoveryPathText(wallet) {
|
|||||||
// rounded number would report "0.0000 ETH" for an address holding real money.
|
// rounded number would report "0.0000 ETH" for an address holding real money.
|
||||||
// The lines below it carry the amounts, in the same format as Home and
|
// The lines below it carry the amounts, in the same format as Home and
|
||||||
// AddressDetail, followed by the USD total when there is one to give — no
|
// AddressDetail, followed by the USD total when there is one to give — no
|
||||||
// total line at all on testnet or before the first price fetch, and no figure
|
// total line at all on testnet, before the first price fetch or while no
|
||||||
// when every holding here is one with no price, since "$0.00" directly under
|
// refresh has read the ETH balance, and no figure when every holding here is
|
||||||
// "This address holds a balance." is a contradiction. A token holding below
|
// one with no price, since "$0.00" directly under "This address holds a
|
||||||
// 0.000001 does not count, as the lines below leave it out.
|
// balance." is a contradiction. A token holding below 0.000001 does not count,
|
||||||
|
// as the lines below leave it out.
|
||||||
//
|
//
|
||||||
// While no refresh has read the ETH balance the address may be empty, so the
|
// While no refresh has read the ETH balance the address may be empty, so the
|
||||||
// sentence says "may hold".
|
// sentence says "may hold".
|
||||||
|
|||||||
@@ -272,7 +272,7 @@ function clearFlash() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// The flash line reserves exactly one line, and a message that wrapped would
|
// The flash line reserves exactly one line, and a message that wrapped would
|
||||||
// push the screen below it down (README, No Layout Shift). So #flash-msg never
|
// push the screen below it down (README, Appearance). So #flash-msg never
|
||||||
// wraps: text too long for the line is cut with an ellipsis, and the whole
|
// wraps: text too long for the line is cut with an ellipsis, and the whole
|
||||||
// message is also put in the line's title. Write messages to fit, at most 50
|
// message is also put in the line's title. Write messages to fit, at most 50
|
||||||
// characters, so none is cut.
|
// characters, so none is cut.
|
||||||
@@ -528,9 +528,9 @@ const ADDRESS_MIN_DISPLAY_LEN = 32;
|
|||||||
|
|
||||||
// Truncate the middle of a string, replacing removed characters with "…".
|
// Truncate the middle of a string, replacing removed characters with "…".
|
||||||
// Safety: refuses to truncate more than 10 characters, which is the maximum
|
// Safety: refuses to truncate more than 10 characters, which is the maximum
|
||||||
// that still prevents address spoofing attacks (see Display Consistency in
|
// that still prevents address spoofing attacks (see Addresses in README).
|
||||||
// README). Callers that need to display less should use a different UI
|
// Callers that need to display less should use a different UI approach rather
|
||||||
// approach rather than silently making addresses insecure.
|
// than silently making addresses insecure.
|
||||||
function truncateMiddle(str, maxLen) {
|
function truncateMiddle(str, maxLen) {
|
||||||
if (str.length <= maxLen) return str;
|
if (str.length <= maxLen) return str;
|
||||||
const removed = str.length - maxLen + 1; // +1 for the ellipsis char
|
const removed = str.length - maxLen + 1; // +1 for the ellipsis char
|
||||||
@@ -633,9 +633,9 @@ function formatAddressHtml(address, ensName, maxLen, title) {
|
|||||||
return renderAddressHtml(address, { title, ensName, maxLen });
|
return renderAddressHtml(address, { title, ensName, maxLen });
|
||||||
}
|
}
|
||||||
|
|
||||||
// A transaction's time as every screen shows it (README, Display
|
// A transaction's time as every screen shows it (README, Amounts): the ISO
|
||||||
// Consistency): the ISO datetime, in UTC when the UTC Timestamps setting is
|
// datetime, in UTC when the UTC Timestamps setting is on, and the relative age.
|
||||||
// on, and the relative age. Views import these two; they keep no copies.
|
// Views import these two; they keep no copies.
|
||||||
function isoDate(timestamp) {
|
function isoDate(timestamp) {
|
||||||
const d = new Date(timestamp * 1000);
|
const d = new Date(timestamp * 1000);
|
||||||
const pad = (n) => String(n).padStart(2, "0");
|
const pad = (n) => String(n).padStart(2, "0");
|
||||||
|
|||||||
@@ -265,7 +265,7 @@ async function renderSiteLists() {
|
|||||||
// A network switch, made by the user here or saved by the background once the
|
// A network switch, made by the user here or saved by the background once the
|
||||||
// user approved a site's request (src/popup/index.js), leaves every screen on
|
// user approved a site's request (src/popup/index.js), leaves every screen on
|
||||||
// the page showing the previous network. Two functions leave the page as
|
// the page showing the previous network. Two functions leave the page as
|
||||||
// README's Screen Map describes:
|
// README's Navigation section describes:
|
||||||
//
|
//
|
||||||
// - cutViewStack(), before the switch is saved, so that save keeps the cut for
|
// - cutViewStack(), before the switch is saved, so that save keeps the cut for
|
||||||
// a popup closed straight after
|
// a popup closed straight after
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// The 4-decimal amount rule from README.md's Display Consistency section, and
|
// The 4-decimal amount rule from README.md's Amounts section, and
|
||||||
// the one exception to it, in one place. Three call sites had grown their own
|
// the one exception to it, in one place. Three call sites had grown their own
|
||||||
// copy of the truncation — the history and balance lists
|
// copy of the truncation — the history and balance lists
|
||||||
// (`src/shared/transactions.js`), the approval screen's ERC-20 amount line
|
// (`src/shared/transactions.js`), the approval screen's ERC-20 amount line
|
||||||
|
|||||||
@@ -240,10 +240,9 @@ function onStorageChanged(listener) {
|
|||||||
/**
|
/**
|
||||||
* Run `fn` while holding the browser's lock called `name`
|
* Run `fn` while holding the browser's lock called `name`
|
||||||
* (`navigator.locks`), and settle as `fn` settles. Every page of the extension
|
* (`navigator.locks`), and settle as `fn` settles. Every page of the extension
|
||||||
* and its background share these locks, except a page in a Firefox private
|
* and its background share these locks, so while one of them holds `name`
|
||||||
* window (README, "Stored state and its version"), so while one of them holds
|
* another asking for it waits, in the order they asked. The lock is let go when
|
||||||
* `name` another asking for it waits, in the order they asked. The lock is let
|
* `fn` settles, whether it resolved or rejected.
|
||||||
* go when `fn` settles, whether it resolved or rejected.
|
|
||||||
*
|
*
|
||||||
* Every write of the stored record holds the lock named after its key,
|
* Every write of the stored record holds the lock named after its key,
|
||||||
* "autistmask", from its read to its write: saveState() in
|
* "autistmask", from its read to its write: saveState() in
|
||||||
@@ -266,8 +265,8 @@ function withLock(name, fn) {
|
|||||||
|
|
||||||
// `storage.session`, the browser's session storage: what is kept there lasts
|
// `storage.session`, the browser's session storage: what is kept there lasts
|
||||||
// until the browser closes. It survives the background worker being stopped
|
// until the browser closes. It survives the background worker being stopped
|
||||||
// and started again, which module-level variables do not (README, Background
|
// and started again, which module-level variables do not (README, The
|
||||||
// scheduling). The background keeps the connections allowed without
|
// background worker). The background keeps the connections allowed without
|
||||||
// "Remember" there; see src/background/index.js. Firefox has it from version
|
// "Remember" there; see src/background/index.js. Firefox has it from version
|
||||||
// 115, the minimum manifest/firefox.json declares.
|
// 115, the minimum manifest/firefox.json declares.
|
||||||
//
|
//
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
// The floor of the approval screen's amount line.
|
// The floor of the approval screen's amount line.
|
||||||
//
|
//
|
||||||
// Amounts are truncated to four decimal places for scannability (README.md,
|
// Amounts are truncated to four decimal places for scannability (README.md,
|
||||||
// Display Consistency). With the token's true scale resolved, that truncation
|
// Amounts). With the token's true scale resolved, that truncation can still
|
||||||
// can still take a real amount below the floor and print it as `0.0000`: one
|
// take a real amount below the floor and print it as `0.0000`: one base unit of
|
||||||
// base unit of an 18-decimal token, or a few hundred of an 8-decimal one. On
|
// an 18-decimal token, or a few hundred of an 8-decimal one. On the one screen
|
||||||
// the one screen whose job is to state what is being authorized, a nonzero
|
// whose job is to state what is being authorized, a nonzero transfer or
|
||||||
// transfer or allowance then reads as nothing.
|
// allowance then reads as nothing.
|
||||||
//
|
//
|
||||||
// The invariant asserted here is narrow: a nonzero amount never renders as
|
// The invariant asserted here is narrow: a nonzero amount never renders as
|
||||||
// zero. The four-decimal rule itself is unchanged, and the string the
|
// zero. The four-decimal rule itself is unchanged, and the string the
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ describe("parsing the dust threshold", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("the rejection message", () => {
|
describe("the rejection message", () => {
|
||||||
// README, Language & Labeling: error messages are full sentences.
|
// README, Wording: error messages are full sentences.
|
||||||
test("is a full sentence naming the constraint", () => {
|
test("is a full sentence naming the constraint", () => {
|
||||||
expect(DUST_THRESHOLD_MESSAGE).toMatch(/^[A-Z].*\.$/);
|
expect(DUST_THRESHOLD_MESSAGE).toMatch(/^[A-Z].*\.$/);
|
||||||
expect(DUST_THRESHOLD_MESSAGE).toContain("whole number of gwei");
|
expect(DUST_THRESHOLD_MESSAGE).toContain("whole number of gwei");
|
||||||
@@ -95,9 +95,9 @@ describe("the flash line the message is shown in", () => {
|
|||||||
// NOT and CANNOT assert that the message fits inside it: jest runs on
|
// NOT and CANNOT assert that the message fits inside it: jest runs on
|
||||||
// the node environment here, with no layout engine, so every rendered
|
// the node environment here, with no layout engine, so every rendered
|
||||||
// height is zero. An earlier version of this block claimed to pin the
|
// height is zero. An earlier version of this block claimed to pin the
|
||||||
// No Layout Shift policy with this regex, and it passed at any message
|
// reserved space rule in README, Appearance, with this regex, and it
|
||||||
// length, including one that wrapped to two lines and pushed the
|
// passed at any message length, including one that wrapped to two lines
|
||||||
// settings view down 12px.
|
// and pushed the settings view down 12px.
|
||||||
//
|
//
|
||||||
// The line cuts a message too long for it with an ellipsis (see
|
// The line cuts a message too long for it with an ellipsis (see
|
||||||
// showFlash() in src/popup/views/helpers.js). The assertions that
|
// showFlash() in src/popup/views/helpers.js). The assertions that
|
||||||
|
|||||||
+7
-7
@@ -1280,7 +1280,7 @@ test("the correct password reveals the full phrase, and nothing logs it (#161)",
|
|||||||
);
|
);
|
||||||
assert(!promptShown, "the password prompt is still shown after unlock");
|
assert(!promptShown, "the password prompt is still shown after unlock");
|
||||||
|
|
||||||
// Full Identifiers Policy: shown whole, and copyable.
|
// README, ShowRecoveryPhrase: shown whole, and copyable.
|
||||||
const title = await env.page.getAttribute(
|
const title = await env.page.getAttribute(
|
||||||
"#show-phrase-value",
|
"#show-phrase-value",
|
||||||
"title",
|
"title",
|
||||||
@@ -2255,7 +2255,7 @@ async function waitForFilledFlashLine(page) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// README, No Layout Shift: the rejection message goes into #flash-msg,
|
// README, Appearance: the rejection message goes into #flash-msg,
|
||||||
// whose min-h-[1.25rem] reserves exactly ONE line at text-xs, and which
|
// whose min-h-[1.25rem] reserves exactly ONE line at text-xs, and which
|
||||||
// cuts a message too long for that line with an ellipsis rather than wrap
|
// cuts a message too long for that line with an ellipsis rather than wrap
|
||||||
// it. This shows the real message and measures that nothing moves; the
|
// it. This shows the real message and measures that nothing moves; the
|
||||||
@@ -2603,7 +2603,7 @@ function toHexWei(wei) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// A fee in wei as the confirmation screen writes it: truncated to four decimal
|
// A fee in wei as the confirmation screen writes it: truncated to four decimal
|
||||||
// places (README.md, Display Consistency). Deliberately a second
|
// places (README.md, Amounts). Deliberately a second
|
||||||
// implementation rather than an import of src/shared/amountDisplay.js:
|
// implementation rather than an import of src/shared/amountDisplay.js:
|
||||||
// asserting against an independent rendering is stronger than asserting a
|
// asserting against an independent rendering is stronger than asserting a
|
||||||
// function equals itself. The fixture's fees are above 0.0001 ETH, so the
|
// function equals itself. The fixture's fees are above 0.0001 ETH, so the
|
||||||
@@ -3298,7 +3298,7 @@ async function goToTokenConfirm(env) {
|
|||||||
amount: TOKEN_AMOUNT,
|
amount: TOKEN_AMOUNT,
|
||||||
});
|
});
|
||||||
await waitForEstimate(env.page);
|
await waitForEstimate(env.page);
|
||||||
// TOKEN_AMOUNT to four decimal places (README.md, Display Consistency).
|
// TOKEN_AMOUNT to four decimal places (README.md, Amounts).
|
||||||
const shown = await shownAmount(env.page);
|
const shown = await shownAmount(env.page);
|
||||||
assert(
|
assert(
|
||||||
shown === "0.2500 " + STUB_TOKEN.symbol,
|
shown === "0.2500 " + STUB_TOKEN.symbol,
|
||||||
@@ -3453,7 +3453,7 @@ test("a token that lies about decimals() at signing time broadcasts nothing (#30
|
|||||||
|
|
||||||
// ------------------------- a send Sign & Send refuses moves nothing (#583)
|
// ------------------------- a send Sign & Send refuses moves nothing (#583)
|
||||||
//
|
//
|
||||||
// README, No Layout Shift. Sign & Send refuses a send on the confirmation
|
// README, Appearance. Sign & Send refuses a send on the confirmation
|
||||||
// screen when the active network changed since review and when its gas limit
|
// screen when the active network changed since review and when its gas limit
|
||||||
// or fee is over its bound, and each refusal's sentence sits in a box that held
|
// or fee is over its bound, and each refusal's sentence sits in a box that held
|
||||||
// its space from the first paint. This drives the total fee refusal, the longer
|
// its space from the first paint. This drives the total fee refusal, the longer
|
||||||
@@ -5770,8 +5770,8 @@ async function main() {
|
|||||||
// There is no fourth: once the context is closed nothing can record,
|
// There is no fourth: once the context is closed nothing can record,
|
||||||
// because the route handler and the console listeners died with it.
|
// because the route handler and the console listeners died with it.
|
||||||
// Traffic that a test defers past the trailing drain is therefore
|
// Traffic that a test defers past the trailing drain is therefore
|
||||||
// never observed at all — a real limit of this design, stated in the
|
// never observed at all — a real limit of this design, and not one
|
||||||
// README, and not one any post-teardown hook could close.
|
// any post-teardown hook could close.
|
||||||
//
|
//
|
||||||
// Two green-but-vacuous runs on this harness were the same shape: a
|
// Two green-but-vacuous runs on this harness were the same shape: a
|
||||||
// record falling outside somebody's window and being dropped. First
|
// record falling outside somebody's window and being dropped. First
|
||||||
|
|||||||
@@ -165,9 +165,9 @@ describe("declared icons", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// README, Installing on Firefox: the extension does not run in private windows.
|
||||||
// Firefox gives a page in a private window locks of its own, so that page's
|
// Firefox gives a page in a private window locks of its own, so that page's
|
||||||
// save could land between the background's read and its write (README,
|
// save could land between the background's read and its write.
|
||||||
// "Stored state and its version").
|
|
||||||
describe("private windows", () => {
|
describe("private windows", () => {
|
||||||
test("a page in a firefox private window cannot read or write extension storage", () => {
|
test("a page in a firefox private window cannot read or write extension storage", () => {
|
||||||
expect(readManifest("firefox").incognito).toBe("not_allowed");
|
expect(readManifest("firefox").incognito).toBe("not_allowed");
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
// browser's own does for the one call the extension makes,
|
// browser's own does for the one call the extension makes,
|
||||||
// `navigator.locks.request(name, callback)`. Every module a test file loads
|
// `navigator.locks.request(name, callback)`. Every module a test file loads
|
||||||
// shares it, as the extension's background and its pages share the browser's
|
// shares it, as the extension's background and its pages share the browser's
|
||||||
// (README, "Stored state and its version").
|
// (README, "Stored data").
|
||||||
//
|
//
|
||||||
// Run before each test file by `setupFiles` in package.json.
|
// Run before each test file by `setupFiles` in package.json.
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// A transaction's time is written by isoDate() and timeAgo() in
|
// A transaction's time is written by isoDate() and timeAgo() in
|
||||||
// src/popup/views/helpers.js on every screen that shows one (README, Display
|
// src/popup/views/helpers.js on every screen that shows one (README, Amounts;
|
||||||
// Consistency; https://git.eeqj.de/sneak/AutistMask/issues/168). AddressDetail
|
// https://git.eeqj.de/sneak/AutistMask/issues/168). AddressDetail and
|
||||||
// and AddressToken used to define their own copies, so a fix to the shared pair
|
// AddressToken used to define their own copies, so a fix to the shared pair
|
||||||
// would not have reached them.
|
// would not have reached them.
|
||||||
//
|
//
|
||||||
// The pair is replaced before the views are loaded, because a view takes it
|
// The pair is replaced before the views are loaded, because a view takes it
|
||||||
|
|||||||
+16
-13
@@ -1,15 +1,15 @@
|
|||||||
// Tests for the address-poisoning defense in src/shared/transactions.js.
|
// Tests for the address-poisoning defense in src/shared/transactions.js.
|
||||||
//
|
//
|
||||||
// README.md:730-814 describes four filters as a core security property of
|
// README, Scam protection, describes four filters as a core security property
|
||||||
// the wallet: known token symbol verification, the low-holder threshold, the
|
// of the wallet: known token symbol verification, the low-holder threshold, the
|
||||||
// fraud contract blocklist, and the dust threshold. A regression in any of
|
// fraud contract blocklist, and the dust threshold. A regression in any of
|
||||||
// them does not crash — it silently stops filtering, and poisoned look-alike
|
// them does not crash — it silently stops filtering, and poisoned look-alike
|
||||||
// addresses reappear in the user's transaction history. These tests pin the
|
// addresses reappear in the user's transaction history. These tests pin the
|
||||||
// behaviour down in both directions: the documented attacks must be filtered,
|
// behaviour down in both directions: the two attacks below must be filtered,
|
||||||
// and legitimate transactions must survive untouched.
|
// and legitimate transactions must survive untouched.
|
||||||
//
|
//
|
||||||
// Fixtures are static local objects modelled on the two real attacks cited in
|
// Fixtures are static local objects modelled on two real attacks.
|
||||||
// the README. Nothing here touches the network: global.fetch is replaced
|
// Nothing here touches the network: global.fetch is replaced
|
||||||
// with a throwing stub and the only fetch path in the module under test
|
// with a throwing stub and the only fetch path in the module under test
|
||||||
// (debugFetch, from src/shared/log) is mocked at the module boundary.
|
// (debugFetch, from src/shared/log) is mocked at the module boundary.
|
||||||
|
|
||||||
@@ -46,11 +46,12 @@ const { debugFetch } = require("../src/shared/log");
|
|||||||
const { state } = require("../src/shared/state");
|
const { state } = require("../src/shared/state");
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Addresses and hashes from the two attacks documented in README.md:730-814.
|
// Addresses and hashes from two real attacks. README, Scam protection, has the
|
||||||
|
// filters that stop them.
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
// The fake "Ethereum" token with symbol "ETH" and zero holders
|
// The fake "Ethereum" token with symbol "ETH" and zero holders. See "Known
|
||||||
// (README.md:735-750).
|
// symbol spoofing" and "Low-holder tokens" in README, Scam protection.
|
||||||
const FAKE_ETH_CONTRACT = "0xd05339f9ea5ab9d9f03b9d57f671d2abd1f55c82";
|
const FAKE_ETH_CONTRACT = "0xd05339f9ea5ab9d9f03b9d57f671d2abd1f55c82";
|
||||||
// The fraudulent Transfer event it emitted.
|
// The fraudulent Transfer event it emitted.
|
||||||
const FAKE_ETH_TRANSFER_HASH =
|
const FAKE_ETH_TRANSFER_HASH =
|
||||||
@@ -62,7 +63,8 @@ const LEGIT_RECIPIENT = "0xc3c693ae04bad5f13c45885c1e85a9557798f37e";
|
|||||||
// The scam address the fake token transfer pointed at ("0xC3C0" vs "0xC3c6").
|
// The scam address the fake token transfer pointed at ("0xC3C0" vs "0xC3c6").
|
||||||
const TOKEN_SCAM_LOOKALIKE = "0xc3c0aea127c575b9ffd03bf11c6a878e8979c37f";
|
const TOKEN_SCAM_LOOKALIKE = "0xc3c0aea127c575b9ffd03bf11c6a878e8979c37f";
|
||||||
|
|
||||||
// The second wave: a real 1 gwei native transfer (README.md:800-808).
|
// The second wave: a real 1 gwei native transfer. See "Dust transactions" in
|
||||||
|
// README, Scam protection.
|
||||||
const DUST_TX_HASH =
|
const DUST_TX_HASH =
|
||||||
"0x2708ebddfb9b5fa3f7a89d3ea398ef9fd8771b83ed861ecb7c21cd55d18edc74";
|
"0x2708ebddfb9b5fa3f7a89d3ea398ef9fd8771b83ed861ecb7c21cd55d18edc74";
|
||||||
const DUST_SENDER_LOOKALIKE = "0xc3c6b3b4402bd78a9582ab6b00e747769344f37e";
|
const DUST_SENDER_LOOKALIKE = "0xc3c6b3b4402bd78a9582ab6b00e747769344f37e";
|
||||||
@@ -79,7 +81,8 @@ const NOVEL_SPAM_SYMBOL = "SPAMTKN";
|
|||||||
// An ordinary counterparty for legitimate-transaction fixtures.
|
// An ordinary counterparty for legitimate-transaction fixtures.
|
||||||
const ORDINARY_PEER = "0x5aa0f9f1e0a1d0e0e5c1e7ce3b7dbbe9c19f0a11";
|
const ORDINARY_PEER = "0x5aa0f9f1e0a1d0e0e5c1e7ce3b7dbbe9c19f0a11";
|
||||||
|
|
||||||
// The documented default settings (README.md:810-814, state.js:24-27).
|
// The documented default settings (README, Settings; DEFAULT_STATE in
|
||||||
|
// src/shared/persistedState.js).
|
||||||
const DEFAULT_FILTERS = {
|
const DEFAULT_FILTERS = {
|
||||||
hideSpoofedSymbols: true,
|
hideSpoofedSymbols: true,
|
||||||
hideLowHolderTokens: true,
|
hideLowHolderTokens: true,
|
||||||
@@ -220,7 +223,7 @@ describe("token list assumptions the fixtures rely on", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("the real attacks documented in README.md:730-814", () => {
|
describe("the real attacks", () => {
|
||||||
test('the fake "Ethereum"/"ETH" token transfer is filtered by default', () => {
|
test('the fake "Ethereum"/"ETH" token transfer is filtered by default', () => {
|
||||||
const attack = fakeEthTokenTransfer();
|
const attack = fakeEthTokenTransfer();
|
||||||
const result = filterTransactions([attack], filters());
|
const result = filterTransactions([attack], filters());
|
||||||
@@ -399,8 +402,8 @@ describe("known-symbol spoof verification", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Turning the other three filters off must not turn this one off: each
|
// Turning the other three filters off must not turn this one off: each
|
||||||
// filter is independent, and this is the one the README calls out as the
|
// filter is independent, and this is the one that stops the fake "ETH"
|
||||||
// defense against the fake "ETH" attack.
|
// attack.
|
||||||
test("the check still runs when the other three filters are off", () => {
|
test("the check still runs when the other three filters are off", () => {
|
||||||
const result = filterTransactions(
|
const result = filterTransactions(
|
||||||
[fakeEthTokenTransfer()],
|
[fakeEthTokenTransfer()],
|
||||||
|
|||||||
Reference in New Issue
Block a user