fix: the swap decoder reads a V2 already-paid zero and a zero balance check as the router does (closes #415)
A V2 exact-in amountIn of zero is the router's ALREADY_PAID marker: an earlier step sent the tokens to the pair and the swap spends all of them. Amount showed 0.0000 for it; it now reads "Whatever an earlier step sent to the pair (V2 already paid)", in the style of the V4 open delta line. A BALANCE_CHECK_ERC20 passes whenever the balance is at least minBalance, so a zero one guarantees nothing. It now sets the output side only when that side holds no minimum at the point the check is reached; a nonzero one sets the output side as before. README's Display Consistency text and TODO.md are updated to match. Model: opus-5-5
This commit is contained in:
@@ -831,6 +831,104 @@ describe("uniswap decoder", () => {
|
||||
expect(detail(result, "Min. received").value).toBe("0.9900 USDC");
|
||||
});
|
||||
|
||||
// https://git.eeqj.de/sneak/AutistMask/issues/415 — the router's V2
|
||||
// exact-in reads an amountIn of zero as universal-router
|
||||
// Constants.ALREADY_PAID: an earlier step sent the tokens to the pair, and
|
||||
// the swap uses all of them. Against 375998b this read "0.0000 USDT".
|
||||
test("a V2 exact-in already-paid amountIn is named, not printed as zero", () => {
|
||||
const data = buildExecute(
|
||||
"0x08",
|
||||
[
|
||||
encodeV2SwapExactIn(
|
||||
USER_ADDR,
|
||||
0n, // Constants.ALREADY_PAID
|
||||
500000000000000n,
|
||||
[USDT_ADDR, WETH_ADDR],
|
||||
),
|
||||
],
|
||||
9999999999n,
|
||||
);
|
||||
|
||||
const result = uniswap.decode(data, ROUTER_ADDR);
|
||||
expect(result).not.toBeNull();
|
||||
|
||||
expect(detail(result, "Token In").value).toContain("USDT");
|
||||
expect(detail(result, "Amount").value).toBe(
|
||||
"Whatever an earlier step sent to the pair (V2 already paid)",
|
||||
);
|
||||
expect(detail(result, "Amount").rawValue).toBe(
|
||||
"Whatever an earlier step sent to the pair (V2 already paid)",
|
||||
);
|
||||
expect(detail(result, "Min. received").value).toBe("0.0005 WETH");
|
||||
});
|
||||
|
||||
// https://git.eeqj.de/sneak/AutistMask/issues/415 — the router passes a
|
||||
// BALANCE_CHECK_ERC20 whenever the balance is at least minBalance, so a
|
||||
// zero one guarantees nothing. Against 375998b it replaced the swap's
|
||||
// output side: Token Out = USDC, Min. received = "None (no minimum
|
||||
// guaranteed)".
|
||||
test("a zero balance check keeps the minimum a swap step stated", () => {
|
||||
const data = buildExecute(
|
||||
solidityPacked(["uint8", "uint8"], [0x08, 0x0e]),
|
||||
[
|
||||
encodeV2SwapExactIn(USER_ADDR, 1000000n, 500000000000000n, [
|
||||
USDT_ADDR,
|
||||
WETH_ADDR,
|
||||
]),
|
||||
encodeBalanceCheck(USER_ADDR, USDC_ADDR, 0n),
|
||||
],
|
||||
9999999999n,
|
||||
);
|
||||
|
||||
const result = uniswap.decode(data, ROUTER_ADDR);
|
||||
expect(result).not.toBeNull();
|
||||
|
||||
expect(detail(result, "Token Out").value).toContain("WETH");
|
||||
expect(detail(result, "Min. received").value).toBe("0.0005 WETH");
|
||||
});
|
||||
|
||||
// A nonzero balance check still replaces the output side, as before.
|
||||
test("a nonzero balance check replaces the minimum a swap step stated", () => {
|
||||
const data = buildExecute(
|
||||
solidityPacked(["uint8", "uint8"], [0x08, 0x0e]),
|
||||
[
|
||||
encodeV2SwapExactIn(USER_ADDR, 1000000n, 500000000000000n, [
|
||||
USDT_ADDR,
|
||||
WETH_ADDR,
|
||||
]),
|
||||
encodeBalanceCheck(USER_ADDR, USDC_ADDR, 2000000n),
|
||||
],
|
||||
9999999999n,
|
||||
);
|
||||
|
||||
const result = uniswap.decode(data, ROUTER_ADDR);
|
||||
expect(result).not.toBeNull();
|
||||
|
||||
expect(detail(result, "Token Out").value).toContain("USDC");
|
||||
expect(detail(result, "Min. received").value).toBe("2.0000 USDC");
|
||||
});
|
||||
|
||||
// With no minimum stated before it, a zero balance check is what sets the
|
||||
// output side, and it guarantees nothing.
|
||||
test("a zero balance check with no earlier minimum states no minimum", () => {
|
||||
const data = buildExecute(
|
||||
solidityPacked(["uint8", "uint8"], [0x0b, 0x0e]),
|
||||
[
|
||||
encodeWrapEth(ROUTER_ADDR, 1000000000000000000n),
|
||||
encodeBalanceCheck(USER_ADDR, USDT_ADDR, 0n),
|
||||
],
|
||||
9999999999n,
|
||||
);
|
||||
|
||||
const result = uniswap.decode(data, ROUTER_ADDR);
|
||||
expect(result).not.toBeNull();
|
||||
|
||||
expect(detail(result, "Token Out").value).toContain("USDT");
|
||||
expect(detail(result, "Min. received").value).toBe(
|
||||
"None (no minimum guaranteed)",
|
||||
);
|
||||
});
|
||||
|
||||
// Pins what https://git.eeqj.de/sneak/AutistMask/pulls/356 changed without
|
||||
// testing: a non-swap execute() carrying only PERMIT2_PERMIT names no
|
||||
// output currency, so it says so and titles itself "Uniswap Swap" rather
|
||||
|
||||
Reference in New Issue
Block a user