Skip to content

[MEDIUM] Dynamic balances initialization can be skipped when an unrelated token has a non‑zero stored balance #32

@Mehd1b

Description

@Mehd1b

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions