QuantAMM

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

Incorrect order of multiplication and division in `UpliftOnlyExample::onAfterSwap` leads to large errors in calculation `adminFee`

Summary

Incorrect order of multiplication and division in UpliftOnlyExample::onAfterSwap leads to large errors in calculation adminFee. The adminFee will always greater than or equal to the amount it should be base on the UpdateWeightRunner::quantAMMSwapFeeTake percent. And if the UpdateWeightRunner::quantAMMSwapFeeTake larger than 0.5e18 (>50%), adminFee will take all hookFee, leaving ownerFee zero.

Vulnerability Details

In onAfterSwap function, when calculate the fee that quantammAdmin should take on swap fee base on quantAMMSwapFeeTake UpliftOnlyExample.sol#L335.

// But in stead of multiplying `quantAMMFeeTake` by `hookFee` first and then dividing by `1e18`:
uint256 adminFee = hookFee * quantAMMFeeTake / 1e18;
// The function does:
uint256 adminFee = hookFee / (1e18 / quantAMMFeeTake);

However, the value of quantAMMFeeTake is not much smaller than 1e18, the result of (1e18 / quantAMMFeeTake) will have large errors because solidity round down the result. And if quantAMMFeeTake >= 0.5e18 + 1, the result of (1e18 / quantAMMFeeTake) will always 1. Meaning the quantammAdmin will have all the hookFee, the owner have nothing.

PoC

  • First test:
    Place this test into UpliftExample.t.sol
    In /2024-12-quantamm/pkg/pool-hooks run forge test --mt test_largeErrorWhenCalculate_adminFee -vv.
    Look into the logs in terminal, adminFee actual percent always greater than or equal to the quantAMMFeeTake percent. And in worst case, when quantAMMFeeTake > 50%, the adminFee actual percent is 100%.

function test_largeErrorWhenCalculate_adminFee() public {
for (uint256 i = 1; i < 100; i++) {
uint256 quantAMMFeeTake = i * 1e16; // i percent
uint256 hookFee = 1000e18;
uint256 adminFee = hookFee / (1e18 / quantAMMFeeTake);
console.log(' ');
console.log('quantAMMFeeTake percent: ', i);
console.log('Actual adminFee percent: ', (adminFee * 100) / hookFee);
}
}
  • Second test: With QuantAMMUpliftFeeTake > 0.5e18 (50%), quantammAdmin have all the hook fee.
    Place this test into UpliftExample.t.sol and in In /2024-12-quantamm/pkg/pool-hooks run forge test --mt test_quantammAdminHaveAllHookFee. It passes.

function test_quantammAdminHaveAllHookFee() public {
// Set QuantAMMUpliftFeeTake > 0.5e18
vm.startPrank(address(vaultAdmin));
updateWeightRunner.setQuantAMMUpliftFeeTake(0.5e18 + 1);
vm.stopPrank();
uint256 swapAmount = 1e3 * 1e18;
// Hook swap fee percentage between 0 and 100%
uint64 hookSwapFeePercentage = uint64(5e16);
vm.expectEmit();
emit UpliftOnlyExample.HookSwapFeePercentageChanged(
poolHooksContract,
hookSwapFeePercentage
);
vm.prank(owner);
UpliftOnlyExample(payable(poolHooksContract)).setHookSwapFeePercentage(
hookSwapFeePercentage
);
uint256 hookFee = swapAmount.mulUp(hookSwapFeePercentage);
vm.prank(bob);
vm.expectCall(
address(poolHooksContract),
abi.encodeCall(
IHooks.onAfterSwap,
AfterSwapParams({
kind: SwapKind.EXACT_IN,
tokenIn: dai,
tokenOut: usdc,
amountInScaled18: swapAmount,
amountOutScaled18: swapAmount,
tokenInBalanceScaled18: poolInitAmount + swapAmount,
tokenOutBalanceScaled18: poolInitAmount - swapAmount,
amountCalculatedScaled18: swapAmount,
amountCalculatedRaw: swapAmount,
router: address(router),
pool: pool,
userData: bytes('')
})
)
);
if (hookFee > 0) {
vm.expectEmit();
emit UpliftOnlyExample.SwapHookFeeCharged(
address(vaultAdmin),
IERC20(usdc),
hookFee
);
}
router.swapSingleTokenExactIn(
address(pool),
dai,
usdc,
swapAmount,
0,
MAX_UINT256,
false,
bytes('')
);
// the quantAMM admin have all hook fee
assertEq(usdc.balanceOf(address(vaultAdmin)), hookFee);
}

Impact

The adminFee will always greater than or equal to the amount it should be base on the UpdateWeightRunner::quantAMMSwapFeeTake percent. And if the UpdateWeightRunner::quantAMMSwapFeeTake larger than 0.5e18 (>50%), adminFee will take all hookFee, leaving ownerFee zero.

Tools Used

  • Manual review

  • Foundry

Recommendations

Multiply hookFee by quantAMMFeeTake first then divide by 1e18.

- uint256 adminFee = hookFee / (1e18 / quantAMMFeeTake);
+ uint256 adminFee = hookFee * quantAMMFeeTake / 1e18;
Updates

Lead Judging Commences

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

finding_onAfterSwap_adminFee_overestimated_solidity_rounding_down

Likelyhood: High, quantAMMFeeTake is a percentage on calculated fees. Being between 30-70% is very likely. Impact: High, fees for LP providers will be lower than expected and 0 if the admin fees is above 50%.

Support

FAQs

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

Give us feedback!