1 Commits
Author SHA1 Message Date
sneak 3add1a158b fix: Settings refuses an endpoint URL whose network changed before it was stored (closes #663)
check / check (push) Canceled after 0s
e2e / e2e-chrome (push) Canceled after 0s
e2e / e2e-firefox (push) Canceled after 0s
The RPC and Blockscout "Save" buttons in Settings take the network that
Settings was last drawn for. A follow of a switch saved elsewhere sets
`state` first. Settings is drawn again only once the follow's write
returns.

The URL is set in `state` inside the save, holding the lock. The save is
refused if the network that Settings showed is no longer active, on the
page or in the stored record. The flash message then says that the
network changed, and "Saved." is shown only for a stored URL.

Before, a popup that followed a switch saved elsewhere could replace the
URL before the write and still show "Saved.". It could also store a URL
for a network that Settings did not show.

Model: opus-5-5
2026-10-27 05:36:11 +00:00
4 changed files with 111 additions and 315 deletions
+77 -83
View File
@@ -3479,89 +3479,83 @@ Currently supported:
of any amount but `OPEN_DELTA` pays that amount, and a `SETTLE` of of any amount but `OPEN_DELTA` pays that amount, and a `SETTLE` of
`OPEN_DELTA` or a `SETTLE_ALL` pays what the step owes, as above. So a V4 step `OPEN_DELTA` or a `SETTLE_ALL` pays what the step owes, as above. So a V4 step
that swaps 400 USDT, with a `SETTLE` of 5,000 USDT paid by the user and the that swaps 400 USDT, with a `SETTLE` of 5,000 USDT paid by the user and the
rest taken to another address, reads `5000.0000 USDT`. A later step that pays rest taken to another address, reads `5000.0000 USDT`. A later step paid from
an ERC-20 token from the router takes none of that token from the user's the router spends what the router holds and takes nothing, and so does a V2
wallet. A V2 exact-in whose `amountIn` is `ALREADY_PAID` takes nothing either. exact-in whose `amountIn` is `ALREADY_PAID`. A later take of `Token In` is
A V4 `SETTLE` of ETH paid from the router still takes from the user's wallet, added to the total while both are quantities: a V3 exact-in of 600 USDT and a
since the router holds the transaction's value. When the decoder does not know V2 exact-in of 400 USDT, both paid by the user, read `1000.0000 USDT`. When
the amount of such a `SETTLE`, as for `CONTRACT_BALANCE`, that `SETTLE` counts the decoder does not know a take's amount, the total is not known and there is
as a later step's take. When an earlier step left ETH in the router, that ETH no `Amount` line. A `PERMIT2_PERMIT` names `Token In` only when its amount,
may pay part of a `SETTLE` of ETH paid from the router. That `SETTLE` then `Unlimited` included, stands as the `Amount` figure in place of the total,
gets lines of its own, with `Up to` before its amount. A later take of which it does only in the plain shape of Uniswap's own routes: it comes before
`Token In` that only the user's wallet pays is added to the total while both the step that sets the input side, it is the only `PERMIT2_PERMIT` in the
are quantities: a V3 exact-in of 600 USDT and a V2 exact-in of 400 USDT, both transaction and there is no `PERMIT2_PERMIT_BATCH`, its spender is the router,
paid by the user, read `1000.0000 USDT`. When the decoder does not know a the router may not let it fail, no step before it takes its token, a
take's amount, the total is not known and there is no `Amount` line. A `PERMIT2_TRANSFER_FROM` of it included, or a token the decoder does not know,
`PERMIT2_PERMIT` names `Token In` only when its amount, `Unlimited` included, and the decoder reads every step after it in full. Permit2 then lets the
stands as the `Amount` figure in place of the total, which it does only in the router take no more of the token than that amount, so the permit covers every
plain shape of Uniswap's own routes: it comes before the step that sets the take of it. A step before the permit takes under whatever allowance the router
input side, it is the only `PERMIT2_PERMIT` in the transaction and there is no already had, so it may take more than the permit's amount, which would then
`PERMIT2_PERMIT_BATCH`, its spender is the router, the router may not let it read lower than what leaves the user's wallet: a `PERMIT2_TRANSFER_FROM` of
fail, no step before it takes its token, a `PERMIT2_TRANSFER_FROM` of it 2,000 USDT into the router, then a permit of 600 USDT to the router and a V3
included, or a token the decoder does not know, and the decoder reads every exact-in of 2,000 USDT paid from the router, read `Amount 2000.0000 USDT`. A
step after it in full. Permit2 then lets the router take no more of the token later permit or batch may raise what Permit2 lets the router take, and so may
than that amount, so the permit covers every take of it. A step before the a step the decoder does not read in full: a command the decoder does not read
permit takes under whatever allowance the router already had, so it may take (listed at the end of this section), a step whose input it cannot read, in
more than the permit's amount, which would then read lower than what leaves whole or in part, and a step it fails on. Every other `PERMIT2_PERMIT`, before
the user's wallet: a `PERMIT2_TRANSFER_FROM` of 2,000 USDT into the router, the step that sets the input side or after it, leaves `Token In` and the
then a permit of 600 USDT to the router and a V3 exact-in of 2,000 USDT paid `Amount` line as they read with no permit, unless it lets a spender other than
from the router, read `Amount 2000.0000 USDT`. A later permit or batch may the router take `Token In` (see below): a permit of 5,000 DAI to another
raise what Permit2 lets the router take, and so may a step the decoder does address and then a V3 exact-in of 600 USDT read `Swap USDT → USDC` with
not read in full: a command the decoder does not read (listed at the end of `Amount 600.0000 USDT`, and so do that exact-in and then a permit of 5,000 DAI
this section), a step whose input it cannot read, in whole or in part, and a to the router. A permit on the zero address never stands as the `Amount`
step it fails on. Every other `PERMIT2_PERMIT`, before the step that sets the figure, because Permit2 has no allowance of ETH. When a permit's amount stands
input side or after it, leaves `Token In` and the `Amount` line as they read and the step that sets the input side spends a token other than the permit's,
with no permit, unless it lets a spender other than the router take `Token In` or one the decoder does not know, that step's amount gets lines of its own,
(see below): a permit of 5,000 DAI to another address and then a V3 exact-in unless it states no amount and the user pays it none of its input token. Each
of 600 USDT read `Swap USDT → USDC` with `Amount 600.0000 USDT`, and so do take the total does not count gives three lines after the `Amount` line (after
that exact-in and then a permit of 5,000 DAI to the router. A permit on the `Token In` when there is none): one of a token other than `Token In`; one of
zero address never stands as the `Amount` figure, because Permit2 has no `Token In` after an amount that is not a quantity (a V4 open delta,
allowance of ETH. When a permit's amount stands and the step that sets the `ALREADY_PAID`, which keep their wording on the `Amount` line, or no stated
input side spends a token other than the permit's, or one the decoder does not amount, as of a V4 exact-out step); and one whose amount the decoder does not
know, that step's amount gets lines of its own, unless it states no amount and know. When there is no `Amount` line, each take the total counts gets them
the user pays it none of its input token. Each take the total does not count too, so that every amount the decoder reads is shown: the amount the step that
gives three lines after the `Amount` line (after `Token In` when there is sets the input side states, when it is a quantity and the user pays that step
none): one of a token other than `Token In`; one of `Token In` after an amount (a V2 or V3 step whose `payerIsUser` is true, a V4 step the user pays some of
that is not a quantity (a V4 open delta, `ALREADY_PAID`, which keep their its input token, or a `WRAP_ETH`, whose ETH comes from the transaction's
wording on the `Amount` line, or no stated amount, as of a V4 exact-out step); value), and each later take of `Token In` added to it. A step that sets the
an ETH take whose line reads `Up to` (see above); and one whose amount the input side and is paid from the router takes nothing from the user's wallet
decoder does not know. When there is no `Amount` line, each take the total and gets none. A `PERMIT2_PERMIT` the decoder cannot read, then V3 exact-ins
counts gets them too, so that every amount the decoder reads is shown: the of 600 USDT and 400 DAI paid by the user, give lines for both amounts. When
amount the step that sets the input side states, when it is a quantity and the the step that sets the input side states no amount, the user pays it some of
user pays that step (a V2 or V3 step whose `payerIsUser` is true, a V4 step its input token, and there is no `Amount` line, that step gets them too
the user pays some of its input token, or a `WRAP_ETH`, whose ETH comes from whenever another take's lines give a figure, or a `PERMIT2_TRANSFER_FROM` of
the transaction's value), and each later take of `Token In` added to it. A its token gives one, on the lines below or, into the router, on its `Paid to`
step that sets the input side gets none when the user pays it none of its line, so that figure cannot read as all that leaves. When it states no amount
input token. A `PERMIT2_PERMIT` the decoder cannot read, then V3 exact-ins of and the user pays it none of its input token, as when the router pays it, that
600 USDT and 400 DAI paid by the user, give lines for both amounts. When the step takes none of that token from the user's wallet and never gets them, with
step that sets the input side states no amount, the user pays it some of its a permit or without. They come in step order, except that the step that sets
input token, and there is no `Amount` line, that step gets them too whenever the input side comes first when it has them, followed, when there is no
another take's lines give a figure, or a `PERMIT2_TRANSFER_FROM` of its token `Amount` line, by the other takes the total counts: `Moved from the sender`,
gives one, on the lines below or, into the router, on its `Paid to` line, so its amount, worded as the `Amount` line words one, or
that figure cannot read as all that leaves. When it states no amount and the `Unknown (could not be decoded)` when the decoder does not know it;
user pays it none of its input token, that step never gets them, with a permit `Token moved`, giving the token's address as the `Token In` line does; and
or without. They come in step order, except that the step that sets the input `Moved to`, which reads `The swap step that spends it`. A token the decoder
side comes first when it has them, followed, when there is no `Amount` line, does not know gives the `Moved from the sender` line alone. A step may also
by the other takes the total counts: `Moved from the sender`, its amount, take from the user's wallet what the decoder did not read: a V4 step whose
worded as the `Amount` line words one, or `Unknown (could not be decoded)` input or one of whose `SETTLE`s or `SETTLE_ALL`s it cannot read, which the
when the decoder does not know it; `Token moved`, giving the token's address router still runs; a V2 or V3 exact-in or exact-out step or a
as the `Token In` line does; and `Moved to`, which reads `PERMIT2_TRANSFER_FROM` whose input it cannot read, such as a V3 step whose
`The swap step that spends it`. A token the decoder does not know gives the path is shorter than one token address or has bytes after its last hop; every
`Moved from the sender` line alone. A step may also take from the user's `PERMIT2_TRANSFER_FROM_BATCH`; every `EXECUTE_SUB_PLAN`, wherever it stands;
wallet what the decoder did not read: a V4 step whose input or one of whose and every step it fails on. Each such step gives one `Moved from the sender`
`SETTLE`s or `SETTLE_ALL`s it cannot read, which the router still runs; a V2 line among those, reading `Unknown (could not be decoded)`. That line, or any
or V3 exact-in or exact-out step or a `PERMIT2_TRANSFER_FROM` whose input it line for a token the decoder does not know, leaves no `Amount` line, whose
cannot read, such as a V3 step whose path is shorter than one token address or figure might be too low, and such a step before a `PERMIT2_PERMIT` keeps the
has bytes after its last hop; every `PERMIT2_TRANSFER_FROM_BATCH`; every permit's amount from standing. Then each `PERMIT2_TRANSFER_FROM` but one into
`EXECUTE_SUB_PLAN`, wherever it stands; and every step it fails on. Each such the router, in step order, gives three lines: `Moved from the sender`, the
step gives one `Moved from the sender` line among those, reading amount it moves, for example `1000000.0000 USDT`; `Token moved`, giving the
`Unknown (could not be decoded)`. That line, or any line for a token the token's address as the `Token In` line does; and `Moved to`, which reads
decoder does not know, leaves no `Amount` line, whose figure might be too low,
and such a step before a `PERMIT2_PERMIT` keeps the permit's amount from
standing. Then each `PERMIT2_TRANSFER_FROM` but one into the router, in step
order, gives three lines: `Moved from the sender`, the amount it moves, for
example `1000000.0000 USDT`; `Token moved`, giving the token's address as the
`Token In` line does; and `Moved to`, which reads
`The V2 pair the next swap spends` for a hop and `The sender` for `address(1)` `The V2 pair the next swap spends` for a hop and `The sender` for `address(1)`
on every router but `UniversalRouterV1`, and otherwise gives the address. A 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 move into the router needs none of them, because its `Paid to` line and
+25 -80
View File
@@ -1240,10 +1240,9 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
let inputEstablished = false; let inputEstablished = false;
// Whether the user pays the step that set the input side: a V2 or V3 // Whether the user pays the step that set the input side: a V2 or V3
// swap step with `payerIsUser` true, a V4 step the user pays some of // swap step with `payerIsUser` true, a V4 step the user pays some of
// its input currency (see paidByUser in the loop), or a WRAP_ETH, // its input currency (see decodeV4Swap()), or a WRAP_ETH, whose ETH
// whose ETH comes from the transaction's value. A step that pays an // comes from the transaction's value. A step paid from the router
// ERC-20 token from the router takes none of that token from the // takes nothing from the sender.
// sender.
let inputPaidByUser = false; let inputPaidByUser = false;
// 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).
@@ -1339,9 +1338,7 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
// What the steps take from the sender beyond the amount of the step // What the steps take from the sender beyond the amount of the step
// that sets the input side, as { token, amount }, in step order. // that sets the input side, as { token, amount }, in step order.
// After the loop each joins the Amount line or gets lines of its own // After the loop each joins the Amount line or gets lines of its own
// (see permitStands). A take with `atMost` set may take less than // (see permitStands).
// `amount`: it never joins the Amount line, and its line reads
// "Up to" and the amount.
const userPaid = []; const userPaid = [];
// A swap step the user pays for (`payerIsUser` true) takes `amount` // A swap step the user pays for (`payerIsUser` true) takes `amount`
// of `token` from the sender. A V2 exact-in of ALREADY_PAID takes // of `token` from the sender. A V2 exact-in of ALREADY_PAID takes
@@ -1818,41 +1815,6 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
} else if (command === "V4_SWAP") { } else if (command === "V4_SWAP") {
const v4 = decodeV4Swap(inputs[i]); const v4 = decodeV4Swap(inputs[i]);
const inputSetBefore = inputEstablished; const inputSetBefore = inputEstablished;
// V4Router pays a SETTLE of ETH from the router's own
// ETH, whichever payer the SETTLE names. The router holds
// the transaction's value, and any ETH an earlier step
// left it (see heldByRouter). So the user also pays a
// SETTLE of ETH that names the router as payer. The user
// pays all of its amount when the router holds no other
// ETH, and up to its amount otherwise. CONTRACT_BALANCE
// pays all the router holds, an amount the calldata does
// not state.
const routerHeldEth = heldByRouter.has(
"0x0000000000000000000000000000000000000000",
);
const ethFromRouter = (v4?.settledFromRouter ?? [])
.filter(
(s) =>
s.currency ===
"0x0000000000000000000000000000000000000000",
)
.map((s) => ({
currency: s.currency,
amount:
s.amount === CONTRACT_BALANCE ? null : s.amount,
}));
// What the user pays the step: what decodeV4Swap() says,
// and the ETH of each SETTLE from the router of a known
// amount when the router holds no other ETH.
const paidByUser = v4
? v4.paidByUser.concat(
routerHeldEth
? []
: ethFromRouter.filter((s) =>
present(s.amount),
),
)
: [];
if (v4) { if (v4) {
// Each SETTLE paid from the router spends the amount // Each SETTLE paid from the router spends the amount
// decodeV4Swap() says it pays. // decodeV4Swap() says it pays.
@@ -1883,10 +1845,10 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
commandNames[i] += " (could not be decoded)"; commandNames[i] += " (could not be decoded)";
} }
// The step's amount, if it sets the input side: what the // The step's amount, if it sets the input side: what the
// user pays it of its input currency, or, when the user // user pays it of its input currency (see
// pays none of it, what its swap actions spend of it, // decodeV4Swap()), or, when the user pays none of it,
// whoever pays that. // what its swap actions spend of it, whoever pays that.
const paidIn = paidByUser.find( const paidIn = v4?.paidByUser.find(
(p) => p.currency === v4.tokenIn, (p) => p.currency === v4.tokenIn,
); );
const amountIn = paidIn ? paidIn.amount : v4?.amountIn; const amountIn = paidIn ? paidIn.amount : v4?.amountIn;
@@ -1940,24 +1902,10 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
// router still runs it. // router still runs it.
const setInputHere = inputEstablished && !inputSetBefore; const setInputHere = inputEstablished && !inputSetBefore;
if (setInputHere) inputPaidByUser = paidIn !== undefined; if (setInputHere) inputPaidByUser = paidIn !== undefined;
for (const p of paidByUser) { if (v4) {
if (setInputHere && p === paidIn) continue; for (const p of v4.paidByUser) {
addUserPaid(p.currency, p.amount); if (setInputHere && p === paidIn) continue;
} addUserPaid(p.currency, p.amount);
// A SETTLE of ETH from the router that `paidByUser` leaves
// out is a take of its own, even on the step that sets
// the input side. Either this decoder does not know its
// amount, or ETH an earlier step left in the router may
// pay part of that amount (see userPaid).
for (const p of ethFromRouter) {
if (!present(p.amount)) {
addUserPaid(p.currency, null);
} else if (routerHeldEth) {
userPaid.push({
token: p.currency,
amount: p.amount,
atMost: true,
});
} }
} }
if (!v4 || v4.settleUnread) { if (!v4 || v4.settleUnread) {
@@ -2065,10 +2013,10 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
// The total is what the steps take of Token In: the amount of the // The total is what the steps take of Token In: the amount of the
// step that set the input side, with each later take of that token // step that set the input side, with each later take of that token
// added to it while both are quantities. A take that cannot be added, // added to it while both are quantities. A take that cannot be added,
// being of another token, coming after an amount that is not a // being of another token or coming after an amount that is not a
// quantity (a V4 open delta, ALREADY_PAID, or none stated), or having // quantity (a V4 open delta, ALREADY_PAID, or none stated), gets
// `atMost` set, gets lines of its own. One whose amount is not known // lines of its own. One whose amount is not known also leaves the
// also leaves the total unknown, with no figure. // total unknown, with no figure.
// //
// A PERMIT2_PERMIT names Token In only when its amount stands as the // A PERMIT2_PERMIT names Token In only when its amount stands as the
// 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
@@ -2112,14 +2060,14 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
let total = null; let total = null;
// The takes from the sender that `total` adds up, each a quantity, as // The takes from the sender that `total` adds up, each a quantity, as
// { token, amount }. When there is no Amount line, each gets lines of // { token, amount }. When there is no Amount line, each gets lines of
// its own (see below). The amount of the step that set the input side // its own (see below). The amount of a step that set the input side
// counts in `total` whoever pays that step. That amount is among them // and is paid from the router counts in `total` but is not among them.
// only when the user pays the step (see inputPaidByUser).
const inTotal = []; const inTotal = [];
// Whether the step that set the input side counts as a take from the // Whether the step that set the input side counts as a take from the
// sender, which may get lines of its own. The amount it stated counts // sender, which may get lines of its own. The amount it stated counts
// whoever pays it. Stating none, it counts only when the user pays it // whoever pays it. Stating none, it counts only when the user pays it
// some of its input token. // some of its input token: paid from the router, it takes nothing
// from the sender.
const hasFirstTake = const hasFirstTake =
inputEstablished && (present(inputAmount) || inputPaidByUser); inputEstablished && (present(inputAmount) || inputPaidByUser);
// The take of the step that set the input side, when the total // The take of the step that set the input side, when the total
@@ -2145,8 +2093,7 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
if ( if (
ofTokenIn && ofTokenIn &&
typeof total === "bigint" && typeof total === "bigint" &&
typeof t.amount === "bigint" && typeof t.amount === "bigint"
!t.atMost
) { ) {
total += t.amount; total += t.amount;
inTotal.push(t); inTotal.push(t);
@@ -2249,9 +2196,8 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
// V2's already-paid zero, neither of which is a quantity at all (see // V2's already-paid zero, neither of which is a quantity at all (see
// OPEN_DELTA and ALREADY_PAID), and an unbounded permit. Those two // OPEN_DELTA and ALREADY_PAID), and an unbounded permit. Those two
// tests come first — the sentinels are not bigints and cannot be // tests come first — the sentinels are not bigints and cannot be
// compared with one. `atMost` marks an amount that is only the most a // compared with one.
// take may be (see userPaid). const spentText = (amount, info) => {
const spentText = (amount, info, atMost) => {
if (amount === OPEN_DELTA) { if (amount === OPEN_DELTA) {
return { raw: OPEN_DELTA_AMOUNT, display: OPEN_DELTA_AMOUNT }; return { raw: OPEN_DELTA_AMOUNT, display: OPEN_DELTA_AMOUNT };
} }
@@ -2264,7 +2210,7 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
if (amount >= MAX_UINT160) { if (amount >= MAX_UINT160) {
return { raw: "Unlimited", display: "Unlimited" }; return { raw: "Unlimited", display: "Unlimited" };
} }
if (hasV2OrV3ExactOut || atMost) { if (hasV2OrV3ExactOut) {
// A V2 or V3 exact-out swap spends at most this figure, // A V2 or V3 exact-out swap spends at most this figure,
// whichever steps gave it (an amountInMax, the WRAP_ETH of a // whichever steps gave it (an amountInMax, the WRAP_ETH of a
// swap paid in ETH, a permit, or several steps' total), so it // swap paid in ETH, a permit, or several steps' total), so it
@@ -2324,8 +2270,7 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
details.push({ details.push({
label: "Moved from the sender", label: "Moved from the sender",
value: present(s.amount) value: present(s.amount)
? spentText(s.amount, tokenInfo(s.token, sources), s.atMost) ? spentText(s.amount, tokenInfo(s.token, sources)).display
.display
: UNDECODED_CURRENCY, : UNDECODED_CURRENCY,
}); });
if (present(s.token)) { if (present(s.token)) {
-123
View File
@@ -3439,129 +3439,6 @@ describe("uniswap decoder", () => {
]); ]);
}); });
}); });
// 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
// SETTLE of 1 ETH that names the router as payer gave no ETH line.
describe("a V4 SETTLE of ETH that names the router as payer", () => {
const ONE_ETH = 1000000000000000000n;
// A V4 swap of `amount` of ETH for USDC. The SETTLE names the
// router as payer.
const v4EthFromRouter = (amount) =>
encodeV4Swap(
new Uint8Array([
V4_SWAP_EXACT_IN_SINGLE_ID,
V4_SETTLE_ID,
V4_TAKE_ALL_ID,
]),
[
encodeV4ExactInSingle(ZERO_ADDR, USDC_ADDR, amount, 1n),
settle(ZERO_ADDR, amount, false),
encodeV4TakeAll(USDC_ADDR, 1n),
],
);
test("after a V3 exact-in of 600 USDT, shows the 1 ETH moved from the sender", () => {
const result = decodeSteps(
[0x00, 0x10],
[V3_600, v4EthFromRouter(ONE_ETH)],
);
expect(linesBetweenTokens(result)).toEqual([
["Amount", "600.0000 USDT"],
["Moved from the sender", "1.0000 ETH"],
["Token moved", "ETH"],
["Moved to", "The swap step that spends it"],
]);
});
// The UNWRAP_WETH leaves ETH in the router. The SETTLE may spend
// that ETH before the transaction's value.
test("after an UNWRAP_WETH into the router, shows at most 1 ETH moved from the sender", () => {
const result = decodeSteps(
[0x00, 0x0c, 0x10],
[
encodeV3SwapExactIn(ROUTER_ADDR, USDT_600, 1n, [
USDT_ADDR,
WETH_ADDR,
]),
encodeWrapEth(ROUTER_ADDR, 0n), // UNWRAP_WETH, same encoding
v4EthFromRouter(ONE_ETH),
],
);
expect(linesBetweenTokens(result)).toEqual([
["Amount", "600.0000 USDT"],
["Moved from the sender", "Up to 1.0000 ETH"],
["Token moved", "ETH"],
["Moved to", "The swap step that spends it"],
]);
});
test("after a WRAP_ETH of 0.5 ETH, adds the SETTLE's 0.5 ETH to the Amount line", () => {
const result = decodeSteps(
[0x0b, 0x10],
[
encodeWrapEth(ROUTER_ADDR, ONE_ETH / 2n),
v4EthFromRouter(ONE_ETH / 2n),
],
);
expect(linesBetweenTokens(result)).toEqual([
["Amount", "1.0000 ETH"],
]);
});
// The WRAP_ETH sets Token In to ETH, and the UNWRAP_WETH leaves
// ETH in the router. The SETTLE may spend that ETH before the
// transaction's value, so its 1 ETH never joins the Amount line.
test("after a WRAP_ETH and an UNWRAP_WETH into the router, keeps the Amount line and shows at most 1 ETH moved from the sender", () => {
const result = decodeSteps(
[0x0b, 0x0c, 0x10],
[
encodeWrapEth(ROUTER_ADDR, ONE_ETH / 2n),
encodeWrapEth(ROUTER_ADDR, 0n), // UNWRAP_WETH, same encoding
v4EthFromRouter(ONE_ETH),
],
);
expect(linesBetweenTokens(result)).toEqual([
["Amount", "0.5000 ETH"],
["Moved from the sender", "Up to 1.0000 ETH"],
["Token moved", "ETH"],
["Moved to", "The swap step that spends it"],
]);
});
// A SETTLE of CONTRACT_BALANCE pays all the router holds, an
// amount the calldata does not state.
test("of CONTRACT_BALANCE, on the step that sets the input side, shows an unknown amount moved from the sender", () => {
const result = decodeSteps(
[0x10],
[
encodeV4Swap(
new Uint8Array([
V4_SWAP_EXACT_IN_SINGLE_ID,
V4_SETTLE_ID,
V4_TAKE_ALL_ID,
]),
[
encodeV4ExactInSingle(
ZERO_ADDR,
USDC_ADDR,
ONE_ETH,
1n,
),
settle(ZERO_ADDR, 2n ** 255n, false), // CONTRACT_BALANCE
encodeV4TakeAll(USDC_ADDR, 1n),
],
),
],
);
expect(linesBetweenTokens(result)).toEqual([
["Moved from the sender", UNDECODED],
["Token moved", "ETH"],
["Moved to", "The swap step that spends it"],
]);
});
});
}); });
// https://git.eeqj.de/sneak/AutistMask/issues/359 — the output half, in // https://git.eeqj.de/sneak/AutistMask/issues/359 — the output half, in
+9 -29
View File
@@ -1661,18 +1661,9 @@ describe("any token left in the router is not received", () => {
), ),
min: "1990.0000 USDC", min: "1990.0000 USDC",
}, },
])("$kind reads as a swap paid to the user", ({ data, min }) => { {
const result = decode(data); kind: "a swap paying WETH to the router, an UNWRAP_WETH into the router, then a V4 swap settling all the ETH from the router",
expect(labels(result)).toEqual(PAID_TO_USER); data: execute(
expect(detail(result, "Min. received").value).toBe(min);
});
// https://git.eeqj.de/sneak/AutistMask/issues/682 — the SETTLE spends all
// the router's ETH, the transaction's value included, so the sender pays
// an amount the calldata does not state.
test("a swap paying WETH to the router, an UNWRAP_WETH into the router, then a V4 swap settling all the ETH from the router reads as a swap paid to the user", () => {
const result = decode(
execute(
swapToWeth(ADDRESS_THIS), swapToWeth(ADDRESS_THIS),
unwrapWeth(ADDRESS_THIS), unwrapWeth(ADDRESS_THIS),
v4Swap( v4Swap(
@@ -1681,23 +1672,12 @@ describe("any token left in the router is not received", () => {
takeAll(USDC), takeAll(USDC),
), ),
), ),
); min: "1990.0000 USDC",
expect(labels(result)).toEqual([ },
"Protocol", ])("$kind reads as a swap paid to the user", ({ data, min }) => {
"Token In", const result = decode(data);
"Amount", expect(labels(result)).toEqual(PAID_TO_USER);
"Moved from the sender", expect(detail(result, "Min. received").value).toBe(min);
"Token moved",
"Moved to",
"Token Out",
"Min. received",
"Steps",
"Deadline",
]);
expect(detail(result, "Moved from the sender").value).toBe(
"Unknown (could not be decoded)",
);
expect(detail(result, "Min. received").value).toBe("1990.0000 USDC");
}); });
}); });