Porównaj commity
1
Commity
next
...
96bc83ff51
| Autor | SHA1 | Data | |
|---|---|---|---|
|
|
96bc83ff51 |
+12
-5
@@ -953,16 +953,23 @@ read:
|
|||||||
- `Unlimited`: on the ERC-20 `Amount` line, an `approve` of the `uint256`
|
- `Unlimited`: on the ERC-20 `Amount` line, an `approve` of the `uint256`
|
||||||
maximum, an unbounded allowance. On the swap's `Amount` line, any amount at or
|
maximum, an unbounded allowance. On the swap's `Amount` line, any amount at or
|
||||||
above the `uint160` maximum, whichever step set the line: a `PERMIT2_PERMIT`
|
above the `uint160` maximum, whichever step set the line: a `PERMIT2_PERMIT`
|
||||||
amount at that maximum, which is an unbounded permit, or a V2 or V3 exact-in
|
amount at that maximum, which is an unbounded permit, or a V2 or V3 exact-in,
|
||||||
or `WRAP_ETH` amount that large, which is not an allowance. The router's
|
V2 exact-out or `WRAP_ETH` amount that large, which is not an allowance. The
|
||||||
whole-balance value, `CONTRACT_BALANCE` (`2^255`), is one such amount.
|
router's whole-balance value, `CONTRACT_BALANCE` (`2^255`), is one such
|
||||||
|
amount.
|
||||||
|
- `Up to <amount>`: the swap's `Amount` line, when the step that set it is a V2
|
||||||
|
exact-out swap. The figure is that step's `amountInMax`, the most the swap may
|
||||||
|
spend, not the amount it spends; the wait, success and error screens show it
|
||||||
|
with the same words. When such a step also sets `Min. received`, that line
|
||||||
|
shows its `amountOut`, the exact amount it buys.
|
||||||
- `All available (V4 open delta)`: the swap's `Amount` line, when the amount it
|
- `All available (V4 open delta)`: the swap's `Amount` line, when the amount it
|
||||||
shows is a V4 exact-in `amountIn` of zero. V4 reads that zero as "use the
|
shows is a V4 exact-in `amountIn` of zero. V4 reads that zero as "use the
|
||||||
whole open delta", so the calldata states no quantity. The line shows the
|
whole open delta", so the calldata states no quantity. The line shows the
|
||||||
amount of one step that names an input token or amount: the last
|
amount of one step that names an input token or amount: the last
|
||||||
`PERMIT2_PERMIT` step if there is one, otherwise the first V2 or V3 exact-in,
|
`PERMIT2_PERMIT` step if there is one, otherwise the first V2 or V3 exact-in,
|
||||||
`WRAP_ETH` or V4 swap step, a V4 swap step giving the `amountIn` of its first
|
V2 exact-out, `WRAP_ETH` or V4 swap step. A V2 exact-out step gives its
|
||||||
readable exact-in action.
|
`amountInMax`, and a V4 swap step the `amountIn` of its first readable
|
||||||
|
exact-in action.
|
||||||
- `None (no minimum guaranteed)`: the swap's `Min. received` line, when the
|
- `None (no minimum guaranteed)`: the swap's `Min. received` line, when the
|
||||||
minimum it shows is zero, whether a V2, V3 or V4 swap's minimum or a
|
minimum it shows is zero, whether a V2, V3 or V4 swap's minimum or a
|
||||||
`BALANCE_CHECK_ERC20` step's `minBalance`. Before
|
`BALANCE_CHECK_ERC20` step's `minBalance`. Before
|
||||||
|
|||||||
+10
@@ -45,6 +45,16 @@ but the review is broader than any of them.
|
|||||||
|
|
||||||
# Completed Steps
|
# Completed Steps
|
||||||
|
|
||||||
|
- 2026-10-04: A Uniswap V2 exact-out swap (Universal Router command `0x09`) is
|
||||||
|
decoded on the approval screen
|
||||||
|
([#283](https://git.eeqj.de/sneak/AutistMask/issues/283)). `decode()` in
|
||||||
|
`src/shared/uniswap.js` had no arm for it, so the screen named the step and
|
||||||
|
showed no token or amount. The input side is the path's first token with
|
||||||
|
`amountInMax`, the output side the last token with `amountOut`. Because
|
||||||
|
`amountInMax` is a ceiling, the `Amount` line reads `Up to <amount>`, there
|
||||||
|
and on the wait, success and error screens. V3 exact-out (`0x01`) is still not
|
||||||
|
decoded.
|
||||||
|
|
||||||
- 2026-10-04: The flash line keeps to the one line it reserves at any message
|
- 2026-10-04: The flash line keeps to the one line it reserves at any message
|
||||||
length ([#252](https://git.eeqj.de/sneak/AutistMask/issues/252)). A message
|
length ([#252](https://git.eeqj.de/sneak/AutistMask/issues/252)). A message
|
||||||
that wrapped pushed the whole screen below it down. `#flash-msg` no longer
|
that wrapped pushed the whole screen below it down. `#flash-msg` no longer
|
||||||
|
|||||||
@@ -198,11 +198,6 @@ function decodeV2SwapExactIn(input) {
|
|||||||
// Decode V2_SWAP_EXACT_OUT (command 0x09) input bytes.
|
// Decode V2_SWAP_EXACT_OUT (command 0x09) input bytes.
|
||||||
// ABI: (address recipient, uint256 amountOut, uint256 amountInMax,
|
// ABI: (address recipient, uint256 amountOut, uint256 amountInMax,
|
||||||
// address[] path, bool payerIsUser)
|
// address[] path, bool payerIsUser)
|
||||||
//
|
|
||||||
// Nothing calls this: decode() has no 0x09 arm, so a V2 exact-out swap gets
|
|
||||||
// its command name and no token or amount detail. Kept for the fix, which is
|
|
||||||
// https://git.eeqj.de/sneak/AutistMask/issues/283.
|
|
||||||
// eslint-disable-next-line no-unused-vars
|
|
||||||
function decodeV2SwapExactOut(input) {
|
function decodeV2SwapExactOut(input) {
|
||||||
try {
|
try {
|
||||||
const d = coder.decode(
|
const d = coder.decode(
|
||||||
@@ -443,6 +438,7 @@ function decode(data, toAddress, sources) {
|
|||||||
|
|
||||||
let inputToken = null;
|
let inputToken = null;
|
||||||
let inputAmount = null;
|
let inputAmount = null;
|
||||||
|
let inputAmountIsMax = false;
|
||||||
let inputEstablished = false;
|
let inputEstablished = false;
|
||||||
let outputToken = null;
|
let outputToken = null;
|
||||||
let minOutput = null;
|
let minOutput = null;
|
||||||
@@ -465,15 +461,20 @@ function decode(data, toAddress, sources) {
|
|||||||
// output side by the last, because the final leg is what the user
|
// output side by the last, because the final leg is what the user
|
||||||
// receives. A half the establishing hop did not state stays null and
|
// receives. A half the establishing hop did not state stays null and
|
||||||
// the line says so, rather than being filled in from a different hop.
|
// the line says so, rather than being filled in from a different hop.
|
||||||
const setInput = (token, amount) => {
|
//
|
||||||
|
// `isMax` comes from that same hop: true when its amount is the most
|
||||||
|
// the swap may spend (an exact-out swap's amountInMax), not the
|
||||||
|
// amount spent.
|
||||||
|
const setInput = (token, amount, isMax = false) => {
|
||||||
inputToken = present(token) ? token : null;
|
inputToken = present(token) ? token : null;
|
||||||
inputAmount = present(amount) ? amount : null;
|
inputAmount = present(amount) ? amount : null;
|
||||||
|
inputAmountIsMax = isMax;
|
||||||
inputEstablished = true;
|
inputEstablished = true;
|
||||||
};
|
};
|
||||||
const setInputOnce = (token, amount) => {
|
const setInputOnce = (token, amount, isMax = false) => {
|
||||||
if (inputEstablished) return;
|
if (inputEstablished) return;
|
||||||
if (!present(token) && !present(amount)) return;
|
if (!present(token) && !present(amount)) return;
|
||||||
setInput(token, amount);
|
setInput(token, amount, isMax);
|
||||||
};
|
};
|
||||||
const setOutput = (token, amount) => {
|
const setOutput = (token, amount) => {
|
||||||
if (!present(token) && !present(amount)) return;
|
if (!present(token) && !present(amount)) return;
|
||||||
@@ -521,6 +522,15 @@ function decode(data, toAddress, sources) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (cmdId === 0x09) {
|
||||||
|
// Buys exactly amountOut and spends at most amountInMax.
|
||||||
|
const s = decodeV2SwapExactOut(inputs[i]);
|
||||||
|
if (s) {
|
||||||
|
setInputOnce(s.tokenIn, s.amountInMax, true);
|
||||||
|
setOutput(s.tokenOut, s.amountOut);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (cmdId === 0x0b) {
|
if (cmdId === 0x0b) {
|
||||||
const w = decodeWrapEth(inputs[i]);
|
const w = decodeWrapEth(inputs[i]);
|
||||||
if (w) {
|
if (w) {
|
||||||
@@ -613,6 +623,15 @@ function decode(data, toAddress, sources) {
|
|||||||
amount = { raw: OPEN_DELTA_AMOUNT, display: OPEN_DELTA_AMOUNT };
|
amount = { raw: OPEN_DELTA_AMOUNT, display: OPEN_DELTA_AMOUNT };
|
||||||
} else if (inputAmount >= MAX_UINT160) {
|
} else if (inputAmount >= MAX_UINT160) {
|
||||||
amount = { raw: "Unlimited", display: "Unlimited" };
|
amount = { raw: "Unlimited", display: "Unlimited" };
|
||||||
|
} else if (inputAmountIsMax) {
|
||||||
|
// A maximum is said to be one, in `raw` too: the wait and
|
||||||
|
// error screens show `raw` as the transaction's amount, and
|
||||||
|
// there a bare figure would read as the amount spent.
|
||||||
|
const most = amountText(inputAmount, inInfo);
|
||||||
|
amount = {
|
||||||
|
raw: "Up to " + most.raw,
|
||||||
|
display: "Up to " + most.display,
|
||||||
|
};
|
||||||
} else {
|
} else {
|
||||||
amount = amountText(inputAmount, inInfo);
|
amount = amountText(inputAmount, inInfo);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -75,6 +75,14 @@ function encodeV2SwapExactIn(recipient, amountIn, amountOutMin, pathAddrs) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Helper: encode a V2_SWAP_EXACT_OUT input (command 0x09)
|
||||||
|
function encodeV2SwapExactOut(recipient, amountOut, amountInMax, pathAddrs) {
|
||||||
|
return coder.encode(
|
||||||
|
["address", "uint256", "uint256", "address[]", "bool"],
|
||||||
|
[recipient, amountOut, amountInMax, pathAddrs, true],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// Helper: encode a V3_SWAP_EXACT_IN input (command 0x00)
|
// Helper: encode a V3_SWAP_EXACT_IN input (command 0x00)
|
||||||
function encodeV3SwapExactIn(recipient, amountIn, amountOutMin, pathTokens) {
|
function encodeV3SwapExactIn(recipient, amountIn, amountOutMin, pathTokens) {
|
||||||
// V3 path: token(20) + fee(3) + token(20) ...
|
// V3 path: token(20) + fee(3) + token(20) ...
|
||||||
@@ -223,6 +231,36 @@ describe("uniswap decoder", () => {
|
|||||||
expect(minOut.value).toContain("WETH");
|
expect(minOut.value).toContain("WETH");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Buy exactly 0.5 WETH for at most 1,500 USDC, paid by the user, sent to
|
||||||
|
// the caller (the router's MSG_SENDER recipient, address(1)).
|
||||||
|
test("decodes V2_SWAP_EXACT_OUT, stating the input amount as a maximum", () => {
|
||||||
|
const data = buildExecute(
|
||||||
|
"0x09", // V2_SWAP_EXACT_OUT
|
||||||
|
[
|
||||||
|
encodeV2SwapExactOut(
|
||||||
|
"0x0000000000000000000000000000000000000001",
|
||||||
|
500000000000000000n, // amountOut: 0.5 WETH
|
||||||
|
1500000000n, // amountInMax: 1,500 USDC (6 decimals)
|
||||||
|
[USDC_ADDR, WETH_ADDR],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
1767225600n,
|
||||||
|
);
|
||||||
|
|
||||||
|
const result = uniswap.decode(data, ROUTER_ADDR);
|
||||||
|
expect(result.name).toBe("Swap USDC → WETH");
|
||||||
|
expect(detail(result, "Token In").address).toBe(USDC_ADDR);
|
||||||
|
expect(detail(result, "Token Out").address).toBe(WETH_ADDR);
|
||||||
|
|
||||||
|
// The wait and error screens show rawValue as the amount, so it says
|
||||||
|
// "Up to" as well.
|
||||||
|
const amount = detail(result, "Amount");
|
||||||
|
expect(amount.value).toBe("Up to 1500.0000 USDC");
|
||||||
|
expect(amount.rawValue).toBe("Up to 1500.0000");
|
||||||
|
|
||||||
|
expect(detail(result, "Min. received").value).toBe("0.5000 WETH");
|
||||||
|
});
|
||||||
|
|
||||||
test("decodes V3_SWAP_EXACT_IN with known tokens", () => {
|
test("decodes V3_SWAP_EXACT_IN with known tokens", () => {
|
||||||
const data = buildExecute(
|
const data = buildExecute(
|
||||||
"0x00", // V3_SWAP_EXACT_IN
|
"0x00", // V3_SWAP_EXACT_IN
|
||||||
|
|||||||
Reference in New Issue
Block a user