Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f943ca45fa |
@@ -1190,14 +1190,8 @@ number. They can read:
|
|||||||
`amountOut`, the exact amount it buys.
|
`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. The line shows the
|
whole open delta", so the calldata states no quantity. Transaction Decoding
|
||||||
amount of the step that sets the input side: the last `PERMIT2_PERMIT` step if
|
below gives the steps whose amounts the line shows.
|
||||||
there is one, otherwise the first V2 or V3 exact-in, V2 exact-out, `WRAP_ETH`
|
|
||||||
or V4 swap step that names an input token or amount. A V4 step with a swap
|
|
||||||
action sets the input side when no earlier step set it, even when it names
|
|
||||||
neither, as described below. A V2 exact-out step gives its `amountInMax`, and
|
|
||||||
a V4 swap step the `amountIn` of the swap action that spends its input
|
|
||||||
currency.
|
|
||||||
- `Whatever an earlier step sent to the pair (V2 already paid)`: the swap's
|
- `Whatever an earlier step sent to the pair (V2 already paid)`: the swap's
|
||||||
`Amount` line, when the amount it shows is a V2 exact-in `amountIn` of zero.
|
`Amount` line, when the amount it shows is a V2 exact-in `amountIn` of zero.
|
||||||
The router reads that zero as "the pair already holds the input tokens": the
|
The router reads that zero as "the pair already holds the input tokens": the
|
||||||
@@ -1247,6 +1241,12 @@ only when no earlier step set it. It finds each side by three rules:
|
|||||||
`Amount` or `Min. received`. A side they did not name is unknown, and the
|
`Amount` or `Min. received`. A side they did not name is unknown, and the
|
||||||
step sets it as unknown with no figure.
|
step sets it as unknown with no figure.
|
||||||
|
|
||||||
|
Under each of these rules, when the user pays the step any of the input
|
||||||
|
currency, its `Amount` is instead what the user pays it of that currency when
|
||||||
|
the decoder knows that amount, and otherwise there is none (see Transaction
|
||||||
|
Decoding). So a step whose USDT-for-USDC swap action could not be read, with a
|
||||||
|
`SETTLE` of 400 USDT paid by the user, reads `Amount 400.0000 USDT`.
|
||||||
|
|
||||||
A V4 step with no swap action sets only the sides its `SETTLE`, `SETTLE_ALL`,
|
A V4 step with no swap action sets only the sides its `SETTLE`, `SETTLE_ALL`,
|
||||||
`TAKE` or `TAKE_ALL` names.
|
`TAKE` or `TAKE_ALL` names.
|
||||||
|
|
||||||
@@ -2379,10 +2379,11 @@ view would leave a wallet one click from deletion.
|
|||||||
- Site origin (bold, scheme and port included) + "wants to send a
|
- Site origin (bold, scheme and port included) + "wants to send a
|
||||||
transaction"
|
transaction"
|
||||||
- Decoded action (if calldata is recognized): action name, token details,
|
- Decoded action (if calldata is recognized): action name, token details,
|
||||||
amounts, what each `PERMIT2_TRANSFER_FROM` moves and where to, steps,
|
amounts, what each swap step spends that the `Amount` line does not give,
|
||||||
deadline, and unless a swap pays only the From address, a `Paid to` line
|
what each `PERMIT2_TRANSFER_FROM` moves and where to, steps, deadline, and
|
||||||
for each other address it pays, or saying that a token stays in the router
|
unless a swap pays only the From address, a `Paid to` line for each other
|
||||||
or who it pays is unknown (see Transaction Decoding)
|
address it pays, or saying that a token stays in the router or who it pays
|
||||||
|
is unknown (see Transaction Decoding)
|
||||||
- From: color dot + full address + etherscan link
|
- From: color dot + full address + etherscan link
|
||||||
- Contract: color dot + full address + etherscan link, token symbol label if
|
- Contract: color dot + full address + etherscan link, token symbol label if
|
||||||
known; for a contract creation, the same sentence as on WaitTx
|
known; for a contract creation, the same sentence as on WaitTx
|
||||||
@@ -3092,37 +3093,40 @@ 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. The router pays a `SETTLE` of
|
WETH from it reads as leaving the WETH there. A `SETTLE` of `OPEN_DELTA`
|
||||||
`OPEN_DELTA` (zero) only what its step still owes of that token when the
|
(zero), and a `SETTLE_ALL`, pay only what the step still owes of their token
|
||||||
`SETTLE` runs, so one from the router counts as spending the amounts the
|
when they run. The decoder takes that to be the amounts the step's swap
|
||||||
step's swap actions state they spend of that token only when every action in
|
actions state they spend of that token only when every action in the step
|
||||||
the step could be read, every swap action names the token it spends and the
|
could be read, every swap action names the token it spends and the token it
|
||||||
token it buys (a swap action with an empty path swaps nothing), the `SETTLE`
|
buys (a swap action with an empty path swaps nothing), the `SETTLE` or
|
||||||
is the step's only `SETTLE` or `SETTLE_ALL` of that token and comes after
|
`SETTLE_ALL` is the step's only `SETTLE` or `SETTLE_ALL` of that token and
|
||||||
every swap action, no swap action buys that token, and the swap actions that
|
comes after every swap action, no swap action buys that token, no `TAKE`,
|
||||||
spend it state a quantity together; otherwise it counts as spending nothing.
|
`TAKE_ALL` or `TAKE_PORTION` in the step takes it (a `TAKE` of a stated amount
|
||||||
So the rest of a V4 `SETTLE` of more than its swap spends, taken to the
|
adds to what the step owes), and the swap actions that spend it state a
|
||||||
router, the WETH of a `WRAP_ETH`, and a token a swap pays the router on the
|
quantity together; otherwise it does not know the amount, and such a `SETTLE`
|
||||||
way to the output, each stays in the router when later steps do not spend all
|
from the router counts as spending nothing. So the rest of a V4 `SETTLE` of
|
||||||
of it, where anyone can take it in a later transaction. The router sends ETH
|
more than its swap spends, taken to the router, the WETH of a `WRAP_ETH`, and
|
||||||
as written, so a `TRANSFER` of `CONTRACT_BALANCE` of ETH always reverts and
|
a token a swap pays the router on the way to the output, each stays in the
|
||||||
pays out nothing. A step paying a token to the V2 pair that the very next step
|
router when later steps do not spend all of it, where anyone can take it in a
|
||||||
spends, as a V2 exact-in of that token whose `amountIn` is `ALREADY_PAID`
|
later transaction. The router sends ETH as written, so a `TRANSFER` of
|
||||||
(zero) or `CONTRACT_BALANCE`, is a hop, not a payout: Uniswap's mixed routes
|
`CONTRACT_BALANCE` of ETH always reverts and pays out nothing. A step paying a
|
||||||
pay the next V2 pair directly, and the router enforces that swap's own
|
token to the V2 pair that the very next step spends, as a V2 exact-in of that
|
||||||
minimum. The decoder checks that the address paid is that pair, deriving it
|
token whose `amountIn` is `ALREADY_PAID` (zero) or `CONTRACT_BALANCE`, is a
|
||||||
from the V2 factory and pair init code hash the network's routers were
|
hop, not a payout: Uniswap's mixed routes pay the next V2 pair directly, and
|
||||||
deployed with and the first two tokens of that step's path; a payment to any
|
the router enforces that swap's own minimum. The decoder checks that the
|
||||||
other address is named. (Sepolia's `UniversalRouterV1_2_V2Support` was
|
address paid is that pair, deriving it from the V2 factory and pair init code
|
||||||
deployed with placeholders for both values, so no V2 swap step can succeed on
|
hash the network's routers were deployed with and the first two tokens of that
|
||||||
that router.) Every other payment counts, in whichever step and of whatever
|
step's path; a payment to any other address is named. (Sepolia's
|
||||||
token, a share of the output included: an interface fee paid to another
|
`UniversalRouterV1_2_V2Support` was deployed with placeholders for both
|
||||||
address by `PAY_PORTION`, `TRANSFER` or `TAKE_PORTION` is named, and the
|
values, so no V2 swap step can succeed on that router.) Every other payment
|
||||||
minimum reads `Min. paid out`, because the swap's minimum includes the fee.
|
counts, in whichever step and of whatever token, a share of the output
|
||||||
The minimum reads `Min. received` only when some step pays the transaction's
|
included: an interface fee paid to another address by `PAY_PORTION`,
|
||||||
sender (the From address on TxApproval, the sender the explorer reports on
|
`TRANSFER` or `TAKE_PORTION` is named, and the minimum reads `Min. paid out`,
|
||||||
TransactionDetail) and no `Paid to` line is shown. `Paid to` lines after
|
because the swap's minimum includes the fee. The minimum reads `Min. received`
|
||||||
`Token Out` name each other address paid; one reads
|
only when some step pays the transaction's sender (the From address on
|
||||||
|
TxApproval, the sender the explorer reports on TransactionDetail) and no
|
||||||
|
`Paid to` line is shown. `Paid to` lines after `Token Out` name each other
|
||||||
|
address paid; one reads
|
||||||
`The bridge, which pays the deposit's recipient on another network` for a
|
`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)`
|
||||||
@@ -3134,8 +3138,71 @@ Currently supported:
|
|||||||
name); and one more reads `Unknown (could not be decoded)` when a step that
|
name); and one more reads `Unknown (could not be decoded)` when a step that
|
||||||
may pay an address went unread. When no step pays anyone, a single line reads
|
may pay an address went unread. When no step pays anyone, a single line reads
|
||||||
`Unknown (not named in the calldata)`. Wherever there is a `Paid to` line, the
|
`Unknown (not named in the calldata)`. Wherever there is a `Paid to` line, the
|
||||||
minimum reads `Min. paid out`. After the `Amount` line (after `Token In` when
|
minimum reads `Min. paid out`. The `Amount` line gives the total the steps
|
||||||
there is none), each `PERMIT2_TRANSFER_FROM` but one into the router, in step
|
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
|
||||||
|
token or amount. A V4 step with a swap action sets the input side when no
|
||||||
|
earlier step set it, even when it names neither (see Display Consistency).
|
||||||
|
That step counts its own amount, whoever pays it: a V2 exact-out step its
|
||||||
|
`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`
|
||||||
|
of the swap action that spends it. Each later V2 or V3 exact-in or V2
|
||||||
|
exact-out step whose `payerIsUser` is true takes its amount, found in the same
|
||||||
|
way, from the user's wallet. Every V4 step, with a swap action or without,
|
||||||
|
takes from the user's wallet what its `SETTLE`s paid by the user
|
||||||
|
(`payerIsUser` true) and its `SETTLE_ALL`s pay, for each token on its own;
|
||||||
|
what the step that sets the input side takes of any other token counts as a
|
||||||
|
later step's take. A `SETTLE` of any amount but `OPEN_DELTA` pays that amount,
|
||||||
|
and a `SETTLE` of `OPEN_DELTA` or a `SETTLE_ALL` pays what the step owes, as
|
||||||
|
above. So a V4 step that swaps 400 USDT, with a `SETTLE` of 5,000 USDT paid by
|
||||||
|
the user and the rest taken to another address, reads `5000.0000 USDT`. A
|
||||||
|
later step paid from the router spends what the router holds and takes
|
||||||
|
nothing, and so does a V2 exact-in whose `amountIn` is `ALREADY_PAID`. A later
|
||||||
|
take of `Token In` is added to the total while both are quantities: a V3
|
||||||
|
exact-in of 600 USDT and a V2 exact-in of 400 USDT, both paid by the user,
|
||||||
|
read `1000.0000 USDT`. When the decoder does not know a take's amount, the
|
||||||
|
total is not known and there is no `Amount` line. `Token In` is the token of
|
||||||
|
the last `PERMIT2_PERMIT` the decoder reads, when there is one. Its amount,
|
||||||
|
`Unlimited` included, stands as the `Amount` figure in place of the total only
|
||||||
|
in the plain shape of Uniswap's own routes: it is the only `PERMIT2_PERMIT`,
|
||||||
|
its spender is the router, the router may not let it fail, no step before it
|
||||||
|
takes its token or a token the decoder does not know, and the decoder reads
|
||||||
|
every step after it in full. Permit2 then lets the router take no more of the
|
||||||
|
token than that amount, so the permit covers every take of it. A step the
|
||||||
|
decoder does not read in full may raise what Permit2 lets the router take, as
|
||||||
|
a `PERMIT2_PERMIT_BATCH` can: a command the decoder does not read (listed at
|
||||||
|
the end of this section), a step whose input it cannot read, in whole or in
|
||||||
|
part, and a step it fails on. In any other case the line gives the total, as
|
||||||
|
with no permit. When the step that sets the input side spends a token other
|
||||||
|
than the permit's, or one the decoder does not know, its amount gets lines of
|
||||||
|
its own and the total has no figure to start from: unless the permit's amount
|
||||||
|
stands there is no `Amount` line, and each take of the permit's token gets
|
||||||
|
lines of its own. Each take the total does not count gives three lines after
|
||||||
|
the `Amount` line (after `Token In` when there is none): one of a token other
|
||||||
|
than `Token In`; one of `Token In` after an amount that is not a quantity (a
|
||||||
|
V4 open delta, `ALREADY_PAID`, which keep their wording on the `Amount` line,
|
||||||
|
or no stated amount, as of a V4 exact-out step); and one whose amount the
|
||||||
|
decoder does not know. When the step that sets the input side states no amount
|
||||||
|
and there is no `Amount` line, that step gets them too whenever another take's
|
||||||
|
lines give a figure, so that figure cannot read as all that leaves. They come
|
||||||
|
in step 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
|
||||||
|
`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
|
||||||
|
`Moved to`, which reads `The swap step that spends it`. A token the decoder
|
||||||
|
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
|
||||||
|
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
|
||||||
|
`PERMIT2_TRANSFER_FROM` whose input it cannot read; every
|
||||||
|
`PERMIT2_TRANSFER_FROM_BATCH`; every `V3_SWAP_EXACT_OUT`, which the decoder
|
||||||
|
does not read, wherever it stands; every `EXECUTE_SUB_PLAN`, wherever it
|
||||||
|
stands; and every step it fails on. Each such step gives one
|
||||||
|
`Moved from the sender` line among those, reading
|
||||||
|
`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,
|
||||||
|
and such a step before a `PERMIT2_PERMIT` keeps the permit's amount from
|
||||||
|
standing. Then each `PERMIT2_TRANSFER_FROM` but one into the router, in step
|
||||||
order, gives three lines: `Moved from the sender`, the amount it moves, for
|
order, gives three lines: `Moved from the sender`, the amount it moves, for
|
||||||
example `1000000.0000 USDT`; `Token moved`, giving the token's address as the
|
example `1000000.0000 USDT`; `Token moved`, giving the token's address as the
|
||||||
`Token In` line does; and `Moved to`, which reads
|
`Token In` line does; and `Moved to`, which reads
|
||||||
|
|||||||
+368
-85
@@ -335,7 +335,8 @@ const UNNAMED_CURRENCY = "Unknown (not named in the calldata)";
|
|||||||
// commands this decoder does not read. That action names both of its
|
// commands this decoder does not read. That action names both of its
|
||||||
// currencies, and those commands may name one, so saying the calldata did not
|
// currencies, and those commands may name one, so saying the calldata did not
|
||||||
// name it would be false. The Paid to line uses it too, when a step may pay an
|
// name it would be false. The Paid to line uses it too, when a step may pay an
|
||||||
// address this decoder did not read.
|
// address this decoder did not read, and so do the lines for what a step
|
||||||
|
// takes from the sender, when this decoder does not know it.
|
||||||
const UNDECODED_CURRENCY = "Unknown (could not be decoded)";
|
const UNDECODED_CURRENCY = "Unknown (could not be decoded)";
|
||||||
|
|
||||||
// The Paid to line for each token paid to the router, the swap's output or
|
// The Paid to line for each token paid to the router, the swap's output or
|
||||||
@@ -356,6 +357,11 @@ function present(value) {
|
|||||||
return value !== null && value !== undefined;
|
return value !== null && value !== undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Whether `a` and `b` are both known and the same address.
|
||||||
|
function sameAddress(a, b) {
|
||||||
|
return present(a) && present(b) && a.toLowerCase() === b.toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
// Uniswap V4 spells "use the whole open delta" as an amount of zero:
|
// Uniswap V4 spells "use the whole open delta" as an amount of zero:
|
||||||
// v4-periphery `src/libraries/ActionConstants.sol` declares
|
// v4-periphery `src/libraries/ActionConstants.sol` declares
|
||||||
// `uint128 internal constant OPEN_DELTA = 0` ("used to signal that an action
|
// `uint128 internal constant OPEN_DELTA = 0` ("used to signal that an action
|
||||||
@@ -566,6 +572,7 @@ function decodeV2SwapExactIn(input) {
|
|||||||
// Decode V2_SWAP_EXACT_OUT (command 0x09) input bytes.
|
// Decode V2_SWAP_EXACT_OUT (command 0x09) input bytes.
|
||||||
// ABI: (address recipient, uint256 amountOut, uint256 amountInMax,
|
// ABI: (address recipient, uint256 amountOut, uint256 amountInMax,
|
||||||
// address[] path, bool payerIsUser)
|
// address[] path, bool payerIsUser)
|
||||||
|
// With `payerIsUser` false the router pays the input from its own balance.
|
||||||
function decodeV2SwapExactOut(input) {
|
function decodeV2SwapExactOut(input) {
|
||||||
try {
|
try {
|
||||||
const d = coder.decode(
|
const d = coder.decode(
|
||||||
@@ -578,6 +585,7 @@ function decodeV2SwapExactOut(input) {
|
|||||||
amountInMax: d[2],
|
amountInMax: d[2],
|
||||||
tokenIn: d[3][0],
|
tokenIn: d[3][0],
|
||||||
tokenOut: d[3][d[3].length - 1],
|
tokenOut: d[3][d[3].length - 1],
|
||||||
|
payerIsUser: d[4],
|
||||||
};
|
};
|
||||||
} catch {
|
} catch {
|
||||||
return null;
|
return null;
|
||||||
@@ -895,16 +903,24 @@ function totalAmount(amounts) {
|
|||||||
// input side from it only when no earlier step set it. A side it sets that
|
// input side from it only when no earlier step set it. A side it sets that
|
||||||
// these rules leave unknown is set unknown, with no figure. A step with no
|
// these rules leave unknown is set unknown, with no figure. A step with no
|
||||||
// swap action sets only the sides SETTLE, SETTLE_ALL, TAKE and TAKE_ALL
|
// swap action sets only the sides SETTLE, SETTLE_ALL, TAKE and TAKE_ALL
|
||||||
// name.
|
// name. Under each of the three rules, rule 3 included, when the user pays
|
||||||
|
// the step any of Token In (`paidByUser` below), decode() takes what the user
|
||||||
|
// pays it of Token In as the step's amount in place of the rules' Amount, or
|
||||||
|
// no amount when this decoder cannot tell what that is.
|
||||||
//
|
//
|
||||||
// `takes` is every TAKE, TAKE_ALL and TAKE_PORTION, as the currency it takes
|
// `takes` is every TAKE, TAKE_ALL and TAKE_PORTION, as the currency it takes
|
||||||
// and its recipient as written, MSG_SENDER for a TAKE_ALL. TAKE_PORTION takes
|
// and its recipient as written, MSG_SENDER for a TAKE_ALL. TAKE_PORTION takes
|
||||||
// a share of the currency, so it does not name the output side as TAKE does.
|
// a share of the currency, so it does not name the output side as TAKE does.
|
||||||
// `recipientUnread` is set when the step has a payout this decoder did not
|
// `recipientUnread` is set when the step has a payout this decoder did not
|
||||||
// read. `settledFromRouter` is every SETTLE that the router pays from its own
|
// read, and `settleUnread` when it has a SETTLE or SETTLE_ALL this decoder
|
||||||
// balance (`payerIsUser` false), as its currency and the amount it pays: a
|
// did not read. `settledFromRouter` is every SETTLE that the router pays from
|
||||||
// quantity, CONTRACT_BALANCE (all the router holds), or null when this
|
// its own balance (`payerIsUser` false), as its currency and the amount it
|
||||||
// decoder cannot tell (see below).
|
// pays: a quantity, CONTRACT_BALANCE (all the router holds), or null when
|
||||||
|
// this decoder cannot tell (see below). `paidByUser` is what the user pays
|
||||||
|
// the step, once per currency, as that currency and the amount: the total of
|
||||||
|
// every SETTLE paid by the user (`payerIsUser` true) and every SETTLE_ALL,
|
||||||
|
// which V4Router always has the user pay, of that currency, or null when
|
||||||
|
// this decoder cannot tell what one of them pays.
|
||||||
function decodeV4Swap(input) {
|
function decodeV4Swap(input) {
|
||||||
try {
|
try {
|
||||||
const d = coder.decode(["bytes", "bytes[]"], input);
|
const d = coder.decode(["bytes", "bytes[]"], input);
|
||||||
@@ -914,16 +930,18 @@ function decodeV4Swap(input) {
|
|||||||
let settleToken = null;
|
let settleToken = null;
|
||||||
let takeToken = null;
|
let takeToken = null;
|
||||||
const takes = [];
|
const takes = [];
|
||||||
// Every SETTLE paid from the router, as its currency, its amount as
|
// Every SETTLE and SETTLE_ALL, as its currency, its amount as
|
||||||
// written and its index among the step's actions.
|
// written, whether the user pays it, and its index among the step's
|
||||||
const routerSettles = [];
|
// actions. A SETTLE_ALL pays what a SETTLE of OPEN_DELTA (zero) paid
|
||||||
// The currency of every SETTLE and SETTLE_ALL.
|
// by the user pays, all the step still owes of its currency, so it
|
||||||
const settledCurrencies = [];
|
// is kept as one; its maxAmount only caps that.
|
||||||
|
const settles = [];
|
||||||
// The index of the step's last swap action, read or not, or -1.
|
// The index of the step's last swap action, read or not, or -1.
|
||||||
let lastSwapAction = -1;
|
let lastSwapAction = -1;
|
||||||
// Set when an action of the step could not be read.
|
// Set when an action of the step could not be read.
|
||||||
let actionUnread = false;
|
let actionUnread = false;
|
||||||
let recipientUnread = false;
|
let recipientUnread = false;
|
||||||
|
let settleUnread = false;
|
||||||
// The step's swap actions that could be read, as decodeV4SwapAction()
|
// The step's swap actions that could be read, as decodeV4SwapAction()
|
||||||
// reads them.
|
// reads them.
|
||||||
const swaps = [];
|
const swaps = [];
|
||||||
@@ -942,19 +960,22 @@ function decodeV4Swap(input) {
|
|||||||
params[i],
|
params[i],
|
||||||
);
|
);
|
||||||
settleToken = s[0];
|
settleToken = s[0];
|
||||||
settledCurrencies.push(s[0]);
|
settles.push({
|
||||||
if (!s[2]) {
|
currency: s[0],
|
||||||
routerSettles.push({
|
amount: s[1],
|
||||||
currency: s[0],
|
payerIsUser: s[2],
|
||||||
amount: s[1],
|
index: i,
|
||||||
index: i,
|
});
|
||||||
});
|
|
||||||
}
|
|
||||||
} else if (actionId === V4_SETTLE_ALL) {
|
} else if (actionId === V4_SETTLE_ALL) {
|
||||||
// SETTLE_ALL: (address currency, uint256 maxAmount)
|
// SETTLE_ALL: (address currency, uint256 maxAmount)
|
||||||
const s = coder.decode(["address", "uint256"], params[i]);
|
const s = coder.decode(["address", "uint256"], params[i]);
|
||||||
settleToken = s[0];
|
settleToken = s[0];
|
||||||
settledCurrencies.push(s[0]);
|
settles.push({
|
||||||
|
currency: s[0],
|
||||||
|
amount: 0n,
|
||||||
|
payerIsUser: true,
|
||||||
|
index: i,
|
||||||
|
});
|
||||||
} else if (actionId === V4_TAKE) {
|
} else if (actionId === V4_TAKE) {
|
||||||
// TAKE: (address currency, address recipient, uint256 amount)
|
// TAKE: (address currency, address recipient, uint256 amount)
|
||||||
const t = coder.decode(
|
const t = coder.decode(
|
||||||
@@ -996,7 +1017,8 @@ function decodeV4Swap(input) {
|
|||||||
} catch {
|
} catch {
|
||||||
// Skip sub-actions we can't decode. A TAKE, TAKE_ALL or
|
// Skip sub-actions we can't decode. A TAKE, TAKE_ALL or
|
||||||
// TAKE_PORTION among them is a payout this decoder did not
|
// TAKE_PORTION among them is a payout this decoder did not
|
||||||
// read.
|
// read, and a SETTLE or SETTLE_ALL may take from the user
|
||||||
|
// what it did not read: the router still runs both.
|
||||||
actionUnread = true;
|
actionUnread = true;
|
||||||
if (
|
if (
|
||||||
actionId === V4_TAKE ||
|
actionId === V4_TAKE ||
|
||||||
@@ -1005,25 +1027,29 @@ function decodeV4Swap(input) {
|
|||||||
) {
|
) {
|
||||||
recipientUnread = true;
|
recipientUnread = true;
|
||||||
}
|
}
|
||||||
|
if (actionId === V4_SETTLE || actionId === V4_SETTLE_ALL) {
|
||||||
|
settleUnread = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// What each SETTLE paid from the router pays. A quantity or
|
// What a SETTLE or SETTLE_ALL pays. A SETTLE of any amount but
|
||||||
// CONTRACT_BALANCE pays as written. A SETTLE of OPEN_DELTA (zero)
|
// OPEN_DELTA (zero) pays as written: a quantity, or, for
|
||||||
// pays whatever the step still owes of its currency when it runs.
|
// CONTRACT_BALANCE, as much as the router holds. A SETTLE of
|
||||||
// That is what the step's swap actions state they spend of it only
|
// OPEN_DELTA, and a SETTLE_ALL, pay whatever the step still owes of
|
||||||
// in the plain shape of Uniswap's own routes, so only then is it
|
// the currency when they run. That is what the step's swap actions
|
||||||
// given that amount: every action in the step was read; every swap
|
// state they spend of it only in the plain shape of Uniswap's own
|
||||||
// action names the currency it spends and the one it buys (an
|
// routes, so only then is it given that amount: every action in the
|
||||||
// exact-in action with an empty path swaps nothing); the SETTLE is
|
// step was read; every swap action names the currency it spends and
|
||||||
// the step's only SETTLE or SETTLE_ALL of its currency and comes
|
// the one it buys (an exact-in action with an empty path swaps
|
||||||
// after every swap action; no swap action buys that currency; and
|
// nothing); it is the step's only SETTLE or SETTLE_ALL of its
|
||||||
// the swap actions that spend it state a quantity together (see
|
// currency and comes after every swap action; no swap action buys
|
||||||
// totalAmount()). Otherwise it is given null.
|
// that currency and nothing in the step takes it (a TAKE of a stated
|
||||||
const settledFromRouter = routerSettles.map((s) => {
|
// amount adds to what the step owes); and the swap actions that spend
|
||||||
if (s.amount !== 0n) {
|
// it state a quantity together (see totalAmount()). Otherwise it is
|
||||||
return { currency: s.currency, amount: s.amount };
|
// given null.
|
||||||
}
|
const pays = (s) => {
|
||||||
|
if (s.amount !== 0n) return s.amount;
|
||||||
const stated = totalAmount(
|
const stated = totalAmount(
|
||||||
swaps
|
swaps
|
||||||
.filter((w) => w.tokenIn === s.currency)
|
.filter((w) => w.tokenIn === s.currency)
|
||||||
@@ -1032,16 +1058,30 @@ function decodeV4Swap(input) {
|
|||||||
const paysWhatSwapsSpend =
|
const paysWhatSwapsSpend =
|
||||||
!actionUnread &&
|
!actionUnread &&
|
||||||
swaps.every((w) => present(w.tokenIn) && present(w.tokenOut)) &&
|
swaps.every((w) => present(w.tokenIn) && present(w.tokenOut)) &&
|
||||||
settledCurrencies.filter((c) => c === s.currency).length ===
|
settles.filter((c) => c.currency === s.currency).length === 1 &&
|
||||||
1 &&
|
|
||||||
s.index > lastSwapAction &&
|
s.index > lastSwapAction &&
|
||||||
!swaps.some((w) => w.tokenOut === s.currency) &&
|
!swaps.some((w) => w.tokenOut === s.currency) &&
|
||||||
|
!takes.some((t) => t.currency === s.currency) &&
|
||||||
typeof stated === "bigint";
|
typeof stated === "bigint";
|
||||||
return {
|
return paysWhatSwapsSpend ? stated : null;
|
||||||
currency: s.currency,
|
};
|
||||||
amount: paysWhatSwapsSpend ? stated : null,
|
const settledFromRouter = settles
|
||||||
};
|
.filter((s) => !s.payerIsUser)
|
||||||
});
|
.map((s) => ({ currency: s.currency, amount: pays(s) }));
|
||||||
|
const paidByUser = [];
|
||||||
|
for (const s of settles.filter((c) => c.payerIsUser)) {
|
||||||
|
const amount = pays(s);
|
||||||
|
const paid = paidByUser.find((p) => p.currency === s.currency);
|
||||||
|
if (!paid) {
|
||||||
|
paidByUser.push({ currency: s.currency, amount });
|
||||||
|
} else {
|
||||||
|
paid.amount =
|
||||||
|
typeof paid.amount === "bigint" &&
|
||||||
|
typeof amount === "bigint"
|
||||||
|
? paid.amount + amount
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Rule 3.
|
// Rule 3.
|
||||||
if (swapUndecoded) {
|
if (swapUndecoded) {
|
||||||
@@ -1052,7 +1092,9 @@ function decodeV4Swap(input) {
|
|||||||
amountOutMin: null,
|
amountOutMin: null,
|
||||||
takes,
|
takes,
|
||||||
settledFromRouter,
|
settledFromRouter,
|
||||||
|
paidByUser,
|
||||||
recipientUnread,
|
recipientUnread,
|
||||||
|
settleUnread,
|
||||||
swapUndecoded,
|
swapUndecoded,
|
||||||
hasSwapAction: true,
|
hasSwapAction: true,
|
||||||
};
|
};
|
||||||
@@ -1094,7 +1136,9 @@ function decodeV4Swap(input) {
|
|||||||
amountOutMin,
|
amountOutMin,
|
||||||
takes,
|
takes,
|
||||||
settledFromRouter,
|
settledFromRouter,
|
||||||
|
paidByUser,
|
||||||
recipientUnread,
|
recipientUnread,
|
||||||
|
settleUnread,
|
||||||
swapUndecoded,
|
swapUndecoded,
|
||||||
hasSwapAction: swaps.length > 0,
|
hasSwapAction: swaps.length > 0,
|
||||||
};
|
};
|
||||||
@@ -1125,6 +1169,13 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
|
|||||||
let inputToken = null;
|
let inputToken = null;
|
||||||
let inputAmount = null;
|
let inputAmount = null;
|
||||||
let inputEstablished = false;
|
let inputEstablished = false;
|
||||||
|
// The last PERMIT2_PERMIT this decoder read, or null (see
|
||||||
|
// permitStands after the loop).
|
||||||
|
let permit = null;
|
||||||
|
// The index of each step this decoder did not read in full: a
|
||||||
|
// command it does not read, a step whose input it cannot read, in
|
||||||
|
// whole or in part, and one it fails on (see permitStands).
|
||||||
|
const stepsUnread = [];
|
||||||
let outputToken = null;
|
let outputToken = null;
|
||||||
let minOutput = null;
|
let minOutput = null;
|
||||||
let outputEstablished = false;
|
let outputEstablished = false;
|
||||||
@@ -1135,8 +1186,8 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
|
|||||||
const commandNames = [];
|
const commandNames = [];
|
||||||
|
|
||||||
// THE INVARIANT: an amount and the token it is counted in always come
|
// THE INVARIANT: an amount and the token it is counted in always come
|
||||||
// from the same hop. A figure is never rendered against a token that
|
// from the same hop, or from hops that all spend that token. A figure
|
||||||
// did not supply it.
|
// is never rendered against a token that did not supply it.
|
||||||
//
|
//
|
||||||
// Both sides are therefore set as a PAIR — never field by field, and
|
// Both sides are therefore set as a PAIR — never field by field, and
|
||||||
// never on truthiness. An address is never falsy once set but an
|
// never on truthiness. An address is never falsy once set but an
|
||||||
@@ -1152,7 +1203,9 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
|
|||||||
// states neither half: the output side always, and the input side
|
// states neither half: the output side always, and the input side
|
||||||
// when no earlier hop set it. A half the establishing hop did not
|
// when no earlier hop set it. A half the establishing hop did not
|
||||||
// state stays null and the line says so, rather than being filled in
|
// state stays null and the line says so, rather than being filled in
|
||||||
// from a different hop.
|
// from a different hop. A PERMIT2_PERMIT sets no side here: after the
|
||||||
|
// loop, the last one read names Token In, and the Amount line is
|
||||||
|
// formed there, in one place (see permitStands).
|
||||||
const setInput = (token, amount) => {
|
const setInput = (token, amount) => {
|
||||||
inputToken = present(token) ? token : null;
|
inputToken = present(token) ? token : null;
|
||||||
inputAmount = present(amount) ? amount : null;
|
inputAmount = present(amount) ? amount : null;
|
||||||
@@ -1170,6 +1223,30 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
|
|||||||
outputEstablished = true;
|
outputEstablished = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// What the steps take from the sender beyond the amount of the step
|
||||||
|
// that sets the input side, as { token, amount }, in step order.
|
||||||
|
// After the loop each joins the Amount line or gets lines of its own
|
||||||
|
// (see permitStands).
|
||||||
|
const userPaid = [];
|
||||||
|
// A swap step the user pays for (`payerIsUser` true) takes `amount`
|
||||||
|
// of `token` from the sender. A V2 exact-in of ALREADY_PAID takes
|
||||||
|
// nothing.
|
||||||
|
const addUserPaid = (token, amount) => {
|
||||||
|
if (amount !== ALREADY_PAID) userPaid.push({ token, amount });
|
||||||
|
};
|
||||||
|
// 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
|
||||||
|
// cannot read; a V2 or V3 exact-in or V2 exact-out step or
|
||||||
|
// PERMIT2_TRANSFER_FROM whose input it cannot read; a
|
||||||
|
// 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
|
||||||
|
// 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
|
||||||
|
// does not stand (see permitStands).
|
||||||
|
const spentUnread = () => {
|
||||||
|
userPaid.push({ token: null, amount: null });
|
||||||
|
};
|
||||||
|
|
||||||
// Who the steps pay, in whichever step and of whatever token.
|
// Who the steps pay, in whichever step and of whatever token.
|
||||||
// MSG_SENDER is the sender. The router itself, as ADDRESS_THIS or by
|
// MSG_SENDER is the sender. The router itself, as ADDRESS_THIS or by
|
||||||
// its own address, holds what it is paid until a later step pays it
|
// its own address, holds what it is paid until a later step pays it
|
||||||
@@ -1300,20 +1377,40 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
|
|||||||
// EXECUTE_SUB_PLAN runs a further list of commands held in
|
// EXECUTE_SUB_PLAN runs a further list of commands held in
|
||||||
// its input, which this decoder does not read. The step
|
// its input, which this decoder does not read. The step
|
||||||
// says so, and after this loop neither side is named.
|
// says so, and after this loop neither side is named.
|
||||||
// Those commands may pay any address.
|
// Those commands may pay any address and take from the
|
||||||
|
// sender.
|
||||||
hasSubPlan = true;
|
hasSubPlan = true;
|
||||||
commandNames[i] += " (its steps are not shown)";
|
commandNames[i] += " (its steps are not shown)";
|
||||||
payeeUnread = true;
|
payeeUnread = true;
|
||||||
|
stepsUnread.push(i);
|
||||||
|
spentUnread();
|
||||||
} else if (command === "PERMIT2_PERMIT") {
|
} else if (command === "PERMIT2_PERMIT") {
|
||||||
const p = decodePermit2(inputs[i]);
|
const p = decodePermit2(inputs[i]);
|
||||||
// A permit states both halves itself, so it may replace an
|
if (p) {
|
||||||
// input side an earlier hop established without breaking
|
// Whether a step before it took its token, or may
|
||||||
// the invariant.
|
// have: the step that set the input side or one in
|
||||||
if (p) setInput(p.token, p.amount);
|
// `userPaid`, when its token is the permit's or not
|
||||||
|
// known.
|
||||||
|
const mayBeItsToken = (token) =>
|
||||||
|
!present(token) || sameAddress(token, p.token);
|
||||||
|
const tookBefore =
|
||||||
|
(inputEstablished && mayBeItsToken(inputToken)) ||
|
||||||
|
userPaid.some((t) => mayBeItsToken(t.token));
|
||||||
|
permit = {
|
||||||
|
...p,
|
||||||
|
step: i,
|
||||||
|
allowedToFail,
|
||||||
|
beforeEveryTake: !tookBefore,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
stepsUnread.push(i);
|
||||||
|
}
|
||||||
} else if (command === "PERMIT2_TRANSFER_FROM") {
|
} else if (command === "PERMIT2_TRANSFER_FROM") {
|
||||||
const p = decodePermit2TransferFrom(inputs[i]);
|
const p = decodePermit2TransferFrom(inputs[i]);
|
||||||
if (!p) {
|
if (!p) {
|
||||||
payeeUnread = true;
|
payeeUnread = true;
|
||||||
|
stepsUnread.push(i);
|
||||||
|
spentUnread();
|
||||||
} else if (
|
} else if (
|
||||||
commandTypeMask === 0x1f &&
|
commandTypeMask === 0x1f &&
|
||||||
[MSG_SENDER, ADDRESS_THIS].includes(
|
[MSG_SENDER, ADDRESS_THIS].includes(
|
||||||
@@ -1351,6 +1448,7 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
|
|||||||
// owner may be any address. It sets no side, and the
|
// owner may be any address. It sets no side, and the
|
||||||
// step says so.
|
// step says so.
|
||||||
commandNames[i] += " (could not be decoded)";
|
commandNames[i] += " (could not be decoded)";
|
||||||
|
stepsUnread.push(i);
|
||||||
} else if (b.owner.toLowerCase() !== sender) {
|
} else if (b.owner.toLowerCase() !== sender) {
|
||||||
// The router reads the owner as written (see
|
// The router reads the owner as written (see
|
||||||
// decodeBalanceCheck()), so address(1) and address(2)
|
// decodeBalanceCheck()), so address(1) and address(2)
|
||||||
@@ -1375,7 +1473,11 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
|
|||||||
} else if (command === "V3_SWAP_EXACT_IN") {
|
} else if (command === "V3_SWAP_EXACT_IN") {
|
||||||
const s = decodeV3SwapExactIn(inputs[i]);
|
const s = decodeV3SwapExactIn(inputs[i]);
|
||||||
if (s) {
|
if (s) {
|
||||||
setInputOnce(s.tokenIn, s.amountIn);
|
if (!inputEstablished) {
|
||||||
|
setInputOnce(s.tokenIn, s.amountIn);
|
||||||
|
} else if (s.payerIsUser) {
|
||||||
|
addUserPaid(s.tokenIn, s.amountIn);
|
||||||
|
}
|
||||||
// Always update output: in multi-step swaps (V3 → V4),
|
// Always update output: in multi-step swaps (V3 → V4),
|
||||||
// the last swap step determines the final output token
|
// the last swap step determines the final output token
|
||||||
// and minimum received amount.
|
// and minimum received amount.
|
||||||
@@ -1388,11 +1490,25 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
payeeUnread = true;
|
payeeUnread = true;
|
||||||
|
stepsUnread.push(i);
|
||||||
|
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) {
|
||||||
setInputOnce(s.tokenIn, s.amountIn);
|
if (!inputEstablished) {
|
||||||
|
setInputOnce(s.tokenIn, s.amountIn);
|
||||||
|
} else if (s.payerIsUser) {
|
||||||
|
addUserPaid(s.tokenIn, s.amountIn);
|
||||||
|
}
|
||||||
setOutput(s.tokenOut, s.amountOutMin);
|
setOutput(s.tokenOut, s.amountOutMin);
|
||||||
if (!s.payerIsUser) {
|
if (!s.payerIsUser) {
|
||||||
spendFromRouter(s.tokenIn, s.amountIn);
|
spendFromRouter(s.tokenIn, s.amountIn);
|
||||||
@@ -1402,20 +1518,34 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
payeeUnread = true;
|
payeeUnread = true;
|
||||||
|
stepsUnread.push(i);
|
||||||
|
spentUnread();
|
||||||
}
|
}
|
||||||
} else if (command === "V2_SWAP_EXACT_OUT") {
|
} else if (command === "V2_SWAP_EXACT_OUT") {
|
||||||
// Buys exactly amountOut and spends at most amountInMax.
|
// Buys exactly amountOut and spends at most amountInMax.
|
||||||
hasV2ExactOut = true;
|
hasV2ExactOut = true;
|
||||||
const s = decodeV2SwapExactOut(inputs[i]);
|
const s = decodeV2SwapExactOut(inputs[i]);
|
||||||
if (s) {
|
if (s) {
|
||||||
setInputOnce(s.tokenIn, s.amountInMax);
|
if (!inputEstablished) {
|
||||||
|
setInputOnce(s.tokenIn, s.amountInMax);
|
||||||
|
} else if (s.payerIsUser) {
|
||||||
|
addUserPaid(s.tokenIn, s.amountInMax);
|
||||||
|
}
|
||||||
setOutput(s.tokenOut, s.amountOut);
|
setOutput(s.tokenOut, s.amountOut);
|
||||||
if (!isHop(i, s.tokenOut, s.recipient)) {
|
if (!isHop(i, s.tokenOut, s.recipient)) {
|
||||||
addPayee(s.recipient, s.tokenOut);
|
addPayee(s.recipient, s.tokenOut);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
payeeUnread = true;
|
payeeUnread = true;
|
||||||
|
stepsUnread.push(i);
|
||||||
|
spentUnread();
|
||||||
}
|
}
|
||||||
|
} else if (command === "PERMIT2_TRANSFER_FROM_BATCH") {
|
||||||
|
// It moves the sender's tokens to any addresses, and this
|
||||||
|
// decoder does not read it.
|
||||||
|
payeeUnread = true;
|
||||||
|
stepsUnread.push(i);
|
||||||
|
spentUnread();
|
||||||
} else if (command === "SWEEP") {
|
} else if (command === "SWEEP") {
|
||||||
const s = decodeSweep(inputs[i]);
|
const s = decodeSweep(inputs[i]);
|
||||||
if (s) {
|
if (s) {
|
||||||
@@ -1425,6 +1555,7 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
payeeUnread = true;
|
payeeUnread = true;
|
||||||
|
stepsUnread.push(i);
|
||||||
}
|
}
|
||||||
} else if (
|
} else if (
|
||||||
command === "TRANSFER" ||
|
command === "TRANSFER" ||
|
||||||
@@ -1461,6 +1592,7 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
payeeUnread = true;
|
payeeUnread = true;
|
||||||
|
stepsUnread.push(i);
|
||||||
}
|
}
|
||||||
} else if (command === "ACROSS_V4_DEPOSIT_V3") {
|
} else if (command === "ACROSS_V4_DEPOSIT_V3") {
|
||||||
const a = decodeAcrossV4DepositV3(inputs[i]);
|
const a = decodeAcrossV4DepositV3(inputs[i]);
|
||||||
@@ -1484,6 +1616,7 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
payeeUnread = true;
|
payeeUnread = true;
|
||||||
|
stepsUnread.push(i);
|
||||||
}
|
}
|
||||||
} else if (command === "WRAP_ETH") {
|
} else if (command === "WRAP_ETH") {
|
||||||
const w = decodeWrapEth(inputs[i]);
|
const w = decodeWrapEth(inputs[i]);
|
||||||
@@ -1514,9 +1647,11 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
payeeUnread = true;
|
payeeUnread = true;
|
||||||
|
stepsUnread.push(i);
|
||||||
}
|
}
|
||||||
} else if (command === "V4_SWAP") {
|
} else if (command === "V4_SWAP") {
|
||||||
const v4 = decodeV4Swap(inputs[i]);
|
const v4 = decodeV4Swap(inputs[i]);
|
||||||
|
const inputSetBefore = inputEstablished;
|
||||||
if (v4) {
|
if (v4) {
|
||||||
// Each SETTLE paid from the router spends the amount
|
// Each SETTLE paid from the router spends the amount
|
||||||
// decodeV4Swap() says it pays.
|
// decodeV4Swap() says it pays.
|
||||||
@@ -1532,12 +1667,28 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
|
|||||||
if (!v4 || v4.recipientUnread) {
|
if (!v4 || v4.recipientUnread) {
|
||||||
payeeUnread = true;
|
payeeUnread = true;
|
||||||
}
|
}
|
||||||
|
if (
|
||||||
|
!v4 ||
|
||||||
|
v4.recipientUnread ||
|
||||||
|
v4.settleUnread ||
|
||||||
|
v4.swapUndecoded
|
||||||
|
) {
|
||||||
|
stepsUnread.push(i);
|
||||||
|
}
|
||||||
if (v4 && v4.swapUndecoded) {
|
if (v4 && v4.swapUndecoded) {
|
||||||
// A swap action of this step went unread, so the step
|
// A swap action of this step went unread, so the step
|
||||||
// is marked.
|
// is marked.
|
||||||
swapUndecoded = true;
|
swapUndecoded = true;
|
||||||
commandNames[i] += " (could not be decoded)";
|
commandNames[i] += " (could not be decoded)";
|
||||||
}
|
}
|
||||||
|
// The step's amount, if it sets the input side: what the
|
||||||
|
// user pays it of its input currency (see
|
||||||
|
// decodeV4Swap()), or, when the user pays none of it,
|
||||||
|
// what its swap actions spend of it, whoever pays that.
|
||||||
|
const paidIn = v4?.paidByUser.find(
|
||||||
|
(p) => p.currency === v4.tokenIn,
|
||||||
|
);
|
||||||
|
const amountIn = paidIn ? paidIn.amount : v4?.amountIn;
|
||||||
if (v4 && v4.hasSwapAction) {
|
if (v4 && v4.hasSwapAction) {
|
||||||
// A step with a swap action always sets the output
|
// A step with a swap action always sets the output
|
||||||
// side, as the last swap step wins, and sets the
|
// side, as the last swap step wins, and sets the
|
||||||
@@ -1549,7 +1700,7 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
|
|||||||
// token, likewise never sits beside an earlier step's
|
// token, likewise never sits beside an earlier step's
|
||||||
// other half.
|
// other half.
|
||||||
if (!inputEstablished) {
|
if (!inputEstablished) {
|
||||||
setInput(v4.tokenIn, v4.amountIn);
|
setInput(v4.tokenIn, amountIn);
|
||||||
}
|
}
|
||||||
outputToken = v4.tokenOut;
|
outputToken = v4.tokenOut;
|
||||||
minOutput = v4.amountOutMin;
|
minOutput = v4.amountOutMin;
|
||||||
@@ -1559,9 +1710,26 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
|
|||||||
// SETTLE, SETTLE_ALL, TAKE or TAKE_ALL names:
|
// SETTLE, SETTLE_ALL, TAKE or TAKE_ALL names:
|
||||||
// setInputOnce() and setOutput() leave alone a side
|
// setInputOnce() and setOutput() leave alone a side
|
||||||
// whose two halves are both unstated.
|
// whose two halves are both unstated.
|
||||||
setInputOnce(v4.tokenIn, v4.amountIn);
|
setInputOnce(v4.tokenIn, amountIn);
|
||||||
setOutput(v4.tokenOut, v4.amountOutMin);
|
setOutput(v4.tokenOut, v4.amountOutMin);
|
||||||
}
|
}
|
||||||
|
// Every V4 step, with a swap action or without, takes
|
||||||
|
// from the sender what the user pays it (an amount an
|
||||||
|
// unread action may change is null), less what the step
|
||||||
|
// that sets the input side counts as its amount. One
|
||||||
|
// whose input, or one of whose SETTLEs or SETTLE_ALLs,
|
||||||
|
// this decoder could not read may take anything: the
|
||||||
|
// router still runs it.
|
||||||
|
const setInputHere = inputEstablished && !inputSetBefore;
|
||||||
|
if (v4) {
|
||||||
|
for (const p of v4.paidByUser) {
|
||||||
|
if (setInputHere && p === paidIn) continue;
|
||||||
|
addUserPaid(p.currency, p.amount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!v4 || v4.settleUnread) {
|
||||||
|
spentUnread();
|
||||||
|
}
|
||||||
} else if (command === "UNWRAP_WETH") {
|
} else if (command === "UNWRAP_WETH") {
|
||||||
hasUnwrapWeth = true;
|
hasUnwrapWeth = true;
|
||||||
const u = decodeUnwrapWeth(inputs[i]);
|
const u = decodeUnwrapWeth(inputs[i]);
|
||||||
@@ -1582,26 +1750,109 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
payeeUnread = true;
|
payeeUnread = true;
|
||||||
|
stepsUnread.push(i);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Any other step may pay an address this decoder does not
|
// Any other step may pay an address this decoder does not
|
||||||
// read: V3_SWAP_EXACT_OUT, PERMIT2_TRANSFER_FROM_BATCH and
|
// read: PERMIT2_PERMIT_BATCH and the NFT commands among
|
||||||
// the NFT commands among them, and a type the release
|
// them, and a type the release names no command for.
|
||||||
// names no command for.
|
|
||||||
payeeUnread = true;
|
payeeUnread = true;
|
||||||
|
stepsUnread.push(i);
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
// Skip commands we can't decode. One may pay any address.
|
// Skip commands we can't decode. One may pay any address and
|
||||||
|
// take from the sender.
|
||||||
payeeUnread = true;
|
payeeUnread = true;
|
||||||
|
stepsUnread.push(i);
|
||||||
|
spentUnread();
|
||||||
}
|
}
|
||||||
if (allowedToFail) {
|
if (allowedToFail) {
|
||||||
commandNames[i] += " (allowed to fail)";
|
commandNames[i] += " (allowed to fail)";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The Amount line, and the lines for what the steps take beyond it.
|
||||||
|
//
|
||||||
|
// The total is what the steps take of Token In: the amount of the
|
||||||
|
// step that set the input side, with each later take of that token
|
||||||
|
// added to it while both are quantities. A take that cannot be added,
|
||||||
|
// being of another token or coming after an amount that is not a
|
||||||
|
// quantity (a V4 open delta, ALREADY_PAID, or none stated), gets
|
||||||
|
// lines of its own. One whose amount is not known also leaves the
|
||||||
|
// total unknown, with no figure.
|
||||||
|
//
|
||||||
|
// The last PERMIT2_PERMIT read names Token In. Its amount stands as
|
||||||
|
// the Amount figure in place of the total only in the plain shape of
|
||||||
|
// Uniswap's own routes: it is the only PERMIT2_PERMIT, its spender is
|
||||||
|
// the router, the router may not let it fail, no step before it took
|
||||||
|
// its token or one not known, and this decoder read every step after
|
||||||
|
// it in full (see stepsUnread). Permit2 then lets the router take no
|
||||||
|
// more of the token than that amount, so the permit covers every take
|
||||||
|
// of it. A later step this decoder did not read may raise what Permit2
|
||||||
|
// lets the router take, as a PERMIT2_PERMIT_BATCH can. A step that
|
||||||
|
// may take from the sender what this decoder did not read leaves no
|
||||||
|
// figure, permit or not (see spentUnread()), and an EXECUTE_SUB_PLAN
|
||||||
|
// step leaves both sides unknown (below).
|
||||||
|
const stepCommands = Array.from(
|
||||||
|
commandsBytes,
|
||||||
|
(b) => commands[b & commandTypeMask],
|
||||||
|
);
|
||||||
|
const permitStands =
|
||||||
|
permit !== null &&
|
||||||
|
stepCommands.filter((c) => c === "PERMIT2_PERMIT").length === 1 &&
|
||||||
|
sameAddress(permit.spender, toAddress) &&
|
||||||
|
!permit.allowedToFail &&
|
||||||
|
permit.beforeEveryTake &&
|
||||||
|
!stepsUnread.some((step) => step > permit.step);
|
||||||
|
// What the steps take that the Amount line does not give, as
|
||||||
|
// { token, amount }. A null token is a step this decoder did not read
|
||||||
|
// (see spentUnread()), or a token it does not know.
|
||||||
|
const spentFromSender = [];
|
||||||
|
let total = null;
|
||||||
|
// The take of the step that set the input side, when the total
|
||||||
|
// starts from it and it stated no amount (see below), or null.
|
||||||
|
let firstTakeUnstated = null;
|
||||||
|
if (permit === null || sameAddress(inputToken, permit.token)) {
|
||||||
|
total = inputAmount;
|
||||||
|
if (inputEstablished && !present(inputAmount)) {
|
||||||
|
firstTakeUnstated = { token: inputToken, amount: null };
|
||||||
|
}
|
||||||
|
} else if (inputEstablished) {
|
||||||
|
// The step that set the input side spent a token other than the
|
||||||
|
// permit's, or one not known, so the total starts with no figure.
|
||||||
|
spentFromSender.push({ token: inputToken, amount: inputAmount });
|
||||||
|
}
|
||||||
|
if (permit !== null) inputToken = permit.token;
|
||||||
|
for (const t of userPaid) {
|
||||||
|
const ofTokenIn = sameAddress(t.token, inputToken);
|
||||||
|
if (ofTokenIn && permitStands) continue;
|
||||||
|
if (
|
||||||
|
ofTokenIn &&
|
||||||
|
typeof total === "bigint" &&
|
||||||
|
typeof t.amount === "bigint"
|
||||||
|
) {
|
||||||
|
total += t.amount;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
spentFromSender.push(t);
|
||||||
|
if (ofTokenIn && !present(t.amount)) total = null;
|
||||||
|
}
|
||||||
|
inputAmount = permitStands ? permit.amount : total;
|
||||||
|
// With no Amount figure, a later take's own figure could read as all
|
||||||
|
// that leaves, so the step that set the input side, when it stated
|
||||||
|
// no amount, then gets lines of its own too, first, saying its amount
|
||||||
|
// is not known.
|
||||||
|
if (
|
||||||
|
!present(inputAmount) &&
|
||||||
|
firstTakeUnstated !== null &&
|
||||||
|
spentFromSender.some((s) => present(s.amount))
|
||||||
|
) {
|
||||||
|
spentFromSender.unshift(firstTakeUnstated);
|
||||||
|
}
|
||||||
|
|
||||||
// The commands of an EXECUTE_SUB_PLAN step may spend the user's tokens
|
// The commands of an EXECUTE_SUB_PLAN step may spend the user's tokens
|
||||||
// or pay the user, before or after any other step, and a permit among
|
// or pay the user, before or after any other step, and a permit among
|
||||||
// them would replace the input side. What the other steps state is
|
// them would name Token In. What the other steps state is
|
||||||
// then not the whole swap, so both sides are set unknown, with no
|
// then not the whole swap, so both sides are set unknown, with no
|
||||||
// figure, wherever the step stands.
|
// figure, wherever the step stands.
|
||||||
if (hasSubPlan) {
|
if (hasSubPlan) {
|
||||||
@@ -1672,37 +1923,49 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (present(inputAmount)) {
|
// The text of an amount the swap steps spend, for the Amount line and
|
||||||
// Three amounts need no scale to describe and are named rather
|
// the lines for other tokens below it. Three amounts need no scale to
|
||||||
// than formatted: V4's open delta and V2's already-paid zero,
|
// describe and are named rather than formatted: V4's open delta and
|
||||||
// neither of which is a quantity at all (see OPEN_DELTA and
|
// V2's already-paid zero, neither of which is a quantity at all (see
|
||||||
// ALREADY_PAID), and an unbounded permit. Those two tests come
|
// OPEN_DELTA and ALREADY_PAID), and an unbounded permit. Those two
|
||||||
// first — the sentinels are not bigints and cannot be compared
|
// tests come first — the sentinels are not bigints and cannot be
|
||||||
// with one.
|
// compared with one.
|
||||||
let amount;
|
const spentText = (amount, info) => {
|
||||||
if (inputAmount === OPEN_DELTA) {
|
if (amount === OPEN_DELTA) {
|
||||||
amount = { raw: OPEN_DELTA_AMOUNT, display: OPEN_DELTA_AMOUNT };
|
return { raw: OPEN_DELTA_AMOUNT, display: OPEN_DELTA_AMOUNT };
|
||||||
} else if (inputAmount === ALREADY_PAID) {
|
}
|
||||||
amount = {
|
if (amount === ALREADY_PAID) {
|
||||||
|
return {
|
||||||
raw: ALREADY_PAID_AMOUNT,
|
raw: ALREADY_PAID_AMOUNT,
|
||||||
display: ALREADY_PAID_AMOUNT,
|
display: ALREADY_PAID_AMOUNT,
|
||||||
};
|
};
|
||||||
} else if (inputAmount >= MAX_UINT160) {
|
}
|
||||||
amount = { raw: "Unlimited", display: "Unlimited" };
|
if (amount >= MAX_UINT160) {
|
||||||
} else if (hasV2ExactOut) {
|
return { raw: "Unlimited", display: "Unlimited" };
|
||||||
|
}
|
||||||
|
if (hasV2ExactOut) {
|
||||||
// A V2 exact-out swap spends at most this figure, whichever
|
// A V2 exact-out swap spends at most this figure, whichever
|
||||||
// step set the line (its amountInMax, the WRAP_ETH of a swap
|
// steps gave it (an amountInMax, the WRAP_ETH of a swap paid
|
||||||
// paid in ETH, a permit), so it is said to be a maximum, in
|
// in ETH, a permit, or several steps' total), so it is said to
|
||||||
// `raw` too: the wait, success and error screens show `raw` as
|
// be a maximum, in `raw` too: the wait, success and error
|
||||||
// the transaction's amount.
|
// screens show `raw` as the transaction's amount.
|
||||||
const most = amountText(inputAmount, inInfo);
|
const most = amountText(amount, info);
|
||||||
amount = {
|
return {
|
||||||
raw: "Up to " + most.raw,
|
raw: "Up to " + most.raw,
|
||||||
display: "Up to " + most.display,
|
display: "Up to " + most.display,
|
||||||
};
|
};
|
||||||
} else {
|
|
||||||
amount = amountText(inputAmount, inInfo);
|
|
||||||
}
|
}
|
||||||
|
return amountText(amount, info);
|
||||||
|
};
|
||||||
|
|
||||||
|
// A null token in `spentFromSender`, a step this decoder did not read
|
||||||
|
// or a token it does not know, leaves no Amount figure, which may
|
||||||
|
// then be too low.
|
||||||
|
if (
|
||||||
|
present(inputAmount) &&
|
||||||
|
spentFromSender.every((s) => present(s.token))
|
||||||
|
) {
|
||||||
|
const amount = spentText(inputAmount, inInfo);
|
||||||
details.push({
|
details.push({
|
||||||
label: "Amount",
|
label: "Amount",
|
||||||
value: amount.display,
|
value: amount.display,
|
||||||
@@ -1710,6 +1973,26 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// What steps take from the sender beyond the Amount line, in the shape
|
||||||
|
// of the lines below for a PERMIT2_TRANSFER_FROM. An amount this
|
||||||
|
// decoder does not know is said so, and a step it did not read gets
|
||||||
|
// that one line alone.
|
||||||
|
for (const s of spentFromSender) {
|
||||||
|
details.push({
|
||||||
|
label: "Moved from the sender",
|
||||||
|
value: present(s.amount)
|
||||||
|
? spentText(s.amount, tokenInfo(s.token, sources)).display
|
||||||
|
: UNDECODED_CURRENCY,
|
||||||
|
});
|
||||||
|
if (present(s.token)) {
|
||||||
|
details.push(tokenMovedLine("Token moved", s.token, sources));
|
||||||
|
details.push({
|
||||||
|
label: "Moved to",
|
||||||
|
value: "The swap step that spends it",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// What each PERMIT2_TRANSFER_FROM moves: its amount, its token and
|
// What each PERMIT2_TRANSFER_FROM moves: its amount, its token and
|
||||||
// where it goes, an address on a line of its own.
|
// where it goes, an address on a line of its own.
|
||||||
for (const m of movedFromSender) {
|
for (const m of movedFromSender) {
|
||||||
|
|||||||
+1006
-2
File diff suppressed because it is too large
Load Diff
@@ -671,6 +671,7 @@ describe("any token left in the router is not received", () => {
|
|||||||
v3SwapExactIn(MSG_SENDER, USDT_2000, USDC_1990, USDT, USDC),
|
v3SwapExactIn(MSG_SENDER, USDT_2000, USDC_1990, USDT, USDC),
|
||||||
),
|
),
|
||||||
left: tokenLine(WETH, "WETH"),
|
left: tokenLine(WETH, "WETH"),
|
||||||
|
spent: "2000.0000 USDT",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
kind: "a WRAP_ETH of 1 ETH into the router, then a swap of 0.5 WETH from the router",
|
kind: "a WRAP_ETH of 1 ETH into the router, then a swap of 0.5 WETH from the router",
|
||||||
@@ -697,6 +698,7 @@ describe("any token left in the router is not received", () => {
|
|||||||
v2Swap(0x08, MSG_SENDER, ONE_WETH, USDC_1990, [WETH, USDC]),
|
v2Swap(0x08, MSG_SENDER, ONE_WETH, USDC_1990, [WETH, USDC]),
|
||||||
),
|
),
|
||||||
left: tokenLine(WETH, "WETH"),
|
left: tokenLine(WETH, "WETH"),
|
||||||
|
spent: "1.0000 WETH",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
kind: "a WRAP_ETH of 1 ETH into the router, then a V3 swap of 1 WETH that the user pays for",
|
kind: "a WRAP_ETH of 1 ETH into the router, then a V3 swap of 1 WETH that the user pays for",
|
||||||
@@ -705,6 +707,7 @@ describe("any token left in the router is not received", () => {
|
|||||||
v3SwapExactIn(MSG_SENDER, ONE_WETH, USDC_1990, WETH, USDC),
|
v3SwapExactIn(MSG_SENDER, ONE_WETH, USDC_1990, WETH, USDC),
|
||||||
),
|
),
|
||||||
left: tokenLine(WETH, "WETH"),
|
left: tokenLine(WETH, "WETH"),
|
||||||
|
spent: "1.0000 WETH",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
kind: "a WRAP_ETH of 1 ETH into the router, then a V4 swap of 1 WETH with a SETTLE of 1 WETH by the user",
|
kind: "a WRAP_ETH of 1 ETH into the router, then a V4 swap of 1 WETH with a SETTLE of 1 WETH by the user",
|
||||||
@@ -717,6 +720,7 @@ describe("any token left in the router is not received", () => {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
left: tokenLine(WETH, "WETH"),
|
left: tokenLine(WETH, "WETH"),
|
||||||
|
spent: "1.0000 WETH",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
kind: "a swap paying WETH to the router, a WRAP_ETH of 1 ETH into the router, then a swap of 1 WETH from the router",
|
kind: "a swap paying WETH to the router, a WRAP_ETH of 1 ETH into the router, then a swap of 1 WETH from the router",
|
||||||
@@ -792,9 +796,13 @@ describe("any token left in the router is not received", () => {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
left: tokenLine(WETH, "WETH"),
|
left: tokenLine(WETH, "WETH"),
|
||||||
|
// The step has two SETTLEs of WETH, so what the user pays is not
|
||||||
|
// known.
|
||||||
|
spent: UNDECODED,
|
||||||
},
|
},
|
||||||
// The user pays for the swap by a SETTLE_ALL or SETTLE whose currency
|
// The user pays for the swap by a SETTLE_ALL or SETTLE whose currency
|
||||||
// the decoder cannot read, so the SETTLE from the router pays nothing.
|
// the decoder cannot read, so the SETTLE from the router pays nothing,
|
||||||
|
// and what the user pays is not known.
|
||||||
{
|
{
|
||||||
kind: "a WRAP_ETH of 1 ETH into the router, then a V4 swap of 1 WETH with a SETTLE_ALL that cannot be read and a SETTLE of OPEN_DELTA from the router",
|
kind: "a WRAP_ETH of 1 ETH into the router, then a V4 swap of 1 WETH with a SETTLE_ALL that cannot be read and a SETTLE of OPEN_DELTA from the router",
|
||||||
data: execute(
|
data: execute(
|
||||||
@@ -807,6 +815,7 @@ describe("any token left in the router is not received", () => {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
left: tokenLine(WETH, "WETH"),
|
left: tokenLine(WETH, "WETH"),
|
||||||
|
unread: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
kind: "a WRAP_ETH of 1 ETH into the router, then a V4 swap of 1 WETH with a SETTLE by the user that cannot be read and a SETTLE of OPEN_DELTA from the router",
|
kind: "a WRAP_ETH of 1 ETH into the router, then a V4 swap of 1 WETH with a SETTLE by the user that cannot be read and a SETTLE of OPEN_DELTA from the router",
|
||||||
@@ -820,6 +829,7 @@ describe("any token left in the router is not received", () => {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
left: tokenLine(WETH, "WETH"),
|
left: tokenLine(WETH, "WETH"),
|
||||||
|
unread: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
kind: "a WRAP_ETH of 1 ETH into the router, then a V4 step with an exact-in of 1 WETH less 1,000 wei and an empty path, a swap of 1,000 wei, and a SETTLE of OPEN_DELTA from the router",
|
kind: "a WRAP_ETH of 1 ETH into the router, then a V4 step with an exact-in of 1 WETH less 1,000 wei and an empty path, a swap of 1,000 wei, and a SETTLE of OPEN_DELTA from the router",
|
||||||
@@ -873,12 +883,20 @@ describe("any token left in the router is not received", () => {
|
|||||||
),
|
),
|
||||||
left: { label: "Token moved into the router", value: "ETH" },
|
left: { label: "Token moved into the router", value: "ETH" },
|
||||||
},
|
},
|
||||||
])("$kind", ({ data, left }) => {
|
])("$kind", ({ data, left, spent, unread }) => {
|
||||||
const result = decode(data);
|
const result = decode(data);
|
||||||
expect(labels(result)).toEqual([
|
expect(labels(result)).toEqual([
|
||||||
"Protocol",
|
"Protocol",
|
||||||
"Token In",
|
"Token In",
|
||||||
"Amount",
|
// A later step whose SETTLE or SETTLE_ALL the decoder cannot read
|
||||||
|
// may take anything from the sender: one line says so, in place
|
||||||
|
// of the Amount line (https://git.eeqj.de/sneak/AutistMask/issues/625).
|
||||||
|
unread ? "Moved from the sender" : "Amount",
|
||||||
|
// A later swap step the user pays for spends `spent` of a token
|
||||||
|
// other than Token In.
|
||||||
|
...(spent
|
||||||
|
? ["Moved from the sender", "Token moved", "Moved to"]
|
||||||
|
: []),
|
||||||
"Token Out",
|
"Token Out",
|
||||||
"Paid to",
|
"Paid to",
|
||||||
"Token moved into the router",
|
"Token moved into the router",
|
||||||
@@ -886,6 +904,14 @@ describe("any token left in the router is not received", () => {
|
|||||||
"Steps",
|
"Steps",
|
||||||
"Deadline",
|
"Deadline",
|
||||||
]);
|
]);
|
||||||
|
if (spent) {
|
||||||
|
expect(detail(result, "Moved from the sender").value).toBe(spent);
|
||||||
|
}
|
||||||
|
if (unread) {
|
||||||
|
expect(detail(result, "Moved from the sender").value).toBe(
|
||||||
|
UNDECODED,
|
||||||
|
);
|
||||||
|
}
|
||||||
expect(paidTo(result)).toEqual([STAYS_IN_ROUTER]);
|
expect(paidTo(result)).toEqual([STAYS_IN_ROUTER]);
|
||||||
expect(detail(result, "Token moved into the router")).toEqual(left);
|
expect(detail(result, "Token moved into the router")).toEqual(left);
|
||||||
expect(detail(result, "Min. paid out").value).toBe("1990.0000 USDC");
|
expect(detail(result, "Min. paid out").value).toBe("1990.0000 USDC");
|
||||||
|
|||||||
Reference in New Issue
Block a user