Description
In _dynamicBalancesXD the helper _loadBalances() decides whether balances must be initialized based on a boolean that is set if ANY token in the provided tokens array has a non‑zero stored balance, not specifically tokenIn or tokenOut. If a different token for the same orderHash has a non‑zero stored balance while the current tokenIn/tokenOut balances are still zero, _loadBalances() returns true and _initBalances() is not called. As a result, ctx.swap.balanceIn and ctx.swap.balanceOut remain zero and downstream swap instructions (e.g., LimitSwap/XYC) revert due to zero balances, breaking multi‑token programs and preventing fills.
function _loadBalances(Context memory ctx, uint256 tokensCount, bytes calldata tokens) private view returns (bool hasNonZeroBalances) {
hasNonZeroBalances = false;
bool foundTokenIn = false;
bool foundTokenOut = false;
for (uint256 i = 0; i < tokensCount; i++) {
address token = address(bytes20(tokens.slice(i * 20)));
uint256 balance = balances[ctx.query.orderHash][token];
hasNonZeroBalances = hasNonZeroBalances || (balance != 0);
if (token == ctx.query.tokenIn) {
ctx.swap.balanceIn = balance;
foundTokenIn = true;
}
else if (token == ctx.query.tokenOut) {
ctx.swap.balanceOut = balance;
foundTokenOut= true;
}
if (foundTokenIn && foundTokenOut && hasNonZeroBalances) {
return hasNonZeroBalances; // may be true due to an unrelated token
}
}
require(foundTokenIn && foundTokenOut, DynamicBalancesLoadingRequiresSettingBothBalances(...));
}
This allows a previously used token in the same order to mask uninitialized balances for a new token pair, causing unexpected reverts and preventing legitimate swaps.
Severity
- Downstream swap instructions in ctx.runLoop require non-zero ctx.swap.balanceIn/Out and revert when zero.
- Integrations may include extra tokens in the Balances args for multi-token programs, making the condition reachable.
Description
In _dynamicBalancesXD the helper _loadBalances() decides whether balances must be initialized based on a boolean that is set if ANY token in the provided tokens array has a non‑zero stored balance, not specifically tokenIn or tokenOut. If a different token for the same orderHash has a non‑zero stored balance while the current tokenIn/tokenOut balances are still zero, _loadBalances() returns true and _initBalances() is not called. As a result, ctx.swap.balanceIn and ctx.swap.balanceOut remain zero and downstream swap instructions (e.g., LimitSwap/XYC) revert due to zero balances, breaking multi‑token programs and preventing fills.
This allows a previously used token in the same order to mask uninitialized balances for a new token pair, causing unexpected reverts and preventing legitimate swaps.
Severity