fix: each PERMIT2_TRANSFER_FROM shows the amount it moves and where to (closes #611) #620
@@ -2368,9 +2368,10 @@ view would leave a wallet one click from deletion.
|
|||||||
- Site origin (bold, scheme and port included) + "wants to send a
|
- Site origin (bold, scheme and port included) + "wants to send a
|
||||||
transaction"
|
transaction"
|
||||||
- Decoded action (if calldata is recognized): action name, token details,
|
- Decoded action (if calldata is recognized): action name, token details,
|
||||||
amounts, steps, deadline, and unless a swap pays only the From address, a
|
amounts, what each `PERMIT2_TRANSFER_FROM` moves and where to, steps,
|
||||||
`Paid to` line for each other address it pays, or saying that a token
|
deadline, and unless a swap pays only the From address, a `Paid to` line
|
||||||
stays in the router or who it pays is unknown (see Transaction Decoding)
|
for each other address it pays, or saying that a token stays in the router
|
||||||
|
or who it pays is unknown (see Transaction Decoding)
|
||||||
- From: color dot + full address + etherscan link
|
- From: color dot + full address + etherscan link
|
||||||
- Contract: color dot + full address + etherscan link, token symbol label if
|
- Contract: color dot + full address + etherscan link, token symbol label if
|
||||||
known; for a contract creation, the same sentence as on WaitTx
|
known; for a contract creation, the same sentence as on WaitTx
|
||||||
@@ -3120,9 +3121,19 @@ Currently supported:
|
|||||||
name); and one more reads `Unknown (could not be decoded)` when a step that
|
name); and one more reads `Unknown (could not be decoded)` when a step that
|
||||||
may pay an address went unread. When no step pays anyone, a single line reads
|
may pay an address went unread. When no step pays anyone, a single line reads
|
||||||
`Unknown (not named in the calldata)`. Wherever there is a `Paid to` line, the
|
`Unknown (not named in the calldata)`. Wherever there is a `Paid to` line, the
|
||||||
minimum reads `Min. paid out`. A step goes unread when its recipient does not
|
minimum reads `Min. paid out`. After the `Amount` line (after `Token In` when
|
||||||
decode as an address, or when it is a command the decoder does not read: every
|
there is none), each `PERMIT2_TRANSFER_FROM` but one into the router, in step
|
||||||
command other than `PERMIT2_PERMIT`, `PERMIT2_TRANSFER_FROM`,
|
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
|
||||||
|
`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
|
||||||
|
move into the router needs none of them, because its `Paid to` line and
|
||||||
|
`Token moved into the router` line already give the amount, the token and
|
||||||
|
where it goes, and so its amount is shown once. These lines change neither the
|
||||||
|
`Paid to` lines nor the minimum's label. A step goes unread when its recipient
|
||||||
|
does not decode as an address, or when it is a command the decoder does not
|
||||||
|
read: every command other than `PERMIT2_PERMIT`, `PERMIT2_TRANSFER_FROM`,
|
||||||
`V2_SWAP_EXACT_IN`, `V2_SWAP_EXACT_OUT`, `V3_SWAP_EXACT_IN`, `V4_SWAP`,
|
`V2_SWAP_EXACT_IN`, `V2_SWAP_EXACT_OUT`, `V3_SWAP_EXACT_IN`, `V4_SWAP`,
|
||||||
`SWEEP`, `TRANSFER`, `PAY_PORTION`, `PAY_PORTION_FULL_PRECISION`,
|
`SWEEP`, `TRANSFER`, `PAY_PORTION`, `PAY_PORTION_FULL_PRECISION`,
|
||||||
`ACROSS_V4_DEPOSIT_V3`, `WRAP_ETH`, `UNWRAP_WETH` and `BALANCE_CHECK_ERC20`
|
`ACROSS_V4_DEPOSIT_V3`, `WRAP_ETH`, `UNWRAP_WETH` and `BALANCE_CHECK_ERC20`
|
||||||
|
|||||||
+66
-21
@@ -488,6 +488,21 @@ function amountText(raw, info) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The line giving a moved token under `label`, in the shape of the Token In
|
||||||
|
// line, so the screens show it as they show that one. ETH has no address.
|
||||||
|
function tokenMovedLine(label, token, sources) {
|
||||||
|
const info = tokenInfo(token, sources);
|
||||||
|
if (!present(info.address)) {
|
||||||
|
return { label, value: info.symbol };
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
label,
|
||||||
|
value: info.symbol ? info.symbol + " (" + token + ")" : token,
|
||||||
|
address: token,
|
||||||
|
isToken: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// Decode PERMIT2_PERMIT (command 0x0a) input bytes.
|
// Decode PERMIT2_PERMIT (command 0x0a) input bytes.
|
||||||
// ABI: ((address token, uint160 amount, uint48 expiration, uint48 nonce),
|
// ABI: ((address token, uint160 amount, uint48 expiration, uint48 nonce),
|
||||||
// address spender, uint256 sigDeadline), bytes signature
|
// address spender, uint256 sigDeadline), bytes signature
|
||||||
@@ -1171,6 +1186,13 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
|
|||||||
// router, as { token, amount }. Later steps may leave part of it
|
// router, as { token, amount }. Later steps may leave part of it
|
||||||
// there: nothing here checks that they spend all of it.
|
// there: nothing here checks that they spend all of it.
|
||||||
const movedIntoRouter = [];
|
const movedIntoRouter = [];
|
||||||
|
// What each PERMIT2_TRANSFER_FROM not into the router moves from the
|
||||||
|
// sender, in step order, whoever counts as paid, as
|
||||||
|
// { token, recipient, amount, to }: `to` says where it goes, the
|
||||||
|
// sender or the V2 pair the next swap spends, and is null when it
|
||||||
|
// goes to `recipient` as written. A move into the router is in
|
||||||
|
// `movedIntoRouter`, whose lines give its amount already.
|
||||||
|
const movedFromSender = [];
|
||||||
// Each token a step paid to the router that no later step paid out
|
// Each token a step paid to the router that no later step paid out
|
||||||
// or spent in full, by its address, lowercased, ETH as the zero
|
// or spent in full, by its address, lowercased, ETH as the zero
|
||||||
// address. The router is paid what a swap step, V4 TAKE or
|
// address. The router is paid what a swap step, V4 TAKE or
|
||||||
@@ -1302,10 +1324,23 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
|
|||||||
// pays address(1) and address(2) as written (see
|
// pays address(1) and address(2) as written (see
|
||||||
// decodePermit2TransferFrom()).
|
// decodePermit2TransferFrom()).
|
||||||
addPaidElsewhere(p.recipient);
|
addPaidElsewhere(p.recipient);
|
||||||
|
movedFromSender.push({ ...p, to: null });
|
||||||
} else if (isRouter(p.recipient)) {
|
} else if (isRouter(p.recipient)) {
|
||||||
movedIntoRouter.push(p);
|
movedIntoRouter.push(p);
|
||||||
} else if (!isHop(i, p.token, p.recipient)) {
|
} else if (isHop(i, p.token, p.recipient)) {
|
||||||
|
movedFromSender.push({
|
||||||
|
...p,
|
||||||
|
to: "The V2 pair the next swap spends",
|
||||||
|
});
|
||||||
|
} else {
|
||||||
addPayee(p.recipient);
|
addPayee(p.recipient);
|
||||||
|
movedFromSender.push({
|
||||||
|
...p,
|
||||||
|
to:
|
||||||
|
p.recipient.toLowerCase() === MSG_SENDER
|
||||||
|
? "The sender"
|
||||||
|
: null,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
} else if (command === "BALANCE_CHECK_ERC20") {
|
} else if (command === "BALANCE_CHECK_ERC20") {
|
||||||
const b = decodeBalanceCheck(inputs[i]);
|
const b = decodeBalanceCheck(inputs[i]);
|
||||||
@@ -1675,6 +1710,26 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// What each PERMIT2_TRANSFER_FROM moves: its amount, its token and
|
||||||
|
// where it goes, an address on a line of its own.
|
||||||
|
for (const m of movedFromSender) {
|
||||||
|
details.push({
|
||||||
|
label: "Moved from the sender",
|
||||||
|
value: amountText(m.amount, tokenInfo(m.token, sources))
|
||||||
|
.display,
|
||||||
|
});
|
||||||
|
details.push(tokenMovedLine("Token moved", m.token, sources));
|
||||||
|
details.push(
|
||||||
|
present(m.to)
|
||||||
|
? { label: "Moved to", value: m.to }
|
||||||
|
: {
|
||||||
|
label: "Moved to",
|
||||||
|
value: m.recipient,
|
||||||
|
address: m.recipient,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// Keyed on the address, not the symbol: a token absent from the
|
// Keyed on the address, not the symbol: a token absent from the
|
||||||
// bundled list has no symbol, and gating the line on one dropped it
|
// bundled list has no symbol, and gating the line on one dropped it
|
||||||
// entirely, leaving a Min. received figure with nothing saying what is
|
// entirely, leaving a Min. received figure with nothing saying what is
|
||||||
@@ -1723,24 +1778,6 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
|
|||||||
if (bridgeDeposit) {
|
if (bridgeDeposit) {
|
||||||
paidTo.push({ label: "Paid to", value: PAID_TO_BRIDGE });
|
paidTo.push({ label: "Paid to", value: PAID_TO_BRIDGE });
|
||||||
}
|
}
|
||||||
// The line giving a token in the router, in the shape of the Token In
|
|
||||||
// line, so the screens show it as they show that one. ETH has no
|
|
||||||
// address.
|
|
||||||
const tokenMovedLine = (token) => {
|
|
||||||
const info = tokenInfo(token, sources);
|
|
||||||
if (!present(info.address)) {
|
|
||||||
return {
|
|
||||||
label: "Token moved into the router",
|
|
||||||
value: info.symbol,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
label: "Token moved into the router",
|
|
||||||
value: info.symbol ? info.symbol + " (" + token + ")" : token,
|
|
||||||
address: token,
|
|
||||||
isToken: true,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
for (const m of movedIntoRouter) {
|
for (const m of movedIntoRouter) {
|
||||||
const info = tokenInfo(m.token, sources);
|
const info = tokenInfo(m.token, sources);
|
||||||
paidTo.push({
|
paidTo.push({
|
||||||
@@ -1750,7 +1787,9 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
|
|||||||
amountText(m.amount, info).display +
|
amountText(m.amount, info).display +
|
||||||
" from the sender (later steps may not spend all of it)",
|
" from the sender (later steps may not spend all of it)",
|
||||||
});
|
});
|
||||||
paidTo.push(tokenMovedLine(m.token));
|
paidTo.push(
|
||||||
|
tokenMovedLine("Token moved into the router", m.token, sources),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if (outputHeld) {
|
if (outputHeld) {
|
||||||
paidTo.push({ label: "Paid to", value: STAYS_IN_ROUTER });
|
paidTo.push({ label: "Paid to", value: STAYS_IN_ROUTER });
|
||||||
@@ -1764,7 +1803,13 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
paidTo.push({ label: "Paid to", value: STAYS_IN_ROUTER });
|
paidTo.push({ label: "Paid to", value: STAYS_IN_ROUTER });
|
||||||
paidTo.push(tokenMovedLine(getAddress(token)));
|
paidTo.push(
|
||||||
|
tokenMovedLine(
|
||||||
|
"Token moved into the router",
|
||||||
|
getAddress(token),
|
||||||
|
sources,
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if (payeeUnread) {
|
if (payeeUnread) {
|
||||||
paidTo.push({ label: "Paid to", value: UNDECODED_CURRENCY });
|
paidTo.push({ label: "Paid to", value: UNDECODED_CURRENCY });
|
||||||
|
|||||||
@@ -6,8 +6,10 @@
|
|||||||
// release runs for a command type
|
// release runs for a command type
|
||||||
// (https://git.eeqj.de/sneak/AutistMask/issues/592), the address whose
|
// (https://git.eeqj.de/sneak/AutistMask/issues/592), the address whose
|
||||||
// balance a BALANCE_CHECK_ERC20 checks
|
// balance a BALANCE_CHECK_ERC20 checks
|
||||||
// (https://git.eeqj.de/sneak/AutistMask/issues/600), and any token a swap
|
// (https://git.eeqj.de/sneak/AutistMask/issues/600), any token a swap
|
||||||
// leaves in the router (https://git.eeqj.de/sneak/AutistMask/issues/606).
|
// leaves in the router (https://git.eeqj.de/sneak/AutistMask/issues/606), and
|
||||||
|
// what each PERMIT2_TRANSFER_FROM moves
|
||||||
|
// (https://git.eeqj.de/sneak/AutistMask/issues/611).
|
||||||
//
|
//
|
||||||
// Every swap command, SWEEP, WRAP_ETH, UNWRAP_WETH, TRANSFER, PAY_PORTION,
|
// Every swap command, SWEEP, WRAP_ETH, UNWRAP_WETH, TRANSFER, PAY_PORTION,
|
||||||
// PAY_PORTION_FULL_PRECISION, PERMIT2_TRANSFER_FROM, ACROSS_V4_DEPOSIT_V3 and
|
// PAY_PORTION_FULL_PRECISION, PERMIT2_TRANSFER_FROM, ACROSS_V4_DEPOSIT_V3 and
|
||||||
@@ -349,6 +351,13 @@ const PAID_ELSEWHERE = [
|
|||||||
"Steps",
|
"Steps",
|
||||||
"Deadline",
|
"Deadline",
|
||||||
];
|
];
|
||||||
|
// `lines` with the lines of one PERMIT2_TRANSFER_FROM after Amount.
|
||||||
|
const withMove = (lines) =>
|
||||||
|
lines.flatMap((l) =>
|
||||||
|
l === "Amount"
|
||||||
|
? [l, "Moved from the sender", "Token moved", "Moved to"]
|
||||||
|
: [l],
|
||||||
|
);
|
||||||
|
|
||||||
// A swap of 2,000 USDT for at least 1 WETH, paid to `recipient`.
|
// A swap of 2,000 USDT for at least 1 WETH, paid to `recipient`.
|
||||||
const swapToWeth = (recipient) =>
|
const swapToWeth = (recipient) =>
|
||||||
@@ -1154,12 +1163,20 @@ describe("each transfer command's recipient", () => {
|
|||||||
test("PERMIT2_TRANSFER_FROM: to the user it pays no one else, to another address that address is named", () => {
|
test("PERMIT2_TRANSFER_FROM: to the user it pays no one else, to another address that address is named", () => {
|
||||||
for (const r of [MSG_SENDER, USER]) {
|
for (const r of [MSG_SENDER, USER]) {
|
||||||
const result = decode(movedThenSwapped(r));
|
const result = decode(movedThenSwapped(r));
|
||||||
expect(labels(result)).toEqual(PAID_TO_USER);
|
expect(labels(result)).toEqual(withMove(PAID_TO_USER));
|
||||||
expect(detail(result, "Min. received").value).toBe("1.0000 WETH");
|
expect(detail(result, "Min. received").value).toBe("1.0000 WETH");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A move to the From address as written shows that address, not The
|
||||||
|
// sender.
|
||||||
|
expect(detail(decode(movedThenSwapped(USER)), "Moved to")).toEqual({
|
||||||
|
label: "Moved to",
|
||||||
|
value: USER,
|
||||||
|
address: USER,
|
||||||
|
});
|
||||||
|
|
||||||
const toOther = decode(movedThenSwapped(OTHER));
|
const toOther = decode(movedThenSwapped(OTHER));
|
||||||
expect(labels(toOther)).toEqual(PAID_ELSEWHERE);
|
expect(labels(toOther)).toEqual(withMove(PAID_ELSEWHERE));
|
||||||
expect(paidTo(toOther)).toEqual([OTHER]);
|
expect(paidTo(toOther)).toEqual([OTHER]);
|
||||||
expect(detail(toOther, "Min. paid out").value).toBe("1.0000 WETH");
|
expect(detail(toOther, "Min. paid out").value).toBe("1.0000 WETH");
|
||||||
|
|
||||||
@@ -1249,6 +1266,11 @@ describe("each transfer command's recipient", () => {
|
|||||||
for (const r of [MSG_SENDER, ADDRESS_THIS]) {
|
for (const r of [MSG_SENDER, ADDRESS_THIS]) {
|
||||||
const result = decode(movedThenSwapped(r), USER, ROUTER_V1);
|
const result = decode(movedThenSwapped(r), USER, ROUTER_V1);
|
||||||
expect(paidTo(result)).toEqual([r]);
|
expect(paidTo(result)).toEqual([r]);
|
||||||
|
expect(detail(result, "Moved to")).toEqual({
|
||||||
|
label: "Moved to",
|
||||||
|
value: r,
|
||||||
|
address: r,
|
||||||
|
});
|
||||||
expect(detail(result, "Min. received")).toBeUndefined();
|
expect(detail(result, "Min. received")).toBeUndefined();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1294,6 +1316,112 @@ describe("each transfer command's recipient", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// https://git.eeqj.de/sneak/AutistMask/issues/611: after the swap's own
|
||||||
|
// Amount, each PERMIT2_TRANSFER_FROM but one into the router gives the amount
|
||||||
|
// of the sender's tokens it moves, the token, and where it goes, whoever
|
||||||
|
// counts as paid.
|
||||||
|
describe("what each PERMIT2_TRANSFER_FROM moves", () => {
|
||||||
|
// The WETH/USDC pair on Ethereum Mainnet, as its V2 factory reports it.
|
||||||
|
const PAIR = "0xB4e16d0168e52d35CaCD2c6185b44281Ec28C9Dc";
|
||||||
|
const USDT_1 = 1000000n;
|
||||||
|
const USDT_1000000 = 1000000000000n;
|
||||||
|
|
||||||
|
// The lines between Amount and Token Out.
|
||||||
|
function afterAmount(result) {
|
||||||
|
const all = labels(result);
|
||||||
|
return result.details.slice(
|
||||||
|
all.indexOf("Amount") + 1,
|
||||||
|
all.indexOf("Token Out"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// The lines of a move of `amount` of `token`, then `to`.
|
||||||
|
const moved = (amount, token, symbol, to) => [
|
||||||
|
{ label: "Moved from the sender", value: amount },
|
||||||
|
{
|
||||||
|
label: "Token moved",
|
||||||
|
value: symbol + " (" + token + ")",
|
||||||
|
address: token,
|
||||||
|
isToken: true,
|
||||||
|
},
|
||||||
|
to,
|
||||||
|
];
|
||||||
|
const movedTo = (value) => ({ label: "Moved to", value });
|
||||||
|
const movedToAddress = (address) => ({
|
||||||
|
label: "Moved to",
|
||||||
|
value: address,
|
||||||
|
address,
|
||||||
|
});
|
||||||
|
|
||||||
|
test("1,000,000 USDT moved to another address, then a swap of 1 USDT", () => {
|
||||||
|
const result = decode(
|
||||||
|
execute(
|
||||||
|
permit2TransferFrom(USDT, OTHER, USDT_1000000),
|
||||||
|
v3SwapExactIn(MSG_SENDER, USDT_1, ONE_WETH, USDT, WETH),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
expect(detail(result, "Amount").value).toBe("1.0000 USDT");
|
||||||
|
expect(afterAmount(result)).toEqual(
|
||||||
|
moved("1000000.0000 USDT", USDT, "USDT", movedToAddress(OTHER)),
|
||||||
|
);
|
||||||
|
expect(paidTo(result)).toEqual([OTHER]);
|
||||||
|
expect(detail(result, "Min. paid out").value).toBe("1.0000 WETH");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("1 WETH moved to the pair the next V2 swap spends", () => {
|
||||||
|
const result = decode(
|
||||||
|
execute(
|
||||||
|
permit2TransferFrom(WETH, PAIR, ONE_WETH),
|
||||||
|
v2Swap(0x08, MSG_SENDER, 0n, USDC_1990, [WETH, USDC], false),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
expect(detail(result, "Amount").value).toBe(
|
||||||
|
"Whatever an earlier step sent to the pair (V2 already paid)",
|
||||||
|
);
|
||||||
|
expect(afterAmount(result)).toEqual(
|
||||||
|
moved(
|
||||||
|
"1.0000 WETH",
|
||||||
|
WETH,
|
||||||
|
"WETH",
|
||||||
|
movedTo("The V2 pair the next swap spends"),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
expect(paidTo(result)).toEqual([]);
|
||||||
|
expect(detail(result, "Min. received").value).toBe("1990.0000 USDC");
|
||||||
|
});
|
||||||
|
|
||||||
|
// Its Paid to and Token moved into the router lines already say what a
|
||||||
|
// move into the router moves, so it gets no lines after Amount.
|
||||||
|
test("2,000 USDT moved into the router, then a swap of 1 USDT from the router's balance, shows the 2,000 USDT once", () => {
|
||||||
|
const result = decode(
|
||||||
|
execute(
|
||||||
|
permit2TransferFrom(USDT, ADDRESS_THIS, USDT_2000),
|
||||||
|
v3SwapExactIn(MSG_SENDER, USDT_1, ONE_WETH, USDT, WETH, false),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
expect(detail(result, "Amount").value).toBe("1.0000 USDT");
|
||||||
|
expect(afterAmount(result)).toEqual([]);
|
||||||
|
expect(paidTo(result)).toEqual([movedIntoRouter("2000.0000 USDT")]);
|
||||||
|
expect(
|
||||||
|
result.details.filter((d) => d.value.includes("2000.0000 USDT")),
|
||||||
|
).toHaveLength(1);
|
||||||
|
expect(detail(result, "Min. paid out").value).toBe("1.0000 WETH");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("a move to address(1) goes to the sender, and each move is shown in step order", () => {
|
||||||
|
const result = decode(
|
||||||
|
execute(
|
||||||
|
permit2TransferFrom(USDT, MSG_SENDER, USDT_2000),
|
||||||
|
permit2TransferFrom(USDC, OTHER, USDC_1990),
|
||||||
|
swapToWeth(MSG_SENDER),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
expect(afterAmount(result)).toEqual([
|
||||||
|
...moved("2000.0000 USDT", USDT, "USDT", movedTo("The sender")),
|
||||||
|
...moved("1990.0000 USDC", USDC, "USDC", movedToAddress(OTHER)),
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
// A payout of only part of what the router holds leaves the rest there.
|
// A payout of only part of what the router holds leaves the rest there.
|
||||||
describe("a payout of part of the router's output", () => {
|
describe("a payout of part of the router's output", () => {
|
||||||
test.each([
|
test.each([
|
||||||
@@ -2787,3 +2915,26 @@ describe("the screens show each token moved into the router as an address", () =
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// The issue's example: 1,000,000 USDT moved to another address, then a swap
|
||||||
|
// of 1 USDT. Both screens show the amount moved and where it goes.
|
||||||
|
describe("the screens show what a PERMIT2_TRANSFER_FROM moves", () => {
|
||||||
|
const millionToOther = execute(
|
||||||
|
permit2TransferFrom(USDT, OTHER, 1000000000000n),
|
||||||
|
v3SwapExactIn(MSG_SENDER, 1000000n, ONE_WETH, USDT, WETH),
|
||||||
|
);
|
||||||
|
|
||||||
|
test.each([
|
||||||
|
{ screen: "approval", show: showApproval },
|
||||||
|
{ screen: "transaction detail", show: showDetail },
|
||||||
|
])("the $screen screen", async ({ show }) => {
|
||||||
|
const html = await show(millionToOther);
|
||||||
|
expect(html).toContain("1000000.0000 USDT");
|
||||||
|
// Each line is drawn in a div of its own; the Paid to line names
|
||||||
|
// OTHER too, so the Moved to line's own div is checked.
|
||||||
|
const movedToLine = html
|
||||||
|
.split('<div class="mb-2">')
|
||||||
|
.find((line) => line.includes(">Moved to<"));
|
||||||
|
expect(movedToLine).toContain('data-copy="' + OTHER + '"');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user