QuantAMM

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

LPs can bypass uplift fees by transferring the LPNFT to another address they control.

Summary

When transferring LP NFTs between addresses, the lpTokenDepositValue is reset, allowing users to bypass uplift fees by transferring NFTs to another address they control.

Vulnerability Details

In the afterUpdate() function, when an LP NFT is transferred between addresses, the deposit value is updated to the current pool value and the timestamp is reset.

function afterUpdate(address _from, address _to, uint256 _tokenID) public {
// ... snip ...
feeDataArray[tokenIdIndex].lpTokenDepositValue = lpTokenDepositValueNow;
feeDataArray[tokenIdIndex].blockTimestampDeposit = uint32(block.number);
feeDataArray[tokenIdIndex].upliftFeeBps = upliftFeeBps;

This effectively eliminates any accrued uplift that would have resulted in higher withdrawal fees because when the LP removes liquidity onAfterRemoveLiquidity is called and from there the value of fee is directly proportional to the lp token deposit value change;

function onAfterRemoveLiquidity( // ...
// ... snip ...
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) {
// @audit for transfered LPNFTs we bypass this because we can make lpTokenDepositValueChange be equal to zero or rather almost zero.
@> 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;
}
// ...

Now from the observation of the code above, it can be seen that when an LP transfers an LPNFT and also removes liquidity by calling removeLiquidityProportional in the same transaction, the values of localData.lpTokenDepositValueNow and localData.lpTokenDepositValue will be exactly equal thus making the localData.lpTokenDepositValueChange to be ZERO or almost zero (if transactions have been done in different neighboring blocks), thus the LP will only pay minWithdrawalFeeBps without accounting for the LP token value deposit change.

PoC

  1. Alice deposits 10 ETH into the pool when ETH price is $2000, receiving an LP NFT

  2. ETH price increases to $3000 (+50% uplift)

  3. Instead of withdrawing and paying uplift fees, Alice transfers her LP NFT to her other address Bob

  4. The transfer resets the deposit value to current $3000 price

  5. Bob can now withdraw immediately with only minimum withdrawal fee, bypassing a significant percentage of the uplift fee

Impact

This allows LPs to bypass the uplift fee mechanism which is core to the protocol's economic model. Any LP can pay less uplift fees by transferring positions between controlled addresses.

Tools Used

Manual Review

Recommendations

When transferring LP NFTs, maintain the original deposit timestamp and value rather than resetting them. Remove these lines from afterUpdate():

- feeDataArray[tokenIdIndex].lpTokenDepositValue = lpTokenDepositValueNow;
- feeDataArray[tokenIdIndex].blockTimestampDeposit = uint32(block.number);
Updates

Lead Judging Commences

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

finding_afterUpdate_bypass_fee_collection_updating_the_deposited_value

Likelihood: High, any transfer will trigger the bug. Impact: High, will update lpTokenDepositValue to the new current value without taking fees on profit.

Support

FAQs

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

Give us feedback!