fix: a V3 exact-out swap step shows its tokens and amounts (closes #601) #636

Open
clawbot wants to merge 1 commits from issue-601-v3-exact-out into next
Collaborator

A V3_SWAP_EXACT_OUT step was named in the Steps line but never read (#601), so it showed neither its tokens nor its amounts. Also fixes #637.

In src/shared/uniswap.js:

  • decodeV3SwapExactOut() reads (recipient, amountOut, amountInMax, path, payerIsUser). The router reads an exact-out path from the token it buys to the token it spends, so Token In is the path's last token and Token Out its first.
  • decode() reads the step in the V2_SWAP_EXACT_OUT branch, so it counts as that step does: amountInMax as its amount (Up to), amountOut as the minimum, its recipient named, and, when the user pays a later one, its take added to the total from #626.
  • decodeV3Path() returns the path's first and last token, which the two V3 readers take in opposite order, or null for a path that is not one token followed by whole 23-byte hops: the router ignores bytes after the last hop, so the last 20 bytes need not be a token it reads. Either V3 swap step with such a path goes unread.

README Display Consistency and Transaction Decoding describe this.

Disclosures:

  • Judgement call: an exact-out step paid from the router, V2 or V3, is not counted against what the router holds, as on next for V2, because the calldata does not state what it spends. I read the plan's "counts against the router's balance" as "takes nothing from the user's wallet".
  • The test with a path shorter than one token also passes on next; it checks that such a step leaves no figure.

Model: opus-5-5

A `V3_SWAP_EXACT_OUT` step was named in the Steps line but never read (https://git.eeqj.de/sneak/AutistMask/issues/601), so it showed neither its tokens nor its amounts. Also fixes https://git.eeqj.de/sneak/AutistMask/issues/637. In `src/shared/uniswap.js`: - `decodeV3SwapExactOut()` reads `(recipient, amountOut, amountInMax, path, payerIsUser)`. The router reads an exact-out path from the token it buys to the token it spends, so `Token In` is the path's last token and `Token Out` its first. - `decode()` reads the step in the `V2_SWAP_EXACT_OUT` branch, so it counts as that step does: `amountInMax` as its amount (`Up to`), `amountOut` as the minimum, its recipient named, and, when the user pays a later one, its take added to the total from https://git.eeqj.de/sneak/AutistMask/pulls/626. - `decodeV3Path()` returns the path's `first` and `last` token, which the two V3 readers take in opposite order, or null for a path that is not one token followed by whole 23-byte hops: the router ignores bytes after the last hop, so the last 20 bytes need not be a token it reads. Either V3 swap step with such a path goes unread. README Display Consistency and Transaction Decoding describe this. Disclosures: - Judgement call: an exact-out step paid from the router, V2 or V3, is not counted against what the router holds, as on `next` for V2, because the calldata does not state what it spends. I read the plan's "counts against the router's balance" as "takes nothing from the user's wallet". - The test with a path shorter than one token also passes on `next`; it checks that such a step leaves no figure. Model: opus-5-5
clawbot self-assigned this 2026-10-20 08:52:42 +02:00
clawbot added the needs-review label 2026-10-20 08:52:48 +02:00
Author
Collaborator

Review: FAIL (needs-rework)

  1. src/shared/uniswap.js, decodeV3SwapExactOut(): the token spent is taken from the last 20 bytes of the path, but the router pays the token that ends the path's last whole hop and ignores any bytes after it. A path of USDC, a fee, WETH and 19 more bytes spends WETH, yet the screen names as Token In, and gives the Amount in, the address formed by WETH's last byte and those 19 bytes, which can be a bundled token. Followed by a PERMIT2_PERMIT of WETH and a swap of it, the Amount reads the permit's figure alone, although the exact-out step also takes WETH. On next this step left no Amount figure. Acceptable: for a V3_SWAP_EXACT_OUT, read the token spent where the router reads it, or treat a path whose length is not 20 bytes plus a whole number of 23-byte hops as unread, as a path shorter than one token is; a test with such a path; the README sentence that the path is read as the router reads it kept true.
  2. README.md, Transaction Decoding: "spends at most its amountInMax, an amount the calldata does not state" says the calldata does not state amountInMax, which it does. Acceptable: the step spends an amount the calldata does not state, at most its amountInMax.

Not raised (on next too): V3_SWAP_EXACT_IN with a path that has bytes after its last whole hop names those last 20 bytes as Token Out, which is not the token the router pays out.

Model: opus-5-5

**Review: FAIL (`needs-rework`)** 1. `src/shared/uniswap.js`, `decodeV3SwapExactOut()`: the token spent is taken from the last 20 bytes of the path, but the router pays the token that ends the path's last whole hop and ignores any bytes after it. A path of USDC, a fee, WETH and 19 more bytes spends WETH, yet the screen names as `Token In`, and gives the `Amount` in, the address formed by WETH's last byte and those 19 bytes, which can be a bundled token. Followed by a `PERMIT2_PERMIT` of WETH and a swap of it, the `Amount` reads the permit's figure alone, although the exact-out step also takes WETH. On `next` this step left no `Amount` figure. Acceptable: for a `V3_SWAP_EXACT_OUT`, read the token spent where the router reads it, or treat a path whose length is not 20 bytes plus a whole number of 23-byte hops as unread, as a path shorter than one token is; a test with such a path; the README sentence that the path is read as the router reads it kept true. 2. `README.md`, Transaction Decoding: "spends at most its `amountInMax`, an amount the calldata does not state" says the calldata does not state `amountInMax`, which it does. Acceptable: the step spends an amount the calldata does not state, at most its `amountInMax`. Not raised (on `next` too): `V3_SWAP_EXACT_IN` with a path that has bytes after its last whole hop names those last 20 bytes as `Token Out`, which is not the token the router pays out. Model: opus-5-5
clawbot added needs-rework and removed needs-review labels 2026-10-20 09:05:07 +02:00
clawbot force-pushed issue-601-v3-exact-out from 4adc65c2bb to 68aafc6c4d 2026-10-20 09:13:59 +02:00 Compare
clawbot added 1 commit 2026-10-20 09:25:47 +02:00
fix: a V3 exact-out swap step shows its tokens and amounts (closes #601)
check / check (push) Waiting to run
e2e / e2e-chrome (push) Waiting to run
e2e / e2e-firefox (push) Waiting to run
52ed4cf5f4
A V3_SWAP_EXACT_OUT step was named in the Steps line but never read, so
its tokens read as not named in the calldata and it gave no amounts. It
is now read as a V2_SWAP_EXACT_OUT is: its path from the token it buys
to the token it spends, as the router reads it, its amountInMax as the
most it spends, its amountOut as the minimum, and its recipient as the
address it pays. One the user pays for after the step that sets the
input side joins the Amount total. A V3 path with bytes after its last
whole hop, which the router ignores, leaves either V3 swap step unread.

Also closes #637.

Model: opus-5-5
clawbot force-pushed issue-601-v3-exact-out from 68aafc6c4d to 52ed4cf5f4 2026-10-20 09:25:47 +02:00 Compare
clawbot added needs-review and removed needs-rework labels 2026-10-20 09:37:10 +02:00
Author
Collaborator

Rework:

  1. decodeV3Path() leaves a path that is not one token followed by whole 23-byte hops unread, for both V3 swap steps; tests use the review's route, permit included, and the route in #637.
  2. README Transaction Decoding now reads "spends an amount the calldata does not state, at most its amountInMax".
  3. #637 is folded in; the commit and PR body say this PR closes it too.

Model: opus-5-5

Rework: 1. `decodeV3Path()` leaves a path that is not one token followed by whole 23-byte hops unread, for both V3 swap steps; tests use the review's route, permit included, and the route in https://git.eeqj.de/sneak/AutistMask/issues/637. 2. README Transaction Decoding now reads "spends an amount the calldata does not state, at most its `amountInMax`". 3. https://git.eeqj.de/sneak/AutistMask/issues/637 is folded in; the commit and PR body say this PR closes it too. Model: opus-5-5
Some checks are pending
check / check (push) Waiting to run
e2e / e2e-chrome (push) Waiting to run
e2e / e2e-firefox (push) Waiting to run
You are not authorized to merge this pull request.
This pull request can be merged automatically.
View command line instructions

Checkout

From your project repository, check out a new branch and test the changes.
git fetch -u origin issue-601-v3-exact-out:issue-601-v3-exact-out
git checkout issue-601-v3-exact-out
Sign in to join this conversation.
No Reviewers
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: sneak/AutistMask#636