Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
427d99e0a0 |
@@ -3491,52 +3491,54 @@ Currently supported:
|
|||||||
which it does only in the plain shape of Uniswap's own routes: it comes before
|
which it does only in the plain shape of Uniswap's own routes: it comes before
|
||||||
the step that sets the input side, it is the only `PERMIT2_PERMIT` in the
|
the step that sets the input side, it is the only `PERMIT2_PERMIT` in the
|
||||||
transaction and there is no `PERMIT2_PERMIT_BATCH`, its spender is the router,
|
transaction and there is no `PERMIT2_PERMIT_BATCH`, its spender is the router,
|
||||||
the router may not let it fail, no step before it takes its token, a
|
its token is not the zero address, the router may not let it fail, no step
|
||||||
`PERMIT2_TRANSFER_FROM` of it included, or a token the decoder does not know,
|
before it takes its token, a `PERMIT2_TRANSFER_FROM` of it included, or a
|
||||||
and the decoder reads every step after it in full. Permit2 then lets the
|
token the decoder does not know, and the decoder reads every step after it in
|
||||||
router take no more of the token than that amount, so the permit covers every
|
full. Permit2 then lets the router take no more of the token than that amount,
|
||||||
take of it. A step before the permit takes under whatever allowance the router
|
so the permit covers every take of it. Permit2 has no allowance of ETH, so a
|
||||||
already had, so it may take more than the permit's amount, which would then
|
permit on the zero address covers no take of ETH. A step before the permit
|
||||||
read lower than what leaves the user's wallet: a `PERMIT2_TRANSFER_FROM` of
|
takes under whatever allowance the router already had, so it may take more
|
||||||
2,000 USDT into the router, then a permit of 600 USDT to the router and a V3
|
than the permit's amount, which would then read lower than what leaves the
|
||||||
exact-in of 2,000 USDT paid from the router, read `Amount 2000.0000 USDT`. A
|
user's wallet: a `PERMIT2_TRANSFER_FROM` of 2,000 USDT into the router, then a
|
||||||
later permit or batch may raise what Permit2 lets the router take, and so may
|
permit of 600 USDT to the router and a V3 exact-in of 2,000 USDT paid from the
|
||||||
a step the decoder does not read in full: a command the decoder does not read
|
router, read `Amount 2000.0000 USDT`. A later permit or batch may raise what
|
||||||
(listed at the end of this section), a step whose input it cannot read, in
|
Permit2 lets the router take, and so may a step the decoder does not read in
|
||||||
whole or in part, and a step it fails on. Every other `PERMIT2_PERMIT`, before
|
full: a command the decoder does not read (listed at the end of this section),
|
||||||
the step that sets the input side or after it, leaves `Token In` and the
|
a step whose input it cannot read, in whole or in part, and a step it fails
|
||||||
`Amount` line as they read with no permit, unless it lets a spender other than
|
on. Every other `PERMIT2_PERMIT`, before the step that sets the input side or
|
||||||
the router take `Token In` (see below): a permit of 5,000 DAI to another
|
after it, leaves `Token In` and the `Amount` line as they read with no permit,
|
||||||
address and then a V3 exact-in of 600 USDT read `Swap USDT → USDC` with
|
unless it lets a spender other than the router take `Token In` (see below): a
|
||||||
`Amount 600.0000 USDT`, and so do that exact-in and then a permit of 5,000 DAI
|
permit of 5,000 DAI to another address and then a V3 exact-in of 600 USDT read
|
||||||
to the router. When a permit's amount stands and the step that sets the input
|
`Swap USDT → USDC` with `Amount 600.0000 USDT`, and so do that exact-in and
|
||||||
side spends a token other than the permit's, or one the decoder does not know,
|
then a permit of 5,000 DAI to the router. When a permit's amount stands and
|
||||||
that step's amount gets lines of its own, unless it states no amount and the
|
the step that sets the input side spends a token other than the permit's, or
|
||||||
user pays it none of its input token. Each take the total does not count gives
|
one the decoder does not know, that step's amount gets lines of its own,
|
||||||
three lines after the `Amount` line (after `Token In` when there is none): one
|
unless it states no amount and the user pays it none of its input token. Each
|
||||||
of a token other than `Token In`; one of `Token In` after an amount that is
|
take the total does not count gives three lines after the `Amount` line (after
|
||||||
not a quantity (a V4 open delta, `ALREADY_PAID`, which keep their wording on
|
`Token In` when there is none): one of a token other than `Token In`; one of
|
||||||
the `Amount` line, or no stated amount, as of a V4 exact-out step); and one
|
`Token In` after an amount that is not a quantity (a V4 open delta,
|
||||||
whose amount the decoder does not know. When there is no `Amount` line, each
|
`ALREADY_PAID`, which keep their wording on the `Amount` line, or no stated
|
||||||
take the total counts gets them too, so that every amount the decoder reads is
|
amount, as of a V4 exact-out step); and one whose amount the decoder does not
|
||||||
shown: the amount the step that sets the input side states, when it is a
|
know. When there is no `Amount` line, each take the total counts gets them
|
||||||
quantity and the user pays that step (a V2 or V3 step whose `payerIsUser` is
|
too, so that every amount the decoder reads is shown: the amount the step that
|
||||||
true, a V4 step the user pays some of its input token, or a `WRAP_ETH`, whose
|
sets the input side states, when it is a quantity and the user pays that step
|
||||||
ETH comes from the transaction's value), and each later take of `Token In`
|
(a V2 or V3 step whose `payerIsUser` is true, a V4 step the user pays some of
|
||||||
added to it. A step that sets the input side and is paid from the router takes
|
its input token, or a `WRAP_ETH`, whose ETH comes from the transaction's
|
||||||
nothing from the user's wallet and gets none. A `PERMIT2_PERMIT` the decoder
|
value), and each later take of `Token In` added to it. A step that sets the
|
||||||
cannot read, then V3 exact-ins of 600 USDT and 400 DAI paid by the user, give
|
input side and is paid from the router takes nothing from the user's wallet
|
||||||
lines for both amounts. When the step that sets the input side states no
|
and gets none. A `PERMIT2_PERMIT` the decoder cannot read, then V3 exact-ins
|
||||||
amount, the user pays it some of its input token, and there is no `Amount`
|
of 600 USDT and 400 DAI paid by the user, give lines for both amounts. When
|
||||||
line, that step gets them too whenever another take's lines give a figure, or
|
the step that sets the input side states no amount, the user pays it some of
|
||||||
a `PERMIT2_TRANSFER_FROM` of its token gives one, on the lines below or, into
|
its input token, and there is no `Amount` line, that step gets them too
|
||||||
the router, on its `Paid to` line, so that figure cannot read as all that
|
whenever another take's lines give a figure, or a `PERMIT2_TRANSFER_FROM` of
|
||||||
leaves. When it states no amount and the user pays it none of its input token,
|
its token gives one, on the lines below or, into the router, on its `Paid to`
|
||||||
as when the router pays it, that step takes none of that token from the user's
|
line, so that figure cannot read as all that leaves. When it states no amount
|
||||||
wallet and never gets them, with a permit or without. They come in step order,
|
and the user pays it none of its input token, as when the router pays it, that
|
||||||
except that the step that sets the input side comes first when it has them,
|
step takes none of that token from the user's wallet and never gets them, with
|
||||||
followed, when there is no `Amount` line, by the other takes the total counts:
|
a permit or without. They come in step order, except that the step that sets
|
||||||
`Moved from the sender`, its amount, worded as the `Amount` line words one, or
|
the input side comes first when it has them, followed, when there is no
|
||||||
|
`Amount` line, by the other takes the total counts: `Moved from the sender`,
|
||||||
|
its amount, worded as the `Amount` line words one, or
|
||||||
`Unknown (could not be decoded)` when the decoder does not know it;
|
`Unknown (could not be decoded)` when the decoder does not know it;
|
||||||
`Token moved`, giving the token's address as the `Token In` line does; and
|
`Token moved`, giving the token's address as the `Token In` line does; and
|
||||||
`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 token the decoder
|
||||||
@@ -3569,30 +3571,32 @@ Currently supported:
|
|||||||
amount stands gives none: the `Amount` line gives its amount, `Token In` its
|
amount stands gives none: the `Amount` line gives its amount, `Token In` its
|
||||||
token, and its spender is the router. Each allowance a `PERMIT2_PERMIT_BATCH`
|
token, and its spender is the router. Each allowance a `PERMIT2_PERMIT_BATCH`
|
||||||
grants, one for each token it names, all for its one spender, gives the same
|
grants, one for each token it names, all for its one spender, gives the same
|
||||||
three lines, in step order with those of each `PERMIT2_PERMIT`. A
|
three lines, in step order with those of each `PERMIT2_PERMIT`. For an
|
||||||
`PERMIT2_PERMIT` or `PERMIT2_PERMIT_BATCH` whose input the decoder cannot
|
allowance of the zero address, `Permit2 allowance` gives the amount in base
|
||||||
read, such as one whose token or spender word has a nonzero top byte, may let
|
units and `Allowance token` gives the zero address. A `PERMIT2_PERMIT` or
|
||||||
any spender take any amount of any token: it gives one `Permit2 allowance`
|
`PERMIT2_PERMIT_BATCH` whose input the decoder cannot read, such as one whose
|
||||||
line alone, reading `Unknown (could not be decoded)`. These lines change no
|
token or spender word has a nonzero top byte, may let any spender take any
|
||||||
`Paid to` line: a permit or batch pays nobody. But code that a step runs, such
|
amount of any token: it gives one `Permit2 allowance` line alone, reading
|
||||||
as a V4 pool's hook or a token's own code, can call a spender other than the
|
`Unknown (could not be decoded)`. These lines change no `Paid to` line: a
|
||||||
router, which can then take up to its allowance from the user's wallet before
|
permit or batch pays nobody. But code that a step runs, such as a V4 pool's
|
||||||
the transaction ends. So an allowance to any spender but the router, or a
|
hook or a token's own code, can call a spender other than the router, which
|
||||||
permit or batch the decoder cannot read, makes the minimum read
|
can then take up to its allowance from the user's wallet before the
|
||||||
`Min. paid out`, and when its token is `Token In`, or the decoder does not
|
transaction ends. So an allowance to any spender but the router, or a permit
|
||||||
know its token or `Token In`, it leaves no `Amount` line, whose figure might
|
or batch the decoder cannot read, makes the minimum read `Min. paid out`, and
|
||||||
be too low: a permit of 5,000 USDT to another address and then a V3 exact-in
|
when its token is `Token In`, or the decoder does not know its token or
|
||||||
of 600 USDT read with no `Amount` line, and the 600 USDT gets lines of its
|
`Token In`, it leaves no `Amount` line, whose figure might be too low: a
|
||||||
own. A step goes unread when its recipient does not decode as an address, when
|
permit of 5,000 USDT to another address and then a V3 exact-in of 600 USDT
|
||||||
it is a V3 swap step whose path is shorter than one token address or has bytes
|
read with no `Amount` line, and the 600 USDT gets lines of its own. A step
|
||||||
after its last hop, or when it is a command the decoder does not read: every
|
goes unread when its recipient does not decode as an address, when it is a V3
|
||||||
command other than `PERMIT2_PERMIT`, `PERMIT2_PERMIT_BATCH`,
|
swap step whose path is shorter than one token address or has bytes after its
|
||||||
`PERMIT2_TRANSFER_FROM`, `V2_SWAP_EXACT_IN`, `V2_SWAP_EXACT_OUT`,
|
last hop, or when it is a command the decoder does not read: every command
|
||||||
`V3_SWAP_EXACT_IN`, `V3_SWAP_EXACT_OUT`, `V4_SWAP`, `SWEEP`, `TRANSFER`,
|
other than `PERMIT2_PERMIT`, `PERMIT2_PERMIT_BATCH`, `PERMIT2_TRANSFER_FROM`,
|
||||||
`PAY_PORTION`, `PAY_PORTION_FULL_PRECISION`, `ACROSS_V4_DEPOSIT_V3`,
|
`V2_SWAP_EXACT_IN`, `V2_SWAP_EXACT_OUT`, `V3_SWAP_EXACT_IN`,
|
||||||
`WRAP_ETH`, `UNWRAP_WETH` and `BALANCE_CHECK_ERC20`
|
`V3_SWAP_EXACT_OUT`, `V4_SWAP`, `SWEEP`, `TRANSFER`, `PAY_PORTION`,
|
||||||
(`PERMIT2_TRANSFER_FROM_BATCH`, `EXECUTE_SUB_PLAN` and the NFT commands among
|
`PAY_PORTION_FULL_PRECISION`, `ACROSS_V4_DEPOSIT_V3`, `WRAP_ETH`,
|
||||||
them), or a type its release names no command for.
|
`UNWRAP_WETH` and `BALANCE_CHECK_ERC20` (`PERMIT2_TRANSFER_FROM_BATCH`,
|
||||||
|
`EXECUTE_SUB_PLAN` and the NFT commands among them), or a type its release
|
||||||
|
names no command for.
|
||||||
|
|
||||||
### Non-Goals Forever
|
### Non-Goals Forever
|
||||||
|
|
||||||
|
|||||||
+41
-17
@@ -2023,14 +2023,16 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
|
|||||||
// plain shape of Uniswap's own routes: it comes before the step that
|
// plain shape of Uniswap's own routes: it comes before the step that
|
||||||
// set the input side, it is the only PERMIT2_PERMIT in the
|
// set the input side, it is the only PERMIT2_PERMIT in the
|
||||||
// transaction and there is no PERMIT2_PERMIT_BATCH, its spender is
|
// transaction and there is no PERMIT2_PERMIT_BATCH, its spender is
|
||||||
// the router, the router may not let it fail, no step before it took
|
// the router, its token is not the zero address, the router may not
|
||||||
// its token or one not known (a PERMIT2_TRANSFER_FROM of its token
|
// let it fail, no step before it took its token or one not known (a
|
||||||
// included), and this decoder read every step after it in full (see
|
// PERMIT2_TRANSFER_FROM of its token included), and this decoder read
|
||||||
// stepsUnread). Permit2 then lets the router take no more of the token
|
// every step after it in full (see stepsUnread). Permit2 then lets the
|
||||||
// than that amount, so the permit covers every take of it. A step
|
// router take no more of the token than that amount, so the permit
|
||||||
// before it takes under whatever allowance the router already had, so
|
// covers every take of it. Permit2 has no allowance of ETH, so a
|
||||||
// it may take more. A later permit or batch, or a later step this
|
// permit on the zero address covers no take of ETH. A step before it
|
||||||
// decoder did not read, may raise what Permit2 lets the router take.
|
// takes under whatever allowance the router already had, so it may
|
||||||
|
// take more. A later permit or batch, or a later step this decoder
|
||||||
|
// did not read, may raise what Permit2 lets the router take.
|
||||||
// Every other PERMIT2_PERMIT leaves Token In and the Amount line as
|
// Every other PERMIT2_PERMIT leaves Token In and the Amount line as
|
||||||
// they read with no permit, and gets lines of its own below. A step
|
// they read with no permit, and gets lines of its own below. A step
|
||||||
// that may take from the sender what this decoder did not read leaves
|
// that may take from the sender what this decoder did not read leaves
|
||||||
@@ -2047,6 +2049,7 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
|
|||||||
stepCommands.filter((c) => c === "PERMIT2_PERMIT").length === 1 &&
|
stepCommands.filter((c) => c === "PERMIT2_PERMIT").length === 1 &&
|
||||||
!stepCommands.includes("PERMIT2_PERMIT_BATCH") &&
|
!stepCommands.includes("PERMIT2_PERMIT_BATCH") &&
|
||||||
sameAddress(permit.spender, toAddress) &&
|
sameAddress(permit.spender, toAddress) &&
|
||||||
|
permit.token !== "0x0000000000000000000000000000000000000000" &&
|
||||||
!permit.allowedToFail &&
|
!permit.allowedToFail &&
|
||||||
permit.beforeEveryTake &&
|
permit.beforeEveryTake &&
|
||||||
!stepsUnread.some((step) => step > permit.step);
|
!stepsUnread.some((step) => step > permit.step);
|
||||||
@@ -2315,15 +2318,36 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
|
|||||||
});
|
});
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
details.push({
|
// Permit2 has no allowance of ETH. An allowance of the zero
|
||||||
label: "Permit2 allowance",
|
// address reads as one of a token nothing knows: its amount in
|
||||||
value:
|
// base units, and the zero address as its token.
|
||||||
p.amount >= MAX_UINT160
|
if (p.token === "0x0000000000000000000000000000000000000000") {
|
||||||
? "Unlimited"
|
details.push({
|
||||||
: amountText(p.amount, tokenInfo(p.token, sources))
|
label: "Permit2 allowance",
|
||||||
.display,
|
value:
|
||||||
});
|
p.amount >= MAX_UINT160
|
||||||
details.push(tokenMovedLine("Allowance token", p.token, sources));
|
? "Unlimited"
|
||||||
|
: unknownDecimalsAmount(p.amount),
|
||||||
|
});
|
||||||
|
details.push({
|
||||||
|
label: "Allowance token",
|
||||||
|
value: p.token,
|
||||||
|
address: p.token,
|
||||||
|
isToken: true,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
details.push({
|
||||||
|
label: "Permit2 allowance",
|
||||||
|
value:
|
||||||
|
p.amount >= MAX_UINT160
|
||||||
|
? "Unlimited"
|
||||||
|
: amountText(p.amount, tokenInfo(p.token, sources))
|
||||||
|
.display,
|
||||||
|
});
|
||||||
|
details.push(
|
||||||
|
tokenMovedLine("Allowance token", p.token, sources),
|
||||||
|
);
|
||||||
|
}
|
||||||
details.push({
|
details.push({
|
||||||
label: "Allowance spender",
|
label: "Allowance spender",
|
||||||
value: p.spender,
|
value: p.spender,
|
||||||
|
|||||||
@@ -3310,6 +3310,98 @@ describe("uniswap decoder", () => {
|
|||||||
|
|
||||||
expect(detail(result, "Amount").value).toBe("Up to 1000.0000 USDT");
|
expect(detail(result, "Amount").value).toBe("Up to 1000.0000 USDT");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// https://git.eeqj.de/sneak/AutistMask/issues/660 — Permit2 has no
|
||||||
|
// allowance of ETH, so a permit on the zero address reads as one on a
|
||||||
|
// token nothing knows. On next at 53551c6 such a permit's Allowance
|
||||||
|
// token line read ETH. A permit of 1 wei of the zero address before a
|
||||||
|
// V4 step of 5 ETH made the Amount line read 0.000000000000000001 ETH.
|
||||||
|
describe("a permit on the zero address", () => {
|
||||||
|
const FIVE_ETH = 5000000000000000000n;
|
||||||
|
// The lines of a permit of `amount` base units of the zero
|
||||||
|
// address for the router.
|
||||||
|
const zeroAddressLines = (amount) => [
|
||||||
|
[
|
||||||
|
"Permit2 allowance",
|
||||||
|
amount + " base units (decimals unknown)",
|
||||||
|
],
|
||||||
|
["Allowance token", ZERO_ADDR],
|
||||||
|
["Allowance spender", getAddress(ROUTER_ADDR)],
|
||||||
|
];
|
||||||
|
|
||||||
|
test("a PERMIT2_PERMIT on the zero address names the address", () => {
|
||||||
|
const result = decodeSteps(
|
||||||
|
[0x00, 0x0a],
|
||||||
|
[
|
||||||
|
V3_600,
|
||||||
|
encodePermit2(
|
||||||
|
ZERO_ADDR,
|
||||||
|
1000000000000000000n,
|
||||||
|
ROUTER_ADDR,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
expect(linesBetweenTokens(result)).toEqual([
|
||||||
|
["Amount", "600.0000 USDT"],
|
||||||
|
...zeroAddressLines("1000000000000000000"),
|
||||||
|
]);
|
||||||
|
expect(detail(result, "Allowance token").address).toBe(
|
||||||
|
ZERO_ADDR,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("a PERMIT2_PERMIT_BATCH entry on the zero address names the address", () => {
|
||||||
|
const result = decodeSteps(
|
||||||
|
[0x03, 0x00],
|
||||||
|
[
|
||||||
|
encodePermit2Batch(
|
||||||
|
[
|
||||||
|
[USDT_ADDR, USDT_600],
|
||||||
|
[ZERO_ADDR, 1000000000000000000n],
|
||||||
|
],
|
||||||
|
ROUTER_ADDR,
|
||||||
|
),
|
||||||
|
V3_600,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
expect(linesBetweenTokens(result)).toEqual([
|
||||||
|
["Amount", "600.0000 USDT"],
|
||||||
|
...USDT_600_LINES,
|
||||||
|
...zeroAddressLines("1000000000000000000"),
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("a PERMIT2_PERMIT of 1 wei of the zero address, then a V4 step of 5 ETH the user settles, shows the 5 ETH", () => {
|
||||||
|
const result = decodeSteps(
|
||||||
|
[0x0a, 0x10],
|
||||||
|
[
|
||||||
|
encodePermit2(ZERO_ADDR, 1n, ROUTER_ADDR),
|
||||||
|
encodeV4Swap(
|
||||||
|
new Uint8Array([
|
||||||
|
V4_SWAP_EXACT_IN_SINGLE_ID,
|
||||||
|
V4_SETTLE_ID,
|
||||||
|
V4_TAKE_ALL_ID,
|
||||||
|
]),
|
||||||
|
[
|
||||||
|
encodeV4ExactInSingle(
|
||||||
|
ZERO_ADDR,
|
||||||
|
USDC_ADDR,
|
||||||
|
FIVE_ETH,
|
||||||
|
1n,
|
||||||
|
),
|
||||||
|
settle(ZERO_ADDR, FIVE_ETH, true),
|
||||||
|
encodeV4TakeAll(USDC_ADDR, 1n),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
expect(detail(result, "Token In").value).toBe("ETH (native)");
|
||||||
|
expect(linesBetweenTokens(result)).toEqual([
|
||||||
|
["Amount", "5.0000 ETH"],
|
||||||
|
...zeroAddressLines("1"),
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// https://git.eeqj.de/sneak/AutistMask/issues/359 — the output half, in
|
// https://git.eeqj.de/sneak/AutistMask/issues/359 — the output half, in
|
||||||
|
|||||||
Reference in New Issue
Block a user