Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
41dfdc8701 |
@@ -3153,36 +3153,40 @@ Currently supported:
|
|||||||
included, stands as the `Amount` figure in place of the total only in the
|
included, stands as the `Amount` figure in place of the total only in the
|
||||||
plain shape of Uniswap's own routes: it is the only `PERMIT2_PERMIT`, its
|
plain shape of Uniswap's own routes: it is the only `PERMIT2_PERMIT`, its
|
||||||
spender is the router, the router may not let it fail, no step before it takes
|
spender is the router, the router may not let it fail, no step before it takes
|
||||||
its token or a token the decoder does not know, and no `PERMIT2_PERMIT_BATCH`
|
its token or a token the decoder does not know, and the decoder reads every
|
||||||
comes after it. Permit2 then lets the router take no more of the token than
|
step after it in full. Permit2 then lets the router take no more of the token
|
||||||
that amount, so the permit covers every take of it. In any other case the line
|
than that amount, so the permit covers every take of it. A step the decoder
|
||||||
gives the total, as with no permit. When the step that sets the input side
|
does not read in full may raise what Permit2 lets the router take, as a
|
||||||
spends a token other than the permit's, or one the decoder does not know, its
|
`PERMIT2_PERMIT_BATCH` can: a command the decoder does not read (listed at the
|
||||||
amount gets lines of its own and the total has no figure to start from: unless
|
end of this section), a step whose input it cannot read, in whole or in part,
|
||||||
the permit's amount stands there is no `Amount` line, and each take of the
|
and a step it fails on. In any other case the line gives the total, as with no
|
||||||
permit's token gets lines of its own. Each take the total does not count gives
|
permit. When the step that sets the input side spends a token other than the
|
||||||
three lines after the `Amount` line (after `Token In` when there is none): one
|
permit's, or one the decoder does not know, its amount gets lines of its own
|
||||||
of a token other than `Token In`; one of `Token In` after an amount that is
|
and the total has no figure to start from: unless the permit's amount stands
|
||||||
not a quantity (a V4 open delta, `ALREADY_PAID`, which keep their wording on
|
there is no `Amount` line, and each take of the permit's token gets lines of
|
||||||
the `Amount` line, or no stated amount, as of a V4 exact-out step); and one
|
its own. Each take the total does not count gives three lines after the
|
||||||
whose amount the decoder does not know. They come in step order, the step that
|
`Amount` line (after `Token In` when there is none): one of a token other than
|
||||||
sets the input side first when it has them: `Moved from the sender`, its
|
`Token In`; one of `Token In` after an amount that is not a quantity (a V4
|
||||||
amount, worded as the `Amount` line words one, or
|
open delta, `ALREADY_PAID`, which keep their wording on the `Amount` line, or
|
||||||
`Unknown (could not be decoded)` when the decoder does not know it;
|
no stated amount, as of a V4 exact-out step); and one whose amount the decoder
|
||||||
`Token moved`, giving the token's address as the `Token In` line does; and
|
does not know. They come in step order, the step that sets the input side
|
||||||
`Moved to`, which reads `The swap step that spends it`. A token the decoder
|
first when it has them: `Moved from the sender`, its amount, worded as the
|
||||||
does not know gives the `Moved from the sender` line alone. A step may also
|
`Amount` line words one, or `Unknown (could not be decoded)` when the decoder
|
||||||
take from the user's wallet what the decoder did not read: a V4 step, other
|
does not know it; `Token moved`, giving the token's address as the `Token In`
|
||||||
than the one that sets the input side, whose input or one of whose `SETTLE`s
|
line does; and `Moved to`, which reads `The swap step that spends it`. A token
|
||||||
or `SETTLE_ALL`s it cannot read, which the router still runs; a V2 or V3
|
the decoder does not know gives the `Moved from the sender` line alone. A step
|
||||||
exact-in or V2 exact-out step or a `PERMIT2_TRANSFER_FROM` whose input it
|
may also take from the user's wallet what the decoder did not read: a V4 step,
|
||||||
cannot read; and every `PERMIT2_TRANSFER_FROM_BATCH`. Each such step gives one
|
other than the one that sets the input side, whose input or one of whose
|
||||||
`Moved from the sender` line among those, reading
|
`SETTLE`s or `SETTLE_ALL`s it cannot read, which the router still runs; a V2
|
||||||
`Unknown (could not be decoded)`. That line, or any line for a token the
|
or V3 exact-in or V2 exact-out step or a `PERMIT2_TRANSFER_FROM` whose input
|
||||||
decoder does not know, leaves no `Amount` line, whose figure might be too low.
|
it cannot read; every `PERMIT2_TRANSFER_FROM_BATCH`; and, once the input side
|
||||||
Then each `PERMIT2_TRANSFER_FROM` but one into the router, in step order,
|
is set, every `V3_SWAP_EXACT_OUT`, which the decoder does not read, and every
|
||||||
gives three lines: `Moved from the sender`, the amount it moves, for example
|
step it fails on. Each such step gives one `Moved from the sender` line among
|
||||||
`1000000.0000 USDT`; `Token moved`, giving the token's address as the
|
those, reading `Unknown (could not be decoded)`. That line, or any line for a
|
||||||
|
token the decoder does not know, leaves no `Amount` line, whose figure might
|
||||||
|
be too low. Then each `PERMIT2_TRANSFER_FROM` but one into the router, in step
|
||||||
|
order, gives three lines: `Moved from the sender`, the amount it moves, for
|
||||||
|
example `1000000.0000 USDT`; `Token moved`, giving the token's address as the
|
||||||
`Token In` line does; and `Moved to`, which reads
|
`Token In` line does; and `Moved to`, which reads
|
||||||
`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
|
||||||
|
|||||||
+56
-16
@@ -1169,6 +1169,10 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
|
|||||||
// The last PERMIT2_PERMIT this decoder read, or null (see
|
// The last PERMIT2_PERMIT this decoder read, or null (see
|
||||||
// permitStands after the loop).
|
// permitStands after the loop).
|
||||||
let permit = null;
|
let permit = null;
|
||||||
|
// The index of each step this decoder did not read in full: a
|
||||||
|
// command it does not read, a step whose input it cannot read, in
|
||||||
|
// whole or in part, and one it fails on (see permitStands).
|
||||||
|
const stepsUnread = [];
|
||||||
let outputToken = null;
|
let outputToken = null;
|
||||||
let minOutput = null;
|
let minOutput = null;
|
||||||
let outputEstablished = false;
|
let outputEstablished = false;
|
||||||
@@ -1229,10 +1233,11 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
|
|||||||
// A step may take from the sender what this decoder did not read: a
|
// A step may take from the sender what this decoder did not read: a
|
||||||
// V4 step, 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
|
// exact-in or V2 exact-out step or PERMIT2_TRANSFER_FROM whose input
|
||||||
// PERMIT2_TRANSFER_FROM_BATCH. It is recorded with a null token and
|
// it cannot read; a PERMIT2_TRANSFER_FROM_BATCH; and, once the input
|
||||||
// amount: one line says so, and there is no Amount line, whose figure
|
// side is set, a V3_SWAP_EXACT_OUT or a step it fails on. It is
|
||||||
// may then be too low.
|
// recorded with a null token and amount: one line says so, and there
|
||||||
|
// is no Amount line, whose figure may then be too low.
|
||||||
const spentUnread = () => {
|
const spentUnread = () => {
|
||||||
userPaid.push({ token: null, amount: null });
|
userPaid.push({ token: null, amount: null });
|
||||||
};
|
};
|
||||||
@@ -1371,6 +1376,7 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
|
|||||||
hasSubPlan = true;
|
hasSubPlan = true;
|
||||||
commandNames[i] += " (its steps are not shown)";
|
commandNames[i] += " (its steps are not shown)";
|
||||||
payeeUnread = true;
|
payeeUnread = true;
|
||||||
|
stepsUnread.push(i);
|
||||||
} else if (command === "PERMIT2_PERMIT") {
|
} else if (command === "PERMIT2_PERMIT") {
|
||||||
const p = decodePermit2(inputs[i]);
|
const p = decodePermit2(inputs[i]);
|
||||||
if (p) {
|
if (p) {
|
||||||
@@ -1389,11 +1395,14 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
|
|||||||
allowedToFail,
|
allowedToFail,
|
||||||
beforeEveryTake: !tookBefore,
|
beforeEveryTake: !tookBefore,
|
||||||
};
|
};
|
||||||
|
} else {
|
||||||
|
stepsUnread.push(i);
|
||||||
}
|
}
|
||||||
} else if (command === "PERMIT2_TRANSFER_FROM") {
|
} else if (command === "PERMIT2_TRANSFER_FROM") {
|
||||||
const p = decodePermit2TransferFrom(inputs[i]);
|
const p = decodePermit2TransferFrom(inputs[i]);
|
||||||
if (!p) {
|
if (!p) {
|
||||||
payeeUnread = true;
|
payeeUnread = true;
|
||||||
|
stepsUnread.push(i);
|
||||||
spentUnread();
|
spentUnread();
|
||||||
} else if (
|
} else if (
|
||||||
commandTypeMask === 0x1f &&
|
commandTypeMask === 0x1f &&
|
||||||
@@ -1432,6 +1441,7 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
|
|||||||
// owner may be any address. It sets no side, and the
|
// owner may be any address. It sets no side, and the
|
||||||
// step says so.
|
// step says so.
|
||||||
commandNames[i] += " (could not be decoded)";
|
commandNames[i] += " (could not be decoded)";
|
||||||
|
stepsUnread.push(i);
|
||||||
} else if (b.owner.toLowerCase() !== sender) {
|
} else if (b.owner.toLowerCase() !== sender) {
|
||||||
// The router reads the owner as written (see
|
// The router reads the owner as written (see
|
||||||
// decodeBalanceCheck()), so address(1) and address(2)
|
// decodeBalanceCheck()), so address(1) and address(2)
|
||||||
@@ -1473,8 +1483,17 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
payeeUnread = true;
|
payeeUnread = true;
|
||||||
|
stepsUnread.push(i);
|
||||||
spentUnread();
|
spentUnread();
|
||||||
}
|
}
|
||||||
|
} else if (command === "V3_SWAP_EXACT_OUT") {
|
||||||
|
// This decoder does not read it
|
||||||
|
// (https://git.eeqj.de/sneak/AutistMask/issues/601). It
|
||||||
|
// may pay any address and take from the sender, so once
|
||||||
|
// the input side is set it leaves no Amount figure.
|
||||||
|
payeeUnread = true;
|
||||||
|
stepsUnread.push(i);
|
||||||
|
if (inputEstablished) spentUnread();
|
||||||
} else if (command === "V2_SWAP_EXACT_IN") {
|
} else if (command === "V2_SWAP_EXACT_IN") {
|
||||||
const s = decodeV2SwapExactIn(inputs[i]);
|
const s = decodeV2SwapExactIn(inputs[i]);
|
||||||
if (s) {
|
if (s) {
|
||||||
@@ -1492,6 +1511,7 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
payeeUnread = true;
|
payeeUnread = true;
|
||||||
|
stepsUnread.push(i);
|
||||||
spentUnread();
|
spentUnread();
|
||||||
}
|
}
|
||||||
} else if (command === "V2_SWAP_EXACT_OUT") {
|
} else if (command === "V2_SWAP_EXACT_OUT") {
|
||||||
@@ -1510,12 +1530,14 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
payeeUnread = true;
|
payeeUnread = true;
|
||||||
|
stepsUnread.push(i);
|
||||||
spentUnread();
|
spentUnread();
|
||||||
}
|
}
|
||||||
} else if (command === "PERMIT2_TRANSFER_FROM_BATCH") {
|
} else if (command === "PERMIT2_TRANSFER_FROM_BATCH") {
|
||||||
// It moves the sender's tokens to any addresses, and this
|
// It moves the sender's tokens to any addresses, and this
|
||||||
// decoder does not read it.
|
// decoder does not read it.
|
||||||
payeeUnread = true;
|
payeeUnread = true;
|
||||||
|
stepsUnread.push(i);
|
||||||
spentUnread();
|
spentUnread();
|
||||||
} else if (command === "SWEEP") {
|
} else if (command === "SWEEP") {
|
||||||
const s = decodeSweep(inputs[i]);
|
const s = decodeSweep(inputs[i]);
|
||||||
@@ -1526,6 +1548,7 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
payeeUnread = true;
|
payeeUnread = true;
|
||||||
|
stepsUnread.push(i);
|
||||||
}
|
}
|
||||||
} else if (
|
} else if (
|
||||||
command === "TRANSFER" ||
|
command === "TRANSFER" ||
|
||||||
@@ -1562,6 +1585,7 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
payeeUnread = true;
|
payeeUnread = true;
|
||||||
|
stepsUnread.push(i);
|
||||||
}
|
}
|
||||||
} else if (command === "ACROSS_V4_DEPOSIT_V3") {
|
} else if (command === "ACROSS_V4_DEPOSIT_V3") {
|
||||||
const a = decodeAcrossV4DepositV3(inputs[i]);
|
const a = decodeAcrossV4DepositV3(inputs[i]);
|
||||||
@@ -1585,6 +1609,7 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
payeeUnread = true;
|
payeeUnread = true;
|
||||||
|
stepsUnread.push(i);
|
||||||
}
|
}
|
||||||
} else if (command === "WRAP_ETH") {
|
} else if (command === "WRAP_ETH") {
|
||||||
const w = decodeWrapEth(inputs[i]);
|
const w = decodeWrapEth(inputs[i]);
|
||||||
@@ -1615,6 +1640,7 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
payeeUnread = true;
|
payeeUnread = true;
|
||||||
|
stepsUnread.push(i);
|
||||||
}
|
}
|
||||||
} else if (command === "V4_SWAP") {
|
} else if (command === "V4_SWAP") {
|
||||||
const v4 = decodeV4Swap(inputs[i]);
|
const v4 = decodeV4Swap(inputs[i]);
|
||||||
@@ -1634,6 +1660,14 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
|
|||||||
if (!v4 || v4.recipientUnread) {
|
if (!v4 || v4.recipientUnread) {
|
||||||
payeeUnread = true;
|
payeeUnread = true;
|
||||||
}
|
}
|
||||||
|
if (
|
||||||
|
!v4 ||
|
||||||
|
v4.recipientUnread ||
|
||||||
|
v4.settleUnread ||
|
||||||
|
v4.swapUndecoded
|
||||||
|
) {
|
||||||
|
stepsUnread.push(i);
|
||||||
|
}
|
||||||
if (v4 && v4.swapUndecoded) {
|
if (v4 && v4.swapUndecoded) {
|
||||||
// A swap action of this step went unread, so the step
|
// A swap action of this step went unread, so the step
|
||||||
// is marked.
|
// is marked.
|
||||||
@@ -1701,16 +1735,21 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
payeeUnread = true;
|
payeeUnread = true;
|
||||||
|
stepsUnread.push(i);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Any other step may pay an address this decoder does not
|
// Any other step may pay an address this decoder does not
|
||||||
// read: V3_SWAP_EXACT_OUT and the NFT commands among them,
|
// read: PERMIT2_PERMIT_BATCH and the NFT commands among
|
||||||
// and a type the release names no command for.
|
// them, and a type the release names no command for.
|
||||||
payeeUnread = true;
|
payeeUnread = true;
|
||||||
|
stepsUnread.push(i);
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
// Skip commands we can't decode. One may pay any address.
|
// Skip commands we can't decode. One may pay any address and
|
||||||
|
// take from the sender.
|
||||||
payeeUnread = true;
|
payeeUnread = true;
|
||||||
|
stepsUnread.push(i);
|
||||||
|
if (inputEstablished) spentUnread();
|
||||||
}
|
}
|
||||||
if (allowedToFail) {
|
if (allowedToFail) {
|
||||||
commandNames[i] += " (allowed to fail)";
|
commandNames[i] += " (allowed to fail)";
|
||||||
@@ -1731,12 +1770,15 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
|
|||||||
// the Amount figure in place of the total only in the plain shape of
|
// 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
|
// 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
|
// 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
|
// its token or one not known, and this decoder read every step after
|
||||||
// it. Permit2 then
|
// it in full (see stepsUnread). Permit2 then lets the router take no
|
||||||
// lets the router take no more of the token than that amount, so the
|
// more of the token than that amount, so the permit covers every take
|
||||||
// permit covers every take of it. A step this decoder does not read
|
// of it. A later step this decoder did not read may raise what Permit2
|
||||||
// that may take from the sender leaves no figure in any case, and an
|
// lets the router take, as a PERMIT2_PERMIT_BATCH can. A step this
|
||||||
// EXECUTE_SUB_PLAN step leaves both sides unknown (below).
|
// decoder does not read that may take from the sender leaves no
|
||||||
|
// figure, permit or not, when it comes after the step that sets the
|
||||||
|
// input side (see spentUnread()), and an EXECUTE_SUB_PLAN step leaves
|
||||||
|
// both sides unknown (below).
|
||||||
const stepCommands = Array.from(
|
const stepCommands = Array.from(
|
||||||
commandsBytes,
|
commandsBytes,
|
||||||
(b) => commands[b & commandTypeMask],
|
(b) => commands[b & commandTypeMask],
|
||||||
@@ -1747,9 +1789,7 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
|
|||||||
sameAddress(permit.spender, toAddress) &&
|
sameAddress(permit.spender, toAddress) &&
|
||||||
!permit.allowedToFail &&
|
!permit.allowedToFail &&
|
||||||
permit.beforeEveryTake &&
|
permit.beforeEveryTake &&
|
||||||
!stepCommands
|
!stepsUnread.some((step) => step > permit.step);
|
||||||
.slice(permit.step + 1)
|
|
||||||
.includes("PERMIT2_PERMIT_BATCH");
|
|
||||||
// What the steps take that the Amount line does not give, as
|
// 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
|
// { token, amount }. A null token is a step this decoder did not read
|
||||||
// (see spentUnread()), or a token it does not know.
|
// (see spentUnread()), or a token it does not know.
|
||||||
|
|||||||
@@ -1830,6 +1830,70 @@ describe("uniswap decoder", () => {
|
|||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Any other step this decoder does not read may do the same, for
|
||||||
|
// example through the hook of the pool a V4_INITIALIZE_POOL sets up.
|
||||||
|
test.each([
|
||||||
|
{
|
||||||
|
kind: "a V4_INITIALIZE_POOL",
|
||||||
|
command: 0x13,
|
||||||
|
input: coder.encode(
|
||||||
|
[POOL_KEY, "uint160"],
|
||||||
|
[
|
||||||
|
[USDC_ADDR, USDT_ADDR, 3000, 60, OTHER_ADDR],
|
||||||
|
79228162514264337593543950336n,
|
||||||
|
],
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
kind: "a SWEEP whose input cannot be read",
|
||||||
|
command: 0x04,
|
||||||
|
input: dirtyWord(
|
||||||
|
coder.encode(
|
||||||
|
["address", "address", "uint256"],
|
||||||
|
[USDC_ADDR, USER_ADDR, 0n],
|
||||||
|
),
|
||||||
|
1,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
])(
|
||||||
|
"a split after a PERMIT2_PERMIT, with $kind between its steps, shows the total the steps take",
|
||||||
|
({ command, input }) => {
|
||||||
|
const result = decodeSteps(
|
||||||
|
[0x0a, 0x00, command, 0x08],
|
||||||
|
[
|
||||||
|
encodePermit2(USDT_ADDR, USDT_600, ROUTER_ADDR),
|
||||||
|
V3_600,
|
||||||
|
input,
|
||||||
|
encodeV2SwapExactIn(USER_ADDR, 9400000000n, 1n, [
|
||||||
|
USDT_ADDR,
|
||||||
|
USDC_ADDR,
|
||||||
|
]),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
expect(linesBetweenTokens(result)).toEqual([
|
||||||
|
["Amount", "10000.0000 USDT"],
|
||||||
|
]);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
// This decoder does not read a V3_SWAP_EXACT_OUT, so what one takes
|
||||||
|
// after the step that sets the input side leaves no Amount figure.
|
||||||
|
test("a V3 exact-in of 600 USDT, then a V3_SWAP_EXACT_OUT the user pays for, shows no Amount figure", () => {
|
||||||
|
// Its input is (recipient, amountOut, amountInMax, path,
|
||||||
|
// payerIsUser), laid out as encodeV3SwapExactIn() writes one; an
|
||||||
|
// exact-out path lists the token bought first.
|
||||||
|
const exactOut = encodeV3SwapExactIn(
|
||||||
|
USER_ADDR,
|
||||||
|
390000000n,
|
||||||
|
USDT_400,
|
||||||
|
[USDC_ADDR, USDT_ADDR],
|
||||||
|
);
|
||||||
|
const result = decodeSteps([0x00, 0x01], [V3_600, exactOut]);
|
||||||
|
expect(linesBetweenTokens(result)).toEqual([
|
||||||
|
["Moved from the sender", UNDECODED],
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
// A later step's amount that cannot be added to the Amount line,
|
// A later step's amount that cannot be added to the Amount line,
|
||||||
// because the amount of the step that set the input side is not a
|
// because the amount of the step that set the input side is not a
|
||||||
// quantity, gets lines of its own.
|
// quantity, gets lines of its own.
|
||||||
|
|||||||
Reference in New Issue
Block a user