fix: a V3 exact-out swap step shows its tokens and amounts (closes #601)
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. One whose input cannot be read still leaves no Amount figure. Model: opus-5-5
This commit is contained in:
@@ -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,10 +3008,14 @@ 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
|
||||||
|
token and its output token the first. A V4 swap action is read only in the
|
||||||
|
parameter shape deployed on mainnet and Sepolia, not in the newer one on
|
||||||
|
`v4-periphery` `main`, which adds a `minHopPriceX36` field. An action in any
|
||||||
|
other shape is not read, and its step is listed as
|
||||||
`V4 Swap (could not be decoded)`. Each step is named and read as the command
|
`V4 Swap (could not be decoded)`. Each step is named and read as the command
|
||||||
that the release of the router at that address runs for its command type, as
|
that the release of the router at that address runs for its command type, as
|
||||||
that release's `contracts/libraries/Commands.sol` names it. The decoder reads
|
that release's `contracts/libraries/Commands.sol` names it. The decoder reads
|
||||||
@@ -3093,40 +3097,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 at most its `amountInMax`, an amount the calldata does
|
||||||
when they run. The decoder takes that to be the amounts the step's swap
|
not state, so the decoder counts it as spending nothing the router holds. A
|
||||||
actions state they spend of that token only when every action in the step
|
`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 +3146,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,12 +3199,11 @@ 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; every `PERMIT2_TRANSFER_FROM_BATCH`;
|
||||||
does not read, wherever it stands; every `EXECUTE_SUB_PLAN`, wherever it
|
every `EXECUTE_SUB_PLAN`, wherever it stands; and every step it fails on. Each
|
||||||
stands; and every step it fails on. Each such step gives one
|
such step gives one `Moved from the sender` line among those, reading
|
||||||
`Moved from the sender` line among those, reading
|
|
||||||
`Unknown (could not be decoded)`. That line, or any line for a token the
|
`Unknown (could not be decoded)`. That line, or any line for a token the
|
||||||
decoder does not know, leaves no `Amount` line, whose figure might be too low,
|
decoder does not know, leaves no `Amount` line, whose figure might be too low,
|
||||||
and such a step before a `PERMIT2_PERMIT` keeps the permit's amount from
|
and such a step before a `PERMIT2_PERMIT` keeps the permit's amount from
|
||||||
@@ -3212,13 +3217,15 @@ Currently supported:
|
|||||||
`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 when it is a command the decoder does not read:
|
||||||
`V2_SWAP_EXACT_IN`, `V2_SWAP_EXACT_OUT`, `V3_SWAP_EXACT_IN`, `V4_SWAP`,
|
every command other than `PERMIT2_PERMIT`, `PERMIT2_TRANSFER_FROM`,
|
||||||
`SWEEP`, `TRANSFER`, `PAY_PORTION`, `PAY_PORTION_FULL_PRECISION`,
|
`V2_SWAP_EXACT_IN`, `V2_SWAP_EXACT_OUT`, `V3_SWAP_EXACT_IN`,
|
||||||
`ACROSS_V4_DEPOSIT_V3`, `WRAP_ETH`, `UNWRAP_WETH` and `BALANCE_CHECK_ERC20`
|
`V3_SWAP_EXACT_OUT`, `V4_SWAP`, `SWEEP`, `TRANSFER`, `PAY_PORTION`,
|
||||||
(`V3_SWAP_EXACT_OUT`, `PERMIT2_TRANSFER_FROM_BATCH`, `EXECUTE_SUB_PLAN` and
|
`PAY_PORTION_FULL_PRECISION`, `ACROSS_V4_DEPOSIT_V3`, `WRAP_ETH`,
|
||||||
the NFT commands among them), or a type its release names no command for.
|
`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
|
||||||
|
|
||||||
|
|||||||
+59
-27
@@ -592,13 +592,14 @@ 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 shorter than one token.
|
||||||
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) 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 +618,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 +1212,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 +1268,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 +1524,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 +1544,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 +1975,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,
|
||||||
|
|||||||
+131
-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,33 @@ 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);
|
||||||
|
});
|
||||||
|
|
||||||
// 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