fix: an allowance to another spender leaves no Amount figure for its token and no Min. received (closes #658)
check / check (push) Waiting to run
e2e / e2e-chrome (push) Waiting to run
e2e / e2e-firefox (push) Waiting to run

Code that a step runs, such as a V4 pool's hook, can call a spender other
than the router, which can then take up to its allowance from the user's
wallet before the transaction ends. The Amount figure and Min. received took
no account of it.

In decode(), an allowance of Token In to a spender other than the router
now leaves no Amount line, as an unread permit already did, so the swap
steps' own takes get lines of their own. Any allowance to another spender,
or a permit or batch the decoder cannot read, makes the minimum read
Min. paid out. Permits to the router read as before.

Model: opus-5-5
This commit was merged in pull request #666.
This commit is contained in:
2026-10-21 06:24:51 +02:00
parent e2965469ba
commit 4be625dc06
4 changed files with 234 additions and 91 deletions
+27 -18
View File
@@ -1173,8 +1173,8 @@ fetched, without that cross-address check, and shows the row's figure.
**Decoded amount lines on the transaction approval screen:** the `Amount` line
of a decoded ERC-20 call, and the `Amount` and `Min. received` lines of a
decoded swap (see TxApproval below; `Min. received` reads `Min. paid out` unless
only the user is paid, see Transaction Decoding), do not always read as a
number. They can read:
only the user is paid and no allowance goes to a spender other than the router,
see Transaction Decoding), do not always read as a number. They can read:
- A formatted quantity, e.g. `17.1900 USDT`, when the token's scale is known:
truncated to four decimals, with the floor and the zero cases described above.
@@ -3197,8 +3197,9 @@ Currently supported:
`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
TransactionDetail), no `Paid to` line is shown, and no permit or batch grants
an allowance to a spender other than the router (see below). `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)`
@@ -3252,7 +3253,8 @@ Currently supported:
(listed at the end of this section), a step whose input it cannot read, in
whole or in part, and a step it fails on. Every other `PERMIT2_PERMIT`, before
the step that sets the input side or after it, leaves `Token In` and the
`Amount` line as they read with no permit: a permit of 5,000 DAI to another
`Amount` line as they read with no permit, unless it lets a spender other than
the router take `Token In` (see below): a permit of 5,000 DAI to another
address and then a V3 exact-in of 600 USDT read `Swap USDT → USDC` with
`Amount 600.0000 USDT`, and so do that exact-in and then a permit of 5,000 DAI
to the router. When a permit's amount stands and the step that sets the input
@@ -3319,19 +3321,26 @@ Currently supported:
`PERMIT2_PERMIT` or `PERMIT2_PERMIT_BATCH` whose input the decoder cannot
read, such as one whose token or spender word has a nonzero top byte, may let
any spender take any amount of any token: it gives one `Permit2 allowance`
line alone, reading `Unknown (could not be decoded)`, and leaves no `Amount`
line, whose figure might be too low. These lines change neither the `Paid to`
lines nor the minimum's label: a permit or batch pays nobody. A step goes
unread when its recipient does not decode as an address, when it is a V3 swap
step whose path is shorter than one token address or has bytes after its last
hop, or when it is a command the decoder does not read: every command other
than `PERMIT2_PERMIT`, `PERMIT2_PERMIT_BATCH`, `PERMIT2_TRANSFER_FROM`,
`V2_SWAP_EXACT_IN`, `V2_SWAP_EXACT_OUT`, `V3_SWAP_EXACT_IN`,
`V3_SWAP_EXACT_OUT`, `V4_SWAP`, `SWEEP`, `TRANSFER`, `PAY_PORTION`,
`PAY_PORTION_FULL_PRECISION`, `ACROSS_V4_DEPOSIT_V3`, `WRAP_ETH`,
`UNWRAP_WETH` and `BALANCE_CHECK_ERC20` (`PERMIT2_TRANSFER_FROM_BATCH`,
`EXECUTE_SUB_PLAN` and the NFT commands among them), or a type its release
names no command for.
line alone, reading `Unknown (could not be decoded)`. These lines change no
`Paid to` line: a permit or batch pays nobody. But code that a step runs, such
as a V4 pool's hook or a token's own code, can call a spender other than the
router, which can then take up to its allowance from the user's wallet before
the transaction ends. So an allowance to any spender but the router, or a
permit or batch the decoder cannot read, makes the minimum read
`Min. paid out`, and when its token is `Token In`, or the decoder does not
know its token or `Token In`, it leaves no `Amount` line, whose figure might
be too low: a permit of 5,000 USDT to another address and then a V3 exact-in
of 600 USDT read with no `Amount` line, and the 600 USDT gets lines of its
own. A step goes unread when its recipient does not decode as an address, when
it is a V3 swap step whose path is shorter than one token address or has bytes
after its last hop, or when it is a command the decoder does not read: every
command other than `PERMIT2_PERMIT`, `PERMIT2_PERMIT_BATCH`,
`PERMIT2_TRANSFER_FROM`, `V2_SWAP_EXACT_IN`, `V2_SWAP_EXACT_OUT`,
`V3_SWAP_EXACT_IN`, `V3_SWAP_EXACT_OUT`, `V4_SWAP`, `SWEEP`, `TRANSFER`,
`PAY_PORTION`, `PAY_PORTION_FULL_PRECISION`, `ACROSS_V4_DEPOSIT_V3`,
`WRAP_ETH`, `UNWRAP_WETH` and `BALANCE_CHECK_ERC20`
(`PERMIT2_TRANSFER_FROM_BATCH`, `EXECUTE_SUB_PLAN` and the NFT commands among
them), or a type its release names no command for.
### Non-Goals Forever