Skip to content

[MEDIUM] Silent value mismatch with fee-on-transfer/rebasing tokens breaks economic expectations and taker thresholds #29

@Mehd1b

Description

@Mehd1b

Description

The swap execution validates slippage/thresholds against computed VM amounts, but the actual token transfers are performed with plain safeTransferFrom / AQUA.pull and the code never verifies how many tokens were actually received by the maker or delivered to the taker. For tokens that burn, skim fees, or otherwise modify balances on transfer (fee-on-transfer, rebasing-on-transfer, hooks), the on-chain settlement can deviate from the computed amounts while the transaction still succeeds.

// src/SwapVM.sol
function _transferIn(...) private {
 ...
 if (ctx.swap.amountIn > 0) {
   if (order.traits.useAquaInsteadOfSignature()) {
   ... // optional AQUA.push or balance check
   } else {
   _transferFrom(ctx.query.taker, order.traits.receiver(order.maker), ctx.query.tokenIn, ctx.swap.amountIn, ctx.query.orderHash, false, order.traits.shouldUnwrapWeth());
   }
 }
 ...
}
function _transferOut(...) private {
 ...
   _transferFrom(order.maker, takerTraits.to(takerData, msg.sender), ctx.query.tokenOut, ctx.swap.amountOut, ctx.query.orderHash, order.traits.useAquaInsteadOfSignature(), takerTraits.shouldUnwrapWeth());
 ...
}
function _transferOrPull(address from, address to, address token, uint256 amount, bytes32 orderHash, bool useAqua) private {
   if (useAqua) {
     AQUA.pull(from, orderHash, token, amount, to);
   } else {
     IERC20(token).safeTransferFrom(from, to, amount); // no post transfer balance check
   }
}

Consequences

  • If tokenOut is fee-on-transfer (e.g., 10% burn), the taker may receive less than amountOut despite the threshold check passing earlier on computed amounts. The function still returns (amountIn, amountOut) and emits Swapped with those values, creating a mismatch between reported and delivered amounts.
  • If tokenIn is fee-on-transfer, the maker can receive less than amountIn credited by the VM, breaking the economic assumptions of the order logic

Because settlement never reconciles actual post-transfer balances with the computed VM amounts, adversaries can intentionally pick such tokens to undermine thresholds and invariant-based pricing logic without triggering a revert. This affects both non‑Aqua and Aqua paths (AQUA.pull similarly does not reconcile received amounts on the taker side).

Severity Note:

  • Makers may list or accept tokens that are fee-on-transfer/rebasing or have transfer hooks.
  • AQUA.push/pull do not themselves guarantee reconciliation of the receiver’s actual amount for tokenOut
    transfers.
  • takerTraits.validate and order.traits.validate enforce thresholds on computed VM amounts only, not on actual
    received/sent balances.

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