QuantAMM

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

Users might repeatedly transfer NFTs between addresses they control to continually reset `lpTokenDepositValue`

Summary

https://github.com/Cyfrin/2024-12-quantamm/blob/a775db4273eb36e7b4536c5b60207c9f17541b92/pkg/pool-hooks/contracts/hooks-quantamm/UpliftOnlyExample.sol#L605C9-L627C10

Vulnerability Details

if (tokenIdIndexFound) {
if (_to != address(0)) {
// Update the deposit value to the current value of the pool in base currency (e.g. USD) and the block index to the current block number
//vault.transferLPTokens(_from, _to, feeDataArray[i].amount);
feeDataArray[tokenIdIndex].lpTokenDepositValue = lpTokenDepositValueNow;
feeDataArray[tokenIdIndex].blockTimestampDeposit = uint32(block.number);
feeDataArray[tokenIdIndex].upliftFeeBps = upliftFeeBps;
//actual transfer not a afterTokenTransfer caused by a burn
poolsFeeData[poolAddress][_to].push(feeDataArray[tokenIdIndex]);
if (tokenIdIndex != feeDataArrayLength - 1) {
//Reordering the entire array could be expensive but it is the only way to keep true FILO
for (uint i = tokenIdIndex + 1; i < feeDataArrayLength; i++) {
delete feeDataArray[i - 1];
feeDataArray[i - 1] = feeDataArray[i];
}
}
delete feeDataArray[feeDataArrayLength - 1];
feeDataArray.pop();
}
}

https://github.com/Cyfrin/2024-12-quantamm/blob/a775db4273eb36e7b4536c5b60207c9f17541b92/pkg/pool-hooks/contracts/hooks-quantamm/UpliftOnlyExample.sol#L605C9-L627C10

The UpliftOnlyExample contract is designed to manage liquidity positions in a pool. When users deposit assets into the pool, they receive LP tokens represented by NFTs (LPNFT). Upon withdrawal, users are charged a fee based on the uplift, which is the profit they've made since their initial deposit. The fee mechanism is intended to ensure that users pay fees proportional to their gains, benefiting the protocol and discouraging fee avoidance.

When an NFT representing a liquidity position is transferred from one user (_from) to another (_to), certain updates occur in the afterUpdate function.

The afterUpdate function is supposed to handle any necessary state changes when an NFT ownership changes.

The contract updates feeDataArray[tokenIdIndex].lpTokenDepositValue to lpTokenDepositValueNow, which is the current value of the LP token.

This action effectively resets the recorded deposit value to the current value at the time of transfer.

The uplift fee is calculated based on the difference between the current LP token value at withdrawal and the lpTokenDepositValue.

By resetting this value during transfer, the contract allows the new owner to avoid paying fees on any uplift that occurred before the transfer.

Example:

  • User A deposits assets into the pool.

  • Let's assume the LP token's value at deposit (lpTokenDepositValue) is $100.

  • Over time, the pool performs well, and the LP token's value increases to $150.

  • This represents a 50% uplift from the initial deposit.

  • User A wants to withdraw their liquidity.

  • Normally, they would be charged a fee based on the 50% uplift, according to the contract's fee structure.

  • To avoid paying the uplift fee, User A transfers the NFT representing their liquidity position to User B (who could be another address controlled by User A).

  • During the transfer, the afterUpdate function resets lpTokenDepositValue to the current LP token value of $150.

  • For User B, the recorded lpTokenDepositValue is now $150.

  • The prior uplift is effectively erased in terms of fee calculation.

  • User B withdraws the liquidity.

  • The uplift is calculated as (Current Value - lpTokenDepositValue) / lpTokenDepositValue.

  • Substituting the values: (150 - 150) / 150 = 0% uplift.

  • As a result, the uplift fee is zero or significantly reduced.

Impact

User A, by transferring the NFT to User B before withdrawal, successfully avoids paying the intended uplift fees.

This undermines the fee mechanism, allowing users to exploit the contract and depriving the protocol of expected fee revenue. Users might repeatedly transfer NFTs between addresses they control to continually reset lpTokenDepositValue.

Tools Used

Manual Review

Recommendations

Prevent the contract from updating lpTokenDepositValue during NFT transfers. Retain the original deposit data in the FeeData structure.

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!