QuantAMM

QuantAMM
49,600 OP
View results
Submission Details
Severity: low
Valid

Users can escape fees due to no edge case handling

Summary

Users can escape fees due to no edge case handling

Vulnerability Details

Observing the onAfterSwap function in the UpliftOnlyExample contract:

function onAfterSwap(
AfterSwapParams calldata params
) public override onlyVault returns (bool success, uint256 hookAdjustedAmountCalculatedRaw) {
hookAdjustedAmountCalculatedRaw = params.amountCalculatedRaw;
if (hookSwapFeePercentage > 0) {
uint256 hookFee = params.amountCalculatedRaw.mulUp(hookSwapFeePercentage);
if (hookFee > 0) {
IERC20 feeToken;
// Note that we can only alter the calculated amount in this function. This means that the fee will be
// charged in different tokens depending on whether the swap is exact in / out, potentially breaking
// the equivalence (i.e., one direction might "cost" less than the other).
if (params.kind == SwapKind.EXACT_IN) {
// For EXACT_IN swaps, the `amountCalculated` is the amount of `tokenOut`. The fee must be taken
// from `amountCalculated`, so we decrease the amount of tokens the Vault will send to the caller.
//
// The preceding swap operation has already credited the original `amountCalculated`. Since we're
// returning `amountCalculated - hookFee` here, it will only register debt for that reduced amount
// on settlement. This call to `sendTo` pulls `hookFee` tokens of `tokenOut` from the Vault to this
// contract, and registers the additional debt, so that the total debits match the credits and
// settlement succeeds.
feeToken = params.tokenOut;
hookAdjustedAmountCalculatedRaw -= hookFee;
} else {
// For EXACT_OUT swaps, the `amountCalculated` is the amount of `tokenIn`. The fee must be taken
// from `amountCalculated`, so we increase the amount of tokens the Vault will ask from the user.
//
// The preceding swap operation has already registered debt for the original `amountCalculated`.
// Since we're returning `amountCalculated + hookFee` here, it will supply credit for that increased
// amount on settlement. This call to `sendTo` pulls `hookFee` tokens of `tokenIn` from the Vault to
// this contract, and registers the additional debt, so that the total debits match the credits and
// settlement succeeds.
feeToken = params.tokenIn;
hookAdjustedAmountCalculatedRaw += hookFee;
}
uint256 quantAMMFeeTake = IUpdateWeightRunner(_updateWeightRunner).getQuantAMMUpliftFeeTake();
uint256 ownerFee = hookFee;
if (quantAMMFeeTake > 0) {
uint256 adminFee = hookFee / (1e18 / quantAMMFeeTake);
ownerFee = hookFee - adminFee;
address quantAMMAdmin = IUpdateWeightRunner(_updateWeightRunner).getQuantAMMAdmin();
_vault.sendTo(feeToken, quantAMMAdmin, adminFee);
emit SwapHookFeeCharged(quantAMMAdmin, feeToken, adminFee);
}
if (ownerFee > 0) {
_vault.sendTo(feeToken, address(this), ownerFee);
emit SwapHookFeeCharged(address(this), feeToken, ownerFee);
}
}
}
return (true, hookAdjustedAmountCalculatedRaw);
}
uint256 hookFee = params.amountCalculatedRaw.mulUp(hookSwapFeePercentage);

the value amountCalculatedRaw being very small and multiplying it by hookSwapFeePercentage can result in rounding down to zero due to Solidity's fixed-point arithmetic restrictions. For instance, let amountCalculatedRaw = 1 and hookSwapFeePercentage = 0.1% (1e15). The hookfee= 1 * 1e15 / 1e18 = 0.0001 which is truncated due to integer division

Impact

Users can split large swap amounts into smallers ones to incur no fees, which will result in a loss of revenue

Tools Used

Manual Review

Recommendations

Include a minimum fee to ensure that even small amountCalculatedRaw values incur a non-zero fee.

Updates

Lead Judging Commences

n0kto Lead Judge 7 months ago
Submission Judgement Published
Validated
Assigned finding tags:

finding_tokens_with_few_decimals_can_bypass_fees

Likelyihood: Very Low, tokens with 2 or less decimals and few fees. Impact: Low, bypass fees but for very few amounts, gas usage will be equivalent. (No reason to break a big swap in multiple)

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.