QuantAMM

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

Fee Overcharge Due to Inaccurate Calculation of Pool Value Changes

Summary

The truncation of the lpTokenDepositValueChange variable to zero in the onAfterRemoveLiquidity function can lead to an incorrect fee calculation. Specifically, users may pay more fees than they should because the fallback minimum withdrawal fee (minWithdrawalFeeBps) is applied instead of a fee based on the uplift in pool value.

Vulnerability Details

In onAfterRemoveLiquidity function localData.lpTokenDepositValueChange is calculated as follows;

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

Solidity performs division using integer arithmetic, which discards any fractional part of the result. If the numerator (difference between localData.lpTokenDepositValueNow and localData.lpTokenDepositValue) is significantly smaller than the denominator (localData.lpTokenDepositValue), the result will be 0 instead of the actual fractional value.

Example

localData.lpTokenDepositValueNow = 1005
localData.lpTokenDepositValue = 1000
` localData.lpTokenDepositValueChange = (1005-1000)/1000= 0.005
Using integer division, the result will be truncated to 0.

The truncated value of lpTokenDepositValueChange is used in fee calculation

uint256 feePerLP;
// if the pool has increased in value since the deposit, the fee is calculated based on the deposit value
if (localData.lpTokenDepositValueChange > 0) {
feePerLP =
(uint256(localData.lpTokenDepositValueChange) * (uint256(feeDataArray[i].upliftFeeBps) * 1e18)) /
10000;
}
// if the pool has decreased in value since the deposit, the fee is calculated based on the base value - see wp
else {
//in most cases this should be a normal swap fee amount.
//there always myst be at least the swap fee amount to avoid deposit/withdraw attack surgace.
feePerLP = (uint256(minWithdrawalFeeBps) * 1e18) / 10000;
}

Since lpTokenDepositValueChange is truncated to 0, even when there is a small uplift, the calculation defaults to

feePerLP = (uint256(minWithdrawalFeeBps) * 1e18) / 10000

This results in the user paying more than the actual uplift-adjusted fee.

Scenario
localData.lpTokenDepositValueNow = 1005
localData.lpTokenDepositValue = 1000
Uplift Fee Bps: 50 (0.5%)
minWithdrawalFeeBps: 20 (0.2%)
Expected lpTokenDepositValueChange: (1005-1000)/1000= 0.005
Expected Fee Calculation:
feePerLP = 0.005×(50×10^18)/10000=2.5×10^13

Actual (Truncated) Fee Calculation:
Truncated lpTokenDepositValueChange = 0
feePerLP = (20 * 10^18) / 10000 = 2 * 10^15

Impact

When the pool value has increased only slightly (a small uplift), truncating lpTokenDepositValueChange to zero results in users paying the minimum withdrawal fee (minWithdrawalFeeBps). This fee is often higher than the fee calculated based on the actual uplift percentage therefore users wil pay more than they should for withdrawing liquidity.
In addition to cases where a small positive lpTokenDepositValueChange is truncated to zero, the following scenarios also result in users paying the minimum withdrawal fee (minWithdrawalFeeBps), which may be higher than they should pay:

  • If the pool value has decreased since the user's deposit (lpTokenDepositValueChange < 0), the calculation defaults to the minimum withdrawal fee. This leads to overcharging users when the reduced value does not justify such a high fee.

  • Users who experience no change in pool value since their deposit still pay the minimum withdrawal fee, even though no actual gain or loss occurred. This creates a situation where users incur fees disproportionate to the economic reality of their withdrawal.

Tools Used

Manual Review

Recommendations

To avoid truncation, scale values before performing division

- localData.lpTokenDepositValueChange =
- (int256(localData.lpTokenDepositValueNow) - int256(localData.lpTokenDepositValue)) /
- int256(localData.lpTokenDepositValue);
+ localData.lpTokenDepositValueChange =
+ (int256(localData.lpTokenDepositValueNow * 1e18) - int256(localData.lpTokenDepositValue * 1e18)) /
+ int256(localData.lpTokenDepositValue * 1e18);
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!