Compare commits
2
Commits
09e6454545
...
f8f20155ab
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f8f20155ab | ||
|
|
070ad82338 |
@@ -500,9 +500,11 @@ papered over:
|
|||||||
forced by clicking Reveal and the settings gear in one page task, which a
|
forced by clicking Reveal and the settings gear in one page task, which a
|
||||||
person cannot do; the case a person can hit, the first decrypt after the popup
|
person cannot do; the case a person can hit, the first decrypt after the popup
|
||||||
opens while libsodium is still loading, is not driven.
|
opens while libsodium is still loading, is not driven.
|
||||||
- Approval windows open at 1280x720 whatever size the extension asks for, so
|
- Approval windows open at 1280x720 whatever size the extension asks for. The
|
||||||
whether an approval screen fits its window is checked in the Firefox suite
|
site-connection prompt and the transaction approval are checked for sideways
|
||||||
only.
|
scrolling at 396x600 by sizing the page itself, which leaves out the window's
|
||||||
|
frame and the page widening its window, so whether an approval screen fits its
|
||||||
|
window is checked in the Firefox suite only.
|
||||||
|
|
||||||
Any test that drives a failure path on purpose declares the `console.error` it
|
Any test that drives a failure path on purpose declares the `console.error` it
|
||||||
is about to provoke, via `errors.expect()`. That is not a mute: the declaration
|
is about to provoke, via `errors.expect()`. That is not a mute: the declaration
|
||||||
@@ -3185,7 +3187,8 @@ Currently supported:
|
|||||||
step whose input it cannot read, in whole or in part, and a step it fails on.
|
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
|
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
|
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`
|
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
|
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
|
total does not count gives three lines after the `Amount` line (after
|
||||||
@@ -3193,11 +3196,16 @@ Currently supported:
|
|||||||
`Token In` after an amount that is not a quantity (a V4 open delta,
|
`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
|
`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
|
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
|
know. When the step that sets the input side states no amount, the user pays
|
||||||
`Amount` line, that step gets them too whenever another take's lines give a
|
it some of its input token, and there is no `Amount` line, that step gets them
|
||||||
figure, so that figure cannot read as all that leaves. They come in step
|
too whenever another take's lines give a figure, or a `PERMIT2_TRANSFER_FROM`
|
||||||
order, the step that sets the input side first when it has them:
|
of its token gives one, on the lines below or, into the router, on its
|
||||||
`Moved from the sender`, its amount, worded as the `Amount` line words one, or
|
`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;
|
`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
|
||||||
|
|||||||
@@ -90,7 +90,7 @@
|
|||||||
<div id="add-wallet-section-mnemonic">
|
<div id="add-wallet-section-mnemonic">
|
||||||
<!-- The die button shares the instruction's row: on a row
|
<!-- The die button shares the instruction's row: on a row
|
||||||
of its own it pushes Import below the fold at
|
of its own it pushes Import below the fold at
|
||||||
360x600. -->
|
396x600. -->
|
||||||
<div class="mb-2 flex justify-between items-start gap-2">
|
<div class="mb-2 flex justify-between items-start gap-2">
|
||||||
<p>
|
<p>
|
||||||
Enter your 12 or 24 word recovery phrase below, or
|
Enter your 12 or 24 word recovery phrase below, or
|
||||||
@@ -212,7 +212,7 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<!-- The error line sits beside Import, not above it: at
|
<!-- The error line sits beside Import, not above it: at
|
||||||
360x600 the button already starts near the bottom of
|
396x600 the button already starts near the bottom of
|
||||||
the popup, and a line of its own would push it below
|
the popup, and a line of its own would push it below
|
||||||
the fold. The longest error fits on one line here. -->
|
the fold. The longest error fits on one line here. -->
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
|
|||||||
+23
-7
@@ -1204,6 +1204,10 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
|
|||||||
let inputToken = null;
|
let inputToken = null;
|
||||||
let inputAmount = null;
|
let inputAmount = null;
|
||||||
let inputEstablished = false;
|
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
|
// The last PERMIT2_PERMIT this decoder read, or null (see
|
||||||
// permitStands after the loop).
|
// permitStands after the loop).
|
||||||
let permit = null;
|
let permit = null;
|
||||||
@@ -1756,6 +1760,7 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
|
|||||||
// this decoder could not read may take anything: the
|
// this decoder could not read may take anything: the
|
||||||
// router still runs it.
|
// router still runs it.
|
||||||
const setInputHere = inputEstablished && !inputSetBefore;
|
const setInputHere = inputEstablished && !inputSetBefore;
|
||||||
|
if (setInputHere) inputPaidByUser = paidIn !== undefined;
|
||||||
if (v4) {
|
if (v4) {
|
||||||
for (const p of v4.paidByUser) {
|
for (const p of v4.paidByUser) {
|
||||||
if (setInputHere && p === paidIn) continue;
|
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.
|
// (see spentUnread()), or a token it does not know.
|
||||||
const spentFromSender = [];
|
const spentFromSender = [];
|
||||||
let total = null;
|
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
|
// 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.
|
// starts from it and it stated no amount (see below), or null.
|
||||||
let firstTakeUnstated = null;
|
let firstTakeUnstated = null;
|
||||||
if (permit === null || sameAddress(inputToken, permit.token)) {
|
if (permit === null || sameAddress(inputToken, permit.token)) {
|
||||||
total = inputAmount;
|
total = inputAmount;
|
||||||
if (inputEstablished && !present(inputAmount)) {
|
if (hasFirstTake && !present(inputAmount)) {
|
||||||
firstTakeUnstated = { token: inputToken, amount: null };
|
firstTakeUnstated = { token: inputToken, amount: null };
|
||||||
}
|
}
|
||||||
} else if (inputEstablished) {
|
} else if (hasFirstTake) {
|
||||||
// The step that set the input side spent a token other than the
|
// 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.
|
// permit's, or one not known, so the total starts with no figure.
|
||||||
spentFromSender.push({ token: inputToken, amount: inputAmount });
|
spentFromSender.push({ token: inputToken, amount: inputAmount });
|
||||||
@@ -1873,14 +1885,18 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
|
|||||||
if (ofTokenIn && !present(t.amount)) total = null;
|
if (ofTokenIn && !present(t.amount)) total = null;
|
||||||
}
|
}
|
||||||
inputAmount = permitStands ? permit.amount : total;
|
inputAmount = permitStands ? permit.amount : total;
|
||||||
// With no Amount figure, a later take's own figure could read as all
|
// With no Amount figure, a later take's own figure, or that of a
|
||||||
// that leaves, so the step that set the input side, when it stated
|
// PERMIT2_TRANSFER_FROM of the same token, into the router or not,
|
||||||
// no amount, then gets lines of its own too, first, saying its amount
|
// could read as all that leaves, so the step that set the input side,
|
||||||
// is not known.
|
// 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 (
|
if (
|
||||||
!present(inputAmount) &&
|
!present(inputAmount) &&
|
||||||
firstTakeUnstated !== null &&
|
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);
|
spentFromSender.unshift(firstTakeUnstated);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -101,7 +101,7 @@ describe("the flash line the message is shown in", () => {
|
|||||||
//
|
//
|
||||||
// The line cuts a message too long for it with an ellipsis (see
|
// The line cuts a message too long for it with an ellipsis (see
|
||||||
// showFlash() in src/popup/views/helpers.js). The assertions that
|
// showFlash() in src/popup/views/helpers.js). The assertions that
|
||||||
// measure that, in a real browser at the documented 360x600 popup, are
|
// measure that, in a real browser at the documented 396x600 popup, are
|
||||||
// "a rejected dust threshold shifts no layout (#233)" and "an over-long
|
// "a rejected dust threshold shifts no layout (#233)" and "an over-long
|
||||||
// flash message keeps to one line (#252)" in tests/e2e/run.js, run by
|
// flash message keeps to one line (#252)" in tests/e2e/run.js, run by
|
||||||
// make test-e2e. They are not in make check because REPO_POLICIES.md
|
// make test-e2e. They are not in make check because REPO_POLICIES.md
|
||||||
|
|||||||
+20
-9
@@ -60,6 +60,14 @@ const TEST_TIMEOUT_MS = 120000;
|
|||||||
// trailing drain in main().
|
// trailing drain in main().
|
||||||
const TRAILING_WATCH_MS = 1500;
|
const TRAILING_WATCH_MS = 1500;
|
||||||
|
|
||||||
|
// The popup's size, as README gives it: `body` in src/popup/styles/main.css
|
||||||
|
// is 396px wide, and src/background/index.js opens an approval window at
|
||||||
|
// the same 396x600. Pages in this context otherwise get Playwright's
|
||||||
|
// 1280x720 default, where the page never scrolls sideways and a button
|
||||||
|
// below the popup's fold is still on screen, so a layout measured there
|
||||||
|
// passes whatever the page does.
|
||||||
|
const POPUP_VIEWPORT = { width: 396, height: 600 };
|
||||||
|
|
||||||
const tests = [];
|
const tests = [];
|
||||||
|
|
||||||
function test(name, fn) {
|
function test(name, fn) {
|
||||||
@@ -1885,12 +1893,6 @@ test("a network switch in Settings shows and removes the testnet banner at once
|
|||||||
|
|
||||||
// ------------------------------------------------ dust threshold (#233)
|
// ------------------------------------------------ dust threshold (#233)
|
||||||
|
|
||||||
// The popup size README documents the UI as designed for. Pages in this
|
|
||||||
// context otherwise get Playwright's 1280x720 default, at which the flash
|
|
||||||
// line has room for any plausible message and never wraps — measuring
|
|
||||||
// there would pass for every string and prove nothing.
|
|
||||||
const POPUP_VIEWPORT = { width: 360, height: 600 };
|
|
||||||
|
|
||||||
// Everything below the flash line that must not move when it fills, plus
|
// Everything below the flash line that must not move when it fills, plus
|
||||||
// the height of the line itself. Runs in the page.
|
// the height of the line itself. Runs in the page.
|
||||||
//
|
//
|
||||||
@@ -2139,7 +2141,7 @@ test("a password error moves nothing on any screen (#297)", async (env) => {
|
|||||||
|
|
||||||
// ------------------------------------------ add wallet Import button (#493)
|
// ------------------------------------------ add wallet Import button (#493)
|
||||||
|
|
||||||
// At 360x600 the add wallet screen's Import button already starts near the
|
// At 396x600 the add wallet screen's Import button already starts near the
|
||||||
// bottom of the popup, so its password error line sits beside the button
|
// bottom of the popup, so its password error line sits beside the button
|
||||||
// rather than above it. Measures the button and the line empty and again
|
// rather than above it. Measures the button and the line empty and again
|
||||||
// filled with the longest error addWallet.js puts there. Runs in the page.
|
// filled with the longest error addWallet.js puts there. Runs in the page.
|
||||||
@@ -3431,10 +3433,14 @@ test("six failed receipt lookups in a row end on the unreachable-network message
|
|||||||
// timeout ("<iframe id=\"pwn\"> intercepts pointer events") rather than as
|
// timeout ("<iframe id=\"pwn\"> intercepts pointer events") rather than as
|
||||||
// anything that names the defect.
|
// anything that names the defect.
|
||||||
|
|
||||||
// Verbatim from the issue's reproduction.
|
// The issue's reproduction, with the frame sized to the popup.
|
||||||
const HOSTILE_SYMBOL =
|
const HOSTILE_SYMBOL =
|
||||||
'<iframe id="pwn" src="https://dapp.e2e.test/" ' +
|
'<iframe id="pwn" src="https://dapp.e2e.test/" ' +
|
||||||
'style="position:fixed;left:0;top:0;width:360px;height:600px;z-index:99999"></iframe>';
|
'style="position:fixed;left:0;top:0;width:' +
|
||||||
|
POPUP_VIEWPORT.width +
|
||||||
|
"px;height:" +
|
||||||
|
POPUP_VIEWPORT.height +
|
||||||
|
'px;z-index:99999"></iframe>';
|
||||||
|
|
||||||
// What a correctly escaped and capped render of it reads as: the first
|
// What a correctly escaped and capped render of it reads as: the first
|
||||||
// MAX_SYMBOL_LENGTH-1 characters and an ellipsis. Spelled out rather than
|
// MAX_SYMBOL_LENGTH-1 characters and an ellipsis. Spelled out rather than
|
||||||
@@ -5026,7 +5032,12 @@ function addressRowReport(page) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Measured at the popup's 396x600 (POPUP_VIEWPORT). Every page in this
|
||||||
|
// context, approval windows included, is otherwise 1280x720 whatever size
|
||||||
|
// the extension asks for, and a page that wide never scrolls sideways. The
|
||||||
|
// page keeps the new size afterwards.
|
||||||
async function assertAddressesFit(page, where) {
|
async function assertAddressesFit(page, where) {
|
||||||
|
await page.setViewportSize(POPUP_VIEWPORT);
|
||||||
const report = await addressRowReport(page);
|
const report = await addressRowReport(page);
|
||||||
assert(
|
assert(
|
||||||
report.rows.length > 0,
|
report.rows.length > 0,
|
||||||
|
|||||||
@@ -2318,6 +2318,24 @@ describe("uniswap decoder", () => {
|
|||||||
encodeV4TakeAll(WETH_ADDR, 0n),
|
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([
|
test.each([
|
||||||
{
|
{
|
||||||
kind: "a V4 exact-out step with a SETTLE of OPEN_DELTA by the user",
|
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,
|
first: V4_EXACT_OUT_600,
|
||||||
lines: [...UNKNOWN_USDT, ...UP_TO_400_USDT],
|
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
|
// Settled by the user, the step's amount would be what that
|
||||||
// SETTLE pays, which the decoder cannot state here.
|
// 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
|
// 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
|
// take's unknown amount leaves no figure at all, and other ways the
|
||||||
// step that sets the input side states no amount.
|
// 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,
|
// A V2 exact-out step makes every amount the swap steps spend a maximum,
|
||||||
// the total included.
|
// the total included.
|
||||||
test("a split with a V2 exact-out step the user pays for shows the total as a maximum", () => {
|
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