Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
08d760d195 |
@@ -2368,11 +2368,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 swap step spends that the `Amount` line does not give,
|
amounts, what each swap step the user pays for spends of a token other
|
||||||
what each `PERMIT2_TRANSFER_FROM` moves and where to, steps, deadline, and
|
than `Token In`, what each `PERMIT2_TRANSFER_FROM` moves and where to,
|
||||||
unless a swap pays only the From address, a `Paid to` line for each other
|
steps, deadline, and unless a swap pays only the From address, a `Paid to`
|
||||||
address it pays, or saying that a token stays in the router or who it pays
|
line for each other address it pays, or saying that a token stays in the
|
||||||
is unknown (see Transaction Decoding)
|
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
|
||||||
@@ -3125,61 +3125,48 @@ 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`. The `Amount` line gives the total the steps
|
minimum reads `Min. paid out`. The `Amount` line gives the amount of the step
|
||||||
take of `Token In`, from the step that sets the input side on: the first V2 or
|
that sets the input side: the last `PERMIT2_PERMIT` step if there is one,
|
||||||
V3 exact-in, V2 exact-out, `WRAP_ETH` or V4 swap step that names an input
|
otherwise the first V2 or V3 exact-in, V2 exact-out, `WRAP_ETH` or V4 swap
|
||||||
token or amount. A V4 step with a swap action sets the input side when no
|
step that names an input token or amount. A V4 step with a swap action sets
|
||||||
earlier step set it, even when it names neither (see Display Consistency).
|
the input side when no earlier step set it, even when it names neither (see
|
||||||
That step counts its own amount, whoever pays it: a V2 exact-out step its
|
Display Consistency). A V2 exact-out step gives its `amountInMax`, and a V4
|
||||||
`amountInMax`, and a V4 swap step the `amountIn` of the swap action that
|
swap step the `amountIn` of the swap action that spends its input currency.
|
||||||
spends its input currency. Each later V2 or V3 exact-in or V2 exact-out step
|
Each later V2 or V3 exact-in or V2 exact-out step whose `payerIsUser` is true
|
||||||
whose `payerIsUser` is true takes its amount, found in the same way, from the
|
also takes its amount, found in the same way, from the user's wallet. So does
|
||||||
user's wallet. So does every V4 step but the one that sets the input side,
|
every V4 step but the one that sets the input side, with a swap action or
|
||||||
with a swap action or without: it takes what its `SETTLE`s paid by the user
|
without: it takes what its `SETTLE`s paid by the user (`payerIsUser` true) and
|
||||||
(`payerIsUser` true) and its `SETTLE_ALL`s pay, for each token on its own. A
|
its `SETTLE_ALL`s pay, for each token on its own. A `SETTLE` of any amount but
|
||||||
`SETTLE` of any amount but `OPEN_DELTA` pays that amount, and a `SETTLE` of
|
`OPEN_DELTA` pays that amount, and a `SETTLE` of `OPEN_DELTA` or a
|
||||||
`OPEN_DELTA` or a `SETTLE_ALL` pays what the step owes, as above. A later step
|
`SETTLE_ALL` pays what the step owes, as above. What a step takes of
|
||||||
paid from the router spends what the router holds and takes nothing, and so
|
`Token In` is added to the line's, unless a `PERMIT2_PERMIT` set the input
|
||||||
does a V2 exact-in whose `amountIn` is `ALREADY_PAID`. A later take of
|
side, whose amount the line keeps. A permit whose spender is the router and
|
||||||
`Token In` is added to the total while both are quantities: a V3 exact-in of
|
which the router does not let fail caps what the router may take of its token
|
||||||
|
after it; any other permit caps nothing the router takes. So a V3 exact-in of
|
||||||
600 USDT and a V2 exact-in of 400 USDT, both paid by the user, read
|
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
|
`1000.0000 USDT`. When either amount is not a quantity (a V4 open delta,
|
||||||
not known and there is no `Amount` line. `Token In` is the token of the last
|
`ALREADY_PAID`, or an amount the decoder does not know), the total is not
|
||||||
`PERMIT2_PERMIT` the decoder reads, when there is one. Its amount, `Unlimited`
|
known and there is no `Amount` line. A step paid from the router spends what
|
||||||
included, stands as the `Amount` figure in place of the total only in the
|
the router holds and adds nothing, and so does a V2 exact-in whose `amountIn`
|
||||||
plain shape of Uniswap's own routes: it is the only `PERMIT2_PERMIT`, its
|
is `ALREADY_PAID`, which takes nothing from the user. What a step takes of a
|
||||||
spender is the router, the router may not let it fail, no step before it takes
|
token other than `Token In`, or of `Token In` when the decoder does not know
|
||||||
its token or a token the decoder does not know, and no `PERMIT2_PERMIT_BATCH`
|
the amount or when a permit that caps nothing set the input side, gives three
|
||||||
comes after it. Permit2 then lets the router take no more of the token than
|
lines after the `Amount` line (after `Token In` when there is none), in step
|
||||||
that amount, so the permit covers every take of it. In any other case the line
|
order: `Moved from the sender`, its amount, worded as the `Amount` line words
|
||||||
gives the total, as with no permit. When the step that sets the input side
|
one, or `Unknown (could not be decoded)` when the decoder does not know it;
|
||||||
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. 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
|
`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
|
`Moved to`, which reads `The swap step that spends it`. A step may also take
|
||||||
does not know gives the `Moved from the sender` line alone. A step may also
|
from the user's wallet what the decoder did not read: a V4 step, other than
|
||||||
take from the user's wallet what the decoder did not read: a V4 step, other
|
the one that sets the input side, whose input or one of whose `SETTLE`s or
|
||||||
than the one that sets the input side, whose input or one of whose `SETTLE`s
|
`SETTLE_ALL`s it cannot read, which the router still runs; a V2 or V3 exact-in
|
||||||
or `SETTLE_ALL`s it cannot read, which the router still runs; a V2 or V3
|
or V2 exact-out step or a `PERMIT2_TRANSFER_FROM` whose input it cannot read;
|
||||||
exact-in or V2 exact-out step or a `PERMIT2_TRANSFER_FROM` whose input it
|
and every `PERMIT2_TRANSFER_FROM_BATCH`. Each such step gives one
|
||||||
cannot read; and every `PERMIT2_TRANSFER_FROM_BATCH`. Each such step gives one
|
`Moved from the sender` line, among those lines in step order, reading
|
||||||
`Moved from the sender` line among those, reading
|
`Unknown (could not be decoded)`, and there is then no `Amount` line, whose
|
||||||
`Unknown (could not be decoded)`. That line, or any line for a token the
|
figure might be too low. Then each `PERMIT2_TRANSFER_FROM` but one into the
|
||||||
decoder does not know, leaves no `Amount` line, whose figure might be too low.
|
router, in step order, gives three lines: `Moved from the sender`, the amount
|
||||||
Then each `PERMIT2_TRANSFER_FROM` but one into the router, in step order,
|
it moves, for example `1000000.0000 USDT`; `Token moved`, giving the token's
|
||||||
gives three lines: `Moved from the sender`, the amount it moves, for example
|
address as the `Token In` line does; and `Moved to`, which reads
|
||||||
`1000000.0000 USDT`; `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 `The sender` for `address(1)`
|
`The V2 pair the next swap spends` for a hop and `The sender` for `address(1)`
|
||||||
on every router but `UniversalRouterV1`, and otherwise gives the address. A
|
on every router but `UniversalRouterV1`, and otherwise gives the address. A
|
||||||
move into the router needs none of them, because its `Paid to` line and
|
move into the router needs none of them, because its `Paid to` line and
|
||||||
|
|||||||
+62
-104
@@ -357,11 +357,6 @@ 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
|
||||||
@@ -1166,9 +1161,8 @@ 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
|
let inputFromPermit = false;
|
||||||
// permitStands after the loop).
|
let permitCapsInput = false;
|
||||||
let permit = null;
|
|
||||||
let outputToken = null;
|
let outputToken = null;
|
||||||
let minOutput = null;
|
let minOutput = null;
|
||||||
let outputEstablished = false;
|
let outputEstablished = false;
|
||||||
@@ -1196,9 +1190,10 @@ 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. A PERMIT2_PERMIT sets no side here: after the
|
// from a different hop. Each later swap step the user pays for adds
|
||||||
// loop, the last one read names Token In, and the Amount line is
|
// what it spends, in addUserPaid(), the one place the total is formed,
|
||||||
// formed there, in one place (see permitStands).
|
// 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;
|
||||||
@@ -1216,25 +1211,53 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
|
|||||||
outputEstablished = true;
|
outputEstablished = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
// What each step but the one that sets the input side takes from the
|
// What steps take from the sender that the Amount line does not give,
|
||||||
// sender, as { token, amount }, in step order. After the loop each
|
// as { token, amount }, in step order (see addUserPaid()). A step that
|
||||||
// joins the Amount line or gets lines of its own (see permitStands).
|
// may take from the sender what this decoder did not read is here
|
||||||
const userPaid = [];
|
// with a null token (see spentUnread()).
|
||||||
// A swap step the user pays for (`payerIsUser` true) takes `amount`
|
const spentFromSender = [];
|
||||||
// of `token` from the sender. A V2 exact-in of ALREADY_PAID takes
|
// A swap step the user pays for (`payerIsUser` true), after the input
|
||||||
// nothing.
|
// side is set, takes `amount` of `token` from the sender. What it
|
||||||
|
// takes of the input side's token is added to the input side's
|
||||||
|
// amount, so the Amount line is what the steps spend of it together;
|
||||||
|
// when either is not a quantity the total is not known (null), and an
|
||||||
|
// amount not known also goes in `spentFromSender`, so the screen says
|
||||||
|
// so. What it takes of another token goes in `spentFromSender`. A V2
|
||||||
|
// 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) userPaid.push({ token, amount });
|
if (amount === ALREADY_PAID) return;
|
||||||
|
const sameToken =
|
||||||
|
present(inputToken) &&
|
||||||
|
token.toLowerCase() === inputToken.toLowerCase();
|
||||||
|
if (sameToken && inputFromPermit) {
|
||||||
|
if (!permitCapsInput) {
|
||||||
|
spentFromSender.push({ token, amount });
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!sameToken) {
|
||||||
|
spentFromSender.push({ token, amount });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
inputAmount =
|
||||||
|
typeof inputAmount === "bigint" && typeof amount === "bigint"
|
||||||
|
? inputAmount + amount
|
||||||
|
: null;
|
||||||
|
if (!present(amount)) {
|
||||||
|
spentFromSender.push({ token, amount });
|
||||||
|
}
|
||||||
};
|
};
|
||||||
// A step may take from the sender what this decoder did not read: a
|
// A step may take from the sender what this decoder did not read: a
|
||||||
// V4 step, other than the one that sets the input side, whose input
|
// 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
|
// 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
|
// swap step or PERMIT2_TRANSFER_FROM whose input it cannot read; or a
|
||||||
// PERMIT2_TRANSFER_FROM_BATCH. It is recorded with a null token and
|
// PERMIT2_TRANSFER_FROM_BATCH. One line says so, and there is no
|
||||||
// amount: one line says so, and there is no Amount line, whose figure
|
// Amount line, whose figure may then be too low.
|
||||||
// may then be too low.
|
|
||||||
const spentUnread = () => {
|
const spentUnread = () => {
|
||||||
userPaid.push({ token: null, amount: null });
|
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.
|
||||||
@@ -1373,22 +1396,19 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
|
|||||||
payeeUnread = true;
|
payeeUnread = true;
|
||||||
} 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
|
||||||
|
// input side an earlier hop established without breaking
|
||||||
|
// the invariant.
|
||||||
if (p) {
|
if (p) {
|
||||||
// Whether a step before it took its token, or may
|
setInput(p.token, p.amount);
|
||||||
// have: the step that set the input side or one in
|
inputFromPermit = true;
|
||||||
// `userPaid`, when its token is the permit's or not
|
// Permit2 lets the spender take no more of the token
|
||||||
// known.
|
// than the permit's amount. Only a permit to this
|
||||||
const mayBeItsToken = (token) =>
|
// router that the router does not let fail caps what
|
||||||
!present(token) || sameAddress(token, p.token);
|
// the later steps take (see addUserPaid()).
|
||||||
const tookBefore =
|
permitCapsInput =
|
||||||
(inputEstablished && mayBeItsToken(inputToken)) ||
|
!allowedToFail &&
|
||||||
userPaid.some((t) => mayBeItsToken(t.token));
|
p.spender.toLowerCase() === toAddress.toLowerCase();
|
||||||
permit = {
|
|
||||||
...p,
|
|
||||||
step: i,
|
|
||||||
allowedToFail,
|
|
||||||
beforeEveryTake: !tookBefore,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
} else if (command === "PERMIT2_TRANSFER_FROM") {
|
} else if (command === "PERMIT2_TRANSFER_FROM") {
|
||||||
const p = decodePermit2TransferFrom(inputs[i]);
|
const p = decodePermit2TransferFrom(inputs[i]);
|
||||||
@@ -1717,71 +1737,9 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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 no PERMIT2_PERMIT_BATCH comes after
|
|
||||||
// it. Permit2 then
|
|
||||||
// lets the router take no more of the token than that amount, so the
|
|
||||||
// permit covers every take of it. A step this decoder does not read
|
|
||||||
// that may take from the sender leaves no figure in any case, 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 &&
|
|
||||||
!stepCommands
|
|
||||||
.slice(permit.step + 1)
|
|
||||||
.includes("PERMIT2_PERMIT_BATCH");
|
|
||||||
// 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;
|
|
||||||
if (permit === null || sameAddress(inputToken, permit.token)) {
|
|
||||||
total = inputAmount;
|
|
||||||
} 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;
|
|
||||||
|
|
||||||
// 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 name Token In. What the other steps state is
|
// them would replace the input side. 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) {
|
||||||
@@ -1887,9 +1845,9 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
|
|||||||
return amountText(amount, info);
|
return amountText(amount, info);
|
||||||
};
|
};
|
||||||
|
|
||||||
// A null token in `spentFromSender`, a step this decoder did not read
|
// A step that may take from the sender what this decoder did not read,
|
||||||
// or a token it does not know, leaves no Amount figure, which may
|
// a null token in `spentFromSender`, leaves no Amount figure (see
|
||||||
// then be too low.
|
// spentUnread()).
|
||||||
if (
|
if (
|
||||||
present(inputAmount) &&
|
present(inputAmount) &&
|
||||||
spentFromSender.every((s) => present(s.token))
|
spentFromSender.every((s) => present(s.token))
|
||||||
|
|||||||
+23
-138
@@ -1720,9 +1720,8 @@ describe("uniswap decoder", () => {
|
|||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
// A PERMIT2_PERMIT in the plain shape of Uniswap's own routes keeps
|
// A PERMIT2_PERMIT that sets the input side keeps its own amount: the
|
||||||
// its own amount: Permit2 lets the router take no more of the token,
|
// steps it lets the router spend do not add to it.
|
||||||
// so the steps after it 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", () => {
|
||||||
const data = buildExecute(
|
const data = buildExecute(
|
||||||
solidityPacked(["uint8", "uint8", "uint8"], [0x0a, 0x00, 0x08]),
|
solidityPacked(["uint8", "uint8", "uint8"], [0x0a, 0x00, 0x08]),
|
||||||
@@ -1753,36 +1752,27 @@ describe("uniswap decoder", () => {
|
|||||||
expect(detail(result, "Moved from the sender")).toBeUndefined();
|
expect(detail(result, "Moved from the sender")).toBeUndefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Out of that shape the permit caps nothing the Amount line can rely
|
// A permit to another spender, or one the router lets fail (0x80 on
|
||||||
// on, so the line gives the total the steps take, as with no permit.
|
// the command byte), caps nothing the router takes, so what each
|
||||||
const ONE_USDT_PERMIT = (spender) =>
|
// later step takes of its token gets its own lines.
|
||||||
encodePermit2(USDT_ADDR, 1000000n, spender);
|
|
||||||
test.each([
|
test.each([
|
||||||
{
|
{
|
||||||
kind: "to another spender",
|
kind: "to another spender",
|
||||||
commands: [0x0a],
|
command: 0x0a,
|
||||||
permits: [ONE_USDT_PERMIT(OTHER_ADDR)],
|
spender: OTHER_ADDR,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
kind: "that the router lets fail",
|
kind: "that the router lets fail",
|
||||||
commands: [0x8a],
|
command: 0x8a,
|
||||||
permits: [ONE_USDT_PERMIT(ROUTER_ADDR)],
|
spender: ROUTER_ADDR,
|
||||||
},
|
|
||||||
{
|
|
||||||
kind: "followed by a second one",
|
|
||||||
commands: [0x0a, 0x0a],
|
|
||||||
permits: [
|
|
||||||
ONE_USDT_PERMIT(ROUTER_ADDR),
|
|
||||||
ONE_USDT_PERMIT(ROUTER_ADDR),
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
])(
|
])(
|
||||||
"a split after a PERMIT2_PERMIT $kind shows the total the steps take",
|
"a split after a PERMIT2_PERMIT $kind shows what each step takes",
|
||||||
({ commands, permits }) => {
|
({ command, spender }) => {
|
||||||
const result = decodeSteps(
|
const result = decodeSteps(
|
||||||
[...commands, 0x00, 0x08],
|
[command, 0x00, 0x08],
|
||||||
[
|
[
|
||||||
...permits,
|
encodePermit2(USDT_ADDR, 1000000n, spender), // 1 USDT
|
||||||
V3_600,
|
V3_600,
|
||||||
encodeV2SwapExactIn(USER_ADDR, USDT_400, 390000000n, [
|
encodeV2SwapExactIn(USER_ADDR, USDT_400, 390000000n, [
|
||||||
USDT_ADDR,
|
USDT_ADDR,
|
||||||
@@ -1790,126 +1780,21 @@ describe("uniswap decoder", () => {
|
|||||||
]),
|
]),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
// 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([
|
expect(linesBetweenTokens(result)).toEqual([
|
||||||
["Amount", "1000.0000 USDT"],
|
["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 PERMIT2_PERMIT_BATCH, which this decoder does not read, may raise
|
|
||||||
// what the router may take of the permit's token again.
|
|
||||||
test("a split after a PERMIT2_PERMIT and a PERMIT2_PERMIT_BATCH shows the total the steps take", () => {
|
|
||||||
const permitBatch = coder.encode(
|
|
||||||
[
|
|
||||||
"tuple(tuple(address,uint160,uint48,uint48)[],address,uint256)",
|
|
||||||
"bytes",
|
|
||||||
],
|
|
||||||
[
|
|
||||||
[
|
|
||||||
[[USDT_ADDR, 10000000000n, 0, 0]], // 10,000 USDT
|
|
||||||
ROUTER_ADDR,
|
|
||||||
9999999999,
|
|
||||||
],
|
|
||||||
"0x1234",
|
|
||||||
],
|
|
||||||
);
|
|
||||||
const result = decodeSteps(
|
|
||||||
[0x0a, 0x03, 0x00, 0x08],
|
|
||||||
[
|
|
||||||
encodePermit2(USDT_ADDR, USDT_600, ROUTER_ADDR),
|
|
||||||
permitBatch,
|
|
||||||
V3_600,
|
|
||||||
encodeV2SwapExactIn(USER_ADDR, 9400000000n, 1n, [
|
|
||||||
USDT_ADDR,
|
|
||||||
USDC_ADDR,
|
|
||||||
]),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
expect(linesBetweenTokens(result)).toEqual([
|
|
||||||
["Amount", "10000.0000 USDT"],
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
|
|
||||||
// A later step's amount that cannot be added to the Amount line,
|
|
||||||
// because the amount of the step that set the input side is not a
|
|
||||||
// quantity, gets lines of its own.
|
|
||||||
const V2_EXACT_OUT_400 = encodeV2SwapExactOut(
|
|
||||||
USER_ADDR,
|
|
||||||
390000000n,
|
|
||||||
USDT_400,
|
|
||||||
[USDT_ADDR, USDC_ADDR],
|
|
||||||
);
|
|
||||||
const UP_TO_400_USDT = [
|
|
||||||
["Moved from the sender", "Up to 400.0000 USDT"],
|
|
||||||
["Token moved", "USDT (" + USDT_ADDR + ")"],
|
|
||||||
["Moved to", "The swap step that spends it"],
|
|
||||||
];
|
|
||||||
test.each([
|
|
||||||
{
|
|
||||||
kind: "a V4 exact-out step with a SETTLE of OPEN_DELTA by the user",
|
|
||||||
commands: [0x10, 0x09],
|
|
||||||
first: encodeV4Swap(
|
|
||||||
new Uint8Array([
|
|
||||||
V4_SWAP_EXACT_OUT_SINGLE_ID,
|
|
||||||
V4_SETTLE_ID,
|
|
||||||
V4_TAKE_ALL_ID,
|
|
||||||
]),
|
|
||||||
[
|
|
||||||
encodeV4ExactOutSingle(
|
|
||||||
USDT_ADDR,
|
|
||||||
WETH_ADDR,
|
|
||||||
ONE_WETH,
|
|
||||||
USDT_600,
|
|
||||||
),
|
|
||||||
encodeV4Settle(USDT_ADDR),
|
|
||||||
encodeV4TakeAll(WETH_ADDR, 0n),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
lines: UP_TO_400_USDT,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
kind: "a V4 exact-in step of the open delta",
|
|
||||||
commands: [0x10, 0x09],
|
|
||||||
first: encodeV4Swap(
|
|
||||||
new Uint8Array([
|
|
||||||
V4_SWAP_EXACT_IN_SINGLE_ID,
|
|
||||||
V4_SETTLE_ID,
|
|
||||||
V4_TAKE_ALL_ID,
|
|
||||||
]),
|
|
||||||
[
|
|
||||||
encodeV4ExactInSingle(USDT_ADDR, USDC_ADDR, 0n, 1n),
|
|
||||||
encodeV4Settle(USDT_ADDR),
|
|
||||||
encodeV4TakeAll(USDC_ADDR, 0n),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
lines: [
|
|
||||||
["Amount", "All available (V4 open delta)"],
|
|
||||||
...UP_TO_400_USDT,
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
kind: "a V2 exact-in step of ALREADY_PAID",
|
|
||||||
commands: [0x08, 0x09],
|
|
||||||
first: encodeV2SwapExactIn(USER_ADDR, 0n, 1n, [
|
|
||||||
USDT_ADDR,
|
|
||||||
USDC_ADDR,
|
|
||||||
]),
|
|
||||||
lines: [
|
|
||||||
[
|
|
||||||
"Amount",
|
|
||||||
"Whatever an earlier step sent to the pair (V2 already paid)",
|
|
||||||
],
|
|
||||||
...UP_TO_400_USDT,
|
|
||||||
],
|
|
||||||
},
|
|
||||||
])(
|
|
||||||
"$kind, then a V2 exact-out of at most 400 USDT the user pays for, shows the 400 USDT",
|
|
||||||
({ commands, first, lines }) => {
|
|
||||||
const result = decodeSteps(commands, [first, V2_EXACT_OUT_400]);
|
|
||||||
expect(linesBetweenTokens(result)).toEqual(lines);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
// 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"],
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@@ -2255,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: "" },
|
||||||
{
|
{
|
||||||
@@ -2266,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);
|
||||||
@@ -2288,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