Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
68aafc6c4d |
@@ -1176,18 +1176,18 @@ number. They can read:
|
|||||||
- `Unlimited`: on the ERC-20 `Amount` line, an `approve` of the `uint256`
|
- `Unlimited`: on the ERC-20 `Amount` line, an `approve` of the `uint256`
|
||||||
maximum, an unbounded allowance. On the swap's `Amount` line, any amount at or
|
maximum, an unbounded allowance. On the swap's `Amount` line, any amount at or
|
||||||
above the `uint160` maximum, whichever step set the line: a `PERMIT2_PERMIT`
|
above the `uint160` maximum, whichever step set the line: a `PERMIT2_PERMIT`
|
||||||
amount at that maximum, which is an unbounded permit, or a V2 or V3 exact-in,
|
amount at that maximum, which is an unbounded permit, or a V2 or V3 exact-in
|
||||||
V2 exact-out or `WRAP_ETH` amount that large, which is not an allowance. The
|
or exact-out or `WRAP_ETH` amount that large, which is not an allowance. The
|
||||||
router's whole-balance value, `CONTRACT_BALANCE` (`2^255`), is one such
|
router's whole-balance value, `CONTRACT_BALANCE` (`2^255`), is one such
|
||||||
amount.
|
amount.
|
||||||
- `Up to <amount>`: the swap's `Amount` line, when the transaction has a V2
|
- `Up to <amount>`: the swap's `Amount` line, when the transaction has a V2 or
|
||||||
exact-out step, whichever step set the line, including the `WRAP_ETH` of a
|
V3 exact-out step, whichever step set the line, including the `WRAP_ETH` of a
|
||||||
swap paid in ETH and a `PERMIT2_PERMIT`. The swap spends at most that figure,
|
swap paid in ETH and a `PERMIT2_PERMIT`. The swap spends at most that figure,
|
||||||
not necessarily all of it; the wait, success and error screens show it with
|
not necessarily all of it; the wait, success and error screens show it with
|
||||||
the same words. `Unlimited`, `All available (V4 open delta)` and
|
the same words. `Unlimited`, `All available (V4 open delta)` and
|
||||||
`Whatever an earlier step sent to the pair (V2 already paid)` keep their
|
`Whatever an earlier step sent to the pair (V2 already paid)` keep their
|
||||||
wording. When a V2 exact-out step sets `Min. received`, that line shows its
|
wording. When a V2 or V3 exact-out step sets `Min. received`, that line shows
|
||||||
`amountOut`, the exact amount it buys.
|
its `amountOut`, the exact amount it buys.
|
||||||
- `All available (V4 open delta)`: the swap's `Amount` line, when the amount it
|
- `All available (V4 open delta)`: the swap's `Amount` line, when the amount it
|
||||||
shows is a V4 exact-in `amountIn` of zero. V4 reads that zero as "use the
|
shows is a V4 exact-in `amountIn` of zero. V4 reads that zero as "use the
|
||||||
whole open delta", so the calldata states no quantity. Transaction Decoding
|
whole open delta", so the calldata states no quantity. Transaction Decoding
|
||||||
@@ -3008,29 +3008,35 @@ Currently supported:
|
|||||||
Router addresses Uniswap publishes for the network: on TxApproval the network
|
Router addresses Uniswap publishes for the network: on TxApproval the network
|
||||||
the transaction is for, on TransactionDetail the network it was sent on. The
|
the transaction is for, on TransactionDetail the network it was sent on. The
|
||||||
same calldata sent to any other address is shown as raw data. Decodes Permit2,
|
same calldata sent to any other address is shown as raw data. Decodes Permit2,
|
||||||
V2/V3/V4 swaps, wrap/unwrap, and balance checks. A V4 swap action is read only
|
V2/V3/V4 swaps, wrap/unwrap, and balance checks. A V3 swap step's path is read
|
||||||
in the parameter shape deployed on mainnet and Sepolia, not in the newer one
|
as the router reads it: a `V3_SWAP_EXACT_IN` path runs from the token it
|
||||||
on `v4-periphery` `main`, which adds a `minHopPriceX36` field. An action in
|
spends to the token it buys, and a `V3_SWAP_EXACT_OUT` path from the token it
|
||||||
any other shape is not read, and its step is listed as
|
buys to the token it spends, so that step's input token is its path's last
|
||||||
`V4 Swap (could not be decoded)`. Each step is named and read as the command
|
token and its output token the first. The router reads a path as one token
|
||||||
that the release of the router at that address runs for its command type, as
|
address followed by whole hops of a 3-byte fee and a token address, and
|
||||||
that release's `contracts/libraries/Commands.sol` names it. The decoder reads
|
ignores any bytes after the last hop, so the decoder does not read a path with
|
||||||
the type from the command byte with the mask that router uses: `0x1f` for
|
such bytes. A V4 swap action is read only in the parameter shape deployed on
|
||||||
`UniversalRouterV1`, `0x3f` for `UniversalRouterV1_2_V2Support` and
|
mainnet and Sepolia, not in the newer one on `v4-periphery` `main`, which adds
|
||||||
`UniversalRouterV2`, and `0x7f` for `UniversalRouterV2_1_1` and
|
a `minHopPriceX36` field. An action in any other shape is not read, and its
|
||||||
`UniversalRouterV2_1_2`. The allow-revert flag (`0x80`) therefore does not
|
step is listed as `V4 Swap (could not be decoded)`. Each step is named and
|
||||||
change the command a step is read as. Releases run different commands for some
|
read as the command that the release of the router at that address runs for
|
||||||
types: on `UniversalRouterV1` and `UniversalRouterV1_2_V2Support`, `0x10` to
|
its command type, as that release's `contracts/libraries/Commands.sol` names
|
||||||
`0x13` are NFT commands, where the later routers run `V4_SWAP`,
|
it. The decoder reads the type from the command byte with the mask that router
|
||||||
`V3_POSITION_MANAGER_PERMIT`, `V3_POSITION_MANAGER_CALL` and
|
uses: `0x1f` for `UniversalRouterV1`, `0x3f` for
|
||||||
`V4_INITIALIZE_POOL`. The decoder does not read those routers' NFT commands,
|
`UniversalRouterV1_2_V2Support` and `UniversalRouterV2`, and `0x7f` for
|
||||||
and lists each by its name there, for example `Seaport` for `SEAPORT` (`0x10`
|
`UniversalRouterV2_1_1` and `UniversalRouterV2_1_2`. The allow-revert flag
|
||||||
on `UniversalRouterV1`). A type a release names no command for is listed by
|
(`0x80`) therefore does not change the command a step is read as. Releases run
|
||||||
its number, for example `Command 0x0e` on `UniversalRouterV1`, which has no
|
different commands for some types: on `UniversalRouterV1` and
|
||||||
`BALANCE_CHECK_ERC20` and reverts on it. The router reverts the whole
|
`UniversalRouterV1_2_V2Support`, `0x10` to `0x13` are NFT commands, where the
|
||||||
transaction when a step fails, unless the step's command byte has that flag
|
later routers run `V4_SWAP`, `V3_POSITION_MANAGER_PERMIT`,
|
||||||
set and its command is one the router lets fail, as each release's
|
`V3_POSITION_MANAGER_CALL` and `V4_INITIALIZE_POOL`. The decoder does not read
|
||||||
`contracts/base/Dispatcher.sol` shows: on `UniversalRouterV2`,
|
those routers' NFT commands, and lists each by its name there, for example
|
||||||
|
`Seaport` for `SEAPORT` (`0x10` on `UniversalRouterV1`). A type a release
|
||||||
|
names no command for is listed by its number, for example `Command 0x0e` on
|
||||||
|
`UniversalRouterV1`, which has no `BALANCE_CHECK_ERC20` and reverts on it. The
|
||||||
|
router reverts the whole transaction when a step fails, unless the step's
|
||||||
|
command byte has that flag set and its command is one the router lets fail, as
|
||||||
|
each release's `contracts/base/Dispatcher.sol` shows: on `UniversalRouterV2`,
|
||||||
`UniversalRouterV2_1_1` and `UniversalRouterV2_1_2`, `PERMIT2_PERMIT`,
|
`UniversalRouterV2_1_1` and `UniversalRouterV2_1_2`, `PERMIT2_PERMIT`,
|
||||||
`PERMIT2_PERMIT_BATCH`, `BALANCE_CHECK_ERC20`, `V3_POSITION_MANAGER_PERMIT`,
|
`PERMIT2_PERMIT_BATCH`, `BALANCE_CHECK_ERC20`, `V3_POSITION_MANAGER_PERMIT`,
|
||||||
`V3_POSITION_MANAGER_CALL`, `V4_INITIALIZE_POOL`, `V4_POSITION_MANAGER_CALL`
|
`V3_POSITION_MANAGER_CALL`, `V4_INITIALIZE_POOL`, `V4_POSITION_MANAGER_CALL`
|
||||||
@@ -3093,40 +3099,42 @@ Currently supported:
|
|||||||
as a swap paid in ETH does, but only when that amount is not
|
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
|
`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
|
`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. A `SETTLE` of `OPEN_DELTA`
|
WETH from it reads as leaving the WETH there. A V2 or V3 exact-out step paid
|
||||||
(zero), and a `SETTLE_ALL`, pay only what the step still owes of their token
|
from the router spends an amount the calldata does not state, at most its
|
||||||
when they run. The decoder takes that to be the amounts the step's swap
|
`amountInMax`, so the decoder counts it as spending nothing the router holds.
|
||||||
actions state they spend of that token only when every action in the step
|
A `SETTLE` of `OPEN_DELTA` (zero), and a `SETTLE_ALL`, pay only what the step
|
||||||
could be read, every swap action names the token it spends and the token it
|
still owes of their token when they run. The decoder takes that to be the
|
||||||
buys (a swap action with an empty path swaps nothing), the `SETTLE` or
|
amounts the step's swap actions state they spend of that token only when every
|
||||||
`SETTLE_ALL` is the step's only `SETTLE` or `SETTLE_ALL` of that token and
|
action in the step could be read, every swap action names the token it spends
|
||||||
comes after every swap action, no swap action buys that token, no `TAKE`,
|
and the token it buys (a swap action with an empty path swaps nothing), the
|
||||||
`TAKE_ALL` or `TAKE_PORTION` in the step takes it (a `TAKE` of a stated amount
|
`SETTLE` or `SETTLE_ALL` is the step's only `SETTLE` or `SETTLE_ALL` of that
|
||||||
adds to what the step owes), and the swap actions that spend it state a
|
token and comes after every swap action, no swap action buys that token, no
|
||||||
quantity together; otherwise it does not know the amount, and such a `SETTLE`
|
`TAKE`, `TAKE_ALL` or `TAKE_PORTION` in the step takes it (a `TAKE` of a
|
||||||
from the router counts as spending nothing. So the rest of a V4 `SETTLE` of
|
stated amount adds to what the step owes), and the swap actions that spend it
|
||||||
more than its swap spends, taken to the router, the WETH of a `WRAP_ETH`, and
|
state a quantity together; otherwise it does not know the amount, and such a
|
||||||
a token a swap pays the router on the way to the output, each stays in the
|
`SETTLE` from the router counts as spending nothing. So the rest of a V4
|
||||||
router when later steps do not spend all of it, where anyone can take it in a
|
`SETTLE` of more than its swap spends, taken to the router, the WETH of a
|
||||||
later transaction. The router sends ETH as written, so a `TRANSFER` of
|
`WRAP_ETH`, and a token a swap pays the router on the way to the output, each
|
||||||
`CONTRACT_BALANCE` of ETH always reverts and pays out nothing. A step paying a
|
stays in the router when later steps do not spend all of it, where anyone can
|
||||||
token to the V2 pair that the very next step spends, as a V2 exact-in of that
|
take it in a later transaction. The router sends ETH as written, so a
|
||||||
token whose `amountIn` is `ALREADY_PAID` (zero) or `CONTRACT_BALANCE`, is a
|
`TRANSFER` of `CONTRACT_BALANCE` of ETH always reverts and pays out nothing. A
|
||||||
hop, not a payout: Uniswap's mixed routes pay the next V2 pair directly, and
|
step paying a token to the V2 pair that the very next step spends, as a V2
|
||||||
the router enforces that swap's own minimum. The decoder checks that the
|
exact-in of that token whose `amountIn` is `ALREADY_PAID` (zero) or
|
||||||
address paid is that pair, deriving it from the V2 factory and pair init code
|
`CONTRACT_BALANCE`, is a hop, not a payout: Uniswap's mixed routes pay the
|
||||||
hash the network's routers were deployed with and the first two tokens of that
|
next V2 pair directly, and the router enforces that swap's own minimum. The
|
||||||
step's path; a payment to any other address is named. (Sepolia's
|
decoder checks that the address paid is that pair, deriving it from the V2
|
||||||
`UniversalRouterV1_2_V2Support` was deployed with placeholders for both
|
factory and pair init code hash the network's routers were deployed with and
|
||||||
values, so no V2 swap step can succeed on that router.) Every other payment
|
the first two tokens of that step's path; a payment to any other address is
|
||||||
counts, in whichever step and of whatever token, a share of the output
|
named. (Sepolia's `UniversalRouterV1_2_V2Support` was deployed with
|
||||||
included: an interface fee paid to another address by `PAY_PORTION`,
|
placeholders for both values, so no V2 swap step can succeed on that router.)
|
||||||
`TRANSFER` or `TAKE_PORTION` is named, and the minimum reads `Min. paid out`,
|
Every other payment counts, in whichever step and of whatever token, a share
|
||||||
because the swap's minimum includes the fee. The minimum reads `Min. received`
|
of the output included: an interface fee paid to another address by
|
||||||
only when some step pays the transaction's sender (the From address on
|
`PAY_PORTION`, `TRANSFER` or `TAKE_PORTION` is named, and the minimum reads
|
||||||
TxApproval, the sender the explorer reports on TransactionDetail) and no
|
`Min. paid out`, because the swap's minimum includes the fee. The minimum
|
||||||
`Paid to` line is shown. `Paid to` lines after `Token Out` name each other
|
reads `Min. received` only when some step pays the transaction's sender (the
|
||||||
address paid; one reads
|
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
|
`The bridge, which pays the deposit's recipient on another network` for a
|
||||||
bridge deposit; one reads, for example,
|
bridge deposit; one reads, for example,
|
||||||
`The router, 2000.0000 USDT from the sender (later steps may not spend all of it)`
|
`The router, 2000.0000 USDT from the sender (later steps may not spend all of it)`
|
||||||
@@ -3140,52 +3148,52 @@ Currently supported:
|
|||||||
`Unknown (not named in the calldata)`. Wherever there is a `Paid to` line, the
|
`Unknown (not named in the calldata)`. Wherever there is a `Paid to` line, the
|
||||||
minimum reads `Min. paid out`. The `Amount` line gives the total the steps
|
minimum reads `Min. paid out`. The `Amount` line gives the total the steps
|
||||||
take of `Token In`, from the step that sets the input side on: the first V2 or
|
take of `Token In`, from the step that sets the input side on: the first V2 or
|
||||||
V3 exact-in, V2 exact-out, `WRAP_ETH` or V4 swap step that names an input
|
V3 exact-in or exact-out, `WRAP_ETH` or V4 swap step that names an input token
|
||||||
token or amount. A V4 step with a swap action sets the input side when no
|
or amount. A V4 step with a swap action sets the input side when no earlier
|
||||||
earlier step set it, even when it names neither (see Display Consistency).
|
step set it, even when it names neither (see Display Consistency). That step
|
||||||
That step counts its own amount, whoever pays it: a V2 exact-out step its
|
counts its own amount, whoever pays it: a V2 or V3 exact-out step its
|
||||||
`amountInMax`, and a V4 step what it takes of its input token from the user's
|
`amountInMax`, and a V4 step what it takes of its input token from the user's
|
||||||
wallet, as below, or, when the user pays it none of that token, the `amountIn`
|
wallet, as below, or, when the user pays it none of that token, the `amountIn`
|
||||||
of the swap action that spends it. Each later V2 or V3 exact-in or V2
|
of the swap action that spends it. Each later V2 or V3 exact-in or exact-out
|
||||||
exact-out step whose `payerIsUser` is true takes its amount, found in the same
|
step whose `payerIsUser` is true takes its amount, found in the same way, from
|
||||||
way, from the user's wallet. Every V4 step, with a swap action or without,
|
the user's wallet. Every V4 step, with a swap action or without, takes from
|
||||||
takes from the user's wallet what its `SETTLE`s paid by the user
|
the user's wallet what its `SETTLE`s paid by the user (`payerIsUser` true) and
|
||||||
(`payerIsUser` true) and its `SETTLE_ALL`s pay, for each token on its own;
|
its `SETTLE_ALL`s pay, for each token on its own; what the step that sets the
|
||||||
what the step that sets the input side takes of any other token counts as a
|
input side takes of any other token counts as a later step's take. A `SETTLE`
|
||||||
later step's take. A `SETTLE` of any amount but `OPEN_DELTA` pays that amount,
|
of any amount but `OPEN_DELTA` pays that amount, and a `SETTLE` of
|
||||||
and a `SETTLE` of `OPEN_DELTA` or a `SETTLE_ALL` pays what the step owes, as
|
`OPEN_DELTA` or a `SETTLE_ALL` pays what the step owes, as above. So a V4 step
|
||||||
above. So a V4 step that swaps 400 USDT, with a `SETTLE` of 5,000 USDT paid by
|
that swaps 400 USDT, with a `SETTLE` of 5,000 USDT paid by the user and the
|
||||||
the user and the rest taken to another address, reads `5000.0000 USDT`. A
|
rest taken to another address, reads `5000.0000 USDT`. A later step paid from
|
||||||
later step paid from the router spends what the router holds and takes
|
the router spends what the router holds and takes nothing, and so does a V2
|
||||||
nothing, and so does a V2 exact-in whose `amountIn` is `ALREADY_PAID`. A later
|
exact-in whose `amountIn` is `ALREADY_PAID`. A later take of `Token In` is
|
||||||
take of `Token In` is added to the total while both are quantities: a V3
|
added to the total while both are quantities: a V3 exact-in of 600 USDT and a
|
||||||
exact-in of 600 USDT and a V2 exact-in of 400 USDT, both paid by the user,
|
V2 exact-in of 400 USDT, both paid by the user, read `1000.0000 USDT`. When
|
||||||
read `1000.0000 USDT`. When the decoder does not know a take's amount, the
|
the decoder does not know a take's amount, the total is not known and there is
|
||||||
total is not known and there is no `Amount` line. `Token In` is the token of
|
no `Amount` line. `Token In` is the token of the last `PERMIT2_PERMIT` the
|
||||||
the last `PERMIT2_PERMIT` the decoder reads, when there is one. Its amount,
|
decoder reads, when there is one. Its amount, `Unlimited` included, stands as
|
||||||
`Unlimited` included, stands as the `Amount` figure in place of the total only
|
the `Amount` figure in place of the total only in the plain shape of Uniswap's
|
||||||
in the plain shape of Uniswap's own routes: it is the only `PERMIT2_PERMIT`,
|
own routes: it is the only `PERMIT2_PERMIT`, its spender is the router, the
|
||||||
its spender is the router, the router may not let it fail, no step before it
|
router may not let it fail, no step before it takes its token or a token the
|
||||||
takes its token or a token the decoder does not know, and the decoder reads
|
decoder does not know, and the decoder reads every step after it in full.
|
||||||
every step after it in full. Permit2 then lets the router take no more of the
|
Permit2 then lets the router take no more of the token than that amount, so
|
||||||
token than that amount, so the permit covers every take of it. A step the
|
the permit covers every take of it. A step the decoder does not read in full
|
||||||
decoder does not read in full may raise what Permit2 lets the router take, as
|
may raise what Permit2 lets the router take, as a `PERMIT2_PERMIT_BATCH` can:
|
||||||
a `PERMIT2_PERMIT_BATCH` can: a command the decoder does not read (listed at
|
a command the decoder does not read (listed at the end of this section), a
|
||||||
the end of this section), a step whose input it cannot read, in whole or in
|
step whose input it cannot read, in whole or in part, and a step it fails on.
|
||||||
part, and a step it fails on. In any other case the line gives the total, as
|
In any other case the line gives the total, as with no permit. When the step
|
||||||
with no permit. When the step that sets the input side spends a token other
|
that sets the input side spends a token other than the permit's, or one the
|
||||||
than the permit's, or one the decoder does not know, its amount gets lines of
|
decoder does not know, its amount gets lines of its own and the total has no
|
||||||
its own and the total has no figure to start from: unless the permit's amount
|
figure to start from: unless the permit's amount stands there is no `Amount`
|
||||||
stands there is no `Amount` line, and each take of the permit's token gets
|
line, and each take of the permit's token gets lines of its own. Each take the
|
||||||
lines of its own. Each take the total does not count gives three lines after
|
total does not count gives three lines after the `Amount` line (after
|
||||||
the `Amount` line (after `Token In` when there is none): one of a token other
|
`Token In` when there is none): one of a token other than `Token In`; one of
|
||||||
than `Token In`; one of `Token In` after an amount that is not a quantity (a
|
`Token In` after an amount that is not a quantity (a V4 open delta,
|
||||||
V4 open delta, `ALREADY_PAID`, which keep their wording on the `Amount` line,
|
`ALREADY_PAID`, which keep their wording on the `Amount` line, or no stated
|
||||||
or no stated amount, as of a V4 exact-out step); and one whose amount the
|
amount, as of a V4 exact-out step); and one whose amount the decoder does not
|
||||||
decoder does not know. When the step that sets the input side states no amount
|
know. When the step that sets the input side states no amount and there is no
|
||||||
and there is no `Amount` line, that step gets them too whenever another take's
|
`Amount` line, that step gets them too whenever another take's lines give a
|
||||||
lines give a figure, so that figure cannot read as all that leaves. They come
|
figure, so that figure cannot read as all that leaves. They come in step
|
||||||
in step order, the step that sets the input side first when it has them:
|
order, the step that sets the input side first when it has them:
|
||||||
`Moved from the sender`, its amount, worded as the `Amount` line words one, or
|
`Moved from the sender`, its amount, worded as the `Amount` line words one, or
|
||||||
`Unknown (could not be decoded)` when the decoder does not know it;
|
`Unknown (could not be decoded)` when the decoder does not know it;
|
||||||
`Token moved`, giving the token's address as the `Token In` line does; and
|
`Token moved`, giving the token's address as the `Token In` line does; and
|
||||||
@@ -3193,32 +3201,33 @@ Currently supported:
|
|||||||
does not know gives the `Moved from the sender` line alone. A step may also
|
does not know gives the `Moved from the sender` line alone. A step may also
|
||||||
take from the user's wallet what the decoder did not read: a V4 step whose
|
take from the user's wallet what the decoder did not read: a V4 step whose
|
||||||
input or one of whose `SETTLE`s or `SETTLE_ALL`s it cannot read, which the
|
input or one of whose `SETTLE`s or `SETTLE_ALL`s it cannot read, which the
|
||||||
router still runs; a V2 or V3 exact-in or V2 exact-out step or a
|
router still runs; a V2 or V3 exact-in or exact-out step or a
|
||||||
`PERMIT2_TRANSFER_FROM` whose input it cannot read; every
|
`PERMIT2_TRANSFER_FROM` whose input it cannot read, such as a V3 step whose
|
||||||
`PERMIT2_TRANSFER_FROM_BATCH`; every `V3_SWAP_EXACT_OUT`, which the decoder
|
path is shorter than one token address or has bytes after its last hop; every
|
||||||
does not read, wherever it stands; every `EXECUTE_SUB_PLAN`, wherever it
|
`PERMIT2_TRANSFER_FROM_BATCH`; every `EXECUTE_SUB_PLAN`, wherever it stands;
|
||||||
stands; and every step it fails on. Each such step gives one
|
and every step it fails on. Each such step gives one `Moved from the sender`
|
||||||
`Moved from the sender` line among those, reading
|
line among those, reading `Unknown (could not be decoded)`. That line, or any
|
||||||
`Unknown (could not be decoded)`. That line, or any line for a token the
|
line for a token the decoder does not know, leaves no `Amount` line, whose
|
||||||
decoder does not know, leaves no `Amount` line, whose figure might be too low,
|
figure might be too low, and such a step before a `PERMIT2_PERMIT` keeps the
|
||||||
and such a step before a `PERMIT2_PERMIT` keeps the permit's amount from
|
permit's amount from standing. Then each `PERMIT2_TRANSFER_FROM` but one into
|
||||||
standing. Then each `PERMIT2_TRANSFER_FROM` but one into the router, in step
|
the router, in step order, gives three lines: `Moved from the sender`, the
|
||||||
order, gives three lines: `Moved from the sender`, the amount it moves, for
|
amount it moves, for example `1000000.0000 USDT`; `Token moved`, giving the
|
||||||
example `1000000.0000 USDT`; `Token moved`, giving the token's address as the
|
token's address as the `Token In` line does; and `Moved to`, which reads
|
||||||
`Token In` line does; and `Moved to`, which reads
|
|
||||||
`The V2 pair the next swap spends` for a hop and `The sender` for `address(1)`
|
`The V2 pair the next swap spends` for a hop and `The sender` for `address(1)`
|
||||||
on every router but `UniversalRouterV1`, and otherwise gives the address. A
|
on every router but `UniversalRouterV1`, and otherwise gives the address. A
|
||||||
move into the router needs none of them, because its `Paid to` line and
|
move into the router needs none of them, because its `Paid to` line and
|
||||||
`Token moved into the router` line already give the amount, the token and
|
`Token moved into the router` line already give the amount, the token and
|
||||||
where it goes, and so its amount is shown once. These lines change neither the
|
where it goes, and so its amount is shown once. These lines change neither the
|
||||||
`Paid to` lines nor the minimum's label. A step goes unread when its recipient
|
`Paid to` lines nor the minimum's label. A step goes unread when its recipient
|
||||||
does not decode as an address, or when it is a command the decoder does not
|
does not decode as an address, when it is a V3 swap step whose path is shorter
|
||||||
read: every command other than `PERMIT2_PERMIT`, `PERMIT2_TRANSFER_FROM`,
|
than one token address or has bytes after its last hop, or when it is a
|
||||||
`V2_SWAP_EXACT_IN`, `V2_SWAP_EXACT_OUT`, `V3_SWAP_EXACT_IN`, `V4_SWAP`,
|
command the decoder does not read: every command other than `PERMIT2_PERMIT`,
|
||||||
`SWEEP`, `TRANSFER`, `PAY_PORTION`, `PAY_PORTION_FULL_PRECISION`,
|
`PERMIT2_TRANSFER_FROM`, `V2_SWAP_EXACT_IN`, `V2_SWAP_EXACT_OUT`,
|
||||||
`ACROSS_V4_DEPOSIT_V3`, `WRAP_ETH`, `UNWRAP_WETH` and `BALANCE_CHECK_ERC20`
|
`V3_SWAP_EXACT_IN`, `V3_SWAP_EXACT_OUT`, `V4_SWAP`, `SWEEP`, `TRANSFER`,
|
||||||
(`V3_SWAP_EXACT_OUT`, `PERMIT2_TRANSFER_FROM_BATCH`, `EXECUTE_SUB_PLAN` and
|
`PAY_PORTION`, `PAY_PORTION_FULL_PRECISION`, `ACROSS_V4_DEPOSIT_V3`,
|
||||||
the NFT commands among them), or a type its release names no command for.
|
`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
|
### Non-Goals Forever
|
||||||
|
|
||||||
|
|||||||
+63
-28
@@ -592,13 +592,17 @@ function decodeV2SwapExactOut(input) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Decode V3 swap path (packed: token(20) + fee(3) + token(20) ...)
|
// The first and last token of a V3 swap path (packed: token(20) + fee(3) +
|
||||||
|
// token(20) ...), or null when it is not one token followed by whole hops of
|
||||||
|
// a fee and a token (40 hex digits, then 46 per hop). The router reads only
|
||||||
|
// whole hops and ignores any bytes after the last one, so the last 20 bytes
|
||||||
|
// of such a path are not the token it reads there.
|
||||||
function decodeV3Path(pathHex) {
|
function decodeV3Path(pathHex) {
|
||||||
const hex = pathHex.startsWith("0x") ? pathHex.slice(2) : pathHex;
|
const hex = pathHex.startsWith("0x") ? pathHex.slice(2) : pathHex;
|
||||||
if (hex.length < 40) return null;
|
if (hex.length < 40 || (hex.length - 40) % 46 !== 0) return null;
|
||||||
const tokenIn = "0x" + hex.slice(0, 40);
|
const first = "0x" + hex.slice(0, 40);
|
||||||
const tokenOut = "0x" + hex.slice(-40);
|
const last = "0x" + hex.slice(-40);
|
||||||
return { tokenIn, tokenOut };
|
return { first, last };
|
||||||
}
|
}
|
||||||
|
|
||||||
// Decode V3_SWAP_EXACT_IN (command 0x00) input bytes.
|
// Decode V3_SWAP_EXACT_IN (command 0x00) input bytes.
|
||||||
@@ -617,8 +621,39 @@ function decodeV3SwapExactIn(input) {
|
|||||||
recipient: d[0],
|
recipient: d[0],
|
||||||
amountIn: d[1],
|
amountIn: d[1],
|
||||||
amountOutMin: d[2],
|
amountOutMin: d[2],
|
||||||
tokenIn: path.tokenIn,
|
tokenIn: path.first,
|
||||||
tokenOut: path.tokenOut,
|
tokenOut: path.last,
|
||||||
|
payerIsUser: d[4],
|
||||||
|
};
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode V3_SWAP_EXACT_OUT (command 0x01) input bytes.
|
||||||
|
// ABI: (address recipient, uint256 amountOut, uint256 amountInMax,
|
||||||
|
// bytes path, bool payerIsUser)
|
||||||
|
// The router reads an exact-out path from the token it buys to the token it
|
||||||
|
// spends: it buys `amountOut` of the path's first token from the path's first
|
||||||
|
// pool and pays at most `amountInMax` of its last token to its last pool
|
||||||
|
// (`v3SwapExactOutput()` and `uniswapV3SwapCallback()` in each release's
|
||||||
|
// `contracts/modules/uniswap/v3/V3SwapRouter.sol`, at the commit linked for
|
||||||
|
// it above).
|
||||||
|
// With `payerIsUser` false the router pays the input from its own balance.
|
||||||
|
function decodeV3SwapExactOut(input) {
|
||||||
|
try {
|
||||||
|
const d = coder.decode(
|
||||||
|
["address", "uint256", "uint256", "bytes", "bool"],
|
||||||
|
input,
|
||||||
|
);
|
||||||
|
const path = decodeV3Path(d[3]);
|
||||||
|
if (!path) return null;
|
||||||
|
return {
|
||||||
|
recipient: d[0],
|
||||||
|
amountOut: d[1],
|
||||||
|
amountInMax: d[2],
|
||||||
|
tokenIn: path.last,
|
||||||
|
tokenOut: path.first,
|
||||||
payerIsUser: d[4],
|
payerIsUser: d[4],
|
||||||
};
|
};
|
||||||
} catch {
|
} catch {
|
||||||
@@ -1180,7 +1215,7 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
|
|||||||
let minOutput = null;
|
let minOutput = null;
|
||||||
let outputEstablished = false;
|
let outputEstablished = false;
|
||||||
let hasUnwrapWeth = false;
|
let hasUnwrapWeth = false;
|
||||||
let hasV2ExactOut = false;
|
let hasV2OrV3ExactOut = false;
|
||||||
let swapUndecoded = false;
|
let swapUndecoded = false;
|
||||||
let hasSubPlan = false;
|
let hasSubPlan = false;
|
||||||
const commandNames = [];
|
const commandNames = [];
|
||||||
@@ -1236,9 +1271,8 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
|
|||||||
};
|
};
|
||||||
// A step may take from the sender what this decoder did not read: a
|
// A step may take from the sender what this decoder did not read: a
|
||||||
// V4 step whose input or one of whose SETTLEs or SETTLE_ALLs it
|
// V4 step whose input or one of whose SETTLEs or SETTLE_ALLs it
|
||||||
// cannot read; a V2 or V3 exact-in or V2 exact-out step or
|
// cannot read; a V2 or V3 swap step or PERMIT2_TRANSFER_FROM whose
|
||||||
// PERMIT2_TRANSFER_FROM whose input it cannot read; a
|
// input it cannot read; a PERMIT2_TRANSFER_FROM_BATCH; an
|
||||||
// PERMIT2_TRANSFER_FROM_BATCH; a V3_SWAP_EXACT_OUT; an
|
|
||||||
// EXECUTE_SUB_PLAN; and a step it fails on. It is recorded with a null
|
// EXECUTE_SUB_PLAN; and a step it fails on. It is recorded with a null
|
||||||
// token and amount: one line says so, there is no Amount line, whose
|
// token and amount: one line says so, there is no Amount line, whose
|
||||||
// figure may then be too low, and the amount of a permit after it
|
// figure may then be too low, and the amount of a permit after it
|
||||||
@@ -1493,14 +1527,6 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
|
|||||||
stepsUnread.push(i);
|
stepsUnread.push(i);
|
||||||
spentUnread();
|
spentUnread();
|
||||||
}
|
}
|
||||||
} else if (command === "V3_SWAP_EXACT_OUT") {
|
|
||||||
// This decoder does not read it
|
|
||||||
// (https://git.eeqj.de/sneak/AutistMask/issues/601). It
|
|
||||||
// may pay any address and take from the sender, so
|
|
||||||
// wherever it stands it leaves no Amount figure.
|
|
||||||
payeeUnread = true;
|
|
||||||
stepsUnread.push(i);
|
|
||||||
spentUnread();
|
|
||||||
} else if (command === "V2_SWAP_EXACT_IN") {
|
} else if (command === "V2_SWAP_EXACT_IN") {
|
||||||
const s = decodeV2SwapExactIn(inputs[i]);
|
const s = decodeV2SwapExactIn(inputs[i]);
|
||||||
if (s) {
|
if (s) {
|
||||||
@@ -1521,10 +1547,19 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
|
|||||||
stepsUnread.push(i);
|
stepsUnread.push(i);
|
||||||
spentUnread();
|
spentUnread();
|
||||||
}
|
}
|
||||||
} else if (command === "V2_SWAP_EXACT_OUT") {
|
} else if (
|
||||||
|
command === "V2_SWAP_EXACT_OUT" ||
|
||||||
|
command === "V3_SWAP_EXACT_OUT"
|
||||||
|
) {
|
||||||
// Buys exactly amountOut and spends at most amountInMax.
|
// Buys exactly amountOut and spends at most amountInMax.
|
||||||
hasV2ExactOut = true;
|
// Paid from the router, it spends an amount the calldata
|
||||||
const s = decodeV2SwapExactOut(inputs[i]);
|
// does not state, so it spends nothing spendFromRouter()
|
||||||
|
// could count.
|
||||||
|
hasV2OrV3ExactOut = true;
|
||||||
|
const s =
|
||||||
|
command === "V2_SWAP_EXACT_OUT"
|
||||||
|
? decodeV2SwapExactOut(inputs[i])
|
||||||
|
: decodeV3SwapExactOut(inputs[i]);
|
||||||
if (s) {
|
if (s) {
|
||||||
if (!inputEstablished) {
|
if (!inputEstablished) {
|
||||||
setInputOnce(s.tokenIn, s.amountInMax);
|
setInputOnce(s.tokenIn, s.amountInMax);
|
||||||
@@ -1943,12 +1978,12 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
|
|||||||
if (amount >= MAX_UINT160) {
|
if (amount >= MAX_UINT160) {
|
||||||
return { raw: "Unlimited", display: "Unlimited" };
|
return { raw: "Unlimited", display: "Unlimited" };
|
||||||
}
|
}
|
||||||
if (hasV2ExactOut) {
|
if (hasV2OrV3ExactOut) {
|
||||||
// A V2 exact-out swap spends at most this figure, whichever
|
// A V2 or V3 exact-out swap spends at most this figure,
|
||||||
// steps gave it (an amountInMax, the WRAP_ETH of a swap paid
|
// whichever steps gave it (an amountInMax, the WRAP_ETH of a
|
||||||
// in ETH, a permit, or several steps' total), so it is said to
|
// swap paid in ETH, a permit, or several steps' total), so it
|
||||||
// be a maximum, in `raw` too: the wait, success and error
|
// is said to be a maximum, in `raw` too: the wait, success and
|
||||||
// screens show `raw` as the transaction's amount.
|
// error screens show `raw` as the transaction's amount.
|
||||||
const most = amountText(amount, info);
|
const most = amountText(amount, info);
|
||||||
return {
|
return {
|
||||||
raw: "Up to " + most.raw,
|
raw: "Up to " + most.raw,
|
||||||
|
|||||||
+181
-15
@@ -88,17 +88,42 @@ function encodeV2SwapExactOut(recipient, amountOut, amountInMax, pathAddrs) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper: encode a V3_SWAP_EXACT_IN input (command 0x00)
|
// Helper: a V3 path: token(20) + fee(3) + token(20) ...
|
||||||
function encodeV3SwapExactIn(recipient, amountIn, amountOutMin, pathTokens) {
|
function encodeV3Path(pathTokens) {
|
||||||
// V3 path: token(20) + fee(3) + token(20) ...
|
|
||||||
let pathHex = pathTokens[0].slice(2).toLowerCase();
|
let pathHex = pathTokens[0].slice(2).toLowerCase();
|
||||||
for (let i = 1; i < pathTokens.length; i++) {
|
for (let i = 1; i < pathTokens.length; i++) {
|
||||||
pathHex += "000bb8"; // fee 3000 = 0x000bb8
|
pathHex += "000bb8"; // fee 3000 = 0x000bb8
|
||||||
pathHex += pathTokens[i].slice(2).toLowerCase();
|
pathHex += pathTokens[i].slice(2).toLowerCase();
|
||||||
}
|
}
|
||||||
|
return "0x" + pathHex;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper: encode a V3_SWAP_EXACT_IN input (command 0x00)
|
||||||
|
function encodeV3SwapExactIn(recipient, amountIn, amountOutMin, pathTokens) {
|
||||||
return coder.encode(
|
return coder.encode(
|
||||||
["address", "uint256", "uint256", "bytes", "bool"],
|
["address", "uint256", "uint256", "bytes", "bool"],
|
||||||
[recipient, amountIn, amountOutMin, "0x" + pathHex, true],
|
[recipient, amountIn, amountOutMin, encodeV3Path(pathTokens), true],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper: encode a V3_SWAP_EXACT_OUT input (command 0x01). Its path lists
|
||||||
|
// the token bought first and the token spent last.
|
||||||
|
function encodeV3SwapExactOut(
|
||||||
|
recipient,
|
||||||
|
amountOut,
|
||||||
|
amountInMax,
|
||||||
|
pathTokens,
|
||||||
|
payerIsUser = true,
|
||||||
|
) {
|
||||||
|
return coder.encode(
|
||||||
|
["address", "uint256", "uint256", "bytes", "bool"],
|
||||||
|
[
|
||||||
|
recipient,
|
||||||
|
amountOut,
|
||||||
|
amountInMax,
|
||||||
|
encodeV3Path(pathTokens),
|
||||||
|
payerIsUser,
|
||||||
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -644,6 +669,79 @@ describe("uniswap decoder", () => {
|
|||||||
expect(detail(result, "Min. received").value).toBe("1500.0000 USDC");
|
expect(detail(result, "Min. received").value).toBe("1500.0000 USDC");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// https://git.eeqj.de/sneak/AutistMask/issues/601 — a V3_SWAP_EXACT_OUT
|
||||||
|
// was named but not read, so its tokens read "Unknown (not named in the
|
||||||
|
// calldata)" and it showed no amounts. It is read as a V2_SWAP_EXACT_OUT
|
||||||
|
// is, with its path read as the router reads it: from the token it buys
|
||||||
|
// to the token it spends. Read the other way, each path below would be a
|
||||||
|
// swap of WETH for USDT.
|
||||||
|
test.each([
|
||||||
|
{ hops: "single-hop", path: [WETH_ADDR, USDT_ADDR] },
|
||||||
|
{ hops: "multi-hop", path: [WETH_ADDR, USDC_ADDR, USDT_ADDR] },
|
||||||
|
])(
|
||||||
|
"decodes a $hops V3_SWAP_EXACT_OUT, stating the input amount as a maximum",
|
||||||
|
({ path }) => {
|
||||||
|
// Buy exactly 1 WETH for at most 2,000 USDT, paid by the user.
|
||||||
|
const data = buildExecute(
|
||||||
|
"0x01", // V3_SWAP_EXACT_OUT
|
||||||
|
[encodeV3SwapExactOut(USER_ADDR, ONE_WETH, 2000000000n, path)],
|
||||||
|
9999999999n,
|
||||||
|
);
|
||||||
|
|
||||||
|
const result = uniswap.decode(
|
||||||
|
data,
|
||||||
|
ROUTER_ADDR,
|
||||||
|
"0x1",
|
||||||
|
{},
|
||||||
|
USER_ADDR,
|
||||||
|
);
|
||||||
|
expect(result.name).toBe("Swap USDT → WETH");
|
||||||
|
expect(detail(result, "Token In").address).toBe(
|
||||||
|
USDT_ADDR.toLowerCase(),
|
||||||
|
);
|
||||||
|
expect(detail(result, "Token Out").address).toBe(
|
||||||
|
WETH_ADDR.toLowerCase(),
|
||||||
|
);
|
||||||
|
|
||||||
|
const amount = detail(result, "Amount");
|
||||||
|
expect(amount.value).toBe("Up to 2000.0000 USDT");
|
||||||
|
expect(amount.rawValue).toBe("Up to 2000.0000");
|
||||||
|
|
||||||
|
expect(detail(result, "Min. received").value).toBe("1.0000 WETH");
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
// The V2 exact-out swap paid in ETH above, as a V3_SWAP_EXACT_OUT paid
|
||||||
|
// from the WETH the router holds: it takes nothing more from the user's
|
||||||
|
// wallet.
|
||||||
|
test("a V3 exact-out swap paid in ETH from the router shows the token it buys and a maximum", () => {
|
||||||
|
const data = buildExecute(
|
||||||
|
solidityPacked(["uint8", "uint8", "uint8"], [0x0b, 0x01, 0x0c]),
|
||||||
|
[
|
||||||
|
encodeWrapEth(ROUTER_ADDR, 500000000000000000n),
|
||||||
|
encodeV3SwapExactOut(
|
||||||
|
USER_ADDR,
|
||||||
|
1500000000n, // amountOut: 1,500 USDC
|
||||||
|
500000000000000000n, // amountInMax: 0.5 WETH
|
||||||
|
[USDC_ADDR, WETH_ADDR],
|
||||||
|
false, // paid from the router
|
||||||
|
),
|
||||||
|
encodeWrapEth(USER_ADDR, 0n), // UNWRAP_WETH same encoding
|
||||||
|
],
|
||||||
|
9999999999n,
|
||||||
|
);
|
||||||
|
|
||||||
|
const result = uniswap.decode(data, ROUTER_ADDR, "0x1", {}, USER_ADDR);
|
||||||
|
expect(result.name).toBe("Swap ETH → USDC");
|
||||||
|
expect(detail(result, "Amount").value).toBe("Up to 0.5000 ETH");
|
||||||
|
expect(detail(result, "Moved from the sender")).toBeUndefined();
|
||||||
|
expect(detail(result, "Token Out").address).toBe(
|
||||||
|
USDC_ADDR.toLowerCase(),
|
||||||
|
);
|
||||||
|
expect(detail(result, "Paid to")).toBeUndefined();
|
||||||
|
expect(detail(result, "Min. received").value).toBe("1500.0000 USDC");
|
||||||
|
});
|
||||||
|
|
||||||
// Buy exactly 0.5 ETH for at most 1,500 USDC under a permit: the swap buys
|
// Buy exactly 0.5 ETH for at most 1,500 USDC under a permit: the swap buys
|
||||||
// WETH and UNWRAP_WETH turns it into ETH.
|
// WETH and UNWRAP_WETH turns it into ETH.
|
||||||
test("a V2 exact-out swap that buys ETH shows ETH and the permit as a maximum", () => {
|
test("a V2 exact-out swap that buys ETH shows ETH and the permit as a maximum", () => {
|
||||||
@@ -2048,11 +2146,9 @@ describe("uniswap decoder", () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// A V3_SWAP_EXACT_OUT the user pays for, of at most `amountInMax`
|
// A V3_SWAP_EXACT_OUT the user pays for, of at most `amountInMax`
|
||||||
// USDT for USDC. Its input is (recipient, amountOut, amountInMax,
|
// USDT for USDC.
|
||||||
// path, payerIsUser), laid out as encodeV3SwapExactIn() writes one;
|
|
||||||
// an exact-out path lists the token bought first.
|
|
||||||
const v3ExactOut = (amountInMax) =>
|
const v3ExactOut = (amountInMax) =>
|
||||||
encodeV3SwapExactIn(USER_ADDR, 390000000n, amountInMax, [
|
encodeV3SwapExactOut(USER_ADDR, 390000000n, amountInMax, [
|
||||||
USDC_ADDR,
|
USDC_ADDR,
|
||||||
USDT_ADDR,
|
USDT_ADDR,
|
||||||
]);
|
]);
|
||||||
@@ -2070,14 +2166,16 @@ describe("uniswap decoder", () => {
|
|||||||
[USDC_ADDR, USER_ADDR, 0n],
|
[USDC_ADDR, USER_ADDR, 0n],
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
// This decoder does not read a V3_SWAP_EXACT_OUT, so what one takes
|
// What a V3_SWAP_EXACT_OUT takes, at most its amountInMax, counts
|
||||||
// leaves no Amount figure wherever it stands, and a permit's amount
|
// as a V2_SWAP_EXACT_OUT's does, and the Amount line is a maximum.
|
||||||
// does not stand.
|
// The permit of 10,000 USDT stands; the one of 600 USDT does not,
|
||||||
|
// because the step before it took its token.
|
||||||
test.each([
|
test.each([
|
||||||
{
|
{
|
||||||
kind: "a V3 exact-in of 600 USDT, then a V3_SWAP_EXACT_OUT",
|
kind: "a V3 exact-in of 600 USDT, then a V3_SWAP_EXACT_OUT of at most 400 USDT",
|
||||||
commands: [0x00, 0x01],
|
commands: [0x00, 0x01],
|
||||||
inputs: [V3_600, v3ExactOut(USDT_400)],
|
inputs: [V3_600, v3ExactOut(USDT_400)],
|
||||||
|
amount: "Up to 1000.0000 USDT",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
kind: "a PERMIT2_PERMIT of 10,000 USDT, then a V3_SWAP_EXACT_OUT, a V2_SWAP_EXACT_OUT and a SWEEP",
|
kind: "a PERMIT2_PERMIT of 10,000 USDT, then a V3_SWAP_EXACT_OUT, a V2_SWAP_EXACT_OUT and a SWEEP",
|
||||||
@@ -2086,11 +2184,13 @@ describe("uniswap decoder", () => {
|
|||||||
encodePermit2(USDT_ADDR, 10000000000n, ROUTER_ADDR),
|
encodePermit2(USDT_ADDR, 10000000000n, ROUTER_ADDR),
|
||||||
...EXACT_OUT_SPLIT,
|
...EXACT_OUT_SPLIT,
|
||||||
],
|
],
|
||||||
|
amount: "Up to 10000.0000 USDT",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
kind: "a V3_SWAP_EXACT_OUT, a V2_SWAP_EXACT_OUT and a SWEEP",
|
kind: "a V3_SWAP_EXACT_OUT, a V2_SWAP_EXACT_OUT and a SWEEP",
|
||||||
commands: [0x01, 0x09, 0x04],
|
commands: [0x01, 0x09, 0x04],
|
||||||
inputs: EXACT_OUT_SPLIT,
|
inputs: EXACT_OUT_SPLIT,
|
||||||
|
amount: "Up to 10000.0000 USDT",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
kind: "a V3_SWAP_EXACT_OUT of at most 9,400 USDT, then a PERMIT2_PERMIT of 600 USDT and a V3 exact-in of 600 USDT",
|
kind: "a V3_SWAP_EXACT_OUT of at most 9,400 USDT, then a PERMIT2_PERMIT of 600 USDT and a V3 exact-in of 600 USDT",
|
||||||
@@ -2100,17 +2200,83 @@ describe("uniswap decoder", () => {
|
|||||||
encodePermit2(USDT_ADDR, USDT_600, ROUTER_ADDR),
|
encodePermit2(USDT_ADDR, USDT_600, ROUTER_ADDR),
|
||||||
V3_600,
|
V3_600,
|
||||||
],
|
],
|
||||||
|
amount: "Up to 10000.0000 USDT",
|
||||||
},
|
},
|
||||||
])(
|
])(
|
||||||
"$kind, all paid by the user, shows no Amount figure",
|
"$kind, all paid by the user, shows the total as a maximum",
|
||||||
({ commands, inputs }) => {
|
({ commands, inputs, amount }) => {
|
||||||
const result = decodeSteps(commands, inputs);
|
const result = decodeSteps(commands, inputs);
|
||||||
expect(linesBetweenTokens(result)).toEqual([
|
expect(linesBetweenTokens(result)).toEqual([
|
||||||
["Moved from the sender", UNDECODED],
|
["Amount", amount],
|
||||||
]);
|
]);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// A V3_SWAP_EXACT_OUT whose path is shorter than one token cannot be
|
||||||
|
// read. It may pay any address and take from the sender, so it
|
||||||
|
// leaves no Amount figure.
|
||||||
|
test("a V3 exact-in of 600 USDT, then a V3_SWAP_EXACT_OUT whose path cannot be read, shows no Amount figure", () => {
|
||||||
|
const unreadable = coder.encode(
|
||||||
|
["address", "uint256", "uint256", "bytes", "bool"],
|
||||||
|
[USER_ADDR, 390000000n, USDT_400, "0x", true],
|
||||||
|
);
|
||||||
|
const result = decodeSteps([0x00, 0x01], [V3_600, unreadable]);
|
||||||
|
expect(linesBetweenTokens(result)).toEqual([
|
||||||
|
["Moved from the sender", UNDECODED],
|
||||||
|
]);
|
||||||
|
expect(detail(result, "Paid to").value).toBe(UNDECODED);
|
||||||
|
});
|
||||||
|
|
||||||
|
// https://git.eeqj.de/sneak/AutistMask/issues/637 — the router reads
|
||||||
|
// a V3 path as one token and whole hops of a 3-byte fee and a token,
|
||||||
|
// and ignores any bytes after the last hop. Each path below is
|
||||||
|
// `tokens` and then the last 19 bytes of a bundled token's address
|
||||||
|
// whose first byte is the last token's last byte, so the path's last
|
||||||
|
// 20 bytes spell a token the router does not read. Such a path is not
|
||||||
|
// read, as one shorter than one token is not.
|
||||||
|
const pathEndingIn = (tokens, address) =>
|
||||||
|
encodeV3Path(tokens) + address.slice(4).toLowerCase();
|
||||||
|
const SPK_ADDR = "0xc20059e0317DE91738d13af027DfC4a50781b066";
|
||||||
|
const IUSD_ADDR = "0x48f9e38f3070AD8945DFEae3FA70987722E3D89c";
|
||||||
|
const v3Swap = (amount, otherAmount, path) =>
|
||||||
|
coder.encode(
|
||||||
|
["address", "uint256", "uint256", "bytes", "bool"],
|
||||||
|
[USER_ADDR, amount, otherAmount, path, true],
|
||||||
|
);
|
||||||
|
|
||||||
|
// A V3 exact-in of 1 WETH for USDC pays out USDC, not IUSD.
|
||||||
|
test("a V3_SWAP_EXACT_IN whose path has bytes after its last hop names no Token Out", () => {
|
||||||
|
const path = pathEndingIn([WETH_ADDR, USDC_ADDR], IUSD_ADDR);
|
||||||
|
const result = decodeSteps([0x00], [v3Swap(ONE_WETH, 1n, path)]);
|
||||||
|
expect(detail(result, "Token Out").address).toBeUndefined();
|
||||||
|
expect(linesBetweenTokens(result)).toEqual([
|
||||||
|
["Moved from the sender", UNDECODED],
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
// A V3 exact-out of 1,500 USDC for at most 0.5 WETH spends WETH, not
|
||||||
|
// SPK. After it, a PERMIT2_PERMIT of 1 WETH and a V3 exact-in of 0.5
|
||||||
|
// WETH: the permit's amount does not stand, because the step before
|
||||||
|
// it also takes WETH.
|
||||||
|
test("a V3_SWAP_EXACT_OUT whose path has bytes after its last hop, then a PERMIT2_PERMIT and a V3 exact-in, shows no Amount figure", () => {
|
||||||
|
const path = pathEndingIn([USDC_ADDR, WETH_ADDR], SPK_ADDR);
|
||||||
|
const result = decodeSteps(
|
||||||
|
[0x01, 0x0a, 0x00],
|
||||||
|
[
|
||||||
|
v3Swap(1500000000n, ONE_WETH / 2n, path),
|
||||||
|
encodePermit2(WETH_ADDR, ONE_WETH, ROUTER_ADDR),
|
||||||
|
encodeV3SwapExactIn(USER_ADDR, ONE_WETH / 2n, 1n, [
|
||||||
|
WETH_ADDR,
|
||||||
|
USDC_ADDR,
|
||||||
|
]),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
expect(linesBetweenTokens(result)).toEqual([
|
||||||
|
["Moved from the sender", UNDECODED],
|
||||||
|
]);
|
||||||
|
expect(detail(result, "Paid to").value).toBe(UNDECODED);
|
||||||
|
});
|
||||||
|
|
||||||
// A later step's amount that cannot be added to the Amount line,
|
// A later step's amount that cannot be added to the Amount line,
|
||||||
// because the amount of the step that set the input side is not a
|
// because the amount of the step that set the input side is not a
|
||||||
// quantity, gets lines of its own. When that step stated no amount,
|
// quantity, gets lines of its own. When that step stated no amount,
|
||||||
|
|||||||
@@ -387,6 +387,18 @@ describe("each swap command's recipient", () => {
|
|||||||
data: (r) => execute(swapToWeth(r)),
|
data: (r) => execute(swapToWeth(r)),
|
||||||
min: "1.0000 WETH",
|
min: "1.0000 WETH",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
// Its input has V3_SWAP_EXACT_IN's layout, with amountOut and
|
||||||
|
// amountInMax in place of amountIn and amountOutMin, and a path
|
||||||
|
// that lists the token bought first.
|
||||||
|
kind: "V3_SWAP_EXACT_OUT",
|
||||||
|
data: (r) =>
|
||||||
|
execute([
|
||||||
|
0x01,
|
||||||
|
v3SwapExactIn(r, ONE_WETH, USDT_2000, WETH, USDT)[1],
|
||||||
|
]),
|
||||||
|
min: "1.0000 WETH",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
kind: "V4_SWAP with TAKE",
|
kind: "V4_SWAP with TAKE",
|
||||||
data: (r) =>
|
data: (r) =>
|
||||||
@@ -2019,10 +2031,6 @@ describe("a payout that cannot be read is never said to be received", () => {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
|
||||||
kind: "V3_SWAP_EXACT_OUT",
|
|
||||||
step: [0x01, swapToWeth(OTHER)[1]],
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
kind: "EXECUTE_SUB_PLAN",
|
kind: "EXECUTE_SUB_PLAN",
|
||||||
step: [
|
step: [
|
||||||
|
|||||||
Reference in New Issue
Block a user