fix: a balance check of another address sets no swap minimum (closes #600)
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:
+34
-10
@@ -388,7 +388,13 @@ function decodePermit2(input) {
|
||||
}
|
||||
}
|
||||
|
||||
// Decode BALANCE_CHECK_ERC20 (command 0x0e) input bytes.
|
||||
// Decode BALANCE_CHECK_ERC20 (command 0x0e) input bytes. The router passes the
|
||||
// check when `owner` holds at least `minBalance` of `token`, and reads `owner`
|
||||
// as written, without map(), on every release that has the command:
|
||||
// - UniversalRouterV1_2_V2Support: https://github.com/Uniswap/universal-router/blob/41183d6eb154f0ab0e74a0e911a5ef9ea51fc4bd/contracts/base/Dispatcher.sol
|
||||
// - UniversalRouterV2: https://github.com/Uniswap/universal-router/blob/3663f6db6e2fe121753cd2d899699c2dc75dca86/contracts/base/Dispatcher.sol
|
||||
// - UniversalRouterV2_1_1 and UniversalRouterV2_1_2, the same file at both
|
||||
// commits: https://github.com/Uniswap/universal-router/blob/802fe4c18f47300e0f183e2a42e9146ec2ea9fc3/contracts/base/Dispatcher.sol
|
||||
// ABI: (address owner, address token, uint256 minBalance)
|
||||
function decodeBalanceCheck(input) {
|
||||
try {
|
||||
@@ -1087,15 +1093,33 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
|
||||
}
|
||||
} else if (cmdId === 0x0e) {
|
||||
const b = decodeBalanceCheck(inputs[i]);
|
||||
// The router passes this check whenever the owner holds at
|
||||
// least minBalance, so a zero one guarantees nothing and
|
||||
// does not replace a minimum an earlier step stated. A
|
||||
// check the router lets fail guarantees nothing either, so
|
||||
// it is read as a zero one. Any other minBalance sets the
|
||||
// output side as a swap does. It pays nobody.
|
||||
const minBalance = b && !allowedToFail ? b.minBalance : 0n;
|
||||
if (b && !(minBalance === 0n && present(minOutput))) {
|
||||
setOutput(b.token, minBalance);
|
||||
if (!b) {
|
||||
// The router still runs a check whose input does not
|
||||
// decode, such as an owner word with a nonzero top
|
||||
// byte, of which it reads the last 20 bytes, so the
|
||||
// owner may be any address. It sets no side, and the
|
||||
// step says so.
|
||||
commandNames[i] += " (could not be decoded)";
|
||||
} else if (b.owner.toLowerCase() !== sender) {
|
||||
// The router reads the owner as written (see
|
||||
// decodeBalanceCheck()), so address(1) and address(2)
|
||||
// are not the sender and the router here. A check of
|
||||
// any address but the sender's says nothing about
|
||||
// what the user receives: it sets no side, and the
|
||||
// step says so.
|
||||
commandNames[i] += " (of another address)";
|
||||
} else {
|
||||
// The router passes this check whenever the owner
|
||||
// holds at least minBalance, so a zero one guarantees
|
||||
// nothing and does not replace a minimum an earlier
|
||||
// step stated. A check the router lets fail guarantees
|
||||
// nothing either, so it is read as a zero one. Any
|
||||
// other minBalance sets the output side as a swap
|
||||
// does. It pays nobody.
|
||||
const minBalance = allowedToFail ? 0n : b.minBalance;
|
||||
if (!(minBalance === 0n && present(minOutput))) {
|
||||
setOutput(b.token, minBalance);
|
||||
}
|
||||
}
|
||||
} else if (cmdId === 0x00) {
|
||||
const s = decodeV3SwapExactIn(inputs[i]);
|
||||
|
||||
Reference in New Issue
Block a user