QuantAMM

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

Users can avoid paying fees on up lifts by utilising `afterUpdate` Logic

Summary

Users can weaponize the afterUpdate hook during NFT transfers to dodge paying fees on Up Lifts, since during the hook, it override the lpTokenDepositValue that was previously registered by setting it to lpTokenDepositValueNow tricking the system to have no upLifts by calling removeLiquidityProportional in the same block from their second wallet

Vulnerability Details

During user adding Liquidity, we register the deposit value of LP token here

File: UpliftOnlyExample.sol
250: poolsFeeData[pool][msg.sender].push(
251: FeeData({
252: tokenID: tokenID,
253: amount: exactBptAmountOut,
254: //this rounding favours the LP
255: lpTokenDepositValue: depositValue,
256: //known use of timestamp, caveats are known.
257: blockTimestampDeposit: uint40(block.timestamp),
258: upliftFeeBps: upliftFeeBps
259: })
260: );

This is used as a reference value to see how much the value of LP token has increased since deposit when calling removeLiquidityProportional specifically in onAfterRemoveLiquidity Here

File: UpliftOnlyExample.sol
480: if (localData.lpTokenDepositValueChange > 0) {
481: feePerLP =
482: (uint256(localData.lpTokenDepositValueChange) * (uint256(feeDataArray[i].upliftFeeBps) * 1e18)) /
483: 10000;
484: }

But Users can avoid paying that fee by transferring the NFT to other wallet

File: LPNFT.sol
49: function _update(address to, uint256 tokenId, address auth) internal override returns (address previousOwner) {
50: previousOwner = super._update(to, tokenId, auth);
51: //_update is called during mint, burn and transfer. This functionality is only for transfer
52: if (to != address(0) && previousOwner != address(0)) {
53: //if transfering the record in the vault needs to be changed to reflect the change in ownership
54: router.afterUpdate(previousOwner, to, tokenId);
55: }
56: }
.................
File: UpliftOnlyExample.sol
576: function afterUpdate(address _from, address _to, uint256 _tokenID) public {
586:
587: int256[] memory prices = IUpdateWeightRunner(_updateWeightRunner).getData(poolAddress);
609: feeDataArray[tokenIdIndex].lpTokenDepositValue = lpTokenDepositValueNow;
610: feeDataArray[tokenIdIndex].blockTimestampDeposit = uint32(block.number);
611: feeDataArray[tokenIdIndex].upliftFeeBps = upliftFeeBps;
614: poolsFeeData[poolAddress][_to].push(feeDataArray[tokenIdIndex]);

As you see, we retrieve the current LP Deposit value and override the old one with it and then assign those values to the new owner to in Line 614

Impact

Loss of funds to LP providers and QuantAdmin

Tools Used

Manual Review

Recommendations

Don't override deposit value when transferring, or charge fees during transfers (will require complexity since it will require vault.unlock() etc, more gas cost, )

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.