1 Commits
Author SHA1 Message Date
sneak bde428e98c fix: any token a swap leaves in the router keeps it from reading as received (closes #606)
check / check (push) Waiting to run
e2e / e2e-chrome (push) Waiting to run
e2e / e2e-firefox (push) Waiting to run
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 count as spending it, and so do swap steps from the router
that together spend what a WRAP_ETH stated, when the router held no WETH
before it. A SETTLE of OPEN_DELTA from the router counts only in the
plain shape of Uniswap's own routes.

Model: opus-5-5
2026-10-19 19:46:08 +00:00
2 changed files with 54 additions and 3 deletions
+7 -3
View File
@@ -3064,9 +3064,13 @@ Currently supported:
token to anyone but the router, an `ACROSS_V4_DEPOSIT_V3` of
`CONTRACT_BALANCE` of that token, or of ETH when it sets `useNative`, or a V2
or V3 exact-in swap step or V4 `SETTLE` that pays `CONTRACT_BALANCE` of that
token from the router (`payerIsUser` false). Such steps also spend all the
WETH a `WRAP_ETH` paid the router once together they spend the amount the
`WRAP_ETH` states, as a swap paid in ETH does. The router pays a `SETTLE` of
token from the router (`payerIsUser` false). V2 and V3 exact-in swap steps and
V4 `SETTLE`s that pay WETH from the router also spend all the WETH a
`WRAP_ETH` paid it once together they spend the amount the `WRAP_ETH` states,
as a swap paid in ETH does, but only when that amount is not
`CONTRACT_BALANCE` and the router held no WETH from an earlier step when the
`WRAP_ETH` ran: after two `WRAP_ETH`s of 0.5 ETH into the router, a swap of 1
WETH from it reads as leaving the WETH there. The router pays a `SETTLE` of
`OPEN_DELTA` (zero) only what its step still owes of that token when the
`SETTLE` runs, so one from the router counts as spending the amounts the
step's swap actions state they spend of that token only when every action in
+47
View File
@@ -678,6 +678,53 @@ describe("any token left in the router is not received", () => {
),
left: tokenLine(WETH, "WETH"),
},
// Only a swap the router pays from its own balance spends the WETH
// a WRAP_ETH paid it, and its stated amount is followed only when the
// router held no WETH before the WRAP_ETH.
{
kind: "a WRAP_ETH of 1 ETH into the router, then a V2 swap of 1 WETH that the user pays for",
data: execute(
wrapEth(ADDRESS_THIS, ONE_WETH),
v2Swap(0x08, MSG_SENDER, ONE_WETH, USDC_1990, [WETH, USDC]),
),
left: tokenLine(WETH, "WETH"),
},
{
kind: "a WRAP_ETH of 1 ETH into the router, then a V3 swap of 1 WETH that the user pays for",
data: execute(
wrapEth(ADDRESS_THIS, ONE_WETH),
v3SwapExactIn(MSG_SENDER, ONE_WETH, USDC_1990, WETH, USDC),
),
left: tokenLine(WETH, "WETH"),
},
{
kind: "a WRAP_ETH of 1 ETH into the router, then a V4 swap of 1 WETH with a SETTLE of 1 WETH by the user",
data: execute(
wrapEth(ADDRESS_THIS, ONE_WETH),
v4Swap(
swapExactInSingle(WETH, USDC, ONE_WETH, USDC_1990),
settle(WETH, ONE_WETH, true),
takeAll(USDC),
),
),
left: tokenLine(WETH, "WETH"),
},
{
kind: "a swap paying WETH to the router, a WRAP_ETH of 1 ETH into the router, then a swap of 1 WETH from the router",
data: execute(
swapToWeth(ADDRESS_THIS),
wrapEth(ADDRESS_THIS, ONE_WETH),
v3SwapExactIn(
MSG_SENDER,
ONE_WETH,
USDC_1990,
WETH,
USDC,
false,
),
),
left: tokenLine(WETH, "WETH"),
},
// A SETTLE of OPEN_DELTA from the router pays only what the step
// still owes of its currency when it runs.
{