1 Commits
Author SHA1 Message Date
sneak eb41b90ee0 fix: an open popup follows a network switch the background saves (closes #650)
check / check (push) Canceled after 0s
e2e / e2e-chrome (push) Canceled after 0s
e2e / e2e-firefox (push) Canceled after 0s
When the background saves a network switch after the user approves a
site's request, or another popup page saves one, an open popup takes the
new network from the stored record inside a save, holding the lock, and
checks the record once at start-up for a switch saved before it
listened. It then leaves the page as a switch in Settings does: the cut
and the redraw are two Settings functions both paths call, and both
reset the popup's caches through one function. A switch in Settings is
now made inside its own save too, so one picked while a follow waits for
the lock is made after it. The popup never switches the network itself.

Model: opus-5-5
2026-10-20 23:57:42 +00:00
3 changed files with 94 additions and 292 deletions
+47 -57
View File
@@ -2440,11 +2440,10 @@ view would leave a wallet one click from deletion.
- Decoded action (if calldata is recognized): action name, token details, - Decoded action (if calldata is recognized): action name, token details,
amounts, what each swap step spends that the `Amount` line does not give, amounts, what each swap step spends that the `Amount` line does not give,
what each `PERMIT2_TRANSFER_FROM` moves and where to, what each what each `PERMIT2_TRANSFER_FROM` moves and where to, what each
`PERMIT2_PERMIT` whose amount is not the `Amount` figure, and each `PERMIT2_PERMIT` whose amount is not the `Amount` figure lets its spender
`PERMIT2_PERMIT_BATCH`, lets its spender take, steps, deadline, and unless take, steps, deadline, and unless a swap pays only the From address, a
a swap pays only the From address, a `Paid to` line for each other address `Paid to` line for each other address it pays, or saying that a token
it pays, or saying that a token stays in the router or who it pays is stays in the router or who it pays is unknown (see Transaction Decoding)
unknown (see Transaction Decoding)
- From: color dot + full address + etherscan link - From: color dot + full address + etherscan link
- Contract: color dot + full address + etherscan link, token symbol label if - Contract: color dot + full address + etherscan link, token symbol label if
known; for a contract creation, the same sentence as on WaitTx known; for a contract creation, the same sentence as on WaitTx
@@ -3237,39 +3236,38 @@ Currently supported:
`Unlimited` included, stands as the `Amount` figure in place of the total, `Unlimited` included, stands as the `Amount` figure in place of the total,
which it does only in the plain shape of Uniswap's own routes: it comes before which it does only in the plain shape of Uniswap's own routes: it comes before
the step that sets the input side, it is the only `PERMIT2_PERMIT` in the the step that sets the input side, it is the only `PERMIT2_PERMIT` in the
transaction and there is no `PERMIT2_PERMIT_BATCH`, its spender is the router, transaction, its spender is the router, the router may not let it fail, no
the router may not let it fail, no step before it takes its token or a token step before it takes its token or a token the decoder does not know, and the
the decoder does not know, and the decoder reads every step after it in full. decoder reads every step after it in full. Permit2 then lets the router take
Permit2 then lets the router take no more of the token than that amount, so no more of the token than that amount, so the permit covers every take of it.
the permit covers every take of it. A later permit or batch may raise what A step the decoder does not read in full may raise what Permit2 lets the
Permit2 lets the router take, and so may a step the decoder does not read in router take, as a `PERMIT2_PERMIT_BATCH` can: a command the decoder does not
full: a command the decoder does not read (listed at the end of this section), read (listed at the end of this section), a step whose input it cannot read,
a step whose input it cannot read, in whole or in part, and a step it fails in whole or in part, and a step it fails on. Every other `PERMIT2_PERMIT`,
on. Every other `PERMIT2_PERMIT`, before the step that sets the input side or before the step that sets the input side or after it, leaves `Token In` and
after it, leaves `Token In` and the `Amount` line as they read with no permit: the `Amount` line as they read with no permit: a permit of 5,000 DAI to
a permit of 5,000 DAI to another address and then a V3 exact-in of 600 USDT another address and then a V3 exact-in of 600 USDT read `Swap USDT → USDC`
read `Swap USDT → USDC` with `Amount 600.0000 USDT`, and so do that exact-in with `Amount 600.0000 USDT`, and so do that exact-in and then a permit of
and then a permit of 5,000 DAI to the router. When a permit's amount stands 5,000 DAI to the router. When a permit's amount stands and the step that sets
and the step that sets the input side spends a token other than the permit's, the input side spends a token other than the permit's, or one the decoder does
or one the decoder does not know, that step's amount gets lines of its own, not know, that step's amount gets lines of its own, unless it states no amount
unless it states no amount and the user pays it none of its input token. Each and the user pays it none of its input token. Each take the total does not
take the total does not count gives three lines after the `Amount` line (after count gives three lines after the `Amount` line (after `Token In` when there
`Token In` when there is none): one of a token other than `Token In`; one of is none): one of a token other than `Token In`; one of `Token In` after an
`Token In` after an amount that is not a quantity (a V4 open delta, amount that is not a quantity (a V4 open delta, `ALREADY_PAID`, which keep
`ALREADY_PAID`, which keep their wording on the `Amount` line, or no stated their wording on the `Amount` line, or no stated amount, as of a V4 exact-out
amount, as of a V4 exact-out step); and one whose amount the decoder does not step); and one whose amount the decoder does not know. When the step that sets
know. When the step that sets the input side states no amount, the user pays the input side states no amount, the user pays it some of its input token, and
it some of its input token, and there is no `Amount` line, that step gets them there is no `Amount` line, that step gets them too whenever another take's
too whenever another take's lines give a figure, or a `PERMIT2_TRANSFER_FROM` lines give a figure, or a `PERMIT2_TRANSFER_FROM` of its token gives one, on
of its token gives one, on the lines below or, into the router, on its the lines below or, into the router, on its `Paid to` line, so that figure
`Paid to` line, so that figure cannot read as all that leaves. When it states cannot read as all that leaves. When it states no amount and the user pays it
no amount and the user pays it none of its input token, as when the router none of its input token, as when the router pays it, that step takes none of
pays it, that step takes none of that token from the user's wallet and never that token from the user's wallet and never gets them, with a permit or
gets them, with a permit or without. They come in step order, the step that without. They come in step order, the step that sets the input side first when
sets the input side first when it has them: `Moved from the sender`, its it has them: `Moved from the sender`, its amount, worded as the `Amount` line
amount, worded as the `Amount` line words one, or words one, or `Unknown (could not be decoded)` when the decoder does not know
`Unknown (could not be decoded)` when the decoder does not know it; it; `Token moved`, giving the token's address as the `Token In` line does; and
`Token moved`, giving the token's address as the `Token In` line does; and
`Moved to`, which reads `The swap step that spends it`. A token the decoder `Moved to`, which reads `The swap step that spends it`. A token the decoder
does not know gives the `Moved from the sender` line alone. A step may also does not know gives the `Moved from the sender` line alone. A step may also
take from the user's wallet what the decoder did not read: a V4 step whose take from the user's wallet what the decoder did not read: a V4 step whose
@@ -3298,25 +3296,17 @@ Currently supported:
giving the token's address as the `Token In` line does; and 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 `Allowance spender`, the address it lets take the token. Only a permit whose
amount stands gives none: the `Amount` line gives its amount, `Token In` its amount stands gives none: the `Amount` line gives its amount, `Token In` its
token, and its spender is the router. Each allowance a `PERMIT2_PERMIT_BATCH` token, and its spender is the router. These lines change neither the `Paid to`
grants, one for each token it names, all for its one spender, gives the same lines nor the minimum's label. A step goes unread when its recipient does not
three lines, in step order with those of each `PERMIT2_PERMIT`. A decode as an address, when it is a V3 swap step whose path is shorter than one
`PERMIT2_PERMIT` or `PERMIT2_PERMIT_BATCH` whose input the decoder cannot token address or has bytes after its last hop, or when it is a command the
read, such as one whose token or spender word has a nonzero top byte, may let decoder does not read: every command other than `PERMIT2_PERMIT`,
any spender take any amount of any token: it gives one `Permit2 allowance` `PERMIT2_TRANSFER_FROM`, `V2_SWAP_EXACT_IN`, `V2_SWAP_EXACT_OUT`,
line alone, reading `Unknown (could not be decoded)`, and leaves no `Amount` `V3_SWAP_EXACT_IN`, `V3_SWAP_EXACT_OUT`, `V4_SWAP`, `SWEEP`, `TRANSFER`,
line, whose figure might be too low. These lines change neither the `Paid to` `PAY_PORTION`, `PAY_PORTION_FULL_PRECISION`, `ACROSS_V4_DEPOSIT_V3`,
lines nor the minimum's label: a permit or batch pays nobody. A step goes `WRAP_ETH`, `UNWRAP_WETH` and `BALANCE_CHECK_ERC20`
unread when its recipient does not decode as an address, when it is a V3 swap (`PERMIT2_TRANSFER_FROM_BATCH`, `EXECUTE_SUB_PLAN` and the NFT commands among
step whose path is shorter than one token address or has bytes after its last them), or a type its release names no command for.
hop, or when it is a command the decoder does not read: every command other
than `PERMIT2_PERMIT`, `PERMIT2_PERMIT_BATCH`, `PERMIT2_TRANSFER_FROM`,
`V2_SWAP_EXACT_IN`, `V2_SWAP_EXACT_OUT`, `V3_SWAP_EXACT_IN`,
`V3_SWAP_EXACT_OUT`, `V4_SWAP`, `SWEEP`, `TRANSFER`, `PAY_PORTION`,
`PAY_PORTION_FULL_PRECISION`, `ACROSS_V4_DEPOSIT_V3`, `WRAP_ETH`,
`UNWRAP_WETH` and `BALANCE_CHECK_ERC20` (`PERMIT2_TRANSFER_FROM_BATCH`,
`EXECUTE_SUB_PLAN` and the NFT commands among them), or a type its release
names no command for.
### Non-Goals Forever ### Non-Goals Forever
+30 -89
View File
@@ -336,8 +336,7 @@ const UNNAMED_CURRENCY = "Unknown (not named in the calldata)";
// currencies, and those commands may name one, so saying the calldata did not // currencies, and those commands may name one, so saying the calldata did not
// name it would be false. The Paid to line uses it too, when a step may pay an // name it would be false. The Paid to line uses it too, when a step may pay an
// address this decoder did not read, and so do the lines for what a step // address this decoder did not read, and so do the lines for what a step
// takes from the sender, when this decoder does not know it, and for what a // takes from the sender, when this decoder does not know it.
// permit it could not read allows.
const UNDECODED_CURRENCY = "Unknown (could not be decoded)"; const UNDECODED_CURRENCY = "Unknown (could not be decoded)";
// The Paid to line for each token paid to the router, the swap's output or // The Paid to line for each token paid to the router, the swap's output or
@@ -529,32 +528,6 @@ function decodePermit2(input) {
} }
} }
// Decode PERMIT2_PERMIT_BATCH (command 0x03) input bytes into the allowances
// it grants, one for each token it names, all for its one spender, in the
// shape decodePermit2() returns. Each release's
// `contracts/base/Dispatcher.sol`, at the commit linked for it above, hands
// Permit2 the batch in this shape.
// ABI: (((address token, uint160 amount, uint48 expiration, uint48 nonce)[],
// address spender, uint256 sigDeadline), bytes signature)
function decodePermit2Batch(input) {
try {
const d = coder.decode(
[
"tuple(tuple(address,uint160,uint48,uint48)[],address,uint256)",
"bytes",
],
input,
);
return d[0][0].map((details) => ({
token: details[0],
amount: details[1],
spender: d[0][1],
}));
} catch {
return null;
}
}
// Decode BALANCE_CHECK_ERC20 (command 0x0e) input bytes. The router passes the // Decode BALANCE_CHECK_ERC20 (command 0x0e) input bytes. The router passes the
// check when `owner` holds at least `minBalance` of `token`, and reads `owner` // check when `owner` holds at least `minBalance` of `token`, and reads `owner`
// as written, without map(), on every release that has the command: // as written, without map(), on every release that has the command:
@@ -1241,12 +1214,10 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
// The last PERMIT2_PERMIT this decoder read before a step set the // The last PERMIT2_PERMIT this decoder read before a step set the
// input side, or null (see permitStands after the loop). // input side, or null (see permitStands after the loop).
let permit = null; let permit = null;
// Every allowance a PERMIT2_PERMIT or PERMIT2_PERMIT_BATCH grants, // Every PERMIT2_PERMIT this decoder read, before a step set the input
// before a step set the input side or after, in step order, as // side or after, in step order, as { token, amount, spender, step }.
// { token, amount, spender, step }. A permit or batch whose input // Each whose amount is not the Amount figure gets lines of its own
// this decoder cannot read is one with a null token, amount and // (see after the PERMIT2_TRANSFER_FROM lines).
// spender. Each but a permit whose amount is the Amount figure gets
// lines of its own (see after the PERMIT2_TRANSFER_FROM lines).
const permits = []; const permits = [];
// The index of each step this decoder did not read in full: a // The index of each step this decoder did not read in full: a
// command it does not read, a step whose input it cannot read, in // command it does not read, a step whose input it cannot read, in
@@ -1351,14 +1322,6 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
const spentUnread = () => { const spentUnread = () => {
userPaid.push({ token: null, amount: null }); userPaid.push({ token: null, amount: null });
}; };
// A PERMIT2_PERMIT or PERMIT2_PERMIT_BATCH at `step` whose input this
// decoder cannot read may let any spender take any amount of any
// token: one line says so, and there is no Amount line, whose figure
// may then be too low.
const permitUnread = (step) => {
permits.push({ token: null, amount: null, spender: null, step });
stepsUnread.push(step);
};
// Who the steps pay, in whichever step and of whatever token. // Who the steps pay, in whichever step and of whatever token.
// MSG_SENDER is the sender. The router itself, as ADDRESS_THIS or by // MSG_SENDER is the sender. The router itself, as ADDRESS_THIS or by
@@ -1514,7 +1477,7 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
permits.push({ ...p, step: i }); permits.push({ ...p, step: i });
keepsRouterWeth = true; keepsRouterWeth = true;
} else { } else {
permitUnread(i); stepsUnread.push(i);
} }
// Only a permit whose amount stands as the Amount figure // Only a permit whose amount stands as the Amount figure
// names Token In (see permitStands). Every other permit, // names Token In (see permitStands). Every other permit,
@@ -1539,15 +1502,6 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
beforeEveryTake: !tookBefore, beforeEveryTake: !tookBefore,
}; };
} }
} else if (command === "PERMIT2_PERMIT_BATCH") {
// Each allowance it grants gets the lines of a permit
// whose amount does not stand. It pays nobody.
const batch = decodePermit2Batch(inputs[i]);
if (batch) {
permits.push(...batch.map((p) => ({ ...p, step: i })));
} else {
permitUnread(i);
}
} else if (command === "PERMIT2_TRANSFER_FROM") { } else if (command === "PERMIT2_TRANSFER_FROM") {
const p = decodePermit2TransferFrom(inputs[i]); const p = decodePermit2TransferFrom(inputs[i]);
if (!p) { if (!p) {
@@ -1956,8 +1910,8 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
} }
} else { } else {
// Any other step may pay an address this decoder does not // Any other step may pay an address this decoder does not
// read: the NFT commands among them, and a type the // read: PERMIT2_PERMIT_BATCH and the NFT commands among
// release names no command for. // them, and a type the release names no command for.
payeeUnread = true; payeeUnread = true;
stepsUnread.push(i); stepsUnread.push(i);
} }
@@ -2007,19 +1961,17 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
// Amount figure in place of the total, which it does only in the // Amount figure in place of the total, which it does only in the
// plain shape of Uniswap's own routes: it comes before the step that // plain shape of Uniswap's own routes: it comes before the step that
// set the input side, it is the only PERMIT2_PERMIT in the // set the input side, it is the only PERMIT2_PERMIT in the
// transaction and there is no PERMIT2_PERMIT_BATCH, its spender is // transaction, its spender is the router, the router may not let it
// the router, the router may not let it fail, no step before it took // fail, no step before it took its token or one not known, and this
// its token or one not known, and this decoder read every step after // decoder read every step after it in full (see stepsUnread). Permit2
// it in full (see stepsUnread). Permit2 then lets the router take no // then lets the router take no more of the token than that amount, so
// more of the token than that amount, so the permit covers every take // the permit covers every take of it. A later step this decoder did
// of it. A later permit or batch, or a later step this decoder did // not read may raise what Permit2 lets the router take, as a
// not read, may raise what Permit2 lets the router take. Every other // PERMIT2_PERMIT_BATCH can. Every other PERMIT2_PERMIT leaves Token In
// PERMIT2_PERMIT leaves Token In and the Amount line as they read with // and the Amount line as they read with no permit, and gets lines of
// no permit, and gets lines of its own below. A step that may take // its own below. A step that may take from the sender what this decoder
// from the sender what this decoder did not read leaves no figure, // did not read leaves no figure, permit or not (see spentUnread()),
// permit or not (see spentUnread()), and so does a permit or batch // and an EXECUTE_SUB_PLAN step leaves both sides unknown (below).
// whose input it cannot read (see permitUnread()). An
// EXECUTE_SUB_PLAN step leaves both sides unknown (below).
const stepCommands = Array.from( const stepCommands = Array.from(
commandsBytes, commandsBytes,
(b) => commands[b & commandTypeMask], (b) => commands[b & commandTypeMask],
@@ -2027,7 +1979,6 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
const permitStands = const permitStands =
permit !== null && permit !== null &&
stepCommands.filter((c) => c === "PERMIT2_PERMIT").length === 1 && stepCommands.filter((c) => c === "PERMIT2_PERMIT").length === 1 &&
!stepCommands.includes("PERMIT2_PERMIT_BATCH") &&
sameAddress(permit.spender, toAddress) && sameAddress(permit.spender, toAddress) &&
!permit.allowedToFail && !permit.allowedToFail &&
permit.beforeEveryTake && permit.beforeEveryTake &&
@@ -2196,13 +2147,11 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
}; };
// A null token in `spentFromSender`, a step this decoder did not read // A null token in `spentFromSender`, a step this decoder did not read
// or a token it does not know, and one in `permits`, a permit or // or a token it does not know, leaves no Amount figure, which may
// batch it could not read, leave no Amount figure, which may then be // then be too low.
// too low.
if ( if (
present(inputAmount) && present(inputAmount) &&
spentFromSender.every((s) => present(s.token)) && spentFromSender.every((s) => present(s.token))
permits.every((p) => present(p.token))
) { ) {
const amount = spentText(inputAmount, inInfo); const amount = spentText(inputAmount, inInfo);
details.push({ details.push({
@@ -2252,24 +2201,16 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
); );
} }
// What each allowance of a PERMIT2_PERMIT or PERMIT2_PERMIT_BATCH lets // What each PERMIT2_PERMIT lets its spender take: its amount, its
// its spender take: its amount, its token and the spender, an address // token and the spender, an address on a line of its own. Permit2's
// on a line of its own. Permit2's maximum is unbounded. Only a permit // maximum is unbounded. Only a permit whose amount stands as the
// whose amount stands as the Amount figure (see permitStands) gets // Amount figure (see permitStands) gets none: that line already gives
// none: that line already gives its amount and Token In its token, // its amount and Token In its token, and its spender is the router.
// and its spender is the router. Every other allowance, one after the // Every other permit, one after the step that set the input side
// step that set the input side included, gets these lines, so nothing // included, gets these lines, so nothing it lets anyone take goes
// it lets anyone take goes unstated. A permit or batch this decoder // unstated.
// could not read gets one line saying so.
for (const p of permits) { for (const p of permits) {
if (permitStands && p.step === permit.step) continue; if (permitStands && p.step === permit.step) continue;
if (!present(p.token)) {
details.push({
label: "Permit2 allowance",
value: UNDECODED_CURRENCY,
});
continue;
}
details.push({ details.push({
label: "Permit2 allowance", label: "Permit2 allowance",
value: value:
+17 -146
View File
@@ -59,25 +59,6 @@ function encodePermit2(token, amount, spender) {
); );
} }
// Helper: encode a PERMIT2_PERMIT_BATCH input (command 0x03), one allowance
// for each [token, amount], all for `spender`
function encodePermit2Batch(allowances, spender) {
return coder.encode(
[
"tuple(tuple(address,uint160,uint48,uint48)[],address,uint256)",
"bytes",
],
[
[
allowances.map(([token, amount]) => [token, amount, 0, 0]),
spender,
9999999999,
],
"0x1234",
],
);
}
// Helper: encode a BALANCE_CHECK_ERC20 input (command 0x0e) // Helper: encode a BALANCE_CHECK_ERC20 input (command 0x0e)
function encodeBalanceCheck(owner, token, minBalance) { function encodeBalanceCheck(owner, token, minBalance) {
return coder.encode( return coder.encode(
@@ -2119,17 +2100,28 @@ describe("uniswap decoder", () => {
ROUTER_ADDR, ROUTER_ADDR,
); );
// A PERMIT2_PERMIT_BATCH may raise what the router may take of the // A PERMIT2_PERMIT_BATCH, which this decoder does not read, may raise
// permit's token again, and its allowance gets lines of its own. // what the router may take of the permit's token again.
test("a split after a PERMIT2_PERMIT and a PERMIT2_PERMIT_BATCH shows the total the steps take", () => { test("a split after a PERMIT2_PERMIT and a PERMIT2_PERMIT_BATCH shows the total the steps take", () => {
const permitBatch = coder.encode(
[
"tuple(tuple(address,uint160,uint48,uint48)[],address,uint256)",
"bytes",
],
[
[
[[USDT_ADDR, 10000000000n, 0, 0]], // 10,000 USDT
ROUTER_ADDR,
9999999999,
],
"0x1234",
],
);
const result = decodeSteps( const result = decodeSteps(
[0x0a, 0x03, 0x00, 0x08], [0x0a, 0x03, 0x00, 0x08],
[ [
encodePermit2(USDT_ADDR, USDT_600, ROUTER_ADDR), encodePermit2(USDT_ADDR, USDT_600, ROUTER_ADDR),
encodePermit2Batch( permitBatch,
[[USDT_ADDR, 10000000000n]], // 10,000 USDT
ROUTER_ADDR,
),
V3_600, V3_600,
encodeV2SwapExactIn(USER_ADDR, 9400000000n, 1n, [ encodeV2SwapExactIn(USER_ADDR, 9400000000n, 1n, [
USDT_ADDR, USDT_ADDR,
@@ -2140,12 +2132,6 @@ describe("uniswap decoder", () => {
expect(linesBetweenTokens(result)).toEqual([ expect(linesBetweenTokens(result)).toEqual([
["Amount", "10000.0000 USDT"], ["Amount", "10000.0000 USDT"],
...USDT_600_LINES, ...USDT_600_LINES,
...permitLines(
"10000.0000 USDT",
USDT_ADDR,
"USDT",
ROUTER_ADDR,
),
]); ]);
}); });
@@ -2432,121 +2418,6 @@ describe("uniswap decoder", () => {
}); });
}); });
// https://git.eeqj.de/sneak/AutistMask/issues/652 — a
// PERMIT2_PERMIT_BATCH, and a PERMIT2_PERMIT this decoder cannot read,
// showed nothing of what they allow. Each allowance a batch grants now
// gets the lines of a permit whose amount does not stand, and a permit
// or batch that cannot be read gets one line saying so and leaves no
// Amount figure.
describe("a PERMIT2_PERMIT_BATCH, or a permit that cannot be read", () => {
const USDT_5000 = 5000000000n;
const DAI_5000 = 5000000000000000000000n;
const MAX_UINT160 = 2n ** 160n - 1n;
// A batch names one spender for all its allowances.
test.each([
{ kind: "the router", spender: ROUTER_ADDR },
{ kind: "another spender", spender: OTHER_ADDR },
])(
"a PERMIT2_PERMIT_BATCH of 5,000 USDT and unlimited DAI to $kind, then a V3 exact-in of 600 USDT, shows each allowance",
({ spender }) => {
const result = decodeSteps(
[0x03, 0x00],
[
encodePermit2Batch(
[
[USDT_ADDR, USDT_5000],
[DAI_ADDR, MAX_UINT160],
],
spender,
),
V3_600,
],
);
expect(result.name).toBe("Swap USDT → USDC");
expect(linesBetweenTokens(result)).toEqual([
["Amount", "600.0000 USDT"],
...permitLines(
"5000.0000 USDT",
USDT_ADDR,
"USDT",
spender,
),
...permitLines("Unlimited", DAI_ADDR, "DAI", spender),
]);
// A batch pays nobody, so the swap's minimum is what the
// sender receives.
expect(detail(result, "Paid to")).toBeUndefined();
expect(detail(result, "Min. received").value).toBe(
"590.0000 USDC",
);
},
);
// A batch keeps a PERMIT2_PERMIT's amount from standing as the
// Amount figure, before the permit as after it.
test("a PERMIT2_PERMIT_BATCH of 5,000 DAI, then a PERMIT2_PERMIT of 5,000 USDT to the router and a V3 exact-in of 600 USDT, shows the swap's 600 USDT and both permits' lines", () => {
const result = decodeSteps(
[0x03, 0x0a, 0x00],
[
encodePermit2Batch([[DAI_ADDR, DAI_5000]], ROUTER_ADDR),
encodePermit2(USDT_ADDR, USDT_5000, ROUTER_ADDR),
V3_600,
],
);
expect(linesBetweenTokens(result)).toEqual([
["Amount", "600.0000 USDT"],
...permitLines(
"5000.0000 DAI",
DAI_ADDR,
"DAI",
ROUTER_ADDR,
),
...permitLines(
"5000.0000 USDT",
USDT_ADDR,
"USDT",
ROUTER_ADDR,
),
]);
});
// The permit's token word, and the batch's spender word, have a
// nonzero top byte.
test.each([
{
kind: "a PERMIT2_PERMIT",
command: 0x0a,
input: dirtyWord(
encodePermit2(USDT_ADDR, USDT_5000, ROUTER_ADDR),
0,
),
},
{
kind: "a PERMIT2_PERMIT_BATCH",
command: 0x03,
input: dirtyWord(
encodePermit2Batch(
[[USDT_ADDR, USDT_5000]],
ROUTER_ADDR,
),
3,
),
},
])(
"$kind that cannot be read, then a V3 exact-in of 600 USDT, says its allowances could not be decoded and shows no Amount figure",
({ command, input }) => {
const result = decodeSteps(
[command, 0x00],
[input, V3_600],
);
expect(linesBetweenTokens(result)).toEqual([
["Permit2 allowance", UNDECODED],
]);
},
);
});
// A V3_SWAP_EXACT_OUT whose path is shorter than one token cannot be // A V3_SWAP_EXACT_OUT whose path is shorter than one token cannot be
// read. It may pay any address and take from the sender, so it // read. It may pay any address and take from the sender, so it
// leaves no Amount figure. // leaves no Amount figure.