Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f8f20155ab | ||
|
|
070ad82338 |
@@ -3187,7 +3187,8 @@ Currently supported:
|
||||
step whose input it cannot read, in whole or in part, and a step it fails on.
|
||||
In any other case the line gives the total, as with no permit. When the step
|
||||
that sets the input side spends a token other than the permit's, or one the
|
||||
decoder does not know, its amount gets lines of its own and the total has no
|
||||
decoder does not know, its amount gets lines of its own, unless it states no
|
||||
amount and the user pays it none of its input token, 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
|
||||
@@ -3195,11 +3196,16 @@ Currently supported:
|
||||
`Token In` after an amount that is not a quantity (a V4 open delta,
|
||||
`ALREADY_PAID`, which keep their wording on the `Amount` line, or no stated
|
||||
amount, as of a V4 exact-out step); and one whose amount the decoder does not
|
||||
know. When the step that sets the input side states no amount and there is no
|
||||
`Amount` line, that step gets them too whenever another take's lines give a
|
||||
figure, so that figure cannot read as all that leaves. They come in step
|
||||
order, the step that sets the input side first when it has them:
|
||||
`Moved from the sender`, its amount, worded as the `Amount` line words one, or
|
||||
know. When the step that sets the input side states no amount, the user pays
|
||||
it some of its input token, and there is no `Amount` line, that step gets them
|
||||
too whenever another take's lines give a figure, or a `PERMIT2_TRANSFER_FROM`
|
||||
of its token gives one, on the lines below or, into the router, on its
|
||||
`Paid to` line, so that figure cannot read as all that leaves. When it states
|
||||
no amount and the user pays it none of its input token, as when the router
|
||||
pays it, that step takes none of that token from the user's wallet and never
|
||||
gets them, with a permit or without. They come in step order, the step that
|
||||
sets the input side first when it has them: `Moved from the sender`, its
|
||||
amount, worded as the `Amount` line words one, or
|
||||
`Unknown (could not be decoded)` when the decoder does not know it;
|
||||
`Token moved`, giving the token's address as the `Token In` line does; and
|
||||
`Moved to`, which reads `The swap step that spends it`. A token the decoder
|
||||
|
||||
+23
-7
@@ -1204,6 +1204,10 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
|
||||
let inputToken = null;
|
||||
let inputAmount = null;
|
||||
let inputEstablished = false;
|
||||
// Whether the user pays the V4 step that set the input side any of
|
||||
// its input currency (see decodeV4Swap()). Only a V4 step can set the
|
||||
// input side with no amount, the one case in which this is read.
|
||||
let inputPaidByUser = false;
|
||||
// The last PERMIT2_PERMIT this decoder read, or null (see
|
||||
// permitStands after the loop).
|
||||
let permit = null;
|
||||
@@ -1756,6 +1760,7 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
|
||||
// this decoder could not read may take anything: the
|
||||
// router still runs it.
|
||||
const setInputHere = inputEstablished && !inputSetBefore;
|
||||
if (setInputHere) inputPaidByUser = paidIn !== undefined;
|
||||
if (v4) {
|
||||
for (const p of v4.paidByUser) {
|
||||
if (setInputHere && p === paidIn) continue;
|
||||
@@ -1844,15 +1849,22 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
|
||||
// (see spentUnread()), or a token it does not know.
|
||||
const spentFromSender = [];
|
||||
let total = null;
|
||||
// Whether the step that set the input side counts as a take from the
|
||||
// sender, which may get lines of its own. The amount it stated counts
|
||||
// whoever pays it. Stating none, it counts only when the user pays it
|
||||
// some of its input token: paid from the router, it takes nothing
|
||||
// from the sender.
|
||||
const hasFirstTake =
|
||||
inputEstablished && (present(inputAmount) || inputPaidByUser);
|
||||
// The take of the step that set the input side, when the total
|
||||
// starts from it and it stated no amount (see below), or null.
|
||||
let firstTakeUnstated = null;
|
||||
if (permit === null || sameAddress(inputToken, permit.token)) {
|
||||
total = inputAmount;
|
||||
if (inputEstablished && !present(inputAmount)) {
|
||||
if (hasFirstTake && !present(inputAmount)) {
|
||||
firstTakeUnstated = { token: inputToken, amount: null };
|
||||
}
|
||||
} else if (inputEstablished) {
|
||||
} else if (hasFirstTake) {
|
||||
// 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 });
|
||||
@@ -1873,14 +1885,18 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
|
||||
if (ofTokenIn && !present(t.amount)) total = null;
|
||||
}
|
||||
inputAmount = permitStands ? permit.amount : total;
|
||||
// With no Amount figure, a later take's own figure could read as all
|
||||
// that leaves, so the step that set the input side, when it stated
|
||||
// no amount, then gets lines of its own too, first, saying its amount
|
||||
// is not known.
|
||||
// With no Amount figure, a later take's own figure, or that of a
|
||||
// PERMIT2_TRANSFER_FROM of the same token, into the router or not,
|
||||
// could read as all that leaves, so the step that set the input side,
|
||||
// when the user pays it and it stated no amount, then gets lines of
|
||||
// its own too, first, saying its amount is not known.
|
||||
if (
|
||||
!present(inputAmount) &&
|
||||
firstTakeUnstated !== null &&
|
||||
spentFromSender.some((s) => present(s.amount))
|
||||
(spentFromSender.some((s) => present(s.amount)) ||
|
||||
[...movedFromSender, ...movedIntoRouter].some((m) =>
|
||||
sameAddress(m.token, firstTakeUnstated.token),
|
||||
))
|
||||
) {
|
||||
spentFromSender.unshift(firstTakeUnstated);
|
||||
}
|
||||
|
||||
@@ -2318,6 +2318,24 @@ describe("uniswap decoder", () => {
|
||||
encodeV4TakeAll(WETH_ADDR, 0n),
|
||||
],
|
||||
);
|
||||
// The same step with its SETTLE of OPEN_DELTA paid from the router.
|
||||
const V4_EXACT_OUT_600_FROM_ROUTER = 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,
|
||||
),
|
||||
settle(USDT_ADDR, 0n, false),
|
||||
encodeV4TakeAll(WETH_ADDR, 0n),
|
||||
],
|
||||
);
|
||||
test.each([
|
||||
{
|
||||
kind: "a V4 exact-out step with a SETTLE of OPEN_DELTA by the user",
|
||||
@@ -2325,6 +2343,13 @@ describe("uniswap decoder", () => {
|
||||
first: V4_EXACT_OUT_600,
|
||||
lines: [...UNKNOWN_USDT, ...UP_TO_400_USDT],
|
||||
},
|
||||
// Paid from the router, the step takes nothing from the wallet.
|
||||
{
|
||||
kind: "a V4 exact-out step with a SETTLE of OPEN_DELTA from the router",
|
||||
commands: [0x10, 0x09],
|
||||
first: V4_EXACT_OUT_600_FROM_ROUTER,
|
||||
lines: UP_TO_400_USDT,
|
||||
},
|
||||
// Settled by the user, the step's amount would be what that
|
||||
// SETTLE pays, which the decoder cannot state here.
|
||||
{
|
||||
@@ -2370,6 +2395,36 @@ describe("uniswap decoder", () => {
|
||||
},
|
||||
);
|
||||
|
||||
// https://git.eeqj.de/sneak/AutistMask/issues/634 — after a
|
||||
// PERMIT2_PERMIT of another token, the V4 exact-out step paid from
|
||||
// the router still takes nothing from the wallet, so it gets no lines
|
||||
// of its own. On next it got lines saying its amount is not known.
|
||||
const ONE_DAI_PERMIT = encodePermit2(DAI_ADDR, 10n ** 18n, ROUTER_ADDR);
|
||||
test.each([
|
||||
{
|
||||
kind: "a PERMIT2_PERMIT of 1 DAI, then a V4 exact-out step of at most 600 USDT paid from the router",
|
||||
commands: [0x0a, 0x10],
|
||||
inputs: [ONE_DAI_PERMIT, V4_EXACT_OUT_600_FROM_ROUTER],
|
||||
lines: [["Amount", "1.0000 DAI"]],
|
||||
},
|
||||
{
|
||||
kind: "a PERMIT2_PERMIT of 1 DAI, a V4 exact-out step of at most 600 USDT paid from the router, then a V2 exact-out of at most 400 USDT the user pays for",
|
||||
commands: [0x0a, 0x10, 0x09],
|
||||
inputs: [
|
||||
ONE_DAI_PERMIT,
|
||||
V4_EXACT_OUT_600_FROM_ROUTER,
|
||||
V2_EXACT_OUT_400,
|
||||
],
|
||||
lines: [["Amount", "Up to 1.0000 DAI"], ...UP_TO_400_USDT],
|
||||
},
|
||||
])(
|
||||
"$kind, gives the V4 step no lines of its own",
|
||||
({ commands, inputs, lines }) => {
|
||||
const result = decodeSteps(commands, inputs);
|
||||
expect(linesBetweenTokens(result)).toEqual(lines);
|
||||
},
|
||||
);
|
||||
|
||||
// The V4 exact-out route above the other way round, where the later
|
||||
// take's unknown amount leaves no figure at all, and other ways the
|
||||
// step that sets the input side states no amount.
|
||||
@@ -2423,6 +2478,98 @@ describe("uniswap decoder", () => {
|
||||
},
|
||||
);
|
||||
|
||||
// https://git.eeqj.de/sneak/AutistMask/issues/634 — a
|
||||
// PERMIT2_TRANSFER_FROM of the token the V4 exact-out step spends,
|
||||
// before or after it, shows its own figure, so the step's amount is
|
||||
// shown as not known beside it, and the 400 USDT cannot read as all
|
||||
// that leaves. On next only the 400 USDT was shown. Into the router,
|
||||
// the 400 USDT is on its Paid to line.
|
||||
const transfer = (token, recipient, amount) =>
|
||||
coder.encode(
|
||||
["address", "address", "uint160"],
|
||||
[token, recipient, amount],
|
||||
);
|
||||
const MOVED_400_USDT = [
|
||||
["Moved from the sender", "400.0000 USDT"],
|
||||
["Token moved", "USDT (" + USDT_ADDR + ")"],
|
||||
["Moved to", OTHER_ADDR],
|
||||
];
|
||||
test.each([
|
||||
{
|
||||
kind: "a V4 exact-out step of at most 600 USDT, then a PERMIT2_TRANSFER_FROM of 400 USDT to another address",
|
||||
commands: [0x10, 0x02],
|
||||
inputs: [
|
||||
V4_EXACT_OUT_600,
|
||||
transfer(USDT_ADDR, OTHER_ADDR, USDT_400),
|
||||
],
|
||||
lines: [...UNKNOWN_USDT, ...MOVED_400_USDT],
|
||||
},
|
||||
{
|
||||
kind: "a PERMIT2_TRANSFER_FROM of 400 USDT to another address, then a V4 exact-out step of at most 600 USDT",
|
||||
commands: [0x02, 0x10],
|
||||
inputs: [
|
||||
transfer(USDT_ADDR, OTHER_ADDR, USDT_400),
|
||||
V4_EXACT_OUT_600,
|
||||
],
|
||||
lines: [...UNKNOWN_USDT, ...MOVED_400_USDT],
|
||||
},
|
||||
{
|
||||
kind: "a V4 exact-out step of at most 600 USDT, then a PERMIT2_TRANSFER_FROM of 400 USDT into the router",
|
||||
commands: [0x10, 0x02],
|
||||
inputs: [
|
||||
V4_EXACT_OUT_600,
|
||||
transfer(USDT_ADDR, ROUTER_ADDR, USDT_400),
|
||||
],
|
||||
lines: UNKNOWN_USDT,
|
||||
},
|
||||
{
|
||||
kind: "a PERMIT2_TRANSFER_FROM of 400 USDT into the router, then a V4 exact-out step of at most 600 USDT",
|
||||
commands: [0x02, 0x10],
|
||||
inputs: [
|
||||
transfer(USDT_ADDR, ROUTER_ADDR, USDT_400),
|
||||
V4_EXACT_OUT_600,
|
||||
],
|
||||
lines: UNKNOWN_USDT,
|
||||
},
|
||||
])(
|
||||
"$kind, paid by the user, shows that the step's amount is not known",
|
||||
({ commands, inputs, lines }) => {
|
||||
const result = decodeSteps(commands, inputs);
|
||||
expect(linesBetweenTokens(result)).toEqual(lines);
|
||||
},
|
||||
);
|
||||
|
||||
// Paid from the router, the V4 exact-out step takes nothing from the
|
||||
// wallet, so the moves give all that leaves.
|
||||
test("a PERMIT2_TRANSFER_FROM of 400 USDT into the router, one of 10 USDT to another address, then a V4 exact-out step paid from the router, shows only the moves", () => {
|
||||
const result = decodeSteps(
|
||||
[0x02, 0x02, 0x10],
|
||||
[
|
||||
transfer(USDT_ADDR, ROUTER_ADDR, USDT_400),
|
||||
transfer(USDT_ADDR, OTHER_ADDR, 10000000n),
|
||||
V4_EXACT_OUT_600_FROM_ROUTER,
|
||||
],
|
||||
);
|
||||
expect(linesBetweenTokens(result)).toEqual([
|
||||
["Moved from the sender", "10.0000 USDT"],
|
||||
["Token moved", "USDT (" + USDT_ADDR + ")"],
|
||||
["Moved to", OTHER_ADDR],
|
||||
]);
|
||||
});
|
||||
|
||||
// A move of another token says nothing of how much USDT leaves.
|
||||
test("a V4 exact-out step of at most 600 USDT, then a PERMIT2_TRANSFER_FROM of 400 DAI, shows only the DAI moved", () => {
|
||||
const result = decodeSteps(
|
||||
[0x10, 0x02],
|
||||
[V4_EXACT_OUT_600, transfer(DAI_ADDR, OTHER_ADDR, DAI_400)],
|
||||
);
|
||||
expect(linesBetweenTokens(result)).toEqual([
|
||||
["Moved from the sender", "400.0000 DAI"],
|
||||
["Token moved", "DAI (" + DAI_ADDR + ")"],
|
||||
["Moved to", OTHER_ADDR],
|
||||
]);
|
||||
});
|
||||
|
||||
// A V2 exact-out step makes every amount the swap steps spend a maximum,
|
||||
// the total included.
|
||||
test("a split with a V2 exact-out step the user pays for shows the total as a maximum", () => {
|
||||
|
||||
Reference in New Issue
Block a user