-
Notifications
You must be signed in to change notification settings - Fork 15
[MEDIUM] Silent value mismatch with fee-on-transfer/rebasing tokens breaks economic expectations and taker thresholds #29
Description
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
tokenOutis fee-on-transfer (e.g., 10% burn), the taker may receive less thanamountOutdespite the threshold check passing earlier on computed amounts. The function still returns (amountIn,amountOut) and emitsSwappedwith those values, creating a mismatch between reported and delivered amounts. - If
tokenInis fee-on-transfer, the maker can receive less thanamountIncredited 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.