Skip to content

Accounting assumes standard ERC20 transfers. Fee-on-transfer/rebasing cause balance divergence and swap invariants bypass #26

@mehdi-defiesta

Description

@mehdi-defiesta

Description

Aqua updates internal balances by the requested amount and then performs the ERC20 transfer without verifying the actual amount moved. For fee-on-transfer (taxed) or rebasing tokens, the internal Aqua accounting diverges from real wallet balances, and XYCSwap’s settlement check relies on Aqua’s internal view, not on actual token receipts.

function pull(address maker, bytes32 strategyHash, address token, uint256 amount, address to) external {
   Balance storage balance = _balances[maker][msg.sender][strategyHash][token];
   (uint248 prevBalance, uint8 tokensCount) = balance.load();
   balance.store(prevBalance - amount.toUint248(), tokensCount);
   IERC20(token).safeTransferFrom(maker, to, amount);
   emit Pulled(maker, msg.sender, strategyHash, token, amount);
}
function push(address maker, address app, bytes32 strategyHash, address token, uint256 amount) external {
   Balance storage balance = _balances[maker][app][strategyHash][token];
   (uint248 prevBalance, uint8 tokensCount) = balance.load();
   require(tokensCount > 0 && tokensCount != _DOCKED, PushToNonActiveStrategyPrevented(maker, app, strategyHash, token));
   balance.store(prevBalance + amount.toUint248(), tokensCount);
   IERC20(token).safeTransferFrom(msg.sender, maker, amount);
   emit Pushed(maker, app, strategyHash, token, amount);
}

Problematic dependency (XYCSwap):

_safeCheckAquaPush(strategy.maker, strategyHash, tokenIn, balanceIn + amountIn);

Impact

  • On push(), internal balance increases by amount even if the maker actually receives less due to transfer tax/rebase. _safeCheckAquaPush then considers the swap settled while the maker’s wallet is underfunded.
  • On pull(), internal balance decreases by amount regardless of how much is actually delivered to the recipient in
    deflationary transfers. This can systematically short-change the maker or the taker and create future pull failures
    or stuck strategies. Because neither Aqua nor XYCSwap measure token balances pre/post transfer, the system can be exploited or
    broken when non-standard ERC20s (fee-on-transfer, rebasing) are used.

Severity

  • _safeCheckAquaPush compares Aqua’s internal balance to an expected value and does not read real token balances or measure pre/post transfer deltas.
  • Tokens with fee-on-transfer or rebasing behavior can be used within Aqua/XYCSwap (no allowlist restrictions).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions