Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
981fbdb42c |
@@ -3566,14 +3566,10 @@ Currently supported:
|
||||
on every router but `UniversalRouterV1`, and otherwise gives the address. A
|
||||
move into the router needs none of them, because its `Paid to` line and
|
||||
`Token moved into the router` line already give the amount, the token and
|
||||
where it goes, and so its amount is shown once. For a move of the zero
|
||||
address, `Moved from the sender` gives the amount in base units and
|
||||
`Token moved` gives the zero address. For such a move into the router, the
|
||||
`Paid to` line gives the amount in base units and
|
||||
`Token moved into the router` gives the zero address. Every `PERMIT2_PERMIT`
|
||||
whose amount does not stand as the `Amount` figure, before the step that sets
|
||||
the input side or after it, gives three lines, in step order, after those for
|
||||
each `PERMIT2_TRANSFER_FROM`: `Permit2 allowance`, its amount, for example
|
||||
where it goes, and so its amount is shown once. Every `PERMIT2_PERMIT` whose
|
||||
amount does not stand as the `Amount` figure, before the step that sets the
|
||||
input side or after it, gives three lines, in step order, after those for each
|
||||
`PERMIT2_TRANSFER_FROM`: `Permit2 allowance`, its amount, for example
|
||||
`5000.0000 DAI`, or `Unlimited` for Permit2's maximum; `Allowance token`,
|
||||
giving the token's address as the `Token In` line does; and
|
||||
`Allowance spender`, the address it lets take the token. Only a permit whose
|
||||
|
||||
+11
-19
@@ -2,10 +2,8 @@
|
||||
//
|
||||
// A network switch reaches the popup's `state` in one of two ways:
|
||||
//
|
||||
// - onChainSwitch() below makes the switch the user picks in Settings. It
|
||||
// makes the switch on the stored record with applyChainSwitchFields(), and
|
||||
// copies the switched fields into `state` with copyChainSwitchFields()
|
||||
// (chainSwitchFields.js).
|
||||
// - onChainSwitch() below makes the switch the user picks in Settings, with
|
||||
// applyChainSwitchFields() (chainSwitchFields.js).
|
||||
// - followStoredSettings() in src/popup/index.js takes a switch the background
|
||||
// or another popup page saved, from the stored record, with
|
||||
// takeStoredNetwork() (state.js), which copies it with
|
||||
@@ -29,10 +27,7 @@
|
||||
// against storage, and the singleton is not reachable from the background
|
||||
// bundle at all (enforced by the ESLint rule in eslint.config.js).
|
||||
|
||||
const {
|
||||
applyChainSwitchFields,
|
||||
copyChainSwitchFields,
|
||||
} = require("./chainSwitchFields");
|
||||
const { applyChainSwitchFields } = require("./chainSwitchFields");
|
||||
const { clearPrices } = require("./prices");
|
||||
|
||||
// The popup's own caches a network switch resets. Prices are chain-specific
|
||||
@@ -53,22 +48,19 @@ function clearPopupCaches() {
|
||||
// waits for the lock (followStoredSettings() in src/popup/index.js): that
|
||||
// follow runs first, and this switch is made after it.
|
||||
//
|
||||
// The switch is made on the stored record that the save has just read, as the
|
||||
// background makes its own switch. The switched fields are then copied from
|
||||
// that record into `state`. So the switch also covers what another page stored
|
||||
// since this page's last save:
|
||||
//
|
||||
// - A token tracked on the previous network stays tracked on that network.
|
||||
// - An endpoint URL saved for the previous network stays with that network.
|
||||
// - A balance from the previous network is cleared
|
||||
// (https://git.eeqj.de/sneak/AutistMask/issues/653).
|
||||
// The switch is also applied to the stored record that the save has just read,
|
||||
// as the background applies its own switch. A save writes only the fields that
|
||||
// this page changed since its last save. A cleared balance of "0" is no change
|
||||
// when this page last saved "0" too. The balance that the background stored
|
||||
// since then would otherwise stay under the new network
|
||||
// (https://git.eeqj.de/sneak/AutistMask/issues/653).
|
||||
async function onChainSwitch(newNetworkId) {
|
||||
const { state, saveState } = require("./state");
|
||||
let net = null;
|
||||
await saveState((stored) => {
|
||||
if (state.networkId === newNetworkId) return false;
|
||||
net = applyChainSwitchFields(stored, newNetworkId);
|
||||
copyChainSwitchFields(state, stored);
|
||||
net = applyChainSwitchFields(state, newNetworkId);
|
||||
applyChainSwitchFields(stored, newNetworkId);
|
||||
clearPopupCaches();
|
||||
});
|
||||
return net;
|
||||
|
||||
@@ -3,10 +3,10 @@
|
||||
//
|
||||
// Split out of chainSwitch.js so the background can perform a chain switch
|
||||
// without the singleton being reachable from its bundle at all. The popup
|
||||
// still goes through onChainSwitch() (chainSwitch.js). That function applies
|
||||
// this to the stored record that the switch's save reads, and copies the
|
||||
// result into the singleton. The background applies it to the detached record
|
||||
// of its own read-modify-write (src/background/state.js).
|
||||
// still goes through onChainSwitch() (chainSwitch.js), which applies this to
|
||||
// the singleton and to the stored record that its save reads; the background
|
||||
// applies it to the detached record of its own read-modify-write
|
||||
// (src/background/state.js).
|
||||
//
|
||||
// Everything here is synchronous and touches nothing but the objects it is
|
||||
// given: no storage, no caches, no imports beyond the network table. That is
|
||||
|
||||
+45
-36
@@ -479,17 +479,6 @@ function tokenInfo(address, sources) {
|
||||
};
|
||||
}
|
||||
|
||||
// The token of a Permit2 allowance or of a PERMIT2_TRANSFER_FROM, in the
|
||||
// shape tokenInfo() gives. Permit2 has no allowance of ETH and moves no ETH.
|
||||
// The zero address there reads as a token nothing knows: its amount in base
|
||||
// units, and the zero address as its token.
|
||||
function permit2TokenInfo(address, sources) {
|
||||
if (address === "0x0000000000000000000000000000000000000000") {
|
||||
return { symbol: null, decimals: null, address };
|
||||
}
|
||||
return tokenInfo(address, sources);
|
||||
}
|
||||
|
||||
// A swap amount line. With a scale it is the token quantity; with none it is
|
||||
// the base-unit integer with the unknown scale stated, the same refusal the
|
||||
// ERC-20 amount line makes, so the screen has one way of saying it. `display`
|
||||
@@ -508,18 +497,16 @@ function amountText(raw, info) {
|
||||
|
||||
// The line giving a moved token, or the token of a permit, under `label`, in
|
||||
// the shape of the Token In line, so the screens show it as they show that
|
||||
// one. `info` is what tokenInfo() or permit2TokenInfo() gives for the token.
|
||||
// ETH has no address.
|
||||
function tokenMovedLine(label, info) {
|
||||
// one. ETH has no address.
|
||||
function tokenMovedLine(label, token, sources) {
|
||||
const info = tokenInfo(token, sources);
|
||||
if (!present(info.address)) {
|
||||
return { label, value: info.symbol };
|
||||
}
|
||||
return {
|
||||
label,
|
||||
value: info.symbol
|
||||
? info.symbol + " (" + info.address + ")"
|
||||
: info.address,
|
||||
address: info.address,
|
||||
value: info.symbol ? info.symbol + " (" + token + ")" : token,
|
||||
address: token,
|
||||
isToken: true,
|
||||
};
|
||||
}
|
||||
@@ -2342,9 +2329,7 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
|
||||
: UNDECODED_CURRENCY,
|
||||
});
|
||||
if (present(s.token)) {
|
||||
details.push(
|
||||
tokenMovedLine("Token moved", tokenInfo(s.token, sources)),
|
||||
);
|
||||
details.push(tokenMovedLine("Token moved", s.token, sources));
|
||||
details.push({
|
||||
label: "Moved to",
|
||||
value: "The swap step that spends it",
|
||||
@@ -2355,12 +2340,12 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
|
||||
// What each PERMIT2_TRANSFER_FROM moves: its amount, its token and
|
||||
// where it goes, an address on a line of its own.
|
||||
for (const m of movedFromSender) {
|
||||
const info = permit2TokenInfo(m.token, sources);
|
||||
details.push({
|
||||
label: "Moved from the sender",
|
||||
value: amountText(m.amount, info).display,
|
||||
value: amountText(m.amount, tokenInfo(m.token, sources))
|
||||
.display,
|
||||
});
|
||||
details.push(tokenMovedLine("Token moved", info));
|
||||
details.push(tokenMovedLine("Token moved", m.token, sources));
|
||||
details.push(
|
||||
present(m.to)
|
||||
? { label: "Moved to", value: m.to }
|
||||
@@ -2390,15 +2375,36 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
|
||||
});
|
||||
continue;
|
||||
}
|
||||
const info = permit2TokenInfo(p.token, sources);
|
||||
details.push({
|
||||
label: "Permit2 allowance",
|
||||
value:
|
||||
p.amount >= MAX_UINT160
|
||||
? "Unlimited"
|
||||
: amountText(p.amount, info).display,
|
||||
});
|
||||
details.push(tokenMovedLine("Allowance token", info));
|
||||
// Permit2 has no allowance of ETH. An allowance of the zero
|
||||
// address reads as one of a token nothing knows: its amount in
|
||||
// base units, and the zero address as its token.
|
||||
if (p.token === "0x0000000000000000000000000000000000000000") {
|
||||
details.push({
|
||||
label: "Permit2 allowance",
|
||||
value:
|
||||
p.amount >= MAX_UINT160
|
||||
? "Unlimited"
|
||||
: unknownDecimalsAmount(p.amount),
|
||||
});
|
||||
details.push({
|
||||
label: "Allowance token",
|
||||
value: p.token,
|
||||
address: p.token,
|
||||
isToken: true,
|
||||
});
|
||||
} else {
|
||||
details.push({
|
||||
label: "Permit2 allowance",
|
||||
value:
|
||||
p.amount >= MAX_UINT160
|
||||
? "Unlimited"
|
||||
: amountText(p.amount, tokenInfo(p.token, sources))
|
||||
.display,
|
||||
});
|
||||
details.push(
|
||||
tokenMovedLine("Allowance token", p.token, sources),
|
||||
);
|
||||
}
|
||||
details.push({
|
||||
label: "Allowance spender",
|
||||
value: p.spender,
|
||||
@@ -2456,7 +2462,7 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
|
||||
paidTo.push({ label: "Paid to", value: PAID_TO_BRIDGE });
|
||||
}
|
||||
for (const m of movedIntoRouter) {
|
||||
const info = permit2TokenInfo(m.token, sources);
|
||||
const info = tokenInfo(m.token, sources);
|
||||
paidTo.push({
|
||||
label: "Paid to",
|
||||
value:
|
||||
@@ -2464,7 +2470,9 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
|
||||
amountText(m.amount, info).display +
|
||||
" from the sender (later steps may not spend all of it)",
|
||||
});
|
||||
paidTo.push(tokenMovedLine("Token moved into the router", info));
|
||||
paidTo.push(
|
||||
tokenMovedLine("Token moved into the router", m.token, sources),
|
||||
);
|
||||
}
|
||||
if (outputHeld) {
|
||||
paidTo.push({ label: "Paid to", value: STAYS_IN_ROUTER });
|
||||
@@ -2481,7 +2489,8 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
|
||||
paidTo.push(
|
||||
tokenMovedLine(
|
||||
"Token moved into the router",
|
||||
tokenInfo(getAddress(token), sources),
|
||||
getAddress(token),
|
||||
sources,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -174,9 +174,8 @@ test("a background refresh saved while a switch in Settings waits for the lock l
|
||||
const background = require("../src/background/state");
|
||||
await page.click("btn-settings");
|
||||
|
||||
// The background takes the lock first and holds it until the test calls
|
||||
// `letGo()`. It then saves balances as backgroundRefresh() in
|
||||
// src/background/index.js does.
|
||||
// The background takes the lock first. Once let go, the background saves
|
||||
// balances as backgroundRefresh() in src/background/index.js does.
|
||||
let letGo;
|
||||
const refreshSave = background.updateState(async (fresh) => {
|
||||
await new Promise((resolve) => {
|
||||
|
||||
@@ -47,7 +47,6 @@ function loadModuleWith(persisted) {
|
||||
return {
|
||||
mod: require("../src/shared/state"),
|
||||
chainSwitch: require("../src/shared/chainSwitch"),
|
||||
storage,
|
||||
written: () => storage.read("autistmask"),
|
||||
};
|
||||
}
|
||||
@@ -106,27 +105,6 @@ describe("a custom endpoint survives a chain switch", () => {
|
||||
expect(mod.state.rpcUrl).toBe(CUSTOM_RPC);
|
||||
});
|
||||
|
||||
// Another page saves the user's own node for Ethereum Mainnet after this
|
||||
// page's last save. This page's switch keeps that node for Ethereum
|
||||
// Mainnet.
|
||||
test("an endpoint another page saved for the network being left is remembered", async () => {
|
||||
const { mod, chainSwitch, storage, written } = loadModuleWith({
|
||||
wallets: walletFixture(),
|
||||
networkId: "mainnet",
|
||||
rpcUrl: MAINNET.defaultRpcUrl,
|
||||
blockscoutUrl: MAINNET.defaultBlockscoutUrl,
|
||||
networkEndpoints: {},
|
||||
});
|
||||
await mod.loadState();
|
||||
storage.write("autistmask", { ...written(), rpcUrl: CUSTOM_RPC });
|
||||
|
||||
await chainSwitch.onChainSwitch("sepolia");
|
||||
expect(written().networkEndpoints.mainnet.rpcUrl).toBe(CUSTOM_RPC);
|
||||
|
||||
await chainSwitch.onChainSwitch("mainnet");
|
||||
expect(mod.state.rpcUrl).toBe(CUSTOM_RPC);
|
||||
});
|
||||
|
||||
test("the remembered endpoints survive an extension restart", async () => {
|
||||
const first = loadModuleWith({
|
||||
wallets: walletFixture(),
|
||||
|
||||
@@ -177,25 +177,6 @@ test("a token added by contract address on Sepolia is tracked on Sepolia only",
|
||||
expect(onSepolia).not.toContain("DAI");
|
||||
});
|
||||
|
||||
// Another popup page tracks DAI on Ethereum Mainnet after this page's last
|
||||
// save. This page's switch in Settings keeps DAI, tracked on Ethereum Mainnet.
|
||||
test("a token another page tracked stays on its network through a switch in Settings", async () => {
|
||||
const boot = await bootPopup(unversionedValidProfile());
|
||||
expect(boot.pageErrors).toEqual([]);
|
||||
boot.storage.write("autistmask", {
|
||||
...boot.storage.read("autistmask"),
|
||||
trackedTokens: [MAINNET_TOKEN],
|
||||
});
|
||||
|
||||
await boot.click("btn-settings");
|
||||
await chooseNetwork(boot, "sepolia");
|
||||
|
||||
const stored = boot.storage.read("autistmask");
|
||||
expect(stored.networkId).toBe("sepolia");
|
||||
expect(stored.trackedTokens).toEqual([]);
|
||||
expect(stored.networkTrackedTokens.mainnet).toEqual([MAINNET_TOKEN]);
|
||||
});
|
||||
|
||||
describe("Back after a switch from the Settings gear", () => {
|
||||
test("SettingsAddToken no longer offers the common tokens", async () => {
|
||||
const boot = await bootOnAddToken("mainnet");
|
||||
|
||||
@@ -3440,82 +3440,6 @@ describe("uniswap decoder", () => {
|
||||
});
|
||||
});
|
||||
|
||||
// https://git.eeqj.de/sneak/AutistMask/issues/685 — Permit2 moves no
|
||||
// ETH, so a PERMIT2_TRANSFER_FROM of the zero address reads as a move
|
||||
// of a token nothing knows. On next at ec6b6b5 its lines read ETH,
|
||||
// with an ETH figure.
|
||||
describe("a PERMIT2_TRANSFER_FROM of the zero address", () => {
|
||||
const MOVED_AMOUNT =
|
||||
"1000000000000000000 base units (decimals unknown)";
|
||||
|
||||
test("to another address names the address", () => {
|
||||
const result = decodeSteps(
|
||||
[0x00, 0x02],
|
||||
[V3_600, transfer(ZERO_ADDR, OTHER_ADDR, ONE_WETH)],
|
||||
);
|
||||
expect(linesBetweenTokens(result)).toEqual([
|
||||
["Amount", "600.0000 USDT"],
|
||||
["Moved from the sender", MOVED_AMOUNT],
|
||||
["Token moved", ZERO_ADDR],
|
||||
["Moved to", OTHER_ADDR],
|
||||
]);
|
||||
expect(detail(result, "Token moved").address).toBe(ZERO_ADDR);
|
||||
});
|
||||
|
||||
test("into the router names the address", () => {
|
||||
const result = decodeSteps(
|
||||
[0x02, 0x00],
|
||||
[transfer(ZERO_ADDR, ROUTER_ADDR, ONE_WETH), V3_600],
|
||||
);
|
||||
expect(detail(result, "Paid to").value).toBe(
|
||||
"The router, " +
|
||||
MOVED_AMOUNT +
|
||||
" from the sender (later steps may not spend all of it)",
|
||||
);
|
||||
expect(detail(result, "Token moved into the router")).toEqual({
|
||||
label: "Token moved into the router",
|
||||
value: ZERO_ADDR,
|
||||
address: ZERO_ADDR,
|
||||
isToken: true,
|
||||
});
|
||||
});
|
||||
|
||||
// The V4 step's SETTLE names the user as payer and states no
|
||||
// amount. The step's lines say that the ETH amount is not known.
|
||||
test("after a V4 exact-out step of ETH, also shows that the ETH amount is not known", () => {
|
||||
const v4EthExactOut = encodeV4Swap(
|
||||
new Uint8Array([
|
||||
V4_SWAP_EXACT_OUT_SINGLE_ID,
|
||||
V4_SETTLE_ID,
|
||||
V4_TAKE_ALL_ID,
|
||||
]),
|
||||
[
|
||||
encodeV4ExactOutSingle(
|
||||
ZERO_ADDR,
|
||||
USDC_ADDR,
|
||||
590000000n,
|
||||
ONE_WETH,
|
||||
),
|
||||
encodeV4Settle(ZERO_ADDR),
|
||||
encodeV4TakeAll(USDC_ADDR, 0n),
|
||||
],
|
||||
);
|
||||
const result = decodeSteps(
|
||||
[0x10, 0x02],
|
||||
[v4EthExactOut, transfer(ZERO_ADDR, OTHER_ADDR, ONE_WETH)],
|
||||
);
|
||||
expect(detail(result, "Token In").value).toBe("ETH (native)");
|
||||
expect(linesBetweenTokens(result)).toEqual([
|
||||
["Moved from the sender", UNDECODED],
|
||||
["Token moved", "ETH"],
|
||||
["Moved to", "The swap step that spends it"],
|
||||
["Moved from the sender", MOVED_AMOUNT],
|
||||
["Token moved", ZERO_ADDR],
|
||||
["Moved to", OTHER_ADDR],
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
// https://git.eeqj.de/sneak/AutistMask/issues/682 — V4 pays a SETTLE
|
||||
// of ETH from the router's own ETH, whichever payer the SETTLE names,
|
||||
// and the router holds the transaction's value. On next at 36b2002 a
|
||||
|
||||
Reference in New Issue
Block a user