fix: the swap decoder names where a WRAP_ETH pays and shows a V4 step's ETH as a maximum when the router may hold ETH (closes #665)
A `WRAP_ETH` that sets the input side and pays its WETH to anyone but the router names that recipient on its `Moved to` line. V4 pays a `SETTLE` of ETH from the router's own ETH, whichever payer it names. When an earlier step may leave ETH in the router, the ETH that the user pays a later V4 step gets lines of its own. Their `Moved from the sender` line reads `Up to <amount>`, or `Unknown (could not be decoded)` when the amount is not known. The decoder never subtracts the router's ETH from that amount. Without such an earlier step, the lines read as before. Model: opus-5-5
This commit is contained in:
+332
-3
@@ -1428,8 +1428,8 @@ describe("uniswap decoder", () => {
|
||||
];
|
||||
|
||||
// execute() of these command bytes and inputs, decoded as sent by
|
||||
// the user.
|
||||
function decodeSteps(commands, inputs) {
|
||||
// the user to `router`.
|
||||
function decodeSteps(commands, inputs, router = ROUTER_ADDR) {
|
||||
const data = buildExecute(
|
||||
solidityPacked(
|
||||
commands.map(() => "uint8"),
|
||||
@@ -1438,7 +1438,7 @@ describe("uniswap decoder", () => {
|
||||
inputs,
|
||||
9999999999n,
|
||||
);
|
||||
return uniswap.decode(data, ROUTER_ADDR, "0x1", {}, USER_ADDR);
|
||||
return uniswap.decode(data, router, "0x1", {}, USER_ADDR);
|
||||
}
|
||||
|
||||
// The first step: a V3 exact-in of 600 USDT for USDC, paid by the
|
||||
@@ -2424,6 +2424,335 @@ describe("uniswap decoder", () => {
|
||||
expect(linesBetweenTokens(result)).toEqual(lines);
|
||||
});
|
||||
|
||||
// https://git.eeqj.de/sneak/AutistMask/issues/665 — a WRAP_ETH that
|
||||
// sets the input side and pays its WETH to anyone but the router
|
||||
// names that recipient on its Moved to line. On next each row read
|
||||
// The swap step that spends it.
|
||||
const ONE_ETH = 1000000000000000000n;
|
||||
const MSG_SENDER = "0x0000000000000000000000000000000000000001";
|
||||
// The lines of a WRAP_ETH of 1 ETH whose Moved to line reads `to`.
|
||||
const wrapLines = (to) => [
|
||||
["Moved from the sender", "1.0000 ETH"],
|
||||
["Token moved", "ETH"],
|
||||
["Moved to", to],
|
||||
];
|
||||
test.each([
|
||||
{
|
||||
kind: "a WRAP_ETH of 1 ETH to another address, then an EXECUTE_SUB_PLAN",
|
||||
commands: [0x0b, 0x21],
|
||||
inputs: [encodeWrapEth(OTHER_ADDR, ONE_ETH), SUB_PLAN],
|
||||
lines: [
|
||||
...wrapLines(OTHER_ADDR),
|
||||
["Moved from the sender", UNDECODED],
|
||||
],
|
||||
},
|
||||
{
|
||||
kind: "a WRAP_ETH of 1 ETH to address(1), then an EXECUTE_SUB_PLAN",
|
||||
commands: [0x0b, 0x21],
|
||||
inputs: [encodeWrapEth(MSG_SENDER, ONE_ETH), SUB_PLAN],
|
||||
lines: [
|
||||
...wrapLines("The sender"),
|
||||
["Moved from the sender", UNDECODED],
|
||||
],
|
||||
},
|
||||
{
|
||||
kind: "a PERMIT2_PERMIT of 5,000 USDT to the router, a WRAP_ETH of 1 ETH to another address, then a V3 exact-in of 600 USDT",
|
||||
commands: [0x0a, 0x0b, 0x00],
|
||||
inputs: [
|
||||
encodePermit2(USDT_ADDR, 5000000000n, ROUTER_ADDR),
|
||||
encodeWrapEth(OTHER_ADDR, ONE_ETH),
|
||||
V3_600,
|
||||
],
|
||||
lines: [["Amount", "5000.0000 USDT"], ...wrapLines(OTHER_ADDR)],
|
||||
},
|
||||
])("$kind", ({ commands, inputs, lines }) => {
|
||||
const result = decodeSteps(commands, inputs);
|
||||
expect(linesBetweenTokens(result)).toEqual(lines);
|
||||
});
|
||||
|
||||
// https://git.eeqj.de/sneak/AutistMask/issues/665 — V4Router pays a
|
||||
// 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.
|
||||
//
|
||||
// ADDRESS_THIS is the router on every release.
|
||||
const ADDRESS_THIS = "0x0000000000000000000000000000000000000002";
|
||||
const WRAP_INTO_ROUTER = encodeWrapEth(ADDRESS_THIS, ONE_ETH);
|
||||
// UNWRAP_WETH has the layout of WRAP_ETH.
|
||||
const UNWRAP_INTO_ROUTER = encodeWrapEth(ADDRESS_THIS, 0n);
|
||||
// A V4 step swapping `amount` of ETH for USDC, with a SETTLE of
|
||||
// OPEN_DELTA by the user.
|
||||
const v4EthForUsdc = (amount) =>
|
||||
encodeV4Swap(
|
||||
new Uint8Array([
|
||||
V4_SWAP_EXACT_IN_SINGLE_ID,
|
||||
V4_SETTLE_ID,
|
||||
V4_TAKE_ALL_ID,
|
||||
]),
|
||||
[
|
||||
encodeV4ExactInSingle(ZERO_ADDR, USDC_ADDR, amount, 1n),
|
||||
settle(ZERO_ADDR, 0n, true),
|
||||
encodeV4TakeAll(USDC_ADDR, 0n),
|
||||
],
|
||||
);
|
||||
const V4_ONE_ETH = v4EthForUsdc(ONE_ETH);
|
||||
// A TRANSFER or PAY_PORTION of `amount` of `token` to another
|
||||
// address.
|
||||
const payOut = (token, amount) =>
|
||||
coder.encode(
|
||||
["address", "address", "uint256"],
|
||||
[token, OTHER_ADDR, amount],
|
||||
);
|
||||
// An ACROSS_V4_DEPOSIT_V3 of `amount` of ETH (`useNative` set), from
|
||||
// the sender to the sender on chain id 8453.
|
||||
const acrossEth = (amount) =>
|
||||
coder.encode(
|
||||
[
|
||||
"tuple(address,address,address,address,uint256,uint256,uint256,address,uint32,uint32,uint32,bytes,bool)",
|
||||
],
|
||||
[
|
||||
[
|
||||
USER_ADDR,
|
||||
USER_ADDR,
|
||||
WETH_ADDR,
|
||||
WETH_ADDR,
|
||||
amount,
|
||||
1n,
|
||||
8453n,
|
||||
ZERO_ADDR,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
"0x",
|
||||
true,
|
||||
],
|
||||
],
|
||||
);
|
||||
// The lines of ETH the sender pays a V4 step, of `amount`.
|
||||
const ethLines = (amount) => [
|
||||
["Moved from the sender", amount],
|
||||
["Token moved", "ETH"],
|
||||
["Moved to", "The swap step that spends it"],
|
||||
];
|
||||
test.each([
|
||||
{
|
||||
kind: "a WRAP_ETH of 1 ETH into the router, then a V4 swap of 1 ETH that the user settles, reads as on next",
|
||||
commands: [0x0b, 0x10],
|
||||
inputs: [WRAP_INTO_ROUTER, V4_ONE_ETH],
|
||||
lines: [["Amount", "2.0000 ETH"]],
|
||||
},
|
||||
{
|
||||
kind: "a WRAP_ETH of 1 ETH into the router, an UNWRAP_WETH into the router, then a V4 swap of 1 ETH that the user settles",
|
||||
commands: [0x0b, 0x0c, 0x10],
|
||||
inputs: [WRAP_INTO_ROUTER, UNWRAP_INTO_ROUTER, V4_ONE_ETH],
|
||||
lines: [
|
||||
["Amount", "1.0000 ETH"],
|
||||
...ethLines("Up to 1.0000 ETH"),
|
||||
],
|
||||
},
|
||||
{
|
||||
kind: "the same with a TRANSFER of 1 ETH to another address after the UNWRAP_WETH",
|
||||
commands: [0x0b, 0x0c, 0x05, 0x10],
|
||||
inputs: [
|
||||
WRAP_INTO_ROUTER,
|
||||
UNWRAP_INTO_ROUTER,
|
||||
payOut(ZERO_ADDR, ONE_ETH),
|
||||
V4_ONE_ETH,
|
||||
],
|
||||
lines: [
|
||||
["Amount", "1.0000 ETH"],
|
||||
...ethLines("Up to 1.0000 ETH"),
|
||||
],
|
||||
},
|
||||
{
|
||||
kind: "the same with a PAY_PORTION of 5,000 bips of WETH to another address before the UNWRAP_WETH",
|
||||
commands: [0x0b, 0x06, 0x0c, 0x10],
|
||||
inputs: [
|
||||
WRAP_INTO_ROUTER,
|
||||
payOut(WETH_ADDR, 5000n),
|
||||
UNWRAP_INTO_ROUTER,
|
||||
V4_ONE_ETH,
|
||||
],
|
||||
lines: [
|
||||
["Amount", "1.0000 ETH"],
|
||||
...ethLines("Up to 1.0000 ETH"),
|
||||
],
|
||||
},
|
||||
{
|
||||
kind: "the same with a WRAP_ETH of 1 ETH to another address after the UNWRAP_WETH",
|
||||
commands: [0x0b, 0x0c, 0x0b, 0x10],
|
||||
inputs: [
|
||||
WRAP_INTO_ROUTER,
|
||||
UNWRAP_INTO_ROUTER,
|
||||
encodeWrapEth(OTHER_ADDR, ONE_ETH),
|
||||
V4_ONE_ETH,
|
||||
],
|
||||
lines: [
|
||||
["Amount", "1.0000 ETH"],
|
||||
...ethLines("Up to 1.0000 ETH"),
|
||||
],
|
||||
},
|
||||
{
|
||||
kind: "the same on UniversalRouterV2_1_2 with an ACROSS_V4_DEPOSIT_V3 of 1 ETH after the UNWRAP_WETH",
|
||||
router: ROUTER_V2_1_2_ADDR,
|
||||
commands: [0x0b, 0x0c, 0x40, 0x10],
|
||||
inputs: [
|
||||
WRAP_INTO_ROUTER,
|
||||
UNWRAP_INTO_ROUTER,
|
||||
acrossEth(ONE_ETH),
|
||||
V4_ONE_ETH,
|
||||
],
|
||||
lines: [
|
||||
["Amount", "1.0000 ETH"],
|
||||
...ethLines("Up to 1.0000 ETH"),
|
||||
],
|
||||
},
|
||||
{
|
||||
kind: "a V3 exact-in of 600 USDT that the user pays, a WRAP_ETH of 1 ETH into the router, an UNWRAP_WETH into the router, then a V4 swap of 1 ETH that the user settles",
|
||||
commands: [0x00, 0x0b, 0x0c, 0x10],
|
||||
inputs: [
|
||||
V3_600,
|
||||
WRAP_INTO_ROUTER,
|
||||
UNWRAP_INTO_ROUTER,
|
||||
V4_ONE_ETH,
|
||||
],
|
||||
lines: [
|
||||
["Amount", "600.0000 USDT"],
|
||||
...ethLines("Up to 1.0000 ETH"),
|
||||
],
|
||||
},
|
||||
{
|
||||
kind: "a V3 exact-in of 600 USDT for WETH paid to the router, an UNWRAP_WETH into the router, then a V4 swap of 1 ETH that the user settles",
|
||||
commands: [0x00, 0x0c, 0x10],
|
||||
inputs: [
|
||||
encodeV3SwapExactIn(ADDRESS_THIS, USDT_600, 1n, [
|
||||
USDT_ADDR,
|
||||
WETH_ADDR,
|
||||
]),
|
||||
UNWRAP_INTO_ROUTER,
|
||||
V4_ONE_ETH,
|
||||
],
|
||||
lines: [
|
||||
["Amount", "600.0000 USDT"],
|
||||
...ethLines("Up to 1.0000 ETH"),
|
||||
],
|
||||
},
|
||||
{
|
||||
kind: "a V4 swap of 600 USDT that takes ETH into the router, then a V4 swap of 5 ETH that the user settles",
|
||||
commands: [0x10, 0x10],
|
||||
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),
|
||||
],
|
||||
),
|
||||
v4EthForUsdc(5n * ONE_ETH),
|
||||
],
|
||||
lines: [
|
||||
["Amount", "600.0000 USDT"],
|
||||
...ethLines("Up to 5.0000 ETH"),
|
||||
],
|
||||
},
|
||||
{
|
||||
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",
|
||||
commands: [0x0b, 0x0c, 0x10],
|
||||
inputs: [
|
||||
WRAP_INTO_ROUTER,
|
||||
UNWRAP_INTO_ROUTER,
|
||||
encodeV4Swap(
|
||||
new Uint8Array([
|
||||
V4_SWAP_EXACT_OUT_SINGLE_ID,
|
||||
V4_SETTLE_ALL_ID,
|
||||
V4_TAKE_ALL_ID,
|
||||
]),
|
||||
[
|
||||
encodeV4ExactOutSingle(
|
||||
ZERO_ADDR,
|
||||
USDC_ADDR,
|
||||
390000000n,
|
||||
3n * ONE_ETH,
|
||||
),
|
||||
encodeV4SettleAll(ZERO_ADDR, 3n * ONE_ETH),
|
||||
encodeV4TakeAll(USDC_ADDR, 0n),
|
||||
],
|
||||
),
|
||||
],
|
||||
lines: [
|
||||
...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",
|
||||
commands: [0x0b, 0x01, 0x0c, 0x10],
|
||||
inputs: [
|
||||
WRAP_INTO_ROUTER,
|
||||
encodeV3SwapExactOut(
|
||||
USER_ADDR,
|
||||
390000000n,
|
||||
ONE_ETH,
|
||||
[USDC_ADDR, WETH_ADDR],
|
||||
false,
|
||||
),
|
||||
UNWRAP_INTO_ROUTER,
|
||||
V4_ONE_ETH,
|
||||
],
|
||||
lines: [
|
||||
["Amount", "Up to 1.0000 ETH"],
|
||||
...ethLines("Up to 1.0000 ETH"),
|
||||
],
|
||||
},
|
||||
{
|
||||
kind: "a WRAP_ETH of 1 ETH into the router, a V4 exact-out paid from the router's WETH, an UNWRAP_WETH into the router, then a V4 swap of 1 ETH that the user settles",
|
||||
commands: [0x0b, 0x10, 0x0c, 0x10],
|
||||
inputs: [
|
||||
WRAP_INTO_ROUTER,
|
||||
encodeV4Swap(
|
||||
new Uint8Array([
|
||||
V4_SWAP_EXACT_OUT_SINGLE_ID,
|
||||
V4_SETTLE_ID,
|
||||
V4_TAKE_ALL_ID,
|
||||
]),
|
||||
[
|
||||
encodeV4ExactOutSingle(
|
||||
WETH_ADDR,
|
||||
USDC_ADDR,
|
||||
390000000n,
|
||||
ONE_ETH,
|
||||
),
|
||||
settle(WETH_ADDR, 0n, false),
|
||||
encodeV4TakeAll(USDC_ADDR, 0n),
|
||||
],
|
||||
),
|
||||
UNWRAP_INTO_ROUTER,
|
||||
V4_ONE_ETH,
|
||||
],
|
||||
lines: [
|
||||
["Amount", "1.0000 ETH"],
|
||||
...ethLines("Up to 1.0000 ETH"),
|
||||
],
|
||||
},
|
||||
])("$kind", ({ router, commands, inputs, lines }) => {
|
||||
const result = decodeSteps(commands, inputs, router);
|
||||
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