Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2c2f497dca |
@@ -1235,7 +1235,9 @@ only when no earlier step set it. It finds each side by three rules:
|
||||
is their sum; in any other case the step gives no `Amount`. The decoder reads
|
||||
no amount from an exact-out action, and an open delta is not a stated amount.
|
||||
Its `Min. received` is found the same way, from the `amountOutMin` of the
|
||||
actions that buy the output currency.
|
||||
actions that buy the output currency. When the user pays the step any of the
|
||||
input currency, its `Amount` is what the user pays instead (see Transaction
|
||||
Decoding).
|
||||
3. If any swap action in the step could not be read, only `SETTLE`,
|
||||
`SETTLE_ALL`, `TAKE` and `TAKE_ALL` name its sides, and the step gives no
|
||||
`Amount` or `Min. received`. A side they did not name is unknown, and the
|
||||
@@ -3136,42 +3138,46 @@ Currently supported:
|
||||
token or amount. A V4 step with a swap action sets the input side when no
|
||||
earlier step set it, even when it names neither (see Display Consistency).
|
||||
That step counts its own amount, whoever pays it: a V2 exact-out step its
|
||||
`amountInMax`, and a V4 swap step the `amountIn` of the swap action that
|
||||
spends its input currency. Each later V2 or V3 exact-in or V2 exact-out step
|
||||
whose `payerIsUser` is true takes its amount, found in the same way, from the
|
||||
user's wallet. So does every V4 step but the one that sets the input side,
|
||||
with a swap action or without: it takes what its `SETTLE`s paid by the user
|
||||
(`payerIsUser` true) and its `SETTLE_ALL`s pay, for each token on its own. A
|
||||
`SETTLE` of any amount but `OPEN_DELTA` pays that amount, and a `SETTLE` of
|
||||
`OPEN_DELTA` or a `SETTLE_ALL` pays what the step owes, as above. A later step
|
||||
paid from the router spends what the router holds and takes nothing, and so
|
||||
does a V2 exact-in whose `amountIn` is `ALREADY_PAID`. A later take of
|
||||
`Token In` is added to the total while both are quantities: a V3 exact-in of
|
||||
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
|
||||
not known and there is no `Amount` line. `Token In` is the token of the last
|
||||
`PERMIT2_PERMIT` the decoder reads, when there is one. Its amount, `Unlimited`
|
||||
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
|
||||
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 the decoder reads every
|
||||
step after it in full. Permit2 then lets the router take no more of the token
|
||||
than that amount, so the permit covers every take of it. A step the decoder
|
||||
does not read in full may raise what Permit2 lets the router take, as a
|
||||
`PERMIT2_PERMIT_BATCH` can: a command the decoder does not read (listed at the
|
||||
end of this section), a 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 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. 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
|
||||
`amountInMax`, and a V4 step what it takes of its input token from the user's
|
||||
wallet, as below, or, when the user pays it none of that token, the `amountIn`
|
||||
of the swap action that spends it. Each later V2 or V3 exact-in or V2
|
||||
exact-out step whose `payerIsUser` is true takes its amount, found in the same
|
||||
way, from the user's wallet. Every V4 step, with a swap action or without,
|
||||
takes from the user's wallet what its `SETTLE`s paid by the user
|
||||
(`payerIsUser` true) and its `SETTLE_ALL`s pay, for each token on its own;
|
||||
what the step that sets the input side takes of any other token counts as a
|
||||
later step's take. A `SETTLE` of any amount but `OPEN_DELTA` pays that amount,
|
||||
and a `SETTLE` of `OPEN_DELTA` or a `SETTLE_ALL` pays what the step owes, as
|
||||
above. So a V4 step that swaps 400 USDT, with a `SETTLE` of 5,000 USDT paid by
|
||||
the user and the rest taken to another address, reads `5000.0000 USDT`. A
|
||||
later step paid from the router spends what the router holds and takes
|
||||
nothing, and so does a V2 exact-in whose `amountIn` is `ALREADY_PAID`. A later
|
||||
take of `Token In` is added to the total while both are quantities: a V3
|
||||
exact-in of 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 not known and there is no `Amount` line. `Token In` is the token of
|
||||
the last `PERMIT2_PERMIT` the decoder reads, when there is one. Its amount,
|
||||
`Unlimited` 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 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 the decoder reads
|
||||
every step after it in full. Permit2 then lets the router take no more of the
|
||||
token than that amount, so the permit covers every take of it. A step the
|
||||
decoder does not read in full may raise what Permit2 lets the router take, as
|
||||
a `PERMIT2_PERMIT_BATCH` can: a command the decoder does not read (listed at
|
||||
the end of this section), a 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 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. 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
|
||||
@@ -3179,13 +3185,13 @@ Currently supported:
|
||||
`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
|
||||
does not know gives the `Moved from the sender` line alone. A step may also
|
||||
take from the user's wallet what the decoder did not read: a V4 step, other
|
||||
than the one that sets the input side, whose input or one of whose `SETTLE`s
|
||||
or `SETTLE_ALL`s it cannot read, which the router still runs; a V2 or V3
|
||||
exact-in or V2 exact-out step or a `PERMIT2_TRANSFER_FROM` whose input it
|
||||
cannot read; every `PERMIT2_TRANSFER_FROM_BATCH`; every `V3_SWAP_EXACT_OUT`,
|
||||
which the decoder does not read, wherever it stands; every `EXECUTE_SUB_PLAN`,
|
||||
wherever it stands; and every step it fails on. Each such step gives one
|
||||
take from the user's wallet what the decoder did not read: a V4 step whose
|
||||
input or one of whose `SETTLE`s or `SETTLE_ALL`s it cannot read, which the
|
||||
router still runs; a V2 or V3 exact-in or V2 exact-out step or a
|
||||
`PERMIT2_TRANSFER_FROM` whose input it cannot read; every
|
||||
`PERMIT2_TRANSFER_FROM_BATCH`; every `V3_SWAP_EXACT_OUT`, which the decoder
|
||||
does not read, wherever it stands; every `EXECUTE_SUB_PLAN`, wherever it
|
||||
stands; and every step it fails on. Each such step gives one
|
||||
`Moved from the sender` line among 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,
|
||||
|
||||
+38
-28
@@ -903,7 +903,8 @@ function totalAmount(amounts) {
|
||||
// input side from it only when no earlier step set it. A side it sets that
|
||||
// these rules leave unknown is set unknown, with no figure. A step with no
|
||||
// swap action sets only the sides SETTLE, SETTLE_ALL, TAKE and TAKE_ALL
|
||||
// name.
|
||||
// name. When the user pays the step any of Token In (`paidByUser` below),
|
||||
// decode() takes that, not the Amount of rule 2, as the step's amount.
|
||||
//
|
||||
// `takes` is every TAKE, TAKE_ALL and TAKE_PORTION, as the currency it takes
|
||||
// and its recipient as written, MSG_SENDER for a TAKE_ALL. TAKE_PORTION takes
|
||||
@@ -1220,9 +1221,10 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
|
||||
outputEstablished = true;
|
||||
};
|
||||
|
||||
// What each step but the one that sets the input side takes from the
|
||||
// sender, as { token, amount }, in step order. After the loop each
|
||||
// joins the Amount line or gets lines of its own (see permitStands).
|
||||
// What the steps take from the sender beyond the amount of the step
|
||||
// that sets the input side, as { token, amount }, in step order.
|
||||
// After the loop each joins the Amount line or gets lines of its own
|
||||
// (see permitStands).
|
||||
const userPaid = [];
|
||||
// A swap step the user pays for (`payerIsUser` true) takes `amount`
|
||||
// of `token` from the sender. A V2 exact-in of ALREADY_PAID takes
|
||||
@@ -1231,14 +1233,14 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
|
||||
if (amount !== ALREADY_PAID) userPaid.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
|
||||
// exact-in or V2 exact-out step or PERMIT2_TRANSFER_FROM whose input
|
||||
// it cannot read; a PERMIT2_TRANSFER_FROM_BATCH; a V3_SWAP_EXACT_OUT;
|
||||
// an EXECUTE_SUB_PLAN; and a step it fails on. It is recorded with a
|
||||
// null token and amount: one line says so, there is no Amount line,
|
||||
// whose figure may then be too low, and the amount of a permit after
|
||||
// it does not stand (see permitStands).
|
||||
// V4 step whose input or one of whose SETTLEs or SETTLE_ALLs it
|
||||
// cannot read; a V2 or V3 exact-in or V2 exact-out step or
|
||||
// PERMIT2_TRANSFER_FROM whose input it cannot read; a
|
||||
// PERMIT2_TRANSFER_FROM_BATCH; a V3_SWAP_EXACT_OUT; an
|
||||
// EXECUTE_SUB_PLAN; and a step it fails on. It is recorded with a null
|
||||
// token and amount: one line says so, there is no Amount line, whose
|
||||
// figure may then be too low, and the amount of a permit after it
|
||||
// does not stand (see permitStands).
|
||||
const spentUnread = () => {
|
||||
userPaid.push({ token: null, amount: null });
|
||||
};
|
||||
@@ -1677,6 +1679,14 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
|
||||
swapUndecoded = true;
|
||||
commandNames[i] += " (could not be decoded)";
|
||||
}
|
||||
// The step's amount, if it sets the input side: what the
|
||||
// user pays it of its input currency (see
|
||||
// decodeV4Swap()), or, when the user pays none of it,
|
||||
// what its swap actions spend of it, whoever pays that.
|
||||
const paidIn = v4?.paidByUser.find(
|
||||
(p) => p.currency === v4.tokenIn,
|
||||
);
|
||||
const amountIn = paidIn ? paidIn.amount : v4?.amountIn;
|
||||
if (v4 && v4.hasSwapAction) {
|
||||
// A step with a swap action always sets the output
|
||||
// side, as the last swap step wins, and sets the
|
||||
@@ -1688,7 +1698,7 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
|
||||
// token, likewise never sits beside an earlier step's
|
||||
// other half.
|
||||
if (!inputEstablished) {
|
||||
setInput(v4.tokenIn, v4.amountIn);
|
||||
setInput(v4.tokenIn, amountIn);
|
||||
}
|
||||
outputToken = v4.tokenOut;
|
||||
minOutput = v4.amountOutMin;
|
||||
@@ -1698,26 +1708,26 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
|
||||
// SETTLE, SETTLE_ALL, TAKE or TAKE_ALL names:
|
||||
// setInputOnce() and setOutput() leave alone a side
|
||||
// whose two halves are both unstated.
|
||||
setInputOnce(v4.tokenIn, v4.amountIn);
|
||||
setInputOnce(v4.tokenIn, amountIn);
|
||||
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.
|
||||
// Every V4 step, with a swap action or without, takes
|
||||
// from the sender what the user pays it (an amount an
|
||||
// unread action may change is null), less what the step
|
||||
// that sets the input side counts as its amount. 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();
|
||||
if (v4) {
|
||||
for (const p of v4.paidByUser) {
|
||||
if (setInputHere && p === paidIn) continue;
|
||||
addUserPaid(p.currency, p.amount);
|
||||
}
|
||||
}
|
||||
if (!v4 || v4.settleUnread) {
|
||||
spentUnread();
|
||||
}
|
||||
} else if (command === "UNWRAP_WETH") {
|
||||
hasUnwrapWeth = true;
|
||||
const u = decodeUnwrapWeth(inputs[i]);
|
||||
|
||||
+84
-4
@@ -1590,6 +1590,61 @@ describe("uniswap decoder", () => {
|
||||
},
|
||||
);
|
||||
|
||||
// https://git.eeqj.de/sneak/AutistMask/issues/628 — the V4 step that
|
||||
// sets the input side takes what its SETTLE paid by the user states,
|
||||
// not what its swap spends. Each of these read 400.0000 USDT before;
|
||||
// on next the first two read 400.0000 USDT and 1.0000 USDT.
|
||||
const V4_SETTLE_5000 = encodeV4Swap(
|
||||
new Uint8Array([
|
||||
V4_SETTLE_ID,
|
||||
V4_SWAP_EXACT_IN_SINGLE_ID,
|
||||
V4_TAKE_ID,
|
||||
V4_TAKE_ALL_ID,
|
||||
]),
|
||||
[
|
||||
settle(USDT_ADDR, 5000000000n, true),
|
||||
USDT_FOR_USDC_400,
|
||||
take(USDT_ADDR, OTHER_ADDR, 0n), // the rest
|
||||
encodeV4TakeAll(USDC_ADDR, 0n),
|
||||
],
|
||||
);
|
||||
test.each([
|
||||
{
|
||||
kind: "a V4 step swapping 400 USDT with a SETTLE of 5,000 USDT by the user, the rest taken to another address,",
|
||||
commands: [0x10],
|
||||
inputs: [V4_SETTLE_5000],
|
||||
},
|
||||
{
|
||||
kind: "a PERMIT2_PERMIT of 1 USDT that the router may let fail, then that V4 step,",
|
||||
commands: [0x8a, 0x10],
|
||||
inputs: [
|
||||
encodePermit2(USDT_ADDR, 1000000n, ROUTER_ADDR),
|
||||
V4_SETTLE_5000,
|
||||
],
|
||||
},
|
||||
// The unread SWEEP keeps the permit's amount from standing.
|
||||
{
|
||||
kind: "a PERMIT2_PERMIT of 5,000 USDT to the router, that V4 step, then a SWEEP that cannot be read,",
|
||||
commands: [0x0a, 0x10, 0x04],
|
||||
inputs: [
|
||||
encodePermit2(USDT_ADDR, 5000000000n, ROUTER_ADDR),
|
||||
V4_SETTLE_5000,
|
||||
dirtyWord(
|
||||
coder.encode(
|
||||
["address", "address", "uint256"],
|
||||
[USDC_ADDR, USER_ADDR, 0n],
|
||||
),
|
||||
1,
|
||||
),
|
||||
],
|
||||
},
|
||||
])("$kind shows the 5,000 USDT", ({ commands, inputs }) => {
|
||||
const result = decodeSteps(commands, inputs);
|
||||
expect(linesBetweenTokens(result)).toEqual([
|
||||
["Amount", "5000.0000 USDT"],
|
||||
]);
|
||||
});
|
||||
|
||||
// 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;
|
||||
@@ -1644,6 +1699,24 @@ describe("uniswap decoder", () => {
|
||||
});
|
||||
|
||||
test.each([
|
||||
{
|
||||
kind: "a V4 step swapping 400 USDT whose SETTLE by the user cannot be read",
|
||||
commands: [0x10],
|
||||
inputs: [
|
||||
encodeV4Swap(
|
||||
new Uint8Array([
|
||||
V4_SWAP_EXACT_IN_SINGLE_ID,
|
||||
V4_SETTLE_ID,
|
||||
V4_TAKE_ALL_ID,
|
||||
]),
|
||||
[
|
||||
USDT_FOR_USDC_400,
|
||||
dirtyWord(settle(USDT_ADDR, USDT_400, true), 0),
|
||||
encodeV4TakeAll(USDC_ADDR, 0n),
|
||||
],
|
||||
),
|
||||
],
|
||||
},
|
||||
{
|
||||
kind: "a V3 step of 600 USDT, then a V4 step whose input cannot be read",
|
||||
commands: [0x00, 0x10],
|
||||
@@ -2042,8 +2115,10 @@ describe("uniswap decoder", () => {
|
||||
first: V4_EXACT_OUT_600,
|
||||
lines: [...UNKNOWN_USDT, ...UP_TO_400_USDT],
|
||||
},
|
||||
// Settled by the user, the step's amount would be what that
|
||||
// SETTLE pays, which the decoder cannot state here.
|
||||
{
|
||||
kind: "a V4 exact-in step of the open delta",
|
||||
kind: "a V4 exact-in step of the open delta, settled from the router",
|
||||
commands: [0x10, 0x09],
|
||||
first: encodeV4Swap(
|
||||
new Uint8Array([
|
||||
@@ -2053,7 +2128,7 @@ describe("uniswap decoder", () => {
|
||||
]),
|
||||
[
|
||||
encodeV4ExactInSingle(USDT_ADDR, USDC_ADDR, 0n, 1n),
|
||||
encodeV4Settle(USDT_ADDR),
|
||||
settle(USDT_ADDR, 0n, false),
|
||||
encodeV4TakeAll(USDC_ADDR, 0n),
|
||||
],
|
||||
),
|
||||
@@ -2283,7 +2358,9 @@ describe("uniswap decoder", () => {
|
||||
// (v4-periphery ActionConstants.OPEN_DELTA = 0, applied by V4Router's
|
||||
// _swapExactInputSingle / _swapExactInput). It is not a quantity, and
|
||||
// printing "0.0000 WETH" for it would state the exact inverse of what the
|
||||
// step does. Against c9ebac8 the Amount line was omitted entirely.
|
||||
// step does. Against c9ebac8 the Amount line was omitted entirely. The
|
||||
// SETTLE is paid from the router: one paid by the user would make the
|
||||
// step's amount what that SETTLE pays.
|
||||
test("a V4 open-delta amountIn is named, not printed as zero", () => {
|
||||
const data = buildExecute(
|
||||
"0x10",
|
||||
@@ -2295,7 +2372,10 @@ describe("uniswap decoder", () => {
|
||||
V4_TAKE_ID,
|
||||
]),
|
||||
[
|
||||
encodeV4Settle(WETH_ADDR),
|
||||
coder.encode(
|
||||
["address", "uint256", "bool"],
|
||||
[WETH_ADDR, 0n, false],
|
||||
),
|
||||
encodeV4ExactInSingle(
|
||||
WETH_ADDR,
|
||||
USDC_ADDR,
|
||||
|
||||
Reference in New Issue
Block a user