Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
64a77b58e8 |
@@ -3254,26 +3254,30 @@ Currently supported:
|
||||
whose amount the decoder does not know. When there is no `Amount` line, each
|
||||
take the total counts gets them too, so that every amount the decoder reads is
|
||||
shown: the amount the step that sets the input side states, when it is a
|
||||
quantity, and each later take of `Token In` added to it. A `PERMIT2_PERMIT`
|
||||
the decoder cannot read, then V3 exact-ins of 600 USDT and 400 DAI paid by the
|
||||
user, give lines for both amounts. 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, except that the step that sets 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; `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
|
||||
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 exact-out step or a
|
||||
quantity and the user pays that step (a V2 or V3 step whose `payerIsUser` is
|
||||
true, a V4 step the user pays some of its input token, or a `WRAP_ETH`, whose
|
||||
ETH comes from the transaction's value), and each later take of `Token In`
|
||||
added to it. A step that sets the input side and is paid from the router takes
|
||||
nothing from the user's wallet and gets none. A `PERMIT2_PERMIT` the decoder
|
||||
cannot read, then V3 exact-ins of 600 USDT and 400 DAI paid by the user, give
|
||||
lines for both amounts. 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,
|
||||
except that the step that sets 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;
|
||||
`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 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 exact-out step or a
|
||||
`PERMIT2_TRANSFER_FROM` whose input it cannot read, such as a V3 step whose
|
||||
path is shorter than one token address or has bytes after its last hop; every
|
||||
`PERMIT2_TRANSFER_FROM_BATCH`; every `EXECUTE_SUB_PLAN`, wherever it stands;
|
||||
|
||||
+20
-11
@@ -1234,9 +1234,11 @@ 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.
|
||||
// Whether the user pays the step that set the input side: a V2 or V3
|
||||
// swap step with `payerIsUser` true, a V4 step the user pays some of
|
||||
// its input currency (see decodeV4Swap()), or a WRAP_ETH, whose ETH
|
||||
// comes from the transaction's value. A step paid from the router
|
||||
// takes nothing from the sender.
|
||||
let inputPaidByUser = false;
|
||||
// The last PERMIT2_PERMIT this decoder read before a step set the
|
||||
// input side, or null (see permitStands after the loop).
|
||||
@@ -1622,6 +1624,7 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
|
||||
if (s) {
|
||||
if (!inputEstablished) {
|
||||
setInputOnce(s.tokenIn, s.amountIn);
|
||||
inputPaidByUser = s.payerIsUser;
|
||||
} else if (s.payerIsUser) {
|
||||
addUserPaid(s.tokenIn, s.amountIn);
|
||||
}
|
||||
@@ -1645,6 +1648,7 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
|
||||
if (s) {
|
||||
if (!inputEstablished) {
|
||||
setInputOnce(s.tokenIn, s.amountIn);
|
||||
inputPaidByUser = s.payerIsUser;
|
||||
} else if (s.payerIsUser) {
|
||||
addUserPaid(s.tokenIn, s.amountIn);
|
||||
}
|
||||
@@ -1676,6 +1680,7 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
|
||||
if (s) {
|
||||
if (!inputEstablished) {
|
||||
setInputOnce(s.tokenIn, s.amountInMax);
|
||||
inputPaidByUser = s.payerIsUser;
|
||||
} else if (s.payerIsUser) {
|
||||
addUserPaid(s.tokenIn, s.amountInMax);
|
||||
}
|
||||
@@ -1771,10 +1776,13 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
|
||||
} else if (command === "WRAP_ETH") {
|
||||
const w = decodeWrapEth(inputs[i]);
|
||||
if (w) {
|
||||
setInputOnce(
|
||||
"0x0000000000000000000000000000000000000000",
|
||||
w.amount,
|
||||
);
|
||||
if (!inputEstablished) {
|
||||
setInputOnce(
|
||||
"0x0000000000000000000000000000000000000000",
|
||||
w.amount,
|
||||
);
|
||||
inputPaidByUser = true;
|
||||
}
|
||||
addPayee(w.recipient);
|
||||
// CONTRACT_BALANCE wraps all the router's ETH.
|
||||
if (w.amount === CONTRACT_BALANCE) {
|
||||
@@ -2041,9 +2049,10 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
|
||||
// (see spentUnread()), or a token it does not know.
|
||||
const spentFromSender = [];
|
||||
let total = null;
|
||||
// The takes `total` adds up, each a quantity, as { token, amount }.
|
||||
// When there is no Amount line, each gets lines of its own (see
|
||||
// below).
|
||||
// The takes from the sender that `total` adds up, each a quantity, as
|
||||
// { token, amount }. When there is no Amount line, each gets lines of
|
||||
// its own (see below). The amount of a step that set the input side
|
||||
// and is paid from the router counts in `total` but is not among them.
|
||||
const inTotal = [];
|
||||
// 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
|
||||
@@ -2057,7 +2066,7 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
|
||||
let firstTakeUnstated = null;
|
||||
if (!permitStands || sameAddress(inputToken, permit.token)) {
|
||||
total = inputAmount;
|
||||
if (typeof inputAmount === "bigint") {
|
||||
if (typeof inputAmount === "bigint" && inputPaidByUser) {
|
||||
inTotal.push({ token: inputToken, amount: inputAmount });
|
||||
}
|
||||
if (hasFirstTake && !present(inputAmount)) {
|
||||
|
||||
@@ -2331,6 +2331,103 @@ describe("uniswap decoder", () => {
|
||||
expect(linesBetweenTokens(result)).toEqual(lines);
|
||||
});
|
||||
|
||||
// A step that sets the input side and is paid from the router takes
|
||||
// nothing from the sender, so with no Amount line its amount gets no
|
||||
// lines of its own: in each route below the sender's 600 USDT moves
|
||||
// into the router first, and such lines would say it left twice.
|
||||
const INTO_ROUTER_600 = permit2TransferFrom(
|
||||
USDT_ADDR,
|
||||
ROUTER_ADDR,
|
||||
USDT_600,
|
||||
);
|
||||
const V3_600_FROM_ROUTER = coder.encode(
|
||||
["address", "uint256", "uint256", "bytes", "bool"],
|
||||
[
|
||||
USER_ADDR,
|
||||
USDT_600,
|
||||
590000000n,
|
||||
encodeV3Path([USDT_ADDR, USDC_ADDR]),
|
||||
false,
|
||||
],
|
||||
);
|
||||
const TRANSFER_FROM_BATCH = coder.encode(
|
||||
["tuple(address,address,uint160,address)[]"],
|
||||
[[[USER_ADDR, OTHER_ADDR, USDT_400, USDT_ADDR]]],
|
||||
);
|
||||
test.each([
|
||||
{
|
||||
kind: "a PERMIT2_TRANSFER_FROM of 600 USDT into the router, a V3 exact-in of 600 USDT paid from the router, then a PERMIT2_TRANSFER_FROM_BATCH",
|
||||
commands: [0x02, 0x00, 0x0d],
|
||||
inputs: [
|
||||
INTO_ROUTER_600,
|
||||
V3_600_FROM_ROUTER,
|
||||
TRANSFER_FROM_BATCH,
|
||||
],
|
||||
lines: [["Moved from the sender", UNDECODED]],
|
||||
},
|
||||
{
|
||||
kind: "a PERMIT2_PERMIT that cannot be read, a PERMIT2_TRANSFER_FROM of 600 USDT into the router, then a V3 exact-in of 600 USDT paid from the router",
|
||||
commands: [0x0a, 0x02, 0x00],
|
||||
inputs: [
|
||||
dirtyWord(
|
||||
encodePermit2(USDT_ADDR, 5000000000n, ROUTER_ADDR),
|
||||
0,
|
||||
),
|
||||
INTO_ROUTER_600,
|
||||
V3_600_FROM_ROUTER,
|
||||
],
|
||||
lines: [["Permit2 allowance", UNDECODED]],
|
||||
},
|
||||
{
|
||||
kind: "a PERMIT2_TRANSFER_FROM of 600 USDT into the router, a V4 step swapping 600 USDT whose SETTLE the router pays, then a PERMIT2_TRANSFER_FROM_BATCH",
|
||||
commands: [0x02, 0x10, 0x0d],
|
||||
inputs: [
|
||||
INTO_ROUTER_600,
|
||||
encodeV4Swap(
|
||||
new Uint8Array([
|
||||
V4_SWAP_EXACT_IN_SINGLE_ID,
|
||||
V4_SETTLE_ID,
|
||||
V4_TAKE_ALL_ID,
|
||||
]),
|
||||
[
|
||||
encodeV4ExactInSingle(
|
||||
USDT_ADDR,
|
||||
USDC_ADDR,
|
||||
USDT_600,
|
||||
590000000n,
|
||||
),
|
||||
settle(USDT_ADDR, USDT_600, false),
|
||||
encodeV4TakeAll(USDC_ADDR, 0n),
|
||||
],
|
||||
),
|
||||
TRANSFER_FROM_BATCH,
|
||||
],
|
||||
lines: [["Moved from the sender", UNDECODED]],
|
||||
},
|
||||
{
|
||||
kind: "a PERMIT2_TRANSFER_FROM of 600 USDT into the router, a V2 exact-in of CONTRACT_BALANCE paid from the router, then an EXECUTE_SUB_PLAN",
|
||||
commands: [0x02, 0x08, 0x21],
|
||||
inputs: [
|
||||
INTO_ROUTER_600,
|
||||
coder.encode(
|
||||
["address", "uint256", "uint256", "address[]", "bool"],
|
||||
[
|
||||
USER_ADDR,
|
||||
2n ** 255n, // CONTRACT_BALANCE
|
||||
590000000n,
|
||||
[USDT_ADDR, USDC_ADDR],
|
||||
false,
|
||||
],
|
||||
),
|
||||
SUB_PLAN,
|
||||
],
|
||||
lines: [["Moved from the sender", UNDECODED]],
|
||||
},
|
||||
])("$kind", ({ commands, inputs, lines }) => {
|
||||
const result = decodeSteps(commands, inputs);
|
||||
expect(linesBetweenTokens(result)).toEqual(lines);
|
||||
});
|
||||
|
||||
// A V3_SWAP_EXACT_OUT the user pays for, of at most `amountInMax`
|
||||
// USDT for USDC.
|
||||
const v3ExactOut = (amountInMax) =>
|
||||
|
||||
Reference in New Issue
Block a user