fix: any token a swap leaves in the router keeps it from reading as received (closes #606)
check / check (push) Canceled after 0s
e2e / e2e-chrome (push) Canceled after 0s
e2e / e2e-firefox (push) Canceled after 0s

The decoder checked only the swap's output for being left in the router.
It now records every token a step pays the router, the WETH of a
WRAP_ETH and the rest of a V4 SETTLE taken to the router among them, and
any token still there after the last step gets a Paid to line naming it,
so the minimum reads Min. paid out. Steps that spend all the router holds
of a token, and swap steps from the router that together spend what a
WRAP_ETH stated, count as spending it. A SETTLE of OPEN_DELTA from the
router counts only in the plain shape of Uniswap's own routes.

Model: opus-5-5
This commit is contained in:
2026-10-19 18:58:27 +00:00
parent 2c882c75d6
commit 56014a264b
4 changed files with 741 additions and 80 deletions
+4 -2
View File
@@ -739,7 +739,9 @@ describe("uniswap decoder", () => {
});
// Paid in ETH, with an exact-out step, the last swap step buys WETH, so
// UNWRAP_WETH makes the output ETH.
// UNWRAP_WETH makes the output ETH. That step is paid by the user, so the
// USDC the exact-out step bought stays in the router, and the minimum is
// not received (https://git.eeqj.de/sneak/AutistMask/issues/606).
test("an ETH-paid swap whose last step buys WETH, then UNWRAP_WETH, shows ETH", () => {
const data = buildExecute(
solidityPacked(
@@ -767,7 +769,7 @@ describe("uniswap decoder", () => {
const result = uniswap.decode(data, ROUTER_ADDR, "0x1", {}, USER_ADDR);
expect(detail(result, "Token Out").value).toBe("ETH");
expect(detail(result, "Min. received").value).toBe("0.4000 ETH");
expect(detail(result, "Min. paid out").value).toBe("0.4000 ETH");
});
// Sepolia's WETH is a different contract; UNWRAP_WETH makes it ETH too.