QuantAMM

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

Initial LP Deposit Value Calculation Uses Incorrect Rounding Leading to Fees that don't favour the LP.

Summary

In UpliftOnlyExample.sol, the addLiquidityProportional function uses MULDOWN rounding when recording the initial LP deposit value, contrary to the code comment claiming it favors LPs. This actually results in LPs being charged higher fees than intended during withdrawals.

Vulnerability Details

function addLiquidityProportional(
// ... snip ...
@> uint256 depositValue =
getPoolLPTokenValue(IUpdateWeightRunner(_updateWeightRunner).getData(pool), pool, MULDIRECTION.MULDOWN);
poolsFeeData[pool][msg.sender].push(
FeeData({
tokenID: tokenID,
amount: exactBptAmountOut,
@> //this rounding favours the LP
lpTokenDepositValue: depositValue,
//known use of timestamp, caveats are known.
blockTimestampDeposit: uint40(block.timestamp),
upliftFeeBps: upliftFeeBps
})
);

The comment //this rounding favours the LP suggests that the LP should be favored but on the contrary the code uses MULDIRECTION.MULDOWN meaning that the depositValue calculated when LP adds liquidity appears smaller instead of bigger as should be to favor LP.

Note that the lpTokenDepositValue recorded when the LP is adding liquidity is also used when calculating the fee when that same LP is removing that liquidity i.e onAfterRemoveLiquidity

function onAfterRemoveLiquidity( // ...
// ...
for (uint256 i = localData.feeDataArrayLength - 1; i >= 0; --i) {
@> localData.lpTokenDepositValue = feeDataArray[i].lpTokenDepositValue;
@> localData.lpTokenDepositValueChange = (
int256(localData.lpTokenDepositValueNow) - int256(localData.lpTokenDepositValue)
) / int256(localData.lpTokenDepositValue);
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;
}

Note that the higher the localData.lpTokenDepositValueChange, the higher the feePerLP calculated as seen above.
In order to favor the LP we need a small value of localData.lpTokenDepositValueChange.

Now the use of MULDIRECTION.MULDOWN when calculating lpTokenDepositValue in addLiquidityProportional function creates an issue where by localData.lpTokenDepositValueChange does not become small enough to favor LP because in the calculation it becomes;

  • large numerator (current - recorded)

  • small denominator (recorded value)

large numerator divided by small denominator leads to large localData.lpTokenDepositValueChange

PoC

  1. Alice deposits 100 tokens when the pool value per LP token is 1000 USD. But due to MULDOWN rounding, her deposit is recorded as 990 USD per LP token

  2. Pool value increases to 1100 USD per LP token
    Actual price appreciation: (1100 - 1000)/1000 = 10%
    But calculated as: (1100 - 990)/990 = 11.11%

  3. Alice withdraws her position assuming upliftFeeBps is 2000 (20%)
    Actual fee should have been: 10% * 20% = 2% of withdrawal
    But charged: 11.11% * 20% = 2.22% of withdrawal

  4. Result: Alice pays 0.22% more in fees than intended. On a $100,000 withdrawal, this is $220 in excess fees. the larger the price appreciation, the greater the excess fee becomes.

Impact

LPs are overcharged fees during withdrawal which is against protocol invariant as seen in the comment that LP should be favored.

Tools Used

Manual Review

Recommendations

Change rounding direction in addLiquidityProportional function:

uint256 depositValue = getPoolLPTokenValue(
IUpdateWeightRunner(_updateWeightRunner).getData(pool),
pool,
MULDIRECTION.MULUP // Changed to MULUP to actually favor LP
);
Updates

Lead Judging Commences

n0kto Lead Judge 10 months ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity
Assigned finding tags:

Informational or Gas / Admin is trusted / Pool creation is trusted / User mistake / Suppositions

Please read the CodeHawks documentation to know which submissions are valid. If you disagree, provide a coded PoC and explain the real likelyhood and the detailed impact on the mainnet without any supposition (if, it could, etc) to prove your point.

Appeal created

tinnohofficial Submitter
10 months ago
n0kto Lead Judge
10 months ago
n0kto Lead Judge 10 months ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity
Assigned finding tags:

Informational or Gas / Admin is trusted / Pool creation is trusted / User mistake / Suppositions

Please read the CodeHawks documentation to know which submissions are valid. If you disagree, provide a coded PoC and explain the real likelyhood and the detailed impact on the mainnet without any supposition (if, it could, etc) to prove your point.

Support

FAQs

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

Give us feedback!