fix: a WRAP_ETH says where its ETH goes when no swap step spends it (closes #747)
The `Moved to` line of a WRAP_ETH into the router now reads `The router`, unless a later swap step pays from that WETH. 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 no lines after an earlier step paid ETH to the router, because Uniswap's routes pay back unspent ETH this way. README states the new rule. Model: opus-5-5
This commit is contained in:
@@ -1416,8 +1416,10 @@ when both tokens are known. The lines are:
|
|||||||
after the step that sets `Token In` takes the amount it states from the
|
after the step that sets `Token In` takes the amount it states from the
|
||||||
sender. When it pays anyone but the router, that amount gets these lines. When
|
sender. When it pays anyone but the router, that amount gets these lines. When
|
||||||
it pays the router, that amount joins the `Amount` line if `Token In` is ETH,
|
it pays the router, that amount joins the `Amount` line if `Token In` is ETH,
|
||||||
and gets these lines if not. A later `WRAP_ETH` of `CONTRACT_BALANCE` states
|
and gets these lines if not. A later `WRAP_ETH` of `CONTRACT_BALANCE` to
|
||||||
no amount, so it gets no lines. ETH that the sender pays a V4 step or a
|
anyone but the router gets these lines, unless an earlier step paid ETH to the
|
||||||
|
router. Its `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
|
`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
|
hold ETH from an earlier step. When the decoder knows the amount, its
|
||||||
`Moved from the sender` line reads `Up to` and the amount.
|
`Moved from the sender` line reads `Up to` and the amount.
|
||||||
|
|||||||
+90
-31
@@ -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".
|
||||||
@@ -1271,6 +1277,8 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
|
|||||||
// when it is not the router; otherwise null. That WRAP_ETH pays its
|
// when it is not the router; otherwise null. That WRAP_ETH pays its
|
||||||
// WETH there, not to a swap step (see the Moved to lines).
|
// WETH there, not to a swap step (see the Moved to lines).
|
||||||
let wrapEthRecipient = null;
|
let wrapEthRecipient = null;
|
||||||
|
// The index of the WRAP_ETH that set the input side, or null.
|
||||||
|
let wrapEthStep = 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).
|
||||||
let permit = null;
|
let permit = null;
|
||||||
@@ -1289,6 +1297,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
|
||||||
@@ -1308,6 +1319,10 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
|
|||||||
// other than WETH, and not of ETH to a WETH contract, which pays the
|
// other than WETH, and not of ETH to a WETH contract, which pays the
|
||||||
// router WETH (see keepsRouterWeth in the loop). None of those can.
|
// router WETH (see keepsRouterWeth in the loop). None of those can.
|
||||||
let lastWethStep = -1;
|
let lastWethStep = -1;
|
||||||
|
// The index of the last swap step paid from WETH the router holds, a
|
||||||
|
// V4 SETTLE included, or -1 (see spendFromRouter()). Only such a step
|
||||||
|
// spends the WETH of an earlier WRAP_ETH into the router.
|
||||||
|
let lastRouterWethSwapStep = -1;
|
||||||
// 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,
|
||||||
@@ -1370,7 +1385,9 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
|
|||||||
// `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`, the ETH of a
|
||||||
// later WRAP_ETH that pays anyone but the router, never joins the
|
// later WRAP_ETH that pays anyone but the router, never joins the
|
||||||
// Amount line either: its Moved to line names that recipient.
|
// Amount line either: its Moved to line names that recipient. The
|
||||||
|
// take of a later WRAP_ETH of a stated amount has that step's index as
|
||||||
|
// `wrapStep`.
|
||||||
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
|
||||||
@@ -1445,17 +1462,20 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
|
|||||||
const r = recipient.toLowerCase();
|
const r = recipient.toLowerCase();
|
||||||
return r === MSG_SENDER || r === sender;
|
return r === MSG_SENDER || r === sender;
|
||||||
};
|
};
|
||||||
// A swap step or V4 SETTLE pays `amount` of `token` from the router's
|
// A swap step or V4 SETTLE at `step` pays `amount` of `token` from the
|
||||||
// own balance. CONTRACT_BALANCE pays all of it. Any other quantity
|
// router's own balance. CONTRACT_BALANCE pays all of it. Any other
|
||||||
// counts against the amount a WRAP_ETH stated, and the steps pay all
|
// quantity counts against the amount a WRAP_ETH stated, and the steps
|
||||||
// of it once their amounts reach that one: a swap paid in ETH wraps
|
// pay all of it once their amounts reach that one: a swap paid in ETH
|
||||||
// what its swap steps then spend. An amount that is not a quantity,
|
// wraps what its swap steps then spend. An amount that is not a
|
||||||
// such as the null decodeV4Swap() gives a SETTLE whose payment it
|
// quantity, such as the null decodeV4Swap() gives a SETTLE whose
|
||||||
// cannot tell, or one of a token whose amount is not known, pays out
|
// payment it cannot tell, or one of a token whose amount is not known,
|
||||||
// nothing this decoder can see.
|
// pays out nothing this decoder can see.
|
||||||
const spendFromRouter = (token, amount) => {
|
const spendFromRouter = (step, token, amount) => {
|
||||||
const t = token.toLowerCase();
|
const t = token.toLowerCase();
|
||||||
const held = heldByRouter.get(t);
|
const held = heldByRouter.get(t);
|
||||||
|
if (t === WETH.get(chainId) && heldByRouter.has(t)) {
|
||||||
|
lastRouterWethSwapStep = step;
|
||||||
|
}
|
||||||
if (amount === CONTRACT_BALANCE) {
|
if (amount === CONTRACT_BALANCE) {
|
||||||
heldByRouter.delete(t);
|
heldByRouter.delete(t);
|
||||||
} else if (typeof held === "bigint" && typeof amount === "bigint") {
|
} else if (typeof held === "bigint" && typeof amount === "bigint") {
|
||||||
@@ -1741,7 +1761,7 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
|
|||||||
[s.recipient],
|
[s.recipient],
|
||||||
);
|
);
|
||||||
if (!s.payerIsUser) {
|
if (!s.payerIsUser) {
|
||||||
spendFromRouter(s.tokenIn, s.amountIn);
|
spendFromRouter(i, s.tokenIn, s.amountIn);
|
||||||
}
|
}
|
||||||
if (!isHop(i, s.tokenOut, s.recipient)) {
|
if (!isHop(i, s.tokenOut, s.recipient)) {
|
||||||
addPayee(s.recipient, s.tokenOut);
|
addPayee(s.recipient, s.tokenOut);
|
||||||
@@ -1770,7 +1790,7 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
|
|||||||
[s.recipient],
|
[s.recipient],
|
||||||
);
|
);
|
||||||
if (!s.payerIsUser) {
|
if (!s.payerIsUser) {
|
||||||
spendFromRouter(s.tokenIn, s.amountIn);
|
spendFromRouter(i, s.tokenIn, s.amountIn);
|
||||||
}
|
}
|
||||||
if (!isHop(i, s.tokenOut, s.recipient)) {
|
if (!isHop(i, s.tokenOut, s.recipient)) {
|
||||||
addPayee(s.recipient, s.tokenOut);
|
addPayee(s.recipient, s.tokenOut);
|
||||||
@@ -1786,8 +1806,7 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
|
|||||||
) {
|
) {
|
||||||
// Buys exactly amountOut and spends at most amountInMax.
|
// Buys exactly amountOut and spends at most amountInMax.
|
||||||
// Paid from the router, it spends an amount the calldata
|
// Paid from the router, it spends an amount the calldata
|
||||||
// does not state, so it spends nothing spendFromRouter()
|
// does not state, so spendFromRouter() has none to count.
|
||||||
// could count.
|
|
||||||
hasV2OrV3ExactOut = true;
|
hasV2OrV3ExactOut = true;
|
||||||
const s =
|
const s =
|
||||||
command === "V2_SWAP_EXACT_OUT"
|
command === "V2_SWAP_EXACT_OUT"
|
||||||
@@ -1809,6 +1828,9 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
|
|||||||
]),
|
]),
|
||||||
[s.recipient],
|
[s.recipient],
|
||||||
);
|
);
|
||||||
|
if (!s.payerIsUser) {
|
||||||
|
spendFromRouter(i, s.tokenIn, null);
|
||||||
|
}
|
||||||
if (!isHop(i, s.tokenOut, s.recipient)) {
|
if (!isHop(i, s.tokenOut, s.recipient)) {
|
||||||
addPayee(s.recipient, s.tokenOut);
|
addPayee(s.recipient, s.tokenOut);
|
||||||
}
|
}
|
||||||
@@ -1906,6 +1928,7 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
|
|||||||
w.amount,
|
w.amount,
|
||||||
);
|
);
|
||||||
inputPaidByUser = true;
|
inputPaidByUser = true;
|
||||||
|
wrapEthStep = i;
|
||||||
if (!isRouter(w.recipient)) {
|
if (!isRouter(w.recipient)) {
|
||||||
wrapEthRecipient = w.recipient;
|
wrapEthRecipient = w.recipient;
|
||||||
}
|
}
|
||||||
@@ -1916,8 +1939,7 @@ 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,
|
||||||
@@ -1927,6 +1949,25 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
|
|||||||
recipient: isRouter(w.recipient)
|
recipient: isRouter(w.recipient)
|
||||||
? null
|
? null
|
||||||
: w.recipient,
|
: w.recipient,
|
||||||
|
wrapStep: i,
|
||||||
|
});
|
||||||
|
} else if (
|
||||||
|
w.amount === CONTRACT_BALANCE &&
|
||||||
|
!isRouter(w.recipient) &&
|
||||||
|
!unwrappedIntoRouter &&
|
||||||
|
!heldByRouter.has(
|
||||||
|
"0x0000000000000000000000000000000000000000",
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
// It pays the recipient all the ETH the transaction
|
||||||
|
// carries. After an earlier step paid ETH to the
|
||||||
|
// router there is no line: 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);
|
||||||
@@ -1996,7 +2037,7 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
|
|||||||
// 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.
|
||||||
for (const s of v4.settledFromRouter) {
|
for (const s of v4.settledFromRouter) {
|
||||||
spendFromRouter(s.currency, s.amount);
|
spendFromRouter(i, s.currency, s.amount);
|
||||||
}
|
}
|
||||||
for (const t of v4.takes) {
|
for (const t of v4.takes) {
|
||||||
if (!isHop(i, t.currency, t.recipient)) {
|
if (!isHop(i, t.currency, t.recipient)) {
|
||||||
@@ -2169,6 +2210,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",
|
||||||
@@ -2264,10 +2308,11 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
|
|||||||
permit.beforeEveryTake &&
|
permit.beforeEveryTake &&
|
||||||
!stepsUnread.some((step) => step > permit.step);
|
!stepsUnread.some((step) => step > permit.step);
|
||||||
// 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, wrapStep }. A null token is a
|
||||||
// decoder did not read (see spentUnread()), or a token it does not
|
// step this decoder did not read (see spentUnread()), or a token it
|
||||||
// know. `atMost` is as in `userPaid`. Only the take of a WRAP_ETH that
|
// does not know. `atMost` is as in `userPaid`. Only the take of a
|
||||||
// pays elsewhere has a recipient (see wrapEthRecipient and userPaid).
|
// WRAP_ETH that pays elsewhere has a recipient (see wrapEthRecipient
|
||||||
|
// and userPaid). Only the take of a WRAP_ETH has a `wrapStep`.
|
||||||
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
|
||||||
@@ -2292,6 +2337,7 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
|
|||||||
token: inputToken,
|
token: inputToken,
|
||||||
amount: inputAmount,
|
amount: inputAmount,
|
||||||
recipient: wrapEthRecipient,
|
recipient: wrapEthRecipient,
|
||||||
|
wrapStep: wrapEthStep,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (hasFirstTake && !present(inputAmount)) {
|
if (hasFirstTake && !present(inputAmount)) {
|
||||||
@@ -2304,6 +2350,7 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
|
|||||||
token: inputToken,
|
token: inputToken,
|
||||||
amount: inputAmount,
|
amount: inputAmount,
|
||||||
recipient: wrapEthRecipient,
|
recipient: wrapEthRecipient,
|
||||||
|
wrapStep: wrapEthStep,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (permitStands) inputToken = permit.token;
|
if (permitStands) inputToken = permit.token;
|
||||||
@@ -2413,13 +2460,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 +2478,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 +2543,9 @@ 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. The take of a WRAP_ETH into the router goes to the
|
||||||
// `MSG_SENDER` means the sender.
|
// router, unless a later swap step spends its WETH. A take with a
|
||||||
|
// recipient goes to that recipient; `MSG_SENDER` means the sender.
|
||||||
for (const s of spentFromSender) {
|
for (const s of spentFromSender) {
|
||||||
details.push({
|
details.push({
|
||||||
label: "Moved from the sender",
|
label: "Moved from the sender",
|
||||||
@@ -2512,7 +2567,11 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
|
|||||||
if (!present(s.recipient)) {
|
if (!present(s.recipient)) {
|
||||||
details.push({
|
details.push({
|
||||||
label: "Moved to",
|
label: "Moved to",
|
||||||
value: "The swap step that spends it",
|
value:
|
||||||
|
present(s.wrapStep) &&
|
||||||
|
s.wrapStep > lastRouterWethSwapStep
|
||||||
|
? "The router"
|
||||||
|
: "The swap step that spends it",
|
||||||
});
|
});
|
||||||
} 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" });
|
||||||
|
|||||||
+334
-21
@@ -2474,10 +2474,9 @@ 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. On next every other
|
||||||
// other row added that ETH to the Amount line, or read it as an exact
|
// row added that ETH to the Amount line, or read it as an exact amount.
|
||||||
// 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 +2624,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 +2673,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 +2696,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 +2757,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 +2783,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 +2792,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 +2835,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 +2875,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 +2915,327 @@ describe("uniswap decoder", () => {
|
|||||||
expect(linesBetweenTokens(result)).toEqual(lines);
|
expect(linesBetweenTokens(result)).toEqual(lines);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// https://git.eeqj.de/sneak/AutistMask/issues/747 — the WETH of a
|
||||||
|
// WRAP_ETH into the router goes to a swap step only when a later swap
|
||||||
|
// step paid from the router spends it. On next every row not marked
|
||||||
|
// "reads as on next" read The swap step that spends it.
|
||||||
|
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(SWAP_STEP),
|
||||||
|
...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, reads as on next",
|
||||||
|
commands: [0x00, 0x0b, 0x00],
|
||||||
|
inputs: [V3_600, WRAP_INTO_ROUTER, V3_WETH_FROM_ROUTER],
|
||||||
|
lines: [["Amount", "600.0000 USDT"], ...wrapLines(SWAP_STEP)],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
kind: "the same with a V2 exact-in paid from the router's WETH, reads as on next",
|
||||||
|
commands: [0x00, 0x0b, 0x08],
|
||||||
|
inputs: [V3_600, WRAP_INTO_ROUTER, V2_WETH_FROM_ROUTER],
|
||||||
|
lines: [["Amount", "600.0000 USDT"], ...wrapLines(SWAP_STEP)],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
kind: "the same with a V3 exact-out paid from the router's WETH, reads as on next",
|
||||||
|
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", SWAP_STEP],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
kind: "the same with a V4 swap that the router's WETH settles, reads as on next",
|
||||||
|
commands: [0x00, 0x0b, 0x10],
|
||||||
|
inputs: [V3_600, WRAP_INTO_ROUTER, V4_WETH_FROM_ROUTER],
|
||||||
|
lines: [["Amount", "600.0000 USDT"], ...wrapLines(SWAP_STEP)],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
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, reads as on next",
|
||||||
|
commands: [0x0b, 0x00, 0x21],
|
||||||
|
inputs: [WRAP_INTO_ROUTER, V3_WETH_FROM_ROUTER, SUB_PLAN],
|
||||||
|
lines: [
|
||||||
|
...wrapLines(SWAP_STEP),
|
||||||
|
["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, reads as on next",
|
||||||
|
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(SWAP_STEP)],
|
||||||
|
},
|
||||||
|
])("$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. Uniswap's own routes pay back what
|
||||||
|
// their swap did not spend this way, and carry no ETH, so 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, 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