Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
08d760d195 |
@@ -3080,37 +3080,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)`
|
||||||
@@ -3129,41 +3132,54 @@ Currently supported:
|
|||||||
the input side when no earlier step set it, even when it names neither (see
|
the input side when no earlier step set it, even when it names neither (see
|
||||||
Display Consistency). A V2 exact-out step gives its `amountInMax`, and a V4
|
Display Consistency). A V2 exact-out step gives its `amountInMax`, and a V4
|
||||||
swap step the `amountIn` of the swap action that spends its input currency.
|
swap step the `amountIn` of the swap action that spends its input currency.
|
||||||
Each later swap step that the user pays for also takes its amount, found in
|
Each later V2 or V3 exact-in or V2 exact-out step whose `payerIsUser` is true
|
||||||
the same way, from the user's wallet: a V2 or V3 exact-in or V2 exact-out step
|
also takes its amount, found in the same way, from the user's wallet. So does
|
||||||
whose `payerIsUser` is true, or a V4 step with a swap action whose input
|
every V4 step but the one that sets the input side, with a swap action or
|
||||||
currency a `SETTLE` paid by the user (`payerIsUser` true) or a `SETTLE_ALL`
|
without: it takes what its `SETTLE`s paid by the user (`payerIsUser` true) and
|
||||||
names. When it spends `Token In`, its amount is added to the line's, unless a
|
its `SETTLE_ALL`s pay, for each token on its own. A `SETTLE` of any amount but
|
||||||
`PERMIT2_PERMIT` set the input side, whose amount the line keeps. So a V3
|
`OPEN_DELTA` pays that amount, and a `SETTLE` of `OPEN_DELTA` or a
|
||||||
exact-in of 600 USDT and a V2 exact-in of 400 USDT, both paid by the user,
|
`SETTLE_ALL` pays what the step owes, as above. What a step takes of
|
||||||
read `1000.0000 USDT`. When either amount is not a quantity (a V4 open delta,
|
`Token In` is added to the line's, unless a `PERMIT2_PERMIT` set the input
|
||||||
`ALREADY_PAID`, or an amount the decoder did not read), the total is not known
|
side, whose amount the line keeps. A permit whose spender is the router and
|
||||||
and there is no `Amount` line. A step paid from the router spends what the
|
which the router does not let fail caps what the router may take of its token
|
||||||
router holds and adds nothing, and so does a V2 exact-in whose `amountIn` is
|
after it; any other permit caps nothing the router takes. So a V3 exact-in of
|
||||||
`ALREADY_PAID`, which takes nothing from the user. A step the user pays for
|
600 USDT and a V2 exact-in of 400 USDT, both paid by the user, read
|
||||||
that spends a token other than `Token In` gives three lines after the `Amount`
|
`1000.0000 USDT`. When either amount is not a quantity (a V4 open delta,
|
||||||
line (after `Token In` when there is none), in step order:
|
`ALREADY_PAID`, or an amount the decoder does not know), the total is not
|
||||||
`Moved from the sender`, its amount, worded as the `Amount` line words one, or
|
known and there is no `Amount` line. A step paid from the router spends what
|
||||||
`Unknown (could not be decoded)` when the decoder read none; `Token moved`,
|
the router holds and adds nothing, and so does a V2 exact-in whose `amountIn`
|
||||||
giving the token's address as the `Token In` line does; and `Moved to`, which
|
is `ALREADY_PAID`, which takes nothing from the user. What a step takes of a
|
||||||
reads `The swap step that spends it`. Then each `PERMIT2_TRANSFER_FROM` but
|
token other than `Token In`, or of `Token In` when the decoder does not know
|
||||||
one into the router, in step order, gives three lines:
|
the amount or when a permit that caps nothing set the input side, gives three
|
||||||
`Moved from the sender`, the amount it moves, for example `1000000.0000 USDT`;
|
lines after the `Amount` line (after `Token In` when there is none), in step
|
||||||
|
order: `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
|
`Token moved`, giving the token's address as the `Token In` line does; and
|
||||||
`Moved to`, which reads `The V2 pair the next swap spends` for a hop and
|
`Moved to`, which reads `The swap step that spends it`. A step may also take
|
||||||
`The sender` for `address(1)` on every router but `UniversalRouterV1`, and
|
from the user's wallet what the decoder did not read: a V4 step, other than
|
||||||
otherwise gives the address. A move into the router needs none of them,
|
the one that sets the input side, whose input or one of whose `SETTLE`s or
|
||||||
because its `Paid to` line and `Token moved into the router` line already give
|
`SETTLE_ALL`s it cannot read, which the router still runs; a V2 or V3 exact-in
|
||||||
the amount, the token and where it goes, and so its amount is shown once.
|
or V2 exact-out step or a `PERMIT2_TRANSFER_FROM` whose input it cannot read;
|
||||||
These lines change neither the `Paid to` lines nor the minimum's label. A step
|
and every `PERMIT2_TRANSFER_FROM_BATCH`. Each such step gives one
|
||||||
goes unread when its recipient does not decode as an address, or when it is a
|
`Moved from the sender` line, among those lines in step order, reading
|
||||||
command the decoder does not read: every command other than `PERMIT2_PERMIT`,
|
`Unknown (could not be decoded)`, and there is then no `Amount` line, whose
|
||||||
`PERMIT2_TRANSFER_FROM`, `V2_SWAP_EXACT_IN`, `V2_SWAP_EXACT_OUT`,
|
figure might be too low. Then each `PERMIT2_TRANSFER_FROM` but one into the
|
||||||
`V3_SWAP_EXACT_IN`, `V4_SWAP`, `SWEEP`, `TRANSFER`, `PAY_PORTION`,
|
router, in step order, gives three lines: `Moved from the sender`, the amount
|
||||||
`PAY_PORTION_FULL_PRECISION`, `ACROSS_V4_DEPOSIT_V3`, `WRAP_ETH`,
|
it moves, for example `1000000.0000 USDT`; `Token moved`, giving the token's
|
||||||
`UNWRAP_WETH` and `BALANCE_CHECK_ERC20` (`V3_SWAP_EXACT_OUT`,
|
address as the `Token In` line does; and `Moved to`, which reads
|
||||||
`PERMIT2_TRANSFER_FROM_BATCH`, `EXECUTE_SUB_PLAN` and the NFT commands among
|
`The V2 pair the next swap spends` for a hop and `The sender` for `address(1)`
|
||||||
them), or a type its release names no command for.
|
on every router but `UniversalRouterV1`, and otherwise gives the address. A
|
||||||
|
move into the router needs none of them, because its `Paid to` line and
|
||||||
|
`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
|
||||||
|
`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
|
||||||
|
read: every command other than `PERMIT2_PERMIT`, `PERMIT2_TRANSFER_FROM`,
|
||||||
|
`V2_SWAP_EXACT_IN`, `V2_SWAP_EXACT_OUT`, `V3_SWAP_EXACT_IN`, `V4_SWAP`,
|
||||||
|
`SWEEP`, `TRANSFER`, `PAY_PORTION`, `PAY_PORTION_FULL_PRECISION`,
|
||||||
|
`ACROSS_V4_DEPOSIT_V3`, `WRAP_ETH`, `UNWRAP_WETH` and `BALANCE_CHECK_ERC20`
|
||||||
|
(`V3_SWAP_EXACT_OUT`, `PERMIT2_TRANSFER_FROM_BATCH`, `EXECUTE_SUB_PLAN` and
|
||||||
|
the NFT commands among them), or a type its release names no command for.
|
||||||
|
|
||||||
### Non-Goals Forever
|
### Non-Goals Forever
|
||||||
|
|
||||||
|
|||||||
+170
-81
@@ -335,8 +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, and so does the amount a swap step the
|
// address this decoder did not read, and so do the lines for what a step
|
||||||
// user pays for spends of another token, when this decoder read none.
|
// 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
|
||||||
@@ -904,12 +904,15 @@ function totalAmount(amounts) {
|
|||||||
// 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). `payerIsUser` is true when the user pays
|
// pays: a quantity, CONTRACT_BALANCE (all the router holds), or null when
|
||||||
// the step's Token In: a SETTLE paid by the user (`payerIsUser` true) or a
|
// this decoder cannot tell (see below). `paidByUser` is what the user pays
|
||||||
// SETTLE_ALL, which V4Router always has the user pay, names it.
|
// 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);
|
||||||
@@ -919,18 +922,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.
|
||||||
// The currency of every SETTLE and SETTLE_ALL the user pays.
|
const settles = [];
|
||||||
const settledByUser = [];
|
|
||||||
// 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 = [];
|
||||||
@@ -949,22 +952,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],
|
||||||
settledByUser.push(s[0]);
|
amount: s[1],
|
||||||
} else {
|
payerIsUser: s[2],
|
||||||
routerSettles.push({
|
index: i,
|
||||||
currency: s[0],
|
});
|
||||||
amount: s[1],
|
|
||||||
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({
|
||||||
settledByUser.push(s[0]);
|
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(
|
||||||
@@ -1006,7 +1009,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 ||
|
||||||
@@ -1015,25 +1019,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)
|
||||||
@@ -1042,16 +1050,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) {
|
||||||
@@ -1062,10 +1084,11 @@ function decodeV4Swap(input) {
|
|||||||
amountOutMin: null,
|
amountOutMin: null,
|
||||||
takes,
|
takes,
|
||||||
settledFromRouter,
|
settledFromRouter,
|
||||||
|
paidByUser,
|
||||||
recipientUnread,
|
recipientUnread,
|
||||||
|
settleUnread,
|
||||||
swapUndecoded,
|
swapUndecoded,
|
||||||
hasSwapAction: true,
|
hasSwapAction: true,
|
||||||
payerIsUser: settledByUser.includes(settleToken),
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1105,10 +1128,11 @@ function decodeV4Swap(input) {
|
|||||||
amountOutMin,
|
amountOutMin,
|
||||||
takes,
|
takes,
|
||||||
settledFromRouter,
|
settledFromRouter,
|
||||||
|
paidByUser,
|
||||||
recipientUnread,
|
recipientUnread,
|
||||||
|
settleUnread,
|
||||||
swapUndecoded,
|
swapUndecoded,
|
||||||
hasSwapAction: swaps.length > 0,
|
hasSwapAction: swaps.length > 0,
|
||||||
payerIsUser: present(tokenIn) && settledByUser.includes(tokenIn),
|
|
||||||
};
|
};
|
||||||
} catch {
|
} catch {
|
||||||
return null;
|
return null;
|
||||||
@@ -1138,6 +1162,7 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
|
|||||||
let inputAmount = null;
|
let inputAmount = null;
|
||||||
let inputEstablished = false;
|
let inputEstablished = false;
|
||||||
let inputFromPermit = false;
|
let inputFromPermit = false;
|
||||||
|
let permitCapsInput = false;
|
||||||
let outputToken = null;
|
let outputToken = null;
|
||||||
let minOutput = null;
|
let minOutput = null;
|
||||||
let outputEstablished = false;
|
let outputEstablished = false;
|
||||||
@@ -1166,7 +1191,9 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
|
|||||||
// 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. Each later swap step the user pays for adds
|
// from a different hop. Each later swap step the user pays for adds
|
||||||
// what it spends, in addUserPaid(), the one place the total is formed.
|
// what it spends, in addUserPaid(), the one place the total is formed,
|
||||||
|
// and a step that may take what this decoder did not read leaves no
|
||||||
|
// total at all (see spentUnread()).
|
||||||
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;
|
||||||
@@ -1184,31 +1211,53 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
|
|||||||
outputEstablished = true;
|
outputEstablished = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
// What each swap step the user pays for spends of a token other than
|
// What steps take from the sender that the Amount line does not give,
|
||||||
// the input side's, as { token, amount }, in step order.
|
// as { token, amount }, in step order (see addUserPaid()). A step that
|
||||||
const otherTokensSpent = [];
|
// may take from the sender what this decoder did not read is here
|
||||||
|
// with a null token (see spentUnread()).
|
||||||
|
const spentFromSender = [];
|
||||||
// A swap step the user pays for (`payerIsUser` true), after the input
|
// A swap step the user pays for (`payerIsUser` true), after the input
|
||||||
// side is set, takes `amount` of `token` from the sender. Of the input
|
// side is set, takes `amount` of `token` from the sender. What it
|
||||||
// side's token it adds to the input side's amount, so the Amount line
|
// takes of the input side's token is added to the input side's
|
||||||
// is what the steps spend of it together; when either amount is not a
|
// amount, so the Amount line is what the steps spend of it together;
|
||||||
// quantity, the total is not known and the amount is null. Of another
|
// when either is not a quantity the total is not known (null), and an
|
||||||
// token it goes in `otherTokensSpent`. A V2 exact-in of ALREADY_PAID
|
// amount not known also goes in `spentFromSender`, so the screen says
|
||||||
// takes nothing from the sender, and a permit's amount stays as the
|
// so. What it takes of another token goes in `spentFromSender`. A V2
|
||||||
// permit states it.
|
// exact-in of ALREADY_PAID takes nothing. When a permit set the input
|
||||||
|
// side, the line keeps the permit's amount: a permit that caps what
|
||||||
|
// the router takes of its token (`permitCapsInput`) covers the take,
|
||||||
|
// and after any other permit the take goes in `spentFromSender`.
|
||||||
const addUserPaid = (token, amount) => {
|
const addUserPaid = (token, amount) => {
|
||||||
if (amount === ALREADY_PAID) return;
|
if (amount === ALREADY_PAID) return;
|
||||||
const sameToken =
|
const sameToken =
|
||||||
present(inputToken) &&
|
present(inputToken) &&
|
||||||
token.toLowerCase() === inputToken.toLowerCase();
|
token.toLowerCase() === inputToken.toLowerCase();
|
||||||
|
if (sameToken && inputFromPermit) {
|
||||||
|
if (!permitCapsInput) {
|
||||||
|
spentFromSender.push({ token, amount });
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (!sameToken) {
|
if (!sameToken) {
|
||||||
otherTokensSpent.push({ token, amount });
|
spentFromSender.push({ token, amount });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (inputFromPermit) return;
|
|
||||||
inputAmount =
|
inputAmount =
|
||||||
typeof inputAmount === "bigint" && typeof amount === "bigint"
|
typeof inputAmount === "bigint" && typeof amount === "bigint"
|
||||||
? inputAmount + amount
|
? inputAmount + amount
|
||||||
: null;
|
: null;
|
||||||
|
if (!present(amount)) {
|
||||||
|
spentFromSender.push({ token, amount });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// A step may take from the sender what this decoder did not read: a
|
||||||
|
// V4 step, other than the one that sets the input side, whose input
|
||||||
|
// or one of whose SETTLEs or SETTLE_ALLs it cannot read; a V2 or V3
|
||||||
|
// swap step or PERMIT2_TRANSFER_FROM whose input it cannot read; or a
|
||||||
|
// PERMIT2_TRANSFER_FROM_BATCH. One line says so, and there is no
|
||||||
|
// Amount line, whose figure may then be too low.
|
||||||
|
const spentUnread = () => {
|
||||||
|
spentFromSender.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.
|
||||||
@@ -1353,11 +1402,19 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
|
|||||||
if (p) {
|
if (p) {
|
||||||
setInput(p.token, p.amount);
|
setInput(p.token, p.amount);
|
||||||
inputFromPermit = true;
|
inputFromPermit = true;
|
||||||
|
// Permit2 lets the spender take no more of the token
|
||||||
|
// than the permit's amount. Only a permit to this
|
||||||
|
// router that the router does not let fail caps what
|
||||||
|
// the later steps take (see addUserPaid()).
|
||||||
|
permitCapsInput =
|
||||||
|
!allowedToFail &&
|
||||||
|
p.spender.toLowerCase() === toAddress.toLowerCase();
|
||||||
}
|
}
|
||||||
} 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;
|
||||||
|
spentUnread();
|
||||||
} else if (
|
} else if (
|
||||||
commandTypeMask === 0x1f &&
|
commandTypeMask === 0x1f &&
|
||||||
[MSG_SENDER, ADDRESS_THIS].includes(
|
[MSG_SENDER, ADDRESS_THIS].includes(
|
||||||
@@ -1436,6 +1493,7 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
payeeUnread = true;
|
payeeUnread = true;
|
||||||
|
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]);
|
||||||
@@ -1454,6 +1512,7 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
payeeUnread = true;
|
payeeUnread = true;
|
||||||
|
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.
|
||||||
@@ -1471,7 +1530,13 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
payeeUnread = true;
|
payeeUnread = true;
|
||||||
|
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;
|
||||||
|
spentUnread();
|
||||||
} else if (command === "SWEEP") {
|
} else if (command === "SWEEP") {
|
||||||
const s = decodeSweep(inputs[i]);
|
const s = decodeSweep(inputs[i]);
|
||||||
if (s) {
|
if (s) {
|
||||||
@@ -1573,6 +1638,7 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
|
|||||||
}
|
}
|
||||||
} 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.
|
||||||
@@ -1606,8 +1672,6 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
|
|||||||
// other half.
|
// other half.
|
||||||
if (!inputEstablished) {
|
if (!inputEstablished) {
|
||||||
setInput(v4.tokenIn, v4.amountIn);
|
setInput(v4.tokenIn, v4.amountIn);
|
||||||
} else if (v4.payerIsUser) {
|
|
||||||
addUserPaid(v4.tokenIn, v4.amountIn);
|
|
||||||
}
|
}
|
||||||
outputToken = v4.tokenOut;
|
outputToken = v4.tokenOut;
|
||||||
minOutput = v4.amountOutMin;
|
minOutput = v4.amountOutMin;
|
||||||
@@ -1620,6 +1684,23 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
|
|||||||
setInputOnce(v4.tokenIn, v4.amountIn);
|
setInputOnce(v4.tokenIn, v4.amountIn);
|
||||||
setOutput(v4.tokenOut, v4.amountOutMin);
|
setOutput(v4.tokenOut, v4.amountOutMin);
|
||||||
}
|
}
|
||||||
|
// Every V4 step but the one that sets the input side,
|
||||||
|
// with a swap action or without, takes from the sender
|
||||||
|
// what the user pays it (an amount an unread action may
|
||||||
|
// change is null). 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 (!setInputHere) {
|
||||||
|
if (v4) {
|
||||||
|
for (const p of v4.paidByUser) {
|
||||||
|
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]);
|
||||||
@@ -1643,9 +1724,8 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
|
|||||||
}
|
}
|
||||||
} 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: V3_SWAP_EXACT_OUT and the NFT commands among them,
|
||||||
// the NFT commands among them, and a type the release
|
// and a type the release names no command for.
|
||||||
// names no command for.
|
|
||||||
payeeUnread = true;
|
payeeUnread = true;
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
@@ -1765,7 +1845,13 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
|
|||||||
return amountText(amount, info);
|
return amountText(amount, info);
|
||||||
};
|
};
|
||||||
|
|
||||||
if (present(inputAmount)) {
|
// A step that may take from the sender what this decoder did not read,
|
||||||
|
// a null token in `spentFromSender`, leaves no Amount figure (see
|
||||||
|
// spentUnread()).
|
||||||
|
if (
|
||||||
|
present(inputAmount) &&
|
||||||
|
spentFromSender.every((s) => present(s.token))
|
||||||
|
) {
|
||||||
const amount = spentText(inputAmount, inInfo);
|
const amount = spentText(inputAmount, inInfo);
|
||||||
details.push({
|
details.push({
|
||||||
label: "Amount",
|
label: "Amount",
|
||||||
@@ -1774,21 +1860,24 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// What each swap step the user pays for spends of another token, in
|
// What steps take from the sender beyond the Amount line, in the shape
|
||||||
// the shape of the lines below for a PERMIT2_TRANSFER_FROM. An amount
|
// of the lines below for a PERMIT2_TRANSFER_FROM. An amount this
|
||||||
// this decoder did not read is said so.
|
// decoder does not know is said so, and a step it did not read gets
|
||||||
for (const s of otherTokensSpent) {
|
// that one line alone.
|
||||||
|
for (const s of spentFromSender) {
|
||||||
details.push({
|
details.push({
|
||||||
label: "Moved from the sender",
|
label: "Moved from the sender",
|
||||||
value: present(s.amount)
|
value: present(s.amount)
|
||||||
? spentText(s.amount, tokenInfo(s.token, sources)).display
|
? spentText(s.amount, tokenInfo(s.token, sources)).display
|
||||||
: UNDECODED_CURRENCY,
|
: UNDECODED_CURRENCY,
|
||||||
});
|
});
|
||||||
details.push(tokenMovedLine("Token moved", s.token, sources));
|
if (present(s.token)) {
|
||||||
details.push({
|
details.push(tokenMovedLine("Token moved", s.token, sources));
|
||||||
label: "Moved to",
|
details.push({
|
||||||
value: "The swap step that spends it",
|
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
|
||||||
|
|||||||
+356
-7
@@ -1296,14 +1296,58 @@ describe("uniswap decoder", () => {
|
|||||||
describe("a swap split across steps the user pays for", () => {
|
describe("a swap split across steps the user pays for", () => {
|
||||||
const USDT_600 = 600000000n;
|
const USDT_600 = 600000000n;
|
||||||
const USDT_400 = 400000000n;
|
const USDT_400 = 400000000n;
|
||||||
|
const DAI_400 = 400000000000000000000n;
|
||||||
|
const OTHER_ADDR = "0x1111111111111111111111111111111111111111";
|
||||||
|
const UNDECODED = "Unknown (could not be decoded)";
|
||||||
|
|
||||||
// The lines from Amount on, as [label, value].
|
// The lines between Token In and Token Out, as [label, value].
|
||||||
function linesFromAmount(result, count) {
|
function linesBetweenTokens(result) {
|
||||||
const lines = result.details.map((d) => [d.label, d.value]);
|
const lines = result.details.map((d) => [d.label, d.value]);
|
||||||
const at = lines.findIndex(([label]) => label === "Amount");
|
const from = lines.findIndex(([label]) => label === "Token In");
|
||||||
return lines.slice(at, at + count);
|
const to = lines.findIndex(([label]) => label === "Token Out");
|
||||||
|
return lines.slice(from + 1, to);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// execute() of these command bytes and inputs, decoded as sent by
|
||||||
|
// the user.
|
||||||
|
function decodeSteps(commands, inputs) {
|
||||||
|
const data = buildExecute(
|
||||||
|
solidityPacked(
|
||||||
|
commands.map(() => "uint8"),
|
||||||
|
commands,
|
||||||
|
),
|
||||||
|
inputs,
|
||||||
|
9999999999n,
|
||||||
|
);
|
||||||
|
return uniswap.decode(data, ROUTER_ADDR, "0x1", {}, USER_ADDR);
|
||||||
|
}
|
||||||
|
|
||||||
|
// The first step: a V3 exact-in of 600 USDT for USDC, paid by the
|
||||||
|
// user, which sets the input side.
|
||||||
|
const V3_600 = encodeV3SwapExactIn(USER_ADDR, USDT_600, 590000000n, [
|
||||||
|
USDT_ADDR,
|
||||||
|
USDC_ADDR,
|
||||||
|
]);
|
||||||
|
// V4 SETTLE of `amount` of `currency`, and TAKE of `amount` of
|
||||||
|
// `currency` to `recipient`.
|
||||||
|
const settle = (currency, amount, payerIsUser) =>
|
||||||
|
coder.encode(
|
||||||
|
["address", "uint256", "bool"],
|
||||||
|
[currency, amount, payerIsUser],
|
||||||
|
);
|
||||||
|
const take = (currency, recipient, amount) =>
|
||||||
|
coder.encode(
|
||||||
|
["address", "address", "uint256"],
|
||||||
|
[currency, recipient, amount],
|
||||||
|
);
|
||||||
|
// `input` with the top byte of its word at `index` set. This decoder
|
||||||
|
// cannot read such a word as an address or a length; the router reads
|
||||||
|
// its low bytes and runs the step.
|
||||||
|
const dirtyWord = (input, index) => {
|
||||||
|
const at = 2 + index * 64;
|
||||||
|
return input.slice(0, at) + "ff" + input.slice(at + 2);
|
||||||
|
};
|
||||||
|
|
||||||
test("a split across a V3 and a V2 step the user pays for shows the total", () => {
|
test("a split across a V3 and a V2 step the user pays for shows the total", () => {
|
||||||
const data = buildExecute(
|
const data = buildExecute(
|
||||||
solidityPacked(["uint8", "uint8"], [0x00, 0x08]),
|
solidityPacked(["uint8", "uint8"], [0x00, 0x08]),
|
||||||
@@ -1337,6 +1381,8 @@ describe("uniswap decoder", () => {
|
|||||||
expect(detail(result, "Moved from the sender")).toBeUndefined();
|
expect(detail(result, "Moved from the sender")).toBeUndefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// In the order of Uniswap's own V4 routes: the swap, then a SETTLE of
|
||||||
|
// OPEN_DELTA by the user, which pays what the swap spends.
|
||||||
test("a split across a V3 step and a V4 step the user pays for shows the total", () => {
|
test("a split across a V3 step and a V4 step the user pays for shows the total", () => {
|
||||||
const data = buildExecute(
|
const data = buildExecute(
|
||||||
solidityPacked(["uint8", "uint8"], [0x00, 0x10]),
|
solidityPacked(["uint8", "uint8"], [0x00, 0x10]),
|
||||||
@@ -1347,18 +1393,18 @@ describe("uniswap decoder", () => {
|
|||||||
]),
|
]),
|
||||||
encodeV4Swap(
|
encodeV4Swap(
|
||||||
new Uint8Array([
|
new Uint8Array([
|
||||||
V4_SETTLE_ID,
|
|
||||||
V4_SWAP_EXACT_IN_SINGLE_ID,
|
V4_SWAP_EXACT_IN_SINGLE_ID,
|
||||||
|
V4_SETTLE_ID,
|
||||||
V4_TAKE_ID,
|
V4_TAKE_ID,
|
||||||
]),
|
]),
|
||||||
[
|
[
|
||||||
encodeV4Settle(USDT_ADDR), // paid by the user
|
|
||||||
encodeV4ExactInSingle(
|
encodeV4ExactInSingle(
|
||||||
USDT_ADDR,
|
USDT_ADDR,
|
||||||
USDC_ADDR,
|
USDC_ADDR,
|
||||||
USDT_400,
|
USDT_400,
|
||||||
390000000n,
|
390000000n,
|
||||||
),
|
),
|
||||||
|
encodeV4Settle(USDT_ADDR), // paid by the user
|
||||||
encodeV4Take(USDC_ADDR),
|
encodeV4Take(USDC_ADDR),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -1406,7 +1452,7 @@ describe("uniswap decoder", () => {
|
|||||||
);
|
);
|
||||||
expect(result).not.toBeNull();
|
expect(result).not.toBeNull();
|
||||||
|
|
||||||
expect(linesFromAmount(result, 4)).toEqual([
|
expect(linesBetweenTokens(result)).toEqual([
|
||||||
["Amount", "600.0000 USDT"],
|
["Amount", "600.0000 USDT"],
|
||||||
["Moved from the sender", "400.0000 DAI"],
|
["Moved from the sender", "400.0000 DAI"],
|
||||||
["Token moved", "DAI (" + DAI_ADDR + ")"],
|
["Token moved", "DAI (" + DAI_ADDR + ")"],
|
||||||
@@ -1414,6 +1460,266 @@ describe("uniswap decoder", () => {
|
|||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Every later V4 step takes from the sender what its SETTLEs paid by
|
||||||
|
// the user and its SETTLE_ALLs pay, each currency on its own, with a
|
||||||
|
// swap action or without. Each of these read 600.0000 USDT alone
|
||||||
|
// before.
|
||||||
|
const USDT_FOR_USDC_400 = encodeV4ExactInSingle(
|
||||||
|
USDT_ADDR,
|
||||||
|
USDC_ADDR,
|
||||||
|
USDT_400,
|
||||||
|
390000000n,
|
||||||
|
);
|
||||||
|
const DAI_FOR_USDC_400 = encodeV4ExactInSingle(
|
||||||
|
DAI_ADDR,
|
||||||
|
USDC_ADDR,
|
||||||
|
DAI_400,
|
||||||
|
390000000n,
|
||||||
|
);
|
||||||
|
const DAI_GROUP = [
|
||||||
|
["Moved from the sender", "400.0000 DAI"],
|
||||||
|
["Token moved", "DAI (" + DAI_ADDR + ")"],
|
||||||
|
["Moved to", "The swap step that spends it"],
|
||||||
|
];
|
||||||
|
test.each([
|
||||||
|
{
|
||||||
|
kind: "a V4 step paying USDT and DAI by a SETTLE_ALL of each",
|
||||||
|
actions: [
|
||||||
|
V4_SWAP_EXACT_IN_SINGLE_ID,
|
||||||
|
V4_SWAP_EXACT_IN_SINGLE_ID,
|
||||||
|
V4_SETTLE_ALL_ID,
|
||||||
|
V4_SETTLE_ALL_ID,
|
||||||
|
V4_TAKE_ALL_ID,
|
||||||
|
],
|
||||||
|
params: [
|
||||||
|
USDT_FOR_USDC_400,
|
||||||
|
DAI_FOR_USDC_400,
|
||||||
|
encodeV4SettleAll(USDT_ADDR, USDT_400),
|
||||||
|
encodeV4SettleAll(DAI_ADDR, DAI_400),
|
||||||
|
encodeV4TakeAll(USDC_ADDR, 0n),
|
||||||
|
],
|
||||||
|
lines: [["Amount", "1000.0000 USDT"], ...DAI_GROUP],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
kind: "a V4 step with a SETTLE of USDT by the user, then a SETTLE of WETH from the router",
|
||||||
|
actions: [
|
||||||
|
V4_SETTLE_ID,
|
||||||
|
V4_SETTLE_ID,
|
||||||
|
V4_SWAP_EXACT_IN_SINGLE_ID,
|
||||||
|
V4_SWAP_EXACT_IN_SINGLE_ID,
|
||||||
|
V4_TAKE_ALL_ID,
|
||||||
|
],
|
||||||
|
params: [
|
||||||
|
settle(USDT_ADDR, USDT_400, true),
|
||||||
|
settle(WETH_ADDR, 2n ** 255n, false), // CONTRACT_BALANCE
|
||||||
|
USDT_FOR_USDC_400,
|
||||||
|
encodeV4ExactInSingle(WETH_ADDR, USDC_ADDR, 0n, 1n),
|
||||||
|
encodeV4TakeAll(USDC_ADDR, 0n),
|
||||||
|
],
|
||||||
|
lines: [["Amount", "1000.0000 USDT"]],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
kind: "a V4 step with no swap action: a SETTLE of 400 USDT by the user and a TAKE of it to another address",
|
||||||
|
actions: [V4_SETTLE_ID, V4_TAKE_ID],
|
||||||
|
params: [
|
||||||
|
settle(USDT_ADDR, USDT_400, true),
|
||||||
|
take(USDT_ADDR, OTHER_ADDR, USDT_400),
|
||||||
|
],
|
||||||
|
lines: [["Amount", "1000.0000 USDT"]],
|
||||||
|
},
|
||||||
|
// The SETTLE's amount, not the swap's, is what the user pays.
|
||||||
|
{
|
||||||
|
kind: "a V4 step swapping 400 USDT with a SETTLE of 5,000 USDT by the user, the rest taken to another address",
|
||||||
|
actions: [
|
||||||
|
V4_SETTLE_ID,
|
||||||
|
V4_SWAP_EXACT_IN_SINGLE_ID,
|
||||||
|
V4_TAKE_ID,
|
||||||
|
V4_TAKE_ALL_ID,
|
||||||
|
],
|
||||||
|
params: [
|
||||||
|
settle(USDT_ADDR, 5000000000n, true),
|
||||||
|
USDT_FOR_USDC_400,
|
||||||
|
take(USDT_ADDR, OTHER_ADDR, 0n),
|
||||||
|
encodeV4TakeAll(USDC_ADDR, 0n),
|
||||||
|
],
|
||||||
|
lines: [["Amount", "5600.0000 USDT"]],
|
||||||
|
},
|
||||||
|
// A SETTLE_ALL pays all the step owes, here what the TAKE takes as
|
||||||
|
// well as what the swap spends, so its amount is not known.
|
||||||
|
{
|
||||||
|
kind: "a V4 step with no swap action: a TAKE of 400 USDT to another address, then a SETTLE_ALL of USDT",
|
||||||
|
actions: [V4_TAKE_ID, V4_SETTLE_ALL_ID],
|
||||||
|
params: [
|
||||||
|
take(USDT_ADDR, OTHER_ADDR, USDT_400),
|
||||||
|
encodeV4SettleAll(USDT_ADDR, USDT_400),
|
||||||
|
],
|
||||||
|
lines: [
|
||||||
|
["Moved from the sender", UNDECODED],
|
||||||
|
["Token moved", "USDT (" + USDT_ADDR + ")"],
|
||||||
|
["Moved to", "The swap step that spends it"],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
kind: "a V4 step swapping 400 USDT, with a TAKE of 400 USDT to another address before its SETTLE_ALL of USDT",
|
||||||
|
actions: [
|
||||||
|
V4_SWAP_EXACT_IN_SINGLE_ID,
|
||||||
|
V4_TAKE_ID,
|
||||||
|
V4_SETTLE_ALL_ID,
|
||||||
|
V4_TAKE_ALL_ID,
|
||||||
|
],
|
||||||
|
params: [
|
||||||
|
USDT_FOR_USDC_400,
|
||||||
|
take(USDT_ADDR, OTHER_ADDR, USDT_400),
|
||||||
|
encodeV4SettleAll(USDT_ADDR, 800000000n),
|
||||||
|
encodeV4TakeAll(USDC_ADDR, 0n),
|
||||||
|
],
|
||||||
|
lines: [
|
||||||
|
["Moved from the sender", UNDECODED],
|
||||||
|
["Token moved", "USDT (" + USDT_ADDR + ")"],
|
||||||
|
["Moved to", "The swap step that spends it"],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
])(
|
||||||
|
"a V3 step of 600 USDT, then $kind",
|
||||||
|
({ actions, params, lines }) => {
|
||||||
|
const result = decodeSteps(
|
||||||
|
[0x00, 0x10],
|
||||||
|
[V3_600, encodeV4Swap(new Uint8Array(actions), params)],
|
||||||
|
);
|
||||||
|
expect(linesBetweenTokens(result)).toEqual(lines);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
// A step that may take from the sender what the decoder did not read
|
||||||
|
// leaves no Amount figure, which may be too low, and one line says so.
|
||||||
|
const UNREAD_SWAP = V4_SWAP_ACTION_SHAPES[0].newer;
|
||||||
|
test.each([
|
||||||
|
{
|
||||||
|
kind: "a V4 step whose SETTLE by the user cannot be read",
|
||||||
|
actions: [
|
||||||
|
V4_SWAP_EXACT_IN_SINGLE_ID,
|
||||||
|
V4_SETTLE_ID,
|
||||||
|
V4_TAKE_ALL_ID,
|
||||||
|
],
|
||||||
|
params: [
|
||||||
|
USDT_FOR_USDC_400,
|
||||||
|
dirtyWord(settle(USDT_ADDR, USDT_400, true), 0),
|
||||||
|
encodeV4TakeAll(USDC_ADDR, 0n),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
kind: "a V4 step whose SETTLE_ALL cannot be read",
|
||||||
|
actions: [
|
||||||
|
V4_SWAP_EXACT_IN_SINGLE_ID,
|
||||||
|
V4_SETTLE_ALL_ID,
|
||||||
|
V4_TAKE_ALL_ID,
|
||||||
|
],
|
||||||
|
params: [
|
||||||
|
USDT_FOR_USDC_400,
|
||||||
|
dirtyWord(encodeV4SettleAll(USDT_ADDR, USDT_400), 0),
|
||||||
|
encodeV4TakeAll(USDC_ADDR, 0n),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
kind: "a V4 step whose swap action and SETTLE_ALL cannot be read",
|
||||||
|
actions: [
|
||||||
|
V4_SWAP_EXACT_IN_SINGLE_ID,
|
||||||
|
V4_SETTLE_ALL_ID,
|
||||||
|
V4_TAKE_ALL_ID,
|
||||||
|
],
|
||||||
|
params: [
|
||||||
|
UNREAD_SWAP,
|
||||||
|
dirtyWord(encodeV4SettleAll(WETH_ADDR, ONE_WETH), 0),
|
||||||
|
encodeV4TakeAll(USDC_ADDR, 0n),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
])("a V3 step of 600 USDT, then $kind", ({ actions, params }) => {
|
||||||
|
const result = decodeSteps(
|
||||||
|
[0x00, 0x10],
|
||||||
|
[V3_600, encodeV4Swap(new Uint8Array(actions), params)],
|
||||||
|
);
|
||||||
|
expect(linesBetweenTokens(result)).toEqual([
|
||||||
|
["Moved from the sender", UNDECODED],
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test.each([
|
||||||
|
{
|
||||||
|
kind: "a V3 step of 600 USDT, then a V4 step whose input cannot be read",
|
||||||
|
commands: [0x00, 0x10],
|
||||||
|
inputs: [
|
||||||
|
V3_600,
|
||||||
|
dirtyWord(
|
||||||
|
encodeV4Swap(new Uint8Array([V4_SETTLE_ID]), [
|
||||||
|
settle(USDT_ADDR, USDT_400, true),
|
||||||
|
]),
|
||||||
|
2,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
kind: "a V4 step with no swap action whose SETTLE_ALL cannot be read, then a V3 step of 600 USDT",
|
||||||
|
commands: [0x10, 0x00],
|
||||||
|
inputs: [
|
||||||
|
encodeV4Swap(
|
||||||
|
new Uint8Array([V4_TAKE_ID, V4_SETTLE_ALL_ID]),
|
||||||
|
[
|
||||||
|
take(USDT_ADDR, OTHER_ADDR, USDT_400),
|
||||||
|
dirtyWord(
|
||||||
|
encodeV4SettleAll(USDT_ADDR, USDT_400),
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
V3_600,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
kind: "a V3 step of 600 USDT, then a V2 step the user pays for whose input cannot be read",
|
||||||
|
commands: [0x00, 0x08],
|
||||||
|
inputs: [
|
||||||
|
V3_600,
|
||||||
|
dirtyWord(
|
||||||
|
encodeV2SwapExactIn(OTHER_ADDR, USDT_400, 1n, [
|
||||||
|
USDT_ADDR,
|
||||||
|
USDC_ADDR,
|
||||||
|
]),
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
kind: "a V3 step of 600 USDT, then a PERMIT2_TRANSFER_FROM whose input cannot be read",
|
||||||
|
commands: [0x00, 0x02],
|
||||||
|
inputs: [
|
||||||
|
V3_600,
|
||||||
|
dirtyWord(
|
||||||
|
coder.encode(
|
||||||
|
["address", "address", "uint160"],
|
||||||
|
[USDT_ADDR, OTHER_ADDR, USDT_400],
|
||||||
|
),
|
||||||
|
1,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
kind: "a V3 step of 600 USDT, then a PERMIT2_TRANSFER_FROM_BATCH",
|
||||||
|
commands: [0x00, 0x0d],
|
||||||
|
inputs: [
|
||||||
|
V3_600,
|
||||||
|
coder.encode(
|
||||||
|
["tuple(address,address,uint160,address)[]"],
|
||||||
|
[[[USER_ADDR, OTHER_ADDR, USDT_400, USDT_ADDR]]],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
])("$kind", ({ commands, inputs }) => {
|
||||||
|
const result = decodeSteps(commands, inputs);
|
||||||
|
expect(linesBetweenTokens(result)).toEqual([
|
||||||
|
["Moved from the sender", UNDECODED],
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
// A PERMIT2_PERMIT that sets the input side keeps its own amount: the
|
// A PERMIT2_PERMIT that sets the input side keeps its own amount: the
|
||||||
// steps it lets the router spend do not add to it.
|
// steps it lets the router spend do not add to it.
|
||||||
test("a split after a PERMIT2_PERMIT shows the permit's amount", () => {
|
test("a split after a PERMIT2_PERMIT shows the permit's amount", () => {
|
||||||
@@ -1446,6 +1752,49 @@ describe("uniswap decoder", () => {
|
|||||||
expect(detail(result, "Moved from the sender")).toBeUndefined();
|
expect(detail(result, "Moved from the sender")).toBeUndefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// A permit to another spender, or one the router lets fail (0x80 on
|
||||||
|
// the command byte), caps nothing the router takes, so what each
|
||||||
|
// later step takes of its token gets its own lines.
|
||||||
|
test.each([
|
||||||
|
{
|
||||||
|
kind: "to another spender",
|
||||||
|
command: 0x0a,
|
||||||
|
spender: OTHER_ADDR,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
kind: "that the router lets fail",
|
||||||
|
command: 0x8a,
|
||||||
|
spender: ROUTER_ADDR,
|
||||||
|
},
|
||||||
|
])(
|
||||||
|
"a split after a PERMIT2_PERMIT $kind shows what each step takes",
|
||||||
|
({ command, spender }) => {
|
||||||
|
const result = decodeSteps(
|
||||||
|
[command, 0x00, 0x08],
|
||||||
|
[
|
||||||
|
encodePermit2(USDT_ADDR, 1000000n, spender), // 1 USDT
|
||||||
|
V3_600,
|
||||||
|
encodeV2SwapExactIn(USER_ADDR, USDT_400, 390000000n, [
|
||||||
|
USDT_ADDR,
|
||||||
|
USDC_ADDR,
|
||||||
|
]),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
// The V3 step names its token as its path spells it, in
|
||||||
|
// lower case.
|
||||||
|
const moved = ["Moved to", "The swap step that spends it"];
|
||||||
|
expect(linesBetweenTokens(result)).toEqual([
|
||||||
|
["Amount", "1.0000 USDT"],
|
||||||
|
["Moved from the sender", "600.0000 USDT"],
|
||||||
|
["Token moved", "USDT (" + USDT_ADDR.toLowerCase() + ")"],
|
||||||
|
moved,
|
||||||
|
["Moved from the sender", "400.0000 USDT"],
|
||||||
|
["Token moved", "USDT (" + USDT_ADDR + ")"],
|
||||||
|
moved,
|
||||||
|
]);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
// A V2 exact-out step makes every amount the swap steps spend a maximum,
|
// A V2 exact-out step makes every amount the swap steps spend a maximum,
|
||||||
// the total included.
|
// the total included.
|
||||||
test("a split with a V2 exact-out step the user pays for shows the total as a maximum", () => {
|
test("a split with a V2 exact-out step the user pays for shows the total as a maximum", () => {
|
||||||
|
|||||||
@@ -160,8 +160,8 @@ function transfer(command, token, recipient, amount) {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
// PERMIT2_PERMIT (0x0a) of `amount` of `token`, for the router to spend.
|
// PERMIT2_PERMIT (0x0a) of `amount` of `token`, for `spender` to spend.
|
||||||
function permit2Permit(token, amount) {
|
function permit2Permit(token, amount, spender) {
|
||||||
return [
|
return [
|
||||||
0x0a,
|
0x0a,
|
||||||
coder.encode(
|
coder.encode(
|
||||||
@@ -169,7 +169,7 @@ function permit2Permit(token, amount) {
|
|||||||
"tuple(tuple(address,uint160,uint48,uint48),address,uint256)",
|
"tuple(tuple(address,uint160,uint48,uint48),address,uint256)",
|
||||||
"bytes",
|
"bytes",
|
||||||
],
|
],
|
||||||
[[[token, amount, 0, 0], ROUTER, 0n], "0x"],
|
[[[token, amount, 0, 0], spender, 0n], "0x"],
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@@ -796,10 +796,13 @@ describe("any token left in the router is not received", () => {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
left: tokenLine(WETH, "WETH"),
|
left: tokenLine(WETH, "WETH"),
|
||||||
spent: "1.0000 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(
|
||||||
@@ -812,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",
|
||||||
@@ -825,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",
|
||||||
@@ -878,14 +883,17 @@ 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, spent }) => {
|
])("$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
|
// A later swap step the user pays for spends `spent` of a token
|
||||||
// other than Token In (https://git.eeqj.de/sneak/AutistMask/issues/625).
|
// other than Token In.
|
||||||
...(spent
|
...(spent
|
||||||
? ["Moved from the sender", "Token moved", "Moved to"]
|
? ["Moved from the sender", "Token moved", "Moved to"]
|
||||||
: []),
|
: []),
|
||||||
@@ -899,6 +907,11 @@ describe("any token left in the router is not received", () => {
|
|||||||
if (spent) {
|
if (spent) {
|
||||||
expect(detail(result, "Moved from the sender").value).toBe(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");
|
||||||
@@ -2242,6 +2255,10 @@ describe("the allow-revert flag", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Only the routers from UniversalRouterV2 on let a PERMIT2_PERMIT fail.
|
// Only the routers from UniversalRouterV2 on let a PERMIT2_PERMIT fail.
|
||||||
|
// A permit to the router caps what the router takes of its token after
|
||||||
|
// it, unless the router lets it fail: then what the swap takes gets its
|
||||||
|
// own lines (https://git.eeqj.de/sneak/AutistMask/issues/625). The swap
|
||||||
|
// otherwise reads as without the flag.
|
||||||
test.each([
|
test.each([
|
||||||
{ router: "UniversalRouterV1", address: ROUTER_V1, mark: "" },
|
{ router: "UniversalRouterV1", address: ROUTER_V1, mark: "" },
|
||||||
{
|
{
|
||||||
@@ -2253,17 +2270,19 @@ describe("the allow-revert flag", () => {
|
|||||||
router: "UniversalRouterV2",
|
router: "UniversalRouterV2",
|
||||||
address: ROUTER,
|
address: ROUTER,
|
||||||
mark: " (allowed to fail)",
|
mark: " (allowed to fail)",
|
||||||
|
taken: "2000.0000 USDT",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
router: "UniversalRouterV2_1_2",
|
router: "UniversalRouterV2_1_2",
|
||||||
address: ROUTER_V2_1_2,
|
address: ROUTER_V2_1_2,
|
||||||
mark: " (allowed to fail)",
|
mark: " (allowed to fail)",
|
||||||
|
taken: "2000.0000 USDT",
|
||||||
},
|
},
|
||||||
])(
|
])(
|
||||||
"on $router, a PERMIT2_PERMIT with the flag is listed as 'Permit2 Permit$mark', and the swap reads as without the flag",
|
"on $router, a PERMIT2_PERMIT with the flag is listed as 'Permit2 Permit$mark'",
|
||||||
({ address, mark }) => {
|
({ address, mark, taken }) => {
|
||||||
const steps = [
|
const steps = [
|
||||||
permit2Permit(USDT, USDT_2000),
|
permit2Permit(USDT, USDT_2000, address),
|
||||||
swapToWeth(MSG_SENDER),
|
swapToWeth(MSG_SENDER),
|
||||||
];
|
];
|
||||||
const plain = decode(execute(...steps), USER, address);
|
const plain = decode(execute(...steps), USER, address);
|
||||||
@@ -2275,9 +2294,20 @@ describe("the allow-revert flag", () => {
|
|||||||
expect(detail(withFlag, "Steps").value).toBe(
|
expect(detail(withFlag, "Steps").value).toBe(
|
||||||
"Permit2 Permit" + mark + " → V3 Swap (Exact In)",
|
"Permit2 Permit" + mark + " → V3 Swap (Exact In)",
|
||||||
);
|
);
|
||||||
const withoutSteps = (r) =>
|
const takenLines = [
|
||||||
r.details.filter((d) => d.label !== "Steps");
|
"Moved from the sender",
|
||||||
expect(withoutSteps(withFlag)).toEqual(withoutSteps(plain));
|
"Token moved",
|
||||||
|
"Moved to",
|
||||||
|
];
|
||||||
|
const rest = (r) =>
|
||||||
|
r.details.filter(
|
||||||
|
(d) => d.label !== "Steps" && !takenLines.includes(d.label),
|
||||||
|
);
|
||||||
|
expect(rest(withFlag)).toEqual(rest(plain));
|
||||||
|
expect(detail(plain, "Moved from the sender")).toBeUndefined();
|
||||||
|
expect(detail(withFlag, "Moved from the sender")?.value).toBe(
|
||||||
|
taken,
|
||||||
|
);
|
||||||
expect(detail(withFlag, "Min. received").value).toBe("1.0000 WETH");
|
expect(detail(withFlag, "Min. received").value).toBe("1.0000 WETH");
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user