fix: the swap decoder reads who the router's transfer commands pay (closes #599)
TRANSFER, PAY_PORTION, PAY_PORTION_FULL_PRECISION (0x07, on the routers with the 0x7f mask), PERMIT2_TRANSFER_FROM, ACROSS_V4_DEPOSIT_V3 (0x40) and V4 TAKE_PORTION were payouts the decoder did not read, so a swap using one read Min. paid out with an unknown payee. Each recipient is now read: the sender, the router, a hop into the next V2 pair, or another address, which is named. Paying all the router holds of a token pays it out as SWEEP does; a TRANSFER of CONTRACT_BALANCE of ETH reverts, so it pays nothing out. A PERMIT2_TRANSFER_FROM into the router is shown with the amount it moves and the moved token's address, and a bridge deposit names the bridge and each of its recipient and depositor that is not the sender; either keeps the minimum from reading received. Model: opus-5-5
This commit is contained in:
@@ -2955,45 +2955,73 @@ Currently supported:
|
||||
address uses: `0x1f` for `UniversalRouterV1`, `0x3f` for
|
||||
`UniversalRouterV1_2_V2Support` and `UniversalRouterV2`, and `0x7f` for
|
||||
`UniversalRouterV2_1_1` and `UniversalRouterV2_1_2`. The allow-revert flag
|
||||
(`0x80`) therefore does not change a step's name. The decoder does not read
|
||||
the commands inside an `EXECUTE_SUB_PLAN` (`0x21`) step, and wherever that
|
||||
step stands they may spend the user's tokens or pay the user. That step is
|
||||
listed as `Execute Sub-Plan (its steps are not shown)`, and whatever the other
|
||||
steps state, the summary names no swap direction, `Token In` and `Token Out`
|
||||
read `Unknown (could not be decoded)`, and there is no `Amount` or
|
||||
`Min. received` line. Each swap step, `SWEEP`, `WRAP_ETH`, `UNWRAP_WETH` and
|
||||
V4 `TAKE` names the address it pays, and `TAKE_ALL` pays the transaction's
|
||||
sender. The router reads `address(1)` as the transaction's sender, and
|
||||
`address(2)` as the router itself, which holds what it is paid until a later
|
||||
step pays it out; a payment to the router, as `address(2)` or by its own
|
||||
address, is not counted as paid out. When a swap step or `TAKE` pays the
|
||||
router the swap's output token, the output stays in the router unless a later
|
||||
`SWEEP` of that token, or a later `UNWRAP_WETH` when it is WETH, pays it out.
|
||||
A swap step or `TAKE` paying a token to the V2 pair that the very next step
|
||||
spends, as a V2 exact-in of that token whose `amountIn` is `ALREADY_PAID`
|
||||
(zero) or `CONTRACT_BALANCE`, is a hop, not a payout: Uniswap's mixed routes
|
||||
pay the next V2 pair directly, and the router enforces that swap's own
|
||||
minimum. The decoder checks that the address paid is that pair, deriving it
|
||||
from the V2 factory and pair init code hash the network's routers were
|
||||
deployed with and the first two tokens of that step's path; a payment to any
|
||||
other address is named. (Sepolia's `UniversalRouterV1_2_V2Support` was
|
||||
deployed with placeholders for both values, so no V2 swap step can succeed on
|
||||
that router.) Every other payment counts, in whichever step and of whatever
|
||||
token. The minimum reads `Min. received` only when some step pays the
|
||||
transaction's sender (the From address on TxApproval, the sender the explorer
|
||||
reports on TransactionDetail), no step pays any other address, no step that
|
||||
may pay an address went unread, and the output does not stay in the router.
|
||||
Otherwise it reads `Min. paid out`, a `Paid to` line after `Token Out` names
|
||||
each other address paid, one reads
|
||||
`The router, where it stays (no later step pays it out)` when the output stays
|
||||
in the router, and one more reads `Unknown (could not be decoded)` when such a
|
||||
step went unread, or `Unknown (not named in the calldata)` when no step pays
|
||||
anyone. A step goes unread when its recipient does not decode as an address,
|
||||
or when it is a command or V4 action the decoder does not read: every command
|
||||
other than `PERMIT2_PERMIT`, `V2_SWAP_EXACT_IN`, `V2_SWAP_EXACT_OUT`,
|
||||
`V3_SWAP_EXACT_IN`, `V4_SWAP`, `SWEEP`, `WRAP_ETH`, `UNWRAP_WETH` and
|
||||
`BALANCE_CHECK_ERC20` (`V3_SWAP_EXACT_OUT`, `TRANSFER`, `PAY_PORTION` and
|
||||
`EXECUTE_SUB_PLAN` among them), and V4 `TAKE_PORTION`.
|
||||
(`0x80`) therefore does not change a step's name. Only the routers with the
|
||||
`0x7f` mask have `PAY_PORTION_FULL_PRECISION` (`0x07`), listed as
|
||||
`Pay Portion (Full Precision)`, and `ACROSS_V4_DEPOSIT_V3` (`0x40`), listed as
|
||||
`Across Bridge Deposit`; the other routers revert on `0x07`, and it is listed
|
||||
there as `Command 0x07`. The decoder does not read the commands inside an
|
||||
`EXECUTE_SUB_PLAN` (`0x21`) step, and wherever that step stands they may spend
|
||||
the user's tokens or pay the user. That step is listed as
|
||||
`Execute Sub-Plan (its steps are not shown)`, and whatever the other steps
|
||||
state, the summary names no swap direction, `Token In` and `Token Out` read
|
||||
`Unknown (could not be decoded)`, and there is no `Amount` or `Min. received`
|
||||
line. Each swap step, `SWEEP`, `WRAP_ETH`, `UNWRAP_WETH`, `TRANSFER`,
|
||||
`PAY_PORTION`, `PAY_PORTION_FULL_PRECISION`, `PERMIT2_TRANSFER_FROM` (which
|
||||
moves the user's own tokens) and V4 `TAKE` and `TAKE_PORTION` names the
|
||||
address it pays, and `TAKE_ALL` pays the transaction's sender. The router
|
||||
reads `address(1)` as the transaction's sender, and `address(2)` as the router
|
||||
itself, which holds what it is paid until a later step pays it out; a payment
|
||||
to the router, as `address(2)` or by its own address, is not counted as paid
|
||||
out. `UniversalRouterV1` alone pays a `PERMIT2_TRANSFER_FROM` to `address(1)`
|
||||
or `address(2)` as those addresses, and the decoder names them. A
|
||||
`PERMIT2_TRANSFER_FROM` into the router is named with the amount it moves,
|
||||
because the decoder does not check that later steps spend all of it.
|
||||
`ACROSS_V4_DEPOSIT_V3` pays the bridge, which pays the deposit's recipient on
|
||||
another network and refunds a deposit nobody fills to its depositor; the
|
||||
router passes both on as written, so `address(1)` and `address(2)` there are
|
||||
addresses, and each that is not the transaction's sender is named. When a swap
|
||||
step, `TAKE` or `TAKE_PORTION` pays the router the swap's output token, the
|
||||
output stays in the router unless a later step pays all the router holds of it
|
||||
to anyone but the router: a `SWEEP` of that token, an `UNWRAP_WETH` when it is
|
||||
WETH, a `TRANSFER` of `CONTRACT_BALANCE`, a `PAY_PORTION` of 10,000 bips or a
|
||||
`PAY_PORTION_FULL_PRECISION` of 1e18 of that token, or an
|
||||
`ACROSS_V4_DEPOSIT_V3` of `CONTRACT_BALANCE` of that token, or of ETH when it
|
||||
sets `useNative`. The router sends ETH as written, so a `TRANSFER` of
|
||||
`CONTRACT_BALANCE` of ETH always reverts and pays out nothing. A step paying a
|
||||
token to the V2 pair that the very next step spends, as a V2 exact-in of that
|
||||
token whose `amountIn` is `ALREADY_PAID` (zero) or `CONTRACT_BALANCE`, is a
|
||||
hop, not a payout: Uniswap's mixed routes pay the next V2 pair directly, and
|
||||
the router enforces that swap's own minimum. The decoder checks that the
|
||||
address paid is that pair, deriving it from the V2 factory and pair init code
|
||||
hash the network's routers were deployed with and the first two tokens of that
|
||||
step's path; a payment to any other address is named. (Sepolia's
|
||||
`UniversalRouterV1_2_V2Support` was deployed with placeholders for both
|
||||
values, so no V2 swap step can succeed on that router.) Every other payment
|
||||
counts, in whichever step and of whatever token, a share of the output
|
||||
included: an interface fee paid to another address by `PAY_PORTION`,
|
||||
`TRANSFER` or `TAKE_PORTION` is named, and the minimum reads `Min. paid out`,
|
||||
because the swap's minimum includes the fee. The minimum reads `Min. received`
|
||||
only when some step pays the transaction's sender (the From address on
|
||||
TxApproval, the sender the explorer reports on TransactionDetail) and no
|
||||
`Paid to` line is shown. `Paid to` lines after `Token Out` name each other
|
||||
address paid; one reads
|
||||
`The bridge, which pays the deposit's recipient on another network` for a
|
||||
bridge deposit; one reads, for example,
|
||||
`The router, 2000.0000 USDT from the sender (later steps may not spend all of it)`
|
||||
for each move into the router, followed by a `Token moved into the router`
|
||||
line that gives the moved token's address as the `Token In` line does; one
|
||||
reads `The router, where it stays (no later step pays it out)` when the output
|
||||
stays in the router; and one more reads `Unknown (could not be decoded)` when
|
||||
a step that may pay an address went unread. When no step pays anyone, a single
|
||||
line reads `Unknown (not named in the calldata)`. Wherever there is a
|
||||
`Paid to` line, the minimum reads `Min. paid out`. A step goes unread when its
|
||||
recipient does not decode as an address, or when it is a command the decoder
|
||||
does not read: every command other than `PERMIT2_PERMIT`,
|
||||
`PERMIT2_TRANSFER_FROM`, `V2_SWAP_EXACT_IN`, `V2_SWAP_EXACT_OUT`,
|
||||
`V3_SWAP_EXACT_IN`, `V4_SWAP`, `SWEEP`, `TRANSFER`, `PAY_PORTION`,
|
||||
`PAY_PORTION_FULL_PRECISION`, `ACROSS_V4_DEPOSIT_V3`, `WRAP_ETH`,
|
||||
`UNWRAP_WETH` and `BALANCE_CHECK_ERC20` (`V3_SWAP_EXACT_OUT`,
|
||||
`PERMIT2_TRANSFER_FROM_BATCH` and `EXECUTE_SUB_PLAN` among them).
|
||||
|
||||
### Non-Goals Forever
|
||||
|
||||
|
||||
Reference in New Issue
Block a user