fix: a balance check of another address sets no swap minimum (closes #600)
check / check (push) Canceled after 0s
e2e / e2e-chrome (push) Canceled after 0s
e2e / e2e-firefox (push) Canceled after 0s

BALANCE_CHECK_ERC20 takes (owner, token, minBalance), and every Universal
Router release that has it checks the owner's balance as written, without
map(), so address(1) and address(2) there are plain addresses. The decoder
read only the token and the minimum, so a check of another address's balance
read as Min. received. A check whose owner is not the transaction's sender now
sets neither Token Out nor the minimum, and its step reads "Balance Check (of
another address)"; one whose input does not decode reads "Balance Check (could
not be decoded)". A check of the sender keeps its reading, including the zero
reading for one the router lets fail.

Model: opus-5-5
This commit was merged in pull request #615.
This commit is contained in:
2026-10-19 16:27:41 +02:00
parent c99772a520
commit 3db066413e
4 changed files with 176 additions and 38 deletions
+11 -10
View File
@@ -71,6 +71,8 @@ function exactInParams(currencyIn, path, amountIn, amountOutMin) {
// BALANCE_CHECK_ERC20 (command 0x0e): (address owner, address token,
// uint256 minBalance). It names the output token and nothing about the input.
// Its owner is USER, the transaction's sender: a check of any other address
// names no output token.
function balanceCheck(token, minBalance) {
return coder.encode(
["address", "address", "uint256"],
@@ -78,8 +80,13 @@ function balanceCheck(token, minBalance) {
);
}
// A transaction from USER.
function decode(data) {
return uniswap.decode(data, ROUTER, "0x1", {}, USER);
}
function detail(data, label) {
const decoded = uniswap.decode(data, ROUTER, "0x1", {});
const decoded = decode(data);
expect(decoded).not.toBeNull();
return decoded.details.find((d) => d.label === label);
}
@@ -113,9 +120,7 @@ describe("an execute() whose input currency never decoded", () => {
});
test("does not name ETH in the swap title either", () => {
expect(uniswap.decode(data(), ROUTER, "0x1", {}).name).toBe(
"Uniswap Swap",
);
expect(decode(data()).name).toBe("Uniswap Swap");
});
});
@@ -125,9 +130,7 @@ describe("an execute() that names only an output token", () => {
test("refuses the input rather than defaulting it to ETH", () => {
expect(detail(data(), "Token In").value).toBe(REFUSAL);
expect(uniswap.decode(data(), ROUTER, "0x1", {}).name).toBe(
"Uniswap Swap",
);
expect(decode(data()).name).toBe("Uniswap Swap");
});
test("still states the output it did establish", () => {
@@ -175,9 +178,7 @@ describe("native ETH in, which V4 spells as the zero address", () => {
expect(detail(data, "Token In").value).toBe("ETH (native)");
expect(detail(data, "Amount").value).toBe("1.0000 ETH");
expect(uniswap.decode(data, ROUTER, "0x1", {}).name).toBe(
"Swap ETH → USDT",
);
expect(decode(data).name).toBe("Swap ETH → USDT");
});
test("WRAP_ETH is still ETH at 18 decimals", () => {