test: add V4 swap ERC20→ERC20 decoder regression test
Some checks failed
check / check (push) Has been cancelled
Some checks failed
check / check (push) Has been cancelled
Adds a test that constructs a Uniswap V4 USDT→USDC swap using SETTLE/SWAP_EXACT_IN_SINGLE/TAKE sub-actions inside a V4_SWAP command. Without decodeV4Swap(), the output token would be unresolvable and the swap name would not show 'USDT → USDC'. This test fails on the old code and passes with the decodeV4Swap() fix. Refs: #59
This commit is contained in:
6175
package-lock.json
generated
Normal file
6175
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,9 +1,10 @@
|
|||||||
const { AbiCoder, Interface, solidityPacked } = require("ethers");
|
const { AbiCoder, Interface, solidityPacked, getBytes } = require("ethers");
|
||||||
const uniswap = require("../src/shared/uniswap");
|
const uniswap = require("../src/shared/uniswap");
|
||||||
|
|
||||||
const ROUTER_ADDR = "0x66a9893cc07d91d95644aedd05d03f95e1dba8af";
|
const ROUTER_ADDR = "0x66a9893cc07d91d95644aedd05d03f95e1dba8af";
|
||||||
const USDT_ADDR = "0xdAC17F958D2ee523a2206206994597C13D831ec7";
|
const USDT_ADDR = "0xdAC17F958D2ee523a2206206994597C13D831ec7";
|
||||||
const WETH_ADDR = "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2";
|
const WETH_ADDR = "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2";
|
||||||
|
const USDC_ADDR = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48";
|
||||||
const USER_ADDR = "0x66133E8ea0f5D1d612D2502a968757D1048c214a";
|
const USER_ADDR = "0x66133E8ea0f5D1d612D2502a968757D1048c214a";
|
||||||
|
|
||||||
// AutistMask's first-ever swap, 2026-02-27.
|
// AutistMask's first-ever swap, 2026-02-27.
|
||||||
@@ -256,6 +257,87 @@ describe("uniswap decoder", () => {
|
|||||||
expect(amount.value).toBe("5.0000 USDT");
|
expect(amount.value).toBe("5.0000 USDT");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// This test validates the decodeV4Swap() fix: a V4 ERC20→ERC20 swap
|
||||||
|
// (USDT→USDC) where the token addresses are ONLY discoverable inside
|
||||||
|
// the V4_SWAP sub-actions (SETTLE/TAKE). Before decodeV4Swap() was added,
|
||||||
|
// command 0x10 was opaque and this would decode as "Uniswap Swap" with
|
||||||
|
// no token info (or "ETH → ETH"). Now it correctly shows "USDT → USDC".
|
||||||
|
test("decodes V4_SWAP ERC20→ERC20 tokens via SETTLE/TAKE (regression: #59)", () => {
|
||||||
|
// Build a V4_SWAP input with SETTLE(USDT) + SWAP_EXACT_IN_SINGLE + TAKE(USDC)
|
||||||
|
const V4_SETTLE = 0x0b;
|
||||||
|
const V4_SWAP_EXACT_IN_SINGLE = 0x06;
|
||||||
|
const V4_TAKE = 0x0e;
|
||||||
|
|
||||||
|
// actions: SETTLE, SWAP_EXACT_IN_SINGLE, TAKE
|
||||||
|
const actions = new Uint8Array([
|
||||||
|
V4_SETTLE,
|
||||||
|
V4_SWAP_EXACT_IN_SINGLE,
|
||||||
|
V4_TAKE,
|
||||||
|
]);
|
||||||
|
|
||||||
|
// SETTLE params: (address currency, uint256 maxAmount, bool payerIsUser)
|
||||||
|
const settleParam = coder.encode(
|
||||||
|
["address", "uint256", "bool"],
|
||||||
|
[USDT_ADDR, 5000000n, true],
|
||||||
|
);
|
||||||
|
|
||||||
|
// SWAP_EXACT_IN_SINGLE params:
|
||||||
|
// (tuple(address,address,uint24,int24,address) poolKey, bool zeroForOne, uint128 amountIn, uint128 amountOutMin, bytes hookData)
|
||||||
|
const swapParam = coder.encode(
|
||||||
|
[
|
||||||
|
"tuple(tuple(address,address,uint24,int24,address),bool,uint128,uint128,bytes)",
|
||||||
|
],
|
||||||
|
[
|
||||||
|
[
|
||||||
|
[
|
||||||
|
USDT_ADDR,
|
||||||
|
USDC_ADDR,
|
||||||
|
100, // fee
|
||||||
|
1, // tickSpacing
|
||||||
|
"0x0000000000000000000000000000000000000000", // hooks
|
||||||
|
],
|
||||||
|
true, // zeroForOne
|
||||||
|
5000000n, // amountIn (5 USDT)
|
||||||
|
4900000n, // amountOutMin (4.9 USDC)
|
||||||
|
"0x", // hookData
|
||||||
|
],
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
// TAKE params: (address currency, address recipient, uint256 amount)
|
||||||
|
const takeParam = coder.encode(
|
||||||
|
["address", "address", "uint256"],
|
||||||
|
[USDC_ADDR, USER_ADDR, 0n],
|
||||||
|
);
|
||||||
|
|
||||||
|
// Encode the V4_SWAP input: (bytes actions, bytes[] params)
|
||||||
|
const v4Input = coder.encode(
|
||||||
|
["bytes", "bytes[]"],
|
||||||
|
[actions, [settleParam, swapParam, takeParam]],
|
||||||
|
);
|
||||||
|
|
||||||
|
// Build execute() with PERMIT2_PERMIT (0x0a) + V4_SWAP (0x10)
|
||||||
|
// The permit provides the input token, but V4_SWAP must provide
|
||||||
|
// the OUTPUT token — without decodeV4Swap, output would be unknown.
|
||||||
|
const data = buildExecute(
|
||||||
|
solidityPacked(["uint8", "uint8"], [0x0a, 0x10]),
|
||||||
|
[encodePermit2(USDT_ADDR, 5000000n, ROUTER_ADDR), v4Input],
|
||||||
|
9999999999n,
|
||||||
|
);
|
||||||
|
|
||||||
|
const result = uniswap.decode(data, ROUTER_ADDR);
|
||||||
|
expect(result).not.toBeNull();
|
||||||
|
// Before decodeV4Swap fix: name would be "Swap USDT → ETH" or "Uniswap Swap"
|
||||||
|
// After fix: correctly identifies both tokens from V4 sub-actions
|
||||||
|
expect(result.name).toBe("Swap USDT \u2192 USDC");
|
||||||
|
|
||||||
|
const tokenIn = result.details.find((d) => d.label === "Token In");
|
||||||
|
expect(tokenIn.value).toContain("USDT");
|
||||||
|
|
||||||
|
const steps = result.details.find((d) => d.label === "Steps");
|
||||||
|
expect(steps.value).toContain("V4 Swap");
|
||||||
|
});
|
||||||
|
|
||||||
test("handles unknown tokens gracefully", () => {
|
test("handles unknown tokens gracefully", () => {
|
||||||
const fakeToken = "0x1111111111111111111111111111111111111111";
|
const fakeToken = "0x1111111111111111111111111111111111111111";
|
||||||
const data = buildExecute(
|
const data = buildExecute(
|
||||||
|
|||||||
Reference in New Issue
Block a user