fix: a V4 SETTLE of ETH paid from the router reads as moved from the sender (closes #682)
V4Router pays a SETTLE of ETH from the router's own ETH, whichever payer it names. The router holds the transaction's value. So a SETTLE of ETH that names the router as payer now counts as paid by the user. When an earlier step left ETH in the router, such a SETTLE gets lines of its own. Its line reads "Up to" and its amount, and that amount never joins the Amount total. A SETTLE of CONTRACT_BALANCE reads as an amount the decoder does not know. One existing row in tests/uniswapRecipient.test.js now expects the ETH lines. Its SETTLE spends all the router's ETH, the transaction's value included. Model: opus-5-5
This commit is contained in:
@@ -3439,6 +3439,77 @@ describe("uniswap decoder", () => {
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
// https://git.eeqj.de/sneak/AutistMask/issues/682 — V4 pays a SETTLE
|
||||
// of ETH from the router's own ETH, whichever payer the SETTLE names,
|
||||
// and the router holds the transaction's value. On next at 36b2002 a
|
||||
// SETTLE of 1 ETH that names the router as payer gave no ETH line.
|
||||
describe("a V4 SETTLE of ETH that names the router as payer", () => {
|
||||
const ONE_ETH = 1000000000000000000n;
|
||||
// A V4 swap of `amount` of ETH for USDC. The SETTLE names the
|
||||
// router as payer.
|
||||
const v4EthFromRouter = (amount) =>
|
||||
encodeV4Swap(
|
||||
new Uint8Array([
|
||||
V4_SWAP_EXACT_IN_SINGLE_ID,
|
||||
V4_SETTLE_ID,
|
||||
V4_TAKE_ALL_ID,
|
||||
]),
|
||||
[
|
||||
encodeV4ExactInSingle(ZERO_ADDR, USDC_ADDR, amount, 1n),
|
||||
settle(ZERO_ADDR, amount, false),
|
||||
encodeV4TakeAll(USDC_ADDR, 1n),
|
||||
],
|
||||
);
|
||||
|
||||
test("after a V3 exact-in of 600 USDT, shows the 1 ETH moved from the sender", () => {
|
||||
const result = decodeSteps(
|
||||
[0x00, 0x10],
|
||||
[V3_600, v4EthFromRouter(ONE_ETH)],
|
||||
);
|
||||
expect(linesBetweenTokens(result)).toEqual([
|
||||
["Amount", "600.0000 USDT"],
|
||||
["Moved from the sender", "1.0000 ETH"],
|
||||
["Token moved", "ETH"],
|
||||
["Moved to", "The swap step that spends it"],
|
||||
]);
|
||||
});
|
||||
|
||||
// The UNWRAP_WETH leaves ETH in the router. The SETTLE may spend
|
||||
// that ETH before the transaction's value.
|
||||
test("after an UNWRAP_WETH into the router, shows at most 1 ETH moved from the sender", () => {
|
||||
const result = decodeSteps(
|
||||
[0x00, 0x0c, 0x10],
|
||||
[
|
||||
encodeV3SwapExactIn(ROUTER_ADDR, USDT_600, 1n, [
|
||||
USDT_ADDR,
|
||||
WETH_ADDR,
|
||||
]),
|
||||
encodeWrapEth(ROUTER_ADDR, 0n), // UNWRAP_WETH, same encoding
|
||||
v4EthFromRouter(ONE_ETH),
|
||||
],
|
||||
);
|
||||
expect(linesBetweenTokens(result)).toEqual([
|
||||
["Amount", "600.0000 USDT"],
|
||||
["Moved from the sender", "Up to 1.0000 ETH"],
|
||||
["Token moved", "ETH"],
|
||||
["Moved to", "The swap step that spends it"],
|
||||
]);
|
||||
});
|
||||
|
||||
test("after a WRAP_ETH of 0.5 ETH, adds the SETTLE's 0.5 ETH to the Amount line", () => {
|
||||
const result = decodeSteps(
|
||||
[0x0b, 0x10],
|
||||
[
|
||||
encodeWrapEth(ROUTER_ADDR, ONE_ETH / 2n),
|
||||
v4EthFromRouter(ONE_ETH / 2n),
|
||||
],
|
||||
);
|
||||
expect(linesBetweenTokens(result)).toEqual([
|
||||
["Amount", "1.0000 ETH"],
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// https://git.eeqj.de/sneak/AutistMask/issues/359 — the output half, in
|
||||
|
||||
@@ -1661,9 +1661,18 @@ describe("any token left in the router is not received", () => {
|
||||
),
|
||||
min: "1990.0000 USDC",
|
||||
},
|
||||
{
|
||||
kind: "a swap paying WETH to the router, an UNWRAP_WETH into the router, then a V4 swap settling all the ETH from the router",
|
||||
data: execute(
|
||||
])("$kind reads as a swap paid to the user", ({ data, min }) => {
|
||||
const result = decode(data);
|
||||
expect(labels(result)).toEqual(PAID_TO_USER);
|
||||
expect(detail(result, "Min. received").value).toBe(min);
|
||||
});
|
||||
|
||||
// https://git.eeqj.de/sneak/AutistMask/issues/682 — the SETTLE spends all
|
||||
// the router's ETH, the transaction's value included, so the sender pays
|
||||
// an amount the calldata does not state.
|
||||
test("a swap paying WETH to the router, an UNWRAP_WETH into the router, then a V4 swap settling all the ETH from the router reads as a swap paid to the user", () => {
|
||||
const result = decode(
|
||||
execute(
|
||||
swapToWeth(ADDRESS_THIS),
|
||||
unwrapWeth(ADDRESS_THIS),
|
||||
v4Swap(
|
||||
@@ -1672,12 +1681,23 @@ describe("any token left in the router is not received", () => {
|
||||
takeAll(USDC),
|
||||
),
|
||||
),
|
||||
min: "1990.0000 USDC",
|
||||
},
|
||||
])("$kind reads as a swap paid to the user", ({ data, min }) => {
|
||||
const result = decode(data);
|
||||
expect(labels(result)).toEqual(PAID_TO_USER);
|
||||
expect(detail(result, "Min. received").value).toBe(min);
|
||||
);
|
||||
expect(labels(result)).toEqual([
|
||||
"Protocol",
|
||||
"Token In",
|
||||
"Amount",
|
||||
"Moved from the sender",
|
||||
"Token moved",
|
||||
"Moved to",
|
||||
"Token Out",
|
||||
"Min. received",
|
||||
"Steps",
|
||||
"Deadline",
|
||||
]);
|
||||
expect(detail(result, "Moved from the sender").value).toBe(
|
||||
"Unknown (could not be decoded)",
|
||||
);
|
||||
expect(detail(result, "Min. received").value).toBe("1990.0000 USDC");
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user