Compare commits
1
Commits
next
...
6a878d6836
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6a878d6836 |
@@ -1260,17 +1260,26 @@ runs (Transaction Decoding below gives what the router holds). The router
|
|||||||
unwraps only the WETH it holds, so WETH that step paid to the user, or to anyone
|
unwraps only the WETH it holds, so WETH that step paid to the user, or to anyone
|
||||||
else, stays WETH: a swap that pays the router WETH, then a swap that pays the
|
else, stays WETH: a swap that pays the router WETH, then a swap that pays the
|
||||||
user WETH, then an unwrap, shows WETH with the second swap's minimum. The
|
user WETH, then an unwrap, shows WETH with the second swap's minimum. The
|
||||||
minimum reads in ETH when that step paid all its WETH to the router. When it
|
minimum reads in ETH when that step paid all its WETH to the router and no step
|
||||||
paid only part of it to the router, as a V4 step does that `TAKE`s some of its
|
between it and the `UNWRAP_WETH` is a `TRANSFER`, `PAY_PORTION` or
|
||||||
WETH to the user and the rest to the router, or paid the router WETH beside a
|
`PAY_PORTION_FULL_PRECISION` of WETH to anyone but the router, or a `WRAP_ETH`
|
||||||
payout the decoder cannot read, the swap shows ETH with no minimum: the decoder
|
into the router. Such a step may pay out WETH the router holds, or put other
|
||||||
does not count how much of that step's minimum reaches the router, nor read the
|
WETH in place of WETH an earlier step paid out. With such a step, or when that
|
||||||
`UNWRAP_WETH`'s own minimum. A later step that sets the output side sets it
|
step paid only part of its WETH to the router, as a V4 step does that `TAKE`s
|
||||||
anew. Otherwise `Token Out` and `Min. received` keep the output side's own token
|
some of its WETH to the user and the rest to the router, or paid the router WETH
|
||||||
and figure, whether the swap was paid in ETH or in a token: a V2 exact-out swap
|
beside a payout the decoder cannot read, the swap shows ETH with no minimum: the
|
||||||
that buys USDC and then unwraps the WETH it did not spend shows USDC. The token
|
decoder does not count how much of that step's minimum reaches the
|
||||||
permission warning on the signature screen has its own amount wording, including
|
`UNWRAP_WETH`, nor read the `UNWRAP_WETH`'s own minimum. After an `UNWRAP_WETH`
|
||||||
`Unknown`; the SignApproval section below describes it.
|
that pays the output side's WETH to the router as ETH, a `WRAP_ETH` of
|
||||||
|
`CONTRACT_BALANCE` to anyone but the router, while the router still holds ETH,
|
||||||
|
pays the output out as WETH again: the swap shows WETH with no minimum, as the
|
||||||
|
decoder does not count what steps between the two pay out of that ETH. A later
|
||||||
|
step that sets the output side sets it anew. Otherwise `Token Out` and
|
||||||
|
`Min. received` keep the output side's own token and figure, whether the swap
|
||||||
|
was paid in ETH or in a token: a V2 exact-out swap that buys USDC and then
|
||||||
|
unwraps the WETH it did not spend shows USDC. The token permission warning on
|
||||||
|
the signature screen has its own amount wording, including `Unknown`; the
|
||||||
|
SignApproval section below describes it.
|
||||||
|
|
||||||
The swap's `Deadline` line is the router's deadline as a UTC date and time, e.g.
|
The swap's `Deadline` line is the router's deadline as a UTC date and time, e.g.
|
||||||
`2026-02-27 08:25:51`. A JavaScript date reaches only to 275760-09-13 00:00:00
|
`2026-02-27 08:25:51`. A JavaScript date reaches only to 275760-09-13 00:00:00
|
||||||
|
|||||||
+53
-9
@@ -1221,8 +1221,17 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
|
|||||||
let hasUnwrapWeth = false;
|
let hasUnwrapWeth = false;
|
||||||
// Set when an UNWRAP_WETH pays out, as ETH, the output side's WETH,
|
// Set when an UNWRAP_WETH pays out, as ETH, the output side's WETH,
|
||||||
// which the router holds; cleared whenever the output side is set
|
// which the router holds; cleared whenever the output side is set
|
||||||
// again.
|
// again, or a WRAP_ETH pays it out as WETH again (see WRAP_ETH).
|
||||||
|
// `unwrappedIntoRouter` says whether that UNWRAP_WETH paid the ETH to
|
||||||
|
// the router, which then holds it.
|
||||||
let outputUnwrapped = false;
|
let outputUnwrapped = false;
|
||||||
|
let unwrappedIntoRouter = false;
|
||||||
|
// Set when a step after the one that set the output side may pay out
|
||||||
|
// WETH the router holds, or add to it: a TRANSFER, PAY_PORTION or
|
||||||
|
// PAY_PORTION_FULL_PRECISION of WETH to anyone but the router, or a
|
||||||
|
// WRAP_ETH into the router. Cleared whenever the output side is set
|
||||||
|
// again.
|
||||||
|
let routerWethChanged = false;
|
||||||
// Where the step that set the output side paid its output: the
|
// Where the step that set the output side paid its output: the
|
||||||
// recipient of a swap step, as written, or of each TAKE, TAKE_ALL and
|
// recipient of a swap step, as written, or of each TAKE, TAKE_ALL and
|
||||||
// TAKE_PORTION of it in a V4 step, a TAKE_ALL as paid to the sender,
|
// TAKE_PORTION of it in a V4 step, a TAKE_ALL as paid to the sender,
|
||||||
@@ -1272,6 +1281,7 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
|
|||||||
outputEstablished = true;
|
outputEstablished = true;
|
||||||
outputUnwrapped = false;
|
outputUnwrapped = false;
|
||||||
outputRecipients = recipients;
|
outputRecipients = recipients;
|
||||||
|
routerWethChanged = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
// 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
|
||||||
@@ -1641,6 +1651,12 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
|
|||||||
) {
|
) {
|
||||||
heldByRouter.delete(t.token.toLowerCase());
|
heldByRouter.delete(t.token.toLowerCase());
|
||||||
}
|
}
|
||||||
|
if (
|
||||||
|
WETH_ADDRESSES.includes(t.token.toLowerCase()) &&
|
||||||
|
!isRouter(t.recipient)
|
||||||
|
) {
|
||||||
|
routerWethChanged = true;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
payeeUnread = true;
|
payeeUnread = true;
|
||||||
stepsUnread.push(i);
|
stepsUnread.push(i);
|
||||||
@@ -1677,6 +1693,24 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
|
|||||||
w.amount,
|
w.amount,
|
||||||
);
|
);
|
||||||
addPayee(w.recipient);
|
addPayee(w.recipient);
|
||||||
|
// After an UNWRAP_WETH paid the output side's WETH to
|
||||||
|
// the router as ETH, a WRAP_ETH of all the router's
|
||||||
|
// ETH to anyone else pays it out as WETH again. This
|
||||||
|
// decoder does not count what steps between the two
|
||||||
|
// paid out of that ETH, so the output side keeps no
|
||||||
|
// figure.
|
||||||
|
if (
|
||||||
|
outputUnwrapped &&
|
||||||
|
unwrappedIntoRouter &&
|
||||||
|
w.amount === CONTRACT_BALANCE &&
|
||||||
|
!isRouter(w.recipient) &&
|
||||||
|
heldByRouter.has(
|
||||||
|
"0x0000000000000000000000000000000000000000",
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
outputUnwrapped = false;
|
||||||
|
minOutput = null;
|
||||||
|
}
|
||||||
// CONTRACT_BALANCE wraps all the router's ETH.
|
// CONTRACT_BALANCE wraps all the router's ETH.
|
||||||
if (w.amount === CONTRACT_BALANCE) {
|
if (w.amount === CONTRACT_BALANCE) {
|
||||||
heldByRouter.delete(
|
heldByRouter.delete(
|
||||||
@@ -1687,6 +1721,7 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
|
|||||||
// amount is known when the step states it and the
|
// amount is known when the step states it and the
|
||||||
// router held no WETH before.
|
// router held no WETH before.
|
||||||
if (isRouter(w.recipient) && w.amount > 0n) {
|
if (isRouter(w.recipient) && w.amount > 0n) {
|
||||||
|
routerWethChanged = true;
|
||||||
const weth = WETH.get(chainId);
|
const weth = WETH.get(chainId);
|
||||||
heldByRouter.set(
|
heldByRouter.set(
|
||||||
weth,
|
weth,
|
||||||
@@ -1766,6 +1801,7 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
|
|||||||
outputEstablished = true;
|
outputEstablished = true;
|
||||||
outputUnwrapped = false;
|
outputUnwrapped = false;
|
||||||
outputRecipients = takeRecipients;
|
outputRecipients = takeRecipients;
|
||||||
|
routerWethChanged = false;
|
||||||
} else if (v4) {
|
} else if (v4) {
|
||||||
// A step with no swap action sets only the sides its
|
// A step with no swap action sets only the sides its
|
||||||
// SETTLE, SETTLE_ALL, TAKE or TAKE_ALL names:
|
// SETTLE, SETTLE_ALL, TAKE or TAKE_ALL names:
|
||||||
@@ -1799,12 +1835,16 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
|
|||||||
// among it when the step that set the output side paid it
|
// among it when the step that set the output side paid it
|
||||||
// to the router and the router still holds WETH. WETH
|
// to the router and the router still holds WETH. WETH
|
||||||
// that step paid to the user, or to anyone else, stays
|
// that step paid to the user, or to anyone else, stays
|
||||||
// WETH, so when that step also paid some of its output
|
// WETH. When that step also paid some of its output
|
||||||
// elsewhere, or made a payout this decoder did not read,
|
// elsewhere or made a payout this decoder did not read,
|
||||||
// only part of it may be unwrapped. This decoder does not
|
// or a later step may have paid out WETH the router holds
|
||||||
// count how much of that step's minimum then reaches the
|
// or added to it (see routerWethChanged), the WETH
|
||||||
// router, nor read the UNWRAP_WETH's own minimum, so the
|
// unwrapped may be only part of the output side's, or
|
||||||
// output side keeps no figure.
|
// none of it. This decoder does not count how much of
|
||||||
|
// that step's minimum then reaches the UNWRAP_WETH, nor
|
||||||
|
// read the UNWRAP_WETH's own minimum, so the output side
|
||||||
|
// keeps no figure.
|
||||||
|
const u = decodeUnwrapWeth(inputs[i]);
|
||||||
const toRouter = (r) => present(r) && isRouter(r);
|
const toRouter = (r) => present(r) && isRouter(r);
|
||||||
if (
|
if (
|
||||||
present(outputToken) &&
|
present(outputToken) &&
|
||||||
@@ -1813,11 +1853,15 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
|
|||||||
outputRecipients.some(toRouter)
|
outputRecipients.some(toRouter)
|
||||||
) {
|
) {
|
||||||
outputUnwrapped = true;
|
outputUnwrapped = true;
|
||||||
if (!outputRecipients.every(toRouter)) {
|
unwrappedIntoRouter =
|
||||||
|
u !== null && isRouter(u.recipient);
|
||||||
|
if (
|
||||||
|
!outputRecipients.every(toRouter) ||
|
||||||
|
routerWethChanged
|
||||||
|
) {
|
||||||
minOutput = null;
|
minOutput = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const u = decodeUnwrapWeth(inputs[i]);
|
|
||||||
if (u) {
|
if (u) {
|
||||||
addPayee(u.recipient);
|
addPayee(u.recipient);
|
||||||
// It pays out all the router's WETH, as ETH. Into the
|
// It pays out all the router's WETH, as ETH. Into the
|
||||||
|
|||||||
@@ -11,7 +11,8 @@
|
|||||||
// what each PERMIT2_TRANSFER_FROM moves
|
// what each PERMIT2_TRANSFER_FROM moves
|
||||||
// (https://git.eeqj.de/sneak/AutistMask/issues/611), and when an
|
// (https://git.eeqj.de/sneak/AutistMask/issues/611), and when an
|
||||||
// UNWRAP_WETH makes the output ETH
|
// UNWRAP_WETH makes the output ETH
|
||||||
// (https://git.eeqj.de/sneak/AutistMask/issues/607).
|
// (https://git.eeqj.de/sneak/AutistMask/issues/607 and
|
||||||
|
// https://git.eeqj.de/sneak/AutistMask/issues/642).
|
||||||
//
|
//
|
||||||
// Every swap command, SWEEP, WRAP_ETH, UNWRAP_WETH, TRANSFER, PAY_PORTION,
|
// Every swap command, SWEEP, WRAP_ETH, UNWRAP_WETH, TRANSFER, PAY_PORTION,
|
||||||
// PAY_PORTION_FULL_PRECISION, PERMIT2_TRANSFER_FROM, ACROSS_V4_DEPOSIT_V3 and
|
// PAY_PORTION_FULL_PRECISION, PERMIT2_TRANSFER_FROM, ACROSS_V4_DEPOSIT_V3 and
|
||||||
@@ -745,6 +746,94 @@ describe("an UNWRAP_WETH makes the output ETH only when the router holds it", ()
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// https://git.eeqj.de/sneak/AutistMask/issues/642: a step between the swap and
|
||||||
|
// the UNWRAP_WETH that pays out part of the router's WETH, or a WRAP_ETH into
|
||||||
|
// the router after the swap's WETH was paid out, may leave less of the swap's
|
||||||
|
// WETH to unwrap than its minimum; an UNWRAP_WETH into the router, then a
|
||||||
|
// WRAP_ETH of all its ETH to the user, pays the user WETH. Each read as ETH
|
||||||
|
// with the swap's minimum.
|
||||||
|
describe("what reaches the UNWRAP_WETH of the swap's WETH", () => {
|
||||||
|
const NO_MINIMUM_TO_USER = PAID_TO_USER.filter(
|
||||||
|
(l) => l !== "Min. received",
|
||||||
|
);
|
||||||
|
const NO_MINIMUM_ELSEWHERE = PAID_ELSEWHERE.filter(
|
||||||
|
(l) => l !== "Min. paid out",
|
||||||
|
);
|
||||||
|
|
||||||
|
test.each([
|
||||||
|
{
|
||||||
|
kind: "a TRANSFER of 0.0025 WETH",
|
||||||
|
step: transfer(0x05, WETH, OTHER, ONE_WETH / 400n),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
kind: "a PAY_PORTION of 25 bips",
|
||||||
|
step: transfer(0x06, WETH, OTHER, 25n),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
kind: "a PAY_PORTION_FULL_PRECISION of 1e15",
|
||||||
|
step: transfer(0x07, WETH, OTHER, 10n ** 15n),
|
||||||
|
},
|
||||||
|
])(
|
||||||
|
"$kind to another address between them reads as ETH with no minimum",
|
||||||
|
({ step }) => {
|
||||||
|
const result = decode(
|
||||||
|
execute(swapToWeth(ADDRESS_THIS), step, unwrapWeth(MSG_SENDER)),
|
||||||
|
USER,
|
||||||
|
ROUTER_V2_1_2,
|
||||||
|
);
|
||||||
|
expect(result.name).toBe("Swap USDT → ETH");
|
||||||
|
expect(labels(result)).toEqual(NO_MINIMUM_ELSEWHERE);
|
||||||
|
expect(paidTo(result)).toEqual([OTHER]);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
// The SWEEP pays the user the swap's WETH, so the UNWRAP_WETH unwraps the
|
||||||
|
// WRAP_ETH's.
|
||||||
|
test("a SWEEP of the swap's WETH to the user, then a WRAP_ETH into the router, reads as ETH with no minimum", () => {
|
||||||
|
const result = decode(
|
||||||
|
execute(
|
||||||
|
swapToWeth(ADDRESS_THIS),
|
||||||
|
sweep(WETH, MSG_SENDER),
|
||||||
|
wrapEth(ADDRESS_THIS, ONE_WETH),
|
||||||
|
unwrapWeth(MSG_SENDER),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
expect(result.name).toBe("Swap USDT → ETH");
|
||||||
|
expect(labels(result)).toEqual(NO_MINIMUM_TO_USER);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("an UNWRAP_WETH into the router, then a WRAP_ETH of all its ETH to the user, reads as WETH with no minimum", () => {
|
||||||
|
const result = decode(
|
||||||
|
execute(
|
||||||
|
swapToWeth(ADDRESS_THIS),
|
||||||
|
unwrapWeth(ADDRESS_THIS),
|
||||||
|
wrapEth(MSG_SENDER, CONTRACT_BALANCE),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
expect(result.name).toBe("Swap USDT → WETH");
|
||||||
|
expect(labels(result)).toEqual(NO_MINIMUM_TO_USER);
|
||||||
|
});
|
||||||
|
|
||||||
|
// The UNWRAP_WETH pays the user the swap's WETH as ETH; the WRAP_ETH wraps
|
||||||
|
// the ETH an earlier V4 step paid the router.
|
||||||
|
test("an UNWRAP_WETH to the user, then a WRAP_ETH of all the router's ETH to the user, reads as ETH", () => {
|
||||||
|
const result = decode(
|
||||||
|
execute(
|
||||||
|
v4Swap(
|
||||||
|
SWAP_USDT_FOR_ETH,
|
||||||
|
SETTLE_USDT,
|
||||||
|
take(ZERO, ADDRESS_THIS),
|
||||||
|
),
|
||||||
|
swapToWeth(ADDRESS_THIS),
|
||||||
|
unwrapWeth(MSG_SENDER),
|
||||||
|
wrapEth(MSG_SENDER, CONTRACT_BALANCE),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
expect(detail(result, "Token Out").value).toBe("ETH");
|
||||||
|
expect(detail(result, "Min. received").value).toBe("1.0000 ETH");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
// https://git.eeqj.de/sneak/AutistMask/issues/606: any token a step pays the
|
// https://git.eeqj.de/sneak/AutistMask/issues/606: any token a step pays the
|
||||||
// router stays there unless a later step pays out or spends all of it, not
|
// router stays there unless a later step pays out or spends all of it, not
|
||||||
// only the swap's output. A V4 SETTLE can take more of the user's tokens than
|
// only the swap's output. A V4 SETTLE can take more of the user's tokens than
|
||||||
@@ -1711,15 +1800,6 @@ describe("a payout of part of the router's output", () => {
|
|||||||
),
|
),
|
||||||
min: "1.0000 WETH",
|
min: "1.0000 WETH",
|
||||||
},
|
},
|
||||||
{
|
|
||||||
kind: "PAY_PORTION of 25 bips, then UNWRAP_WETH",
|
|
||||||
data: execute(
|
|
||||||
swapToWeth(ADDRESS_THIS),
|
|
||||||
transfer(0x06, WETH, OTHER, 25n),
|
|
||||||
unwrapWeth(MSG_SENDER),
|
|
||||||
),
|
|
||||||
min: "1.0000 ETH",
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
kind: "V4 TAKE_PORTION of 25 bips, then TAKE_ALL",
|
kind: "V4 TAKE_PORTION of 25 bips, then TAKE_ALL",
|
||||||
data: execute(
|
data: execute(
|
||||||
|
|||||||
Reference in New Issue
Block a user