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:
+356
-21
@@ -2474,10 +2474,11 @@ describe("uniswap decoder", () => {
|
||||
// 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
|
||||
// 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
|
||||
// rows marked "reads as on next" read the same on next. On next every
|
||||
// other row added that ETH to the Amount line, or read it as an exact
|
||||
// amount.
|
||||
// Every row reads at least the ETH that can leave the wallet. The row
|
||||
// marked "reads as on next" reads the same on next. The row with a
|
||||
// SETTLE_ALL of at most 3 ETH read that ETH as not known on next. On
|
||||
// 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.
|
||||
const ADDRESS_THIS = "0x0000000000000000000000000000000000000002";
|
||||
@@ -2625,7 +2626,7 @@ describe("uniswap decoder", () => {
|
||||
],
|
||||
lines: [
|
||||
["Amount", "600.0000 USDT"],
|
||||
...wrapLines("The swap step that spends it"),
|
||||
...wrapLines("The router"),
|
||||
...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],
|
||||
inputs: [
|
||||
WRAP_INTO_ROUTER,
|
||||
@@ -2697,10 +2698,7 @@ describe("uniswap decoder", () => {
|
||||
],
|
||||
),
|
||||
],
|
||||
lines: [
|
||||
...wrapLines("The swap step that spends it"),
|
||||
...ethLines(UNDECODED),
|
||||
],
|
||||
lines: [...wrapLines("The router"), ...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",
|
||||
@@ -2761,8 +2759,8 @@ describe("uniswap decoder", () => {
|
||||
// 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
|
||||
// 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
|
||||
// do.
|
||||
// wrap CONTRACT_BALANCE into the router after their first step, as the
|
||||
// last row does.
|
||||
const CONTRACT_BALANCE = 2n ** 255n;
|
||||
const WRAP_TO_OTHER = encodeWrapEth(OTHER_ADDR, ONE_ETH);
|
||||
test.each([
|
||||
@@ -2787,7 +2785,7 @@ describe("uniswap decoder", () => {
|
||||
inputs: [V3_600, WRAP_INTO_ROUTER],
|
||||
lines: [
|
||||
["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)],
|
||||
lines: [
|
||||
["Amount", "600.0000 USDT"],
|
||||
...wrapLines("The swap step that spends it"),
|
||||
...wrapLines("The router"),
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -2839,7 +2837,9 @@ describe("uniswap decoder", () => {
|
||||
],
|
||||
lines: [
|
||||
["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)],
|
||||
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",
|
||||
commands: [0x10, 0x0b, 0x00],
|
||||
@@ -2923,6 +2917,347 @@ describe("uniswap decoder", () => {
|
||||
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`
|
||||
// USDT for USDC.
|
||||
const v3ExactOut = (amountInMax) =>
|
||||
|
||||
Reference in New Issue
Block a user