QuantAMM

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

Precision Loss in Fee Calculation Due to Rounding Issues

Summary

The current fee calculation logic in the onAfterRemoveLiquidity which is called during removeLiquidityProportional function suffers from precision loss when calculating the uplift value (lpTokenDepositValueChange). As a result, when there is a significant price increase (e.g., 90%), the calculated lpTokenDepositValueChange becomes 0 due to integer division, leading to the application of the minWithdrawalFeeBps instead of the intended uplift fee. This allows users to avoid paying appropriate fees during withdrawal, causing revenue loss and protocol instability.

Vulnerability Details

Root Cause

Exploit

  1. Setup:

    • A user deposits liquidity, creating LP tokens with an initial deposit value (lpTokenDepositValue).

  2. Price Increase:

    • The pool value increases by a significant percentage (e.g., 90%).

  3. Withdrawal:

    • During withdrawal, the fee calculation rounds lpTokenDepositValueChange down to 0 due to integer precision loss.

    • The user is charged the minimal withdrawal fee instead of the uplift fee.

POC

function test_precisionLossInFeeCalculation() public {
uint256[] memory maxAmountsIn = [dai.balanceOf(bob), usdc.balanceOf(bob)].toMemoryArray();
vm.prank(bob);
upliftOnlyRouter.addLiquidityProportional(pool, maxAmountsIn, bptAmount, false, bytes(""));
//price Increases by 90% i.e price increase by 1.9 * initial price
int256[] memory prices = new int256[]();
for (uint256 i = 0; i < tokens.length; ++i) {
prices[i] = int256(i) * 1.9e18;
}
updateWeightRunner.setMockPrices(pool, prices);
BaseVaultTest.Balances memory balancesBefore = getBalances(bob);
uint256[] memory minAmountsOut = [uint256(0), uint256(0)].toMemoryArray();
vm.prank(bob);
upliftOnlyRouter.removeLiquidityProportional(bptAmount, minAmountsOut, false, pool);
BaseVaultTest.Balances memory balancesAfter = getBalances(bob);
console.log(
balancesAfter.bobTokens[daiIdx] - balancesBefore.bobTokens[daiIdx]
);
console.log(
balancesAfter.bobTokens[usdcIdx] - balancesBefore.bobTokens[usdcIdx]
);
/*
Bob has increases his value by 90%.
Uplift fee is supposed to be taken on the uplift.
Given each BPT is worth 90% more now, the fee is 2% of the original value.
Bob has 1000e18 in BPT, so the fee is 18e18.
Bob should get 982e18 in DAI and USDC.
However, this didnt happen bacause of the manner of calculation
lpTokenDepositValueChange is calculated to be 0
hence, based on the logic, during withdraw, the fee is calculated using the `minWithdrawalFeeBps` and then bob pays much less fee
i.e instead of paying 18e18 as fees, bob ends up paying 0.5e18
bob receive 999.5e18 of each token instaed of 982e18
*/
}

Impact

  1. Loss of Protocol Revenue:

    • Users avoid paying accurate fees for uplift in pool value, causing revenue loss.

  2. Exploitation Risk:

    • Attackers can exploit this flaw to minimize fees in scenarios of significant price increases.

Tools Used

manual analysis

Recommendations

Update the onAfterRemoveLiquidity localData.lpTokenDepositValueChange calculation as follows

- localData.lpTokenDepositValueChange =
- (int256(localData.lpTokenDepositValueNow) - int256(localData.lpTokenDepositValue)) /
- int256(localData.lpTokenDepositValue);
+ localData.lpTokenDepositValueChange =
+ ((int256(localData.lpTokenDepositValueNow) - int256(localData.lpTokenDepositValue)) * 100) /
+ int256(localData.lpTokenDepositValue);

then

- feePerLP = ((uint256(localData.lpTokenDepositValueChange) * (uint256(feeDataArray[i].upliftFeeBps) * 1e18)) / 10000);
+ feePerLP = ((uint256(localData.lpTokenDepositValueChange) * (uint256(feeDataArray[i].upliftFeeBps) * 1e18)) / 10000)/100;
Updates

Lead Judging Commences

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

finding_onAfterRemoveLiquidity_lpTokenDepositValueChange_rounding_error_100%_minimum

Likelihood: High, every call to the function (withdraw) Impact: Low/Medium, uplift fees will be applied only when the price of one asset is doubled but fixed fees will still be collected.

Support

FAQs

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

Give us feedback!