fix: a WRAP_ETH says where its ETH goes (closes #747)
A WRAP_ETH into the router now always reads `Moved to: The router`. That is true whichever step then takes the WETH. Other takes keep `The swap step that spends it`. A later WRAP_ETH of CONTRACT_BALANCE to anyone but the router now gets a `Moved from the sender` line reading `All the ETH the transaction carries`, and `Moved to` names the recipient. It gets none after an UNWRAP_WETH into the router, or while the router holds ETH from a V4 step. README states both rules. Model: opus-5-5
This commit was merged in pull request #750.
This commit is contained in:
@@ -1414,15 +1414,19 @@ when both tokens are known. The lines are:
|
|||||||
the sender that the `Amount` line does not include. The same three lines show
|
the sender that the `Amount` line does not include. The same three lines show
|
||||||
what a `PERMIT2_TRANSFER_FROM` moves and where. A `WRAP_ETH` can pay its WETH
|
what a `PERMIT2_TRANSFER_FROM` moves and where. A `WRAP_ETH` can pay its WETH
|
||||||
to a recipient other than the router. When its amount gets these lines, its
|
to a recipient other than the router. When its amount gets these lines, its
|
||||||
`Moved to` line names that recipient, or reads `The sender`. A `WRAP_ETH`
|
`Moved to` line names that recipient, or reads `The sender`. For a `WRAP_ETH`
|
||||||
after the step that sets `Token In` takes the amount it states from the
|
into the router, `Moved to` reads `The router`. A `WRAP_ETH` after the step
|
||||||
sender. When it pays anyone but the router, that amount gets these lines. When
|
that sets `Token In` takes the amount it states from the sender. When it pays
|
||||||
it pays the router, that amount joins the `Amount` line if `Token In` is ETH,
|
anyone but the router, that amount gets these lines. When it pays the router,
|
||||||
and gets these lines if not. A later `WRAP_ETH` of `CONTRACT_BALANCE` states
|
that amount joins the `Amount` line if `Token In` is ETH, and gets these lines
|
||||||
no amount, so it gets no lines. ETH that the sender pays a V4 step or a
|
if not. A later `WRAP_ETH` of `CONTRACT_BALANCE` to anyone but the router gets
|
||||||
`WRAP_ETH` of a stated amount can get these three lines when the router may
|
these lines. It gets none after an `UNWRAP_WETH` into the router, or while the
|
||||||
hold ETH from an earlier step. When the decoder knows the amount, its
|
router holds ETH from a V4 step. When it gets them, its
|
||||||
`Moved from the sender` line reads `Up to` and the amount.
|
`Moved from the sender` line reads `All the ETH the transaction carries`. ETH
|
||||||
|
that the sender pays a V4 step or a `WRAP_ETH` of a stated amount can get
|
||||||
|
these three lines when the router may hold ETH from an earlier step. When the
|
||||||
|
decoder knows the amount, its `Moved from the sender` line reads `Up to` and
|
||||||
|
the amount.
|
||||||
- `Permit2 allowance`, `Allowance token` and `Allowance spender`: each allowance
|
- `Permit2 allowance`, `Allowance token` and `Allowance spender`: each allowance
|
||||||
that a `PERMIT2_PERMIT` or `PERMIT2_PERMIT_BATCH` grants, unless the permit's
|
that a `PERMIT2_PERMIT` or `PERMIT2_PERMIT_BATCH` grants, unless the permit's
|
||||||
amount already stands as the `Amount` figure.
|
amount already stands as the `Amount` figure.
|
||||||
|
|||||||
+61
-26
@@ -404,6 +404,11 @@ const OPEN_DELTA = Symbol("v4-open-delta");
|
|||||||
// that nothing is swapped.
|
// that nothing is swapped.
|
||||||
const ALREADY_PAID = Symbol("v2-already-paid");
|
const ALREADY_PAID = Symbol("v2-already-paid");
|
||||||
|
|
||||||
|
// A later WRAP_ETH of CONTRACT_BALANCE wraps all the ETH the router holds, and
|
||||||
|
// so all the ETH the transaction carries. The calldata states no quantity for
|
||||||
|
// it. The approval screen's Value line gives that ETH.
|
||||||
|
const TRANSACTION_ETH = Symbol("transaction-eth");
|
||||||
|
|
||||||
// The router's whole-balance amount, universal-router
|
// The router's whole-balance amount, universal-router
|
||||||
// `ActionConstants.CONTRACT_BALANCE`: a V2 exact-in given it as `amountIn`
|
// `ActionConstants.CONTRACT_BALANCE`: a V2 exact-in given it as `amountIn`
|
||||||
// pays the pair all the router holds of the input token.
|
// pays the pair all the router holds of the input token.
|
||||||
@@ -416,6 +421,7 @@ const CONTRACT_BALANCE = 2n ** 255n;
|
|||||||
const OPEN_DELTA_AMOUNT = "All available (V4 open delta)";
|
const OPEN_DELTA_AMOUNT = "All available (V4 open delta)";
|
||||||
const ALREADY_PAID_AMOUNT =
|
const ALREADY_PAID_AMOUNT =
|
||||||
"Whatever an earlier step sent to the pair (V2 already paid)";
|
"Whatever an earlier step sent to the pair (V2 already paid)";
|
||||||
|
const TRANSACTION_ETH_AMOUNT = "All the ETH the transaction carries";
|
||||||
const NO_MINIMUM = "None (no minimum guaranteed)";
|
const NO_MINIMUM = "None (no minimum guaranteed)";
|
||||||
|
|
||||||
// Permit2 amounts are uint160; the maximum is Permit2's "unbounded".
|
// Permit2 amounts are uint160; the maximum is Permit2's "unbounded".
|
||||||
@@ -1267,9 +1273,9 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
|
|||||||
// ERC-20 token from the router takes none of that token from the
|
// ERC-20 token from the router takes none of that token from the
|
||||||
// sender.
|
// sender.
|
||||||
let inputPaidByUser = false;
|
let inputPaidByUser = false;
|
||||||
// The recipient of a WRAP_ETH that set the input side, as written,
|
// The recipient of a WRAP_ETH that set the input side, as written;
|
||||||
// when it is not the router; otherwise null. That WRAP_ETH pays its
|
// otherwise null. That WRAP_ETH pays its WETH there (see the Moved to
|
||||||
// WETH there, not to a swap step (see the Moved to lines).
|
// lines).
|
||||||
let wrapEthRecipient = null;
|
let wrapEthRecipient = null;
|
||||||
// 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).
|
||||||
@@ -1289,6 +1295,9 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
|
|||||||
let minOutput = null;
|
let minOutput = null;
|
||||||
let outputEstablished = false;
|
let outputEstablished = false;
|
||||||
let hasUnwrapWeth = false;
|
let hasUnwrapWeth = false;
|
||||||
|
// Set once an UNWRAP_WETH pays ETH to the router, which a later
|
||||||
|
// WRAP_ETH of CONTRACT_BALANCE then wraps.
|
||||||
|
let unwrappedIntoRouter = 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, or a later step may pay that ETH out as WETH (see after the
|
// again, or a later step may pay that ETH out as WETH (see after the
|
||||||
@@ -1368,9 +1377,9 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
|
|||||||
// 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). A take with `atMost` set may take less than
|
||||||
// `amount`: it never joins the Amount line, and its line reads
|
// `amount`: it never joins the Amount line, and its line reads
|
||||||
// "Up to" and the amount. A take with a `recipient`, the ETH of a
|
// "Up to" and the amount. A take with a `recipient` is the ETH of a
|
||||||
// later WRAP_ETH that pays anyone but the router, never joins the
|
// later WRAP_ETH. If that recipient is not the router, the take never
|
||||||
// Amount line either: its Moved to line names that recipient.
|
// joins the Amount line either: its Moved to line names that recipient.
|
||||||
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, or at most that when `atMost` is set. A
|
// of `token` from the sender, or at most that when `atMost` is set. A
|
||||||
@@ -1906,9 +1915,7 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
|
|||||||
w.amount,
|
w.amount,
|
||||||
);
|
);
|
||||||
inputPaidByUser = true;
|
inputPaidByUser = true;
|
||||||
if (!isRouter(w.recipient)) {
|
wrapEthRecipient = w.recipient;
|
||||||
wrapEthRecipient = w.recipient;
|
|
||||||
}
|
|
||||||
} else if (
|
} else if (
|
||||||
w.amount > 0n &&
|
w.amount > 0n &&
|
||||||
w.amount !== CONTRACT_BALANCE
|
w.amount !== CONTRACT_BALANCE
|
||||||
@@ -1916,17 +1923,33 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
|
|||||||
// The ETH of a stated amount comes from the
|
// The ETH of a stated amount comes from the
|
||||||
// transaction's value. ETH an earlier step left in
|
// transaction's value. ETH an earlier step left in
|
||||||
// the router may pay part of it, and the sender
|
// the router may pay part of it, and the sender
|
||||||
// then pays at most that amount. CONTRACT_BALANCE
|
// then pays at most that amount.
|
||||||
// states no amount to take.
|
|
||||||
userPaid.push({
|
userPaid.push({
|
||||||
token: "0x0000000000000000000000000000000000000000",
|
token: "0x0000000000000000000000000000000000000000",
|
||||||
amount: w.amount,
|
amount: w.amount,
|
||||||
atMost: heldByRouter.has(
|
atMost: heldByRouter.has(
|
||||||
"0x0000000000000000000000000000000000000000",
|
"0x0000000000000000000000000000000000000000",
|
||||||
),
|
),
|
||||||
recipient: isRouter(w.recipient)
|
recipient: w.recipient,
|
||||||
? null
|
});
|
||||||
: w.recipient,
|
} else if (
|
||||||
|
w.amount === CONTRACT_BALANCE &&
|
||||||
|
!isRouter(w.recipient) &&
|
||||||
|
!unwrappedIntoRouter &&
|
||||||
|
!heldByRouter.has(
|
||||||
|
"0x0000000000000000000000000000000000000000",
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
// It pays the recipient all the ETH the transaction
|
||||||
|
// carries. It gets no lines after an UNWRAP_WETH
|
||||||
|
// into the router, or while the router holds ETH
|
||||||
|
// from a V4 step: Uniswap's routes pay back what
|
||||||
|
// their swap did not spend this way, and carry no
|
||||||
|
// ETH.
|
||||||
|
userPaid.push({
|
||||||
|
token: "0x0000000000000000000000000000000000000000",
|
||||||
|
amount: TRANSACTION_ETH,
|
||||||
|
recipient: w.recipient,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
addPayee(w.recipient);
|
addPayee(w.recipient);
|
||||||
@@ -2169,6 +2192,9 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
|
|||||||
heldByRouter.has(w),
|
heldByRouter.has(w),
|
||||||
);
|
);
|
||||||
WETH_ADDRESSES.forEach((w) => heldByRouter.delete(w));
|
WETH_ADDRESSES.forEach((w) => heldByRouter.delete(w));
|
||||||
|
if (isRouter(u.recipient)) {
|
||||||
|
unwrappedIntoRouter = true;
|
||||||
|
}
|
||||||
if (isRouter(u.recipient) && heldWeth) {
|
if (isRouter(u.recipient) && heldWeth) {
|
||||||
heldByRouter.set(
|
heldByRouter.set(
|
||||||
"0x0000000000000000000000000000000000000000",
|
"0x0000000000000000000000000000000000000000",
|
||||||
@@ -2266,8 +2292,8 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
|
|||||||
// What the steps take that the Amount line does not give, as
|
// What the steps take that the Amount line does not give, as
|
||||||
// { token, amount, atMost, recipient }. A null token is a step this
|
// { token, amount, atMost, recipient }. A null token is a step this
|
||||||
// decoder did not read (see spentUnread()), or a token it does not
|
// decoder did not read (see spentUnread()), or a token it does not
|
||||||
// know. `atMost` is as in `userPaid`. Only the take of a WRAP_ETH that
|
// know. `atMost` is as in `userPaid`. Only the take of a WRAP_ETH has
|
||||||
// pays elsewhere has a recipient (see wrapEthRecipient and userPaid).
|
// a recipient (see wrapEthRecipient and userPaid).
|
||||||
const spentFromSender = [];
|
const spentFromSender = [];
|
||||||
let total = null;
|
let total = null;
|
||||||
// The takes from the sender that `total` adds up, each a quantity, in
|
// The takes from the sender that `total` adds up, each a quantity, in
|
||||||
@@ -2315,7 +2341,7 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
|
|||||||
typeof total === "bigint" &&
|
typeof total === "bigint" &&
|
||||||
typeof t.amount === "bigint" &&
|
typeof t.amount === "bigint" &&
|
||||||
!t.atMost &&
|
!t.atMost &&
|
||||||
!present(t.recipient)
|
(!present(t.recipient) || isRouter(t.recipient))
|
||||||
) {
|
) {
|
||||||
total += t.amount;
|
total += t.amount;
|
||||||
inTotal.push(t);
|
inTotal.push(t);
|
||||||
@@ -2413,13 +2439,14 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// The text of an amount the swap steps spend, for the Amount line and
|
// The text of an amount the swap steps spend, for the Amount line and
|
||||||
// the lines for other tokens below it. Three amounts need no scale to
|
// the lines for other tokens below it. Four amounts need no scale to
|
||||||
// describe and are named rather than formatted: V4's open delta and
|
// describe and are named rather than formatted: V4's open delta, V2's
|
||||||
// V2's already-paid zero, neither of which is a quantity at all (see
|
// already-paid zero and the ETH the transaction carries, none of which
|
||||||
// OPEN_DELTA and ALREADY_PAID), and an unbounded permit. Those two
|
// is a quantity at all (see OPEN_DELTA, ALREADY_PAID and
|
||||||
// tests come first — the sentinels are not bigints and cannot be
|
// TRANSACTION_ETH), and an unbounded permit. Those three tests come
|
||||||
// compared with one. `atMost` marks an amount that is only the most a
|
// first — the sentinels are not bigints and cannot be compared with
|
||||||
// take may be (see userPaid).
|
// one. `atMost` marks an amount that is only the most a take may be
|
||||||
|
// (see userPaid).
|
||||||
const spentText = (amount, info, atMost) => {
|
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 };
|
||||||
@@ -2430,6 +2457,12 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
|
|||||||
display: ALREADY_PAID_AMOUNT,
|
display: ALREADY_PAID_AMOUNT,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
if (amount === TRANSACTION_ETH) {
|
||||||
|
return {
|
||||||
|
raw: TRANSACTION_ETH_AMOUNT,
|
||||||
|
display: TRANSACTION_ETH_AMOUNT,
|
||||||
|
};
|
||||||
|
}
|
||||||
if (amount >= MAX_UINT160) {
|
if (amount >= MAX_UINT160) {
|
||||||
return { raw: "Unlimited", display: "Unlimited" };
|
return { raw: "Unlimited", display: "Unlimited" };
|
||||||
}
|
}
|
||||||
@@ -2489,8 +2522,8 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
|
|||||||
// of the lines below for a PERMIT2_TRANSFER_FROM. An amount this
|
// of the lines below for a PERMIT2_TRANSFER_FROM. An amount this
|
||||||
// decoder does not know is said so, and a step it did not read gets
|
// decoder does not know is said so, and a step it did not read gets
|
||||||
// that one line alone. A take with no recipient goes to the swap step
|
// that one line alone. A take with no recipient goes to the swap step
|
||||||
// that spends it. A take with a recipient goes to that recipient;
|
// that spends it. A take with a recipient goes to that recipient:
|
||||||
// `MSG_SENDER` means the sender.
|
// the router, the sender as `MSG_SENDER`, or an address.
|
||||||
for (const s of spentFromSender) {
|
for (const s of spentFromSender) {
|
||||||
details.push({
|
details.push({
|
||||||
label: "Moved from the sender",
|
label: "Moved from the sender",
|
||||||
@@ -2514,6 +2547,8 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
|
|||||||
label: "Moved to",
|
label: "Moved to",
|
||||||
value: "The swap step that spends it",
|
value: "The swap step that spends it",
|
||||||
});
|
});
|
||||||
|
} else if (isRouter(s.recipient)) {
|
||||||
|
details.push({ label: "Moved to", value: "The router" });
|
||||||
} else if (s.recipient.toLowerCase() === MSG_SENDER) {
|
} else if (s.recipient.toLowerCase() === MSG_SENDER) {
|
||||||
details.push({ label: "Moved to", value: "The sender" });
|
details.push({ label: "Moved to", value: "The sender" });
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
+356
-21
@@ -2474,10 +2474,11 @@ describe("uniswap decoder", () => {
|
|||||||
// SETTLE of ETH from the router's own ETH, whichever payer it names.
|
// SETTLE of ETH from the router's own ETH, whichever payer it names.
|
||||||
// After an earlier step that may leave ETH in the router, the ETH that
|
// After an earlier step that may leave ETH in the router, the ETH that
|
||||||
// the user pays a V4 step reads as a maximum, on lines of its own.
|
// the user pays a V4 step reads as a maximum, on lines of its own.
|
||||||
// Every row reads at least the ETH that can leave the wallet. The two
|
// Every row reads at least the ETH that can leave the wallet. The row
|
||||||
// rows marked "reads as on next" read the same on next. On next every
|
// marked "reads as on next" reads the same on next. The row with a
|
||||||
// other row added that ETH to the Amount line, or read it as an exact
|
// SETTLE_ALL of at most 3 ETH read that ETH as not known on next. On
|
||||||
// amount.
|
// next every other row added that ETH to the Amount line, or read it as
|
||||||
|
// an exact amount.
|
||||||
//
|
//
|
||||||
// ADDRESS_THIS is the router on every release.
|
// ADDRESS_THIS is the router on every release.
|
||||||
const ADDRESS_THIS = "0x0000000000000000000000000000000000000002";
|
const ADDRESS_THIS = "0x0000000000000000000000000000000000000002";
|
||||||
@@ -2625,7 +2626,7 @@ describe("uniswap decoder", () => {
|
|||||||
],
|
],
|
||||||
lines: [
|
lines: [
|
||||||
["Amount", "600.0000 USDT"],
|
["Amount", "600.0000 USDT"],
|
||||||
...wrapLines("The swap step that spends it"),
|
...wrapLines("The router"),
|
||||||
...ethLines("Up to 1.0000 ETH"),
|
...ethLines("Up to 1.0000 ETH"),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@@ -2674,7 +2675,7 @@ describe("uniswap decoder", () => {
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
kind: "a WRAP_ETH of 1 ETH into the router, an UNWRAP_WETH into the router, then a V4 exact-out paid in ETH with a SETTLE_ALL of at most 3 ETH, reads as on next",
|
kind: "a WRAP_ETH of 1 ETH into the router, an UNWRAP_WETH into the router, then a V4 exact-out paid in ETH with a SETTLE_ALL of at most 3 ETH",
|
||||||
commands: [0x0b, 0x0c, 0x10],
|
commands: [0x0b, 0x0c, 0x10],
|
||||||
inputs: [
|
inputs: [
|
||||||
WRAP_INTO_ROUTER,
|
WRAP_INTO_ROUTER,
|
||||||
@@ -2697,10 +2698,7 @@ describe("uniswap decoder", () => {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
lines: [
|
lines: [...wrapLines("The router"), ...ethLines(UNDECODED)],
|
||||||
...wrapLines("The swap step that spends it"),
|
|
||||||
...ethLines(UNDECODED),
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
kind: "a WRAP_ETH of 1 ETH into the router, a V3 exact-out of at most 1 WETH paid from the router, an UNWRAP_WETH into the router, then a V4 swap of 1 ETH that the user settles",
|
kind: "a WRAP_ETH of 1 ETH into the router, a V3 exact-out of at most 1 WETH paid from the router, an UNWRAP_WETH into the router, then a V4 swap of 1 ETH that the user settles",
|
||||||
@@ -2761,8 +2759,8 @@ describe("uniswap decoder", () => {
|
|||||||
// does not set the input side takes the amount it states from the
|
// does not set the input side takes the amount it states from the
|
||||||
// sender. On next no line gave that ETH. The rows marked "reads as on
|
// sender. On next no line gave that ETH. The rows marked "reads as on
|
||||||
// next" state no amount to take from the sender. Uniswap's own routes
|
// next" state no amount to take from the sender. Uniswap's own routes
|
||||||
// wrap CONTRACT_BALANCE after their first step, as the last two rows
|
// wrap CONTRACT_BALANCE into the router after their first step, as the
|
||||||
// do.
|
// last row does.
|
||||||
const CONTRACT_BALANCE = 2n ** 255n;
|
const CONTRACT_BALANCE = 2n ** 255n;
|
||||||
const WRAP_TO_OTHER = encodeWrapEth(OTHER_ADDR, ONE_ETH);
|
const WRAP_TO_OTHER = encodeWrapEth(OTHER_ADDR, ONE_ETH);
|
||||||
test.each([
|
test.each([
|
||||||
@@ -2787,7 +2785,7 @@ describe("uniswap decoder", () => {
|
|||||||
inputs: [V3_600, WRAP_INTO_ROUTER],
|
inputs: [V3_600, WRAP_INTO_ROUTER],
|
||||||
lines: [
|
lines: [
|
||||||
["Amount", "600.0000 USDT"],
|
["Amount", "600.0000 USDT"],
|
||||||
...wrapLines("The swap step that spends it"),
|
...wrapLines("The router"),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -2796,7 +2794,7 @@ describe("uniswap decoder", () => {
|
|||||||
inputs: [V3_600, encodeWrapEth(ROUTER_ADDR, ONE_ETH)],
|
inputs: [V3_600, encodeWrapEth(ROUTER_ADDR, ONE_ETH)],
|
||||||
lines: [
|
lines: [
|
||||||
["Amount", "600.0000 USDT"],
|
["Amount", "600.0000 USDT"],
|
||||||
...wrapLines("The swap step that spends it"),
|
...wrapLines("The router"),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -2839,7 +2837,9 @@ describe("uniswap decoder", () => {
|
|||||||
],
|
],
|
||||||
lines: [
|
lines: [
|
||||||
["Amount", "1.0000 ETH"],
|
["Amount", "1.0000 ETH"],
|
||||||
...ethLines("Up to 1.0000 ETH"),
|
["Moved from the sender", "Up to 1.0000 ETH"],
|
||||||
|
["Token moved", "ETH"],
|
||||||
|
["Moved to", "The router"],
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -2877,12 +2877,6 @@ describe("uniswap decoder", () => {
|
|||||||
inputs: [V3_600, encodeWrapEth(OTHER_ADDR, 0n)],
|
inputs: [V3_600, encodeWrapEth(OTHER_ADDR, 0n)],
|
||||||
lines: [["Amount", "600.0000 USDT"]],
|
lines: [["Amount", "600.0000 USDT"]],
|
||||||
},
|
},
|
||||||
{
|
|
||||||
kind: "a V3 exact-in of 600 USDT, then a WRAP_ETH of CONTRACT_BALANCE to the sender, reads as on next",
|
|
||||||
commands: [0x00, 0x0b],
|
|
||||||
inputs: [V3_600, encodeWrapEth(MSG_SENDER, CONTRACT_BALANCE)],
|
|
||||||
lines: [["Amount", "600.0000 USDT"]],
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
kind: "a V4 swap of 600 USDT that takes ETH into the router, a WRAP_ETH of CONTRACT_BALANCE into the router, then a V3 exact-in of it, reads as on next",
|
kind: "a V4 swap of 600 USDT that takes ETH into the router, a WRAP_ETH of CONTRACT_BALANCE into the router, then a V3 exact-in of it, reads as on next",
|
||||||
commands: [0x10, 0x0b, 0x00],
|
commands: [0x10, 0x0b, 0x00],
|
||||||
@@ -2923,6 +2917,347 @@ describe("uniswap decoder", () => {
|
|||||||
expect(linesBetweenTokens(result)).toEqual(lines);
|
expect(linesBetweenTokens(result)).toEqual(lines);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// https://git.eeqj.de/sneak/AutistMask/issues/747 — a WRAP_ETH into the
|
||||||
|
// router pays its WETH to the router, whichever step then takes it. On
|
||||||
|
// next that Moved to line read The swap step that spends it in every
|
||||||
|
// row.
|
||||||
|
const SWAP_STEP = "The swap step that spends it";
|
||||||
|
// Each pays 1 WETH from the router, for USDC, to the user.
|
||||||
|
const V3_WETH_FROM_ROUTER = coder.encode(
|
||||||
|
["address", "uint256", "uint256", "bytes", "bool"],
|
||||||
|
[
|
||||||
|
USER_ADDR,
|
||||||
|
ONE_ETH,
|
||||||
|
1n,
|
||||||
|
encodeV3Path([WETH_ADDR, USDC_ADDR]),
|
||||||
|
false,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
const V2_WETH_FROM_ROUTER = coder.encode(
|
||||||
|
["address", "uint256", "uint256", "address[]", "bool"],
|
||||||
|
[USER_ADDR, ONE_ETH, 1n, [WETH_ADDR, USDC_ADDR], false],
|
||||||
|
);
|
||||||
|
const V4_WETH_FROM_ROUTER = encodeV4Swap(
|
||||||
|
new Uint8Array([
|
||||||
|
V4_SWAP_EXACT_IN_SINGLE_ID,
|
||||||
|
V4_SETTLE_ID,
|
||||||
|
V4_TAKE_ALL_ID,
|
||||||
|
]),
|
||||||
|
[
|
||||||
|
encodeV4ExactInSingle(WETH_ADDR, USDC_ADDR, ONE_ETH, 1n),
|
||||||
|
settle(WETH_ADDR, 0n, false),
|
||||||
|
encodeV4TakeAll(USDC_ADDR, 0n),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
// SWEEP has the layout of TRANSFER, and its amount is a minimum.
|
||||||
|
const SWEEP_WETH = payOut(WETH_ADDR, 0n);
|
||||||
|
test.each([
|
||||||
|
{
|
||||||
|
kind: "a V3 exact-in of 600 USDT, a WRAP_ETH of 1 ETH into the router, then a SWEEP of that WETH to another address",
|
||||||
|
commands: [0x00, 0x0b, 0x04],
|
||||||
|
inputs: [V3_600, WRAP_INTO_ROUTER, SWEEP_WETH],
|
||||||
|
lines: [
|
||||||
|
["Amount", "600.0000 USDT"],
|
||||||
|
...wrapLines("The router"),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
kind: "a V3 exact-in of 600 USDT, a WRAP_ETH of 1 ETH into the router, a SWEEP of that WETH to another address, then a V3 exact-in paid from the router's WETH",
|
||||||
|
commands: [0x00, 0x0b, 0x04, 0x00],
|
||||||
|
inputs: [
|
||||||
|
V3_600,
|
||||||
|
WRAP_INTO_ROUTER,
|
||||||
|
SWEEP_WETH,
|
||||||
|
V3_WETH_FROM_ROUTER,
|
||||||
|
],
|
||||||
|
lines: [
|
||||||
|
["Amount", "600.0000 USDT"],
|
||||||
|
...wrapLines("The router"),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
kind: "a V3 exact-in of 600 USDT, a WRAP_ETH of 1 ETH into the router, then a V3 exact-in of WETH that the user pays",
|
||||||
|
commands: [0x00, 0x0b, 0x00],
|
||||||
|
inputs: [
|
||||||
|
V3_600,
|
||||||
|
WRAP_INTO_ROUTER,
|
||||||
|
encodeV3SwapExactIn(USER_ADDR, ONE_ETH, 1n, [
|
||||||
|
WETH_ADDR,
|
||||||
|
USDC_ADDR,
|
||||||
|
]),
|
||||||
|
],
|
||||||
|
lines: [
|
||||||
|
["Amount", "600.0000 USDT"],
|
||||||
|
...wrapLines("The router"),
|
||||||
|
["Moved from the sender", "1.0000 WETH"],
|
||||||
|
["Token moved", "WETH (" + WETH_ADDR + ")"],
|
||||||
|
["Moved to", SWAP_STEP],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
kind: "a V3 exact-in of 600 USDT, a WRAP_ETH of 1 ETH into the router, a V3 exact-in paid from the router's WETH, then another WRAP_ETH of 1 ETH into the router",
|
||||||
|
commands: [0x00, 0x0b, 0x00, 0x0b],
|
||||||
|
inputs: [
|
||||||
|
V3_600,
|
||||||
|
WRAP_INTO_ROUTER,
|
||||||
|
V3_WETH_FROM_ROUTER,
|
||||||
|
WRAP_INTO_ROUTER,
|
||||||
|
],
|
||||||
|
lines: [
|
||||||
|
["Amount", "600.0000 USDT"],
|
||||||
|
...wrapLines("The router"),
|
||||||
|
...wrapLines("The router"),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
kind: "a V3 exact-in of 600 USDT, a WRAP_ETH of 1 ETH into the router, then a V3 exact-in paid from the router's WETH",
|
||||||
|
commands: [0x00, 0x0b, 0x00],
|
||||||
|
inputs: [V3_600, WRAP_INTO_ROUTER, V3_WETH_FROM_ROUTER],
|
||||||
|
lines: [
|
||||||
|
["Amount", "600.0000 USDT"],
|
||||||
|
...wrapLines("The router"),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
kind: "the same with a V2 exact-in paid from the router's WETH",
|
||||||
|
commands: [0x00, 0x0b, 0x08],
|
||||||
|
inputs: [V3_600, WRAP_INTO_ROUTER, V2_WETH_FROM_ROUTER],
|
||||||
|
lines: [
|
||||||
|
["Amount", "600.0000 USDT"],
|
||||||
|
...wrapLines("The router"),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
kind: "the same with a V3 exact-out paid from the router's WETH",
|
||||||
|
commands: [0x00, 0x0b, 0x01],
|
||||||
|
inputs: [
|
||||||
|
V3_600,
|
||||||
|
WRAP_INTO_ROUTER,
|
||||||
|
encodeV3SwapExactOut(
|
||||||
|
USER_ADDR,
|
||||||
|
390000000n,
|
||||||
|
ONE_ETH,
|
||||||
|
[USDC_ADDR, WETH_ADDR],
|
||||||
|
false,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
lines: [
|
||||||
|
["Amount", "Up to 600.0000 USDT"],
|
||||||
|
["Moved from the sender", "Up to 1.0000 ETH"],
|
||||||
|
["Token moved", "ETH"],
|
||||||
|
["Moved to", "The router"],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
kind: "the same with a V4 swap that the router's WETH settles",
|
||||||
|
commands: [0x00, 0x0b, 0x10],
|
||||||
|
inputs: [V3_600, WRAP_INTO_ROUTER, V4_WETH_FROM_ROUTER],
|
||||||
|
lines: [
|
||||||
|
["Amount", "600.0000 USDT"],
|
||||||
|
...wrapLines("The router"),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
kind: "a WRAP_ETH of 1 ETH into the router that sets the input side, a SWEEP of that WETH to another address, then an EXECUTE_SUB_PLAN",
|
||||||
|
commands: [0x0b, 0x04, 0x21],
|
||||||
|
inputs: [WRAP_INTO_ROUTER, SWEEP_WETH, SUB_PLAN],
|
||||||
|
lines: [
|
||||||
|
...wrapLines("The router"),
|
||||||
|
["Moved from the sender", UNDECODED],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
kind: "a WRAP_ETH of 1 ETH into the router that sets the input side, a V3 exact-in paid from the router's WETH, then an EXECUTE_SUB_PLAN",
|
||||||
|
commands: [0x0b, 0x00, 0x21],
|
||||||
|
inputs: [WRAP_INTO_ROUTER, V3_WETH_FROM_ROUTER, SUB_PLAN],
|
||||||
|
lines: [
|
||||||
|
...wrapLines("The router"),
|
||||||
|
["Moved from the sender", UNDECODED],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
kind: "a PERMIT2_PERMIT of 5,000 USDT to the router, a WRAP_ETH of 1 ETH into the router, then a V3 exact-in of 600 USDT",
|
||||||
|
commands: [0x0a, 0x0b, 0x00],
|
||||||
|
inputs: [
|
||||||
|
encodePermit2(USDT_ADDR, 5000000000n, ROUTER_ADDR),
|
||||||
|
WRAP_INTO_ROUTER,
|
||||||
|
V3_600,
|
||||||
|
],
|
||||||
|
lines: [
|
||||||
|
["Amount", "5000.0000 USDT"],
|
||||||
|
...wrapLines("The router"),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
kind: "the same with a V3 exact-in paid from the router's WETH after the WRAP_ETH",
|
||||||
|
commands: [0x0a, 0x0b, 0x00, 0x00],
|
||||||
|
inputs: [
|
||||||
|
encodePermit2(USDT_ADDR, 5000000000n, ROUTER_ADDR),
|
||||||
|
WRAP_INTO_ROUTER,
|
||||||
|
V3_WETH_FROM_ROUTER,
|
||||||
|
V3_600,
|
||||||
|
],
|
||||||
|
lines: [
|
||||||
|
["Amount", "5000.0000 USDT"],
|
||||||
|
...wrapLines("The router"),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
])("$kind", ({ commands, inputs, lines }) => {
|
||||||
|
const result = decodeSteps(commands, inputs);
|
||||||
|
expect(linesBetweenTokens(result)).toEqual(lines);
|
||||||
|
});
|
||||||
|
|
||||||
|
// https://git.eeqj.de/sneak/AutistMask/issues/747 — a later WRAP_ETH
|
||||||
|
// of CONTRACT_BALANCE wraps all the ETH the router holds. Unless an
|
||||||
|
// earlier step paid ETH to the router, that is the ETH the transaction
|
||||||
|
// carries. On next no line gave it. Into the router it gets no lines.
|
||||||
|
// It gets none after an UNWRAP_WETH into the router, or while the
|
||||||
|
// router holds ETH from a V4 step: Uniswap's own routes pay back what
|
||||||
|
// their swap did not spend this way, and carry no ETH. In the rows
|
||||||
|
// marked "reads as on next" that WRAP_ETH gets no lines.
|
||||||
|
const WRAP_ALL_TO_OTHER = encodeWrapEth(OTHER_ADDR, CONTRACT_BALANCE);
|
||||||
|
const WRAP_ALL_TO_SENDER = encodeWrapEth(MSG_SENDER, CONTRACT_BALANCE);
|
||||||
|
// The lines of such a WRAP_ETH whose Moved to line reads `to`.
|
||||||
|
const allEthLines = (to) => [
|
||||||
|
["Moved from the sender", "All the ETH the transaction carries"],
|
||||||
|
["Token moved", "ETH"],
|
||||||
|
["Moved to", to],
|
||||||
|
];
|
||||||
|
// The first steps of a route that spends WETH on a V4 pool of ETH:
|
||||||
|
// a PERMIT2_TRANSFER_FROM of 1 WETH into the router, an UNWRAP_WETH
|
||||||
|
// into the router, then a V4 exact-out paid from the router's ETH.
|
||||||
|
const UNWRAP_ROUTE = [
|
||||||
|
coder.encode(
|
||||||
|
["address", "address", "uint160"],
|
||||||
|
[WETH_ADDR, ADDRESS_THIS, ONE_ETH],
|
||||||
|
),
|
||||||
|
UNWRAP_INTO_ROUTER,
|
||||||
|
encodeV4Swap(
|
||||||
|
new Uint8Array([
|
||||||
|
V4_SWAP_EXACT_OUT_SINGLE_ID,
|
||||||
|
V4_SETTLE_ID,
|
||||||
|
V4_TAKE_ALL_ID,
|
||||||
|
]),
|
||||||
|
[
|
||||||
|
encodeV4ExactOutSingle(
|
||||||
|
ZERO_ADDR,
|
||||||
|
USDC_ADDR,
|
||||||
|
390000000n,
|
||||||
|
ONE_ETH,
|
||||||
|
),
|
||||||
|
settle(ZERO_ADDR, 0n, false),
|
||||||
|
encodeV4TakeAll(USDC_ADDR, 0n),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
];
|
||||||
|
// The lines of that route on next: this decoder cannot tell what the
|
||||||
|
// V4 step's SETTLE of ETH pays.
|
||||||
|
const UNWRAP_ROUTE_LINES = [
|
||||||
|
["Moved from the sender", UNDECODED],
|
||||||
|
["Token moved", "ETH"],
|
||||||
|
["Moved to", SWAP_STEP],
|
||||||
|
];
|
||||||
|
test.each([
|
||||||
|
{
|
||||||
|
kind: "a V3 exact-in of 600 USDT, then a WRAP_ETH of CONTRACT_BALANCE to another address",
|
||||||
|
commands: [0x00, 0x0b],
|
||||||
|
inputs: [V3_600, WRAP_ALL_TO_OTHER],
|
||||||
|
lines: [
|
||||||
|
["Amount", "600.0000 USDT"],
|
||||||
|
...allEthLines(OTHER_ADDR),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
kind: "a V3 exact-in of 600 USDT, then a WRAP_ETH of CONTRACT_BALANCE to the sender",
|
||||||
|
commands: [0x00, 0x0b],
|
||||||
|
inputs: [V3_600, WRAP_ALL_TO_SENDER],
|
||||||
|
lines: [
|
||||||
|
["Amount", "600.0000 USDT"],
|
||||||
|
...allEthLines("The sender"),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
kind: "a V3 exact-out of at most 600 USDT, then a WRAP_ETH of CONTRACT_BALANCE to another address",
|
||||||
|
commands: [0x01, 0x0b],
|
||||||
|
inputs: [
|
||||||
|
encodeV3SwapExactOut(USER_ADDR, 390000000n, USDT_600, [
|
||||||
|
USDC_ADDR,
|
||||||
|
USDT_ADDR,
|
||||||
|
]),
|
||||||
|
WRAP_ALL_TO_OTHER,
|
||||||
|
],
|
||||||
|
lines: [
|
||||||
|
["Amount", "Up to 600.0000 USDT"],
|
||||||
|
...allEthLines(OTHER_ADDR),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
kind: "a WRAP_ETH of 1 ETH into the router, then a WRAP_ETH of CONTRACT_BALANCE to another address",
|
||||||
|
commands: [0x0b, 0x0b],
|
||||||
|
inputs: [WRAP_INTO_ROUTER, WRAP_ALL_TO_OTHER],
|
||||||
|
lines: [["Amount", "1.0000 ETH"], ...allEthLines(OTHER_ADDR)],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
kind: "a V3 exact-in of 600 USDT, then a WRAP_ETH of CONTRACT_BALANCE into the router, reads as on next",
|
||||||
|
commands: [0x00, 0x0b],
|
||||||
|
inputs: [V3_600, encodeWrapEth(ADDRESS_THIS, CONTRACT_BALANCE)],
|
||||||
|
lines: [["Amount", "600.0000 USDT"]],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
kind: "a V3 exact-in of 600 USDT, an UNWRAP_WETH to the sender, then a WRAP_ETH of CONTRACT_BALANCE to another address",
|
||||||
|
commands: [0x00, 0x0c, 0x0b],
|
||||||
|
inputs: [
|
||||||
|
V3_600,
|
||||||
|
encodeWrapEth(MSG_SENDER, 0n),
|
||||||
|
WRAP_ALL_TO_OTHER,
|
||||||
|
],
|
||||||
|
lines: [
|
||||||
|
["Amount", "600.0000 USDT"],
|
||||||
|
...allEthLines(OTHER_ADDR),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
kind: "a V4 swap of 600 USDT that takes ETH into the router, then a WRAP_ETH of CONTRACT_BALANCE to the sender, reads as on next",
|
||||||
|
commands: [0x10, 0x0b],
|
||||||
|
inputs: [
|
||||||
|
encodeV4Swap(
|
||||||
|
new Uint8Array([
|
||||||
|
V4_SWAP_EXACT_IN_SINGLE_ID,
|
||||||
|
V4_SETTLE_ID,
|
||||||
|
V4_TAKE_ID,
|
||||||
|
]),
|
||||||
|
[
|
||||||
|
encodeV4ExactInSingle(
|
||||||
|
USDT_ADDR,
|
||||||
|
ZERO_ADDR,
|
||||||
|
USDT_600,
|
||||||
|
1n,
|
||||||
|
),
|
||||||
|
settle(USDT_ADDR, 0n, true),
|
||||||
|
take(ZERO_ADDR, ADDRESS_THIS, 0n),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
WRAP_ALL_TO_SENDER,
|
||||||
|
],
|
||||||
|
lines: [["Amount", "600.0000 USDT"]],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
kind: "the unwrap route, then a WRAP_ETH of CONTRACT_BALANCE to the sender, reads as on next",
|
||||||
|
commands: [0x02, 0x0c, 0x10, 0x0b],
|
||||||
|
inputs: [...UNWRAP_ROUTE, WRAP_ALL_TO_SENDER],
|
||||||
|
lines: UNWRAP_ROUTE_LINES,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
kind: "the unwrap route, then a WRAP_ETH of CONTRACT_BALANCE to another address, reads as on next",
|
||||||
|
commands: [0x02, 0x0c, 0x10, 0x0b],
|
||||||
|
inputs: [...UNWRAP_ROUTE, WRAP_ALL_TO_OTHER],
|
||||||
|
lines: UNWRAP_ROUTE_LINES,
|
||||||
|
},
|
||||||
|
])("$kind", ({ commands, inputs, lines }) => {
|
||||||
|
const result = decodeSteps(commands, inputs);
|
||||||
|
expect(linesBetweenTokens(result)).toEqual(lines);
|
||||||
|
});
|
||||||
|
|
||||||
// A V3_SWAP_EXACT_OUT the user pays for, of at most `amountInMax`
|
// A V3_SWAP_EXACT_OUT the user pays for, of at most `amountInMax`
|
||||||
// USDT for USDC.
|
// USDT for USDC.
|
||||||
const v3ExactOut = (amountInMax) =>
|
const v3ExactOut = (amountInMax) =>
|
||||||
|
|||||||
Reference in New Issue
Block a user