fix: the swap decoder reads each command's type with its router's mask (closes #535)
Each Universal Router address now carries the mask its own source reads a command's type with: 0x1f for UniversalRouterV1, 0x3f for V1_2 and V2, 0x7f for V2_1_1 and V2_1_2. The fixed 0x1f dropped the 0x20 bit, so EXECUTE_SUB_PLAN (0x21) was listed as a V3 exact-out swap. A sub-plan step is now listed as "Execute Sub-Plan (its steps are not shown)", and a Token In or Token Out line that nothing named reads "Unknown (could not be decoded)", since the sub-plan may name it. Model: opus-5-5
This commit is contained in:
+116
-1
@@ -1,8 +1,12 @@
|
||||
const { AbiCoder, Interface, solidityPacked } = require("ethers");
|
||||
const uniswap = require("../src/shared/uniswap");
|
||||
|
||||
// A Universal Router on Ethereum Mainnet, chain id 0x1.
|
||||
// A Universal Router on Ethereum Mainnet, chain id 0x1: UniversalRouterV2,
|
||||
// which reads a command's type with the mask 0x3f.
|
||||
const ROUTER_ADDR = "0x66a9893cc07d91d95644aedd05d03f95e1dba8af";
|
||||
// Two more there, which read it with 0x1f and 0x7f.
|
||||
const ROUTER_V1_ADDR = "0xEf1c6E67703c7BD7107eed8303Fbe6EC2554BF6B";
|
||||
const ROUTER_V2_1_2_ADDR = "0x23617e59A5925b2A4Bf75d73ff6711cD0b29De85";
|
||||
const USDT_ADDR = "0xdAC17F958D2ee523a2206206994597C13D831ec7";
|
||||
const WETH_ADDR = "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2";
|
||||
const USDC_ADDR = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48";
|
||||
@@ -864,6 +868,117 @@ describe("uniswap decoder", () => {
|
||||
);
|
||||
});
|
||||
|
||||
// https://git.eeqj.de/sneak/AutistMask/issues/535 \u2014 every command's type
|
||||
// was read from its low five bits, as only UniversalRouterV1 reads it, so
|
||||
// the later routers listed 0x21 as a V3 exact-out swap, and
|
||||
// UniversalRouterV2_1_2 listed 0x40 as a V3 exact-in swap.
|
||||
test.each([
|
||||
{
|
||||
router: "UniversalRouterV1",
|
||||
address: ROUTER_V1_ADDR,
|
||||
command: "0x21",
|
||||
steps: "V3 Swap (Exact Out)",
|
||||
},
|
||||
{
|
||||
router: "UniversalRouterV2",
|
||||
address: ROUTER_ADDR,
|
||||
command: "0x21",
|
||||
steps: "Execute Sub-Plan (its steps are not shown)",
|
||||
},
|
||||
{
|
||||
router: "UniversalRouterV2_1_2",
|
||||
address: ROUTER_V2_1_2_ADDR,
|
||||
command: "0x21",
|
||||
steps: "Execute Sub-Plan (its steps are not shown)",
|
||||
},
|
||||
{
|
||||
router: "UniversalRouterV1",
|
||||
address: ROUTER_V1_ADDR,
|
||||
command: "0x40",
|
||||
steps: "V3 Swap (Exact In)",
|
||||
},
|
||||
{
|
||||
router: "UniversalRouterV2",
|
||||
address: ROUTER_ADDR,
|
||||
command: "0x40",
|
||||
steps: "V3 Swap (Exact In)",
|
||||
},
|
||||
{
|
||||
router: "UniversalRouterV2_1_2",
|
||||
address: ROUTER_V2_1_2_ADDR,
|
||||
command: "0x40",
|
||||
steps: "Command 0x40",
|
||||
},
|
||||
])(
|
||||
"command $command sent to $router is listed as $steps",
|
||||
({ address, command, steps }) => {
|
||||
const data = buildExecute(command, ["0x"], 9999999999n);
|
||||
const result = uniswap.decode(data, address, "0x1");
|
||||
expect(detail(result, "Steps").value).toBe(steps);
|
||||
},
|
||||
);
|
||||
|
||||
// A sub-plan holding a V3 swap of USDT for WETH, which the decoder does
|
||||
// not read. With the allow-revert flag (0x80) the command byte is 0xa1.
|
||||
test.each([
|
||||
{ flag: "without", command: 0x21 },
|
||||
{ flag: "with", command: 0xa1 },
|
||||
])(
|
||||
"an EXECUTE_SUB_PLAN step $flag the allow-revert flag says its steps are not shown",
|
||||
({ command }) => {
|
||||
const subPlan = coder.encode(
|
||||
["bytes", "bytes[]"],
|
||||
[
|
||||
"0x00",
|
||||
[
|
||||
encodeV3SwapExactIn(USER_ADDR, 2000000n, ONE_WETH, [
|
||||
USDT_ADDR,
|
||||
WETH_ADDR,
|
||||
]),
|
||||
],
|
||||
],
|
||||
);
|
||||
const data = buildExecute(
|
||||
solidityPacked(["uint8"], [command]),
|
||||
[subPlan],
|
||||
9999999999n,
|
||||
);
|
||||
const result = uniswap.decode(data, ROUTER_ADDR, "0x1");
|
||||
expect(result.name).toBe("Uniswap Swap");
|
||||
expect(detail(result, "Steps").value).toBe(
|
||||
"Execute Sub-Plan (its steps are not shown)",
|
||||
);
|
||||
// The sub-plan names both currencies, so the lines must not say
|
||||
// the calldata did not.
|
||||
expect(detail(result, "Token In").value).toBe(
|
||||
"Unknown (could not be decoded)",
|
||||
);
|
||||
expect(detail(result, "Token Out").value).toBe(
|
||||
"Unknown (could not be decoded)",
|
||||
);
|
||||
expect(detail(result, "Amount")).toBeUndefined();
|
||||
expect(detail(result, "Min. received")).toBeUndefined();
|
||||
},
|
||||
);
|
||||
|
||||
// The permit names the input side; the output side is in the sub-plan.
|
||||
test("a permit, then an EXECUTE_SUB_PLAN step, keeps the permit's token and says the rest is not shown", () => {
|
||||
const data = buildExecute(
|
||||
solidityPacked(["uint8", "uint8"], [0x0a, 0x21]),
|
||||
[encodePermit2(USDT_ADDR, 1000000n, ROUTER_ADDR), "0x"],
|
||||
9999999999n,
|
||||
);
|
||||
const result = uniswap.decode(data, ROUTER_ADDR, "0x1");
|
||||
expect(detail(result, "Token In").address).toBe(USDT_ADDR);
|
||||
expect(detail(result, "Amount").value).toBe("1.0000 USDT");
|
||||
expect(detail(result, "Token Out").value).toBe(
|
||||
"Unknown (could not be decoded)",
|
||||
);
|
||||
expect(detail(result, "Steps").value).toBe(
|
||||
"Permit2 Permit \u2192 Execute Sub-Plan (its steps are not shown)",
|
||||
);
|
||||
});
|
||||
|
||||
test("shows the deadline as a UTC date and time", () => {
|
||||
const result = uniswap.decode(FIRST_SWAP_CALLDATA, ROUTER_ADDR, "0x1");
|
||||
expect(detail(result, "Deadline").value).toBe("2026-02-27 08:25:51");
|
||||
|
||||
Reference in New Issue
Block a user