QuantAMM

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

State Inconsistency in NFT Position Minting Can Lead to Permanently Locked LP Positions

Description

The addLiquidityProportional function in the UpliftOnlyExample contract performs state updates in an unsafe order that could lead to permanently locked LP positions. The function mints an NFT and updates related state mappings non-atomically:

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

uint256 tokenID = lpNFT.mint(msg.sender);
uint256 depositValue = getPoolLPTokenValue(...);
poolsFeeData[pool][msg.sender].push(
FeeData({
tokenID: tokenID,
amount: exactBptAmountOut,
lpTokenDepositValue: depositValue,
blockTimestampDeposit: uint40(block.timestamp),
upliftFeeBps: upliftFeeBps
})
);
nftPool[tokenID] = pool;

Impact

The impact of this vulnerability centers on critical state inconsistencies in LP position tracking. If the transaction reverts after NFT minting but before setting nftPool[tokenID], the protocol enters a permanently broken state for that position.

This creates an NFT representing a position that becomes completely unusable. The NFT exists in ownership state but lacks critical protocol-level mappings required for all position management functions. The nftPool[tokenID] mapping, which serves as the core link between NFT positions and their underlying pool data, remains unset.

This broken state cascades through the protocol's key functionality. Any transfer attempt of the affected NFT will immediately revert in afterUpdate() due to the null pool mapping. The position becomes effectively locked, with no mechanism - even at the admin level - to reconnect the NFT to its intended pool data. Even more critically, the underlying BPT position associated with this NFT becomes permanently inaccessible.

The architectural impact extends beyond individual positions to affect core protocol operations. The presence of these "ghost" positions creates accounting inconsistencies in BTF position management, potentially impacting TFMM calculations and fee assessments. This undermines the protocol's ability to maintain accurate state across its liquidity management functions, directly affecting its primary value proposition of automated BTF management.

Recommended Mitigation Steps

  1. Reorder state updates to set critical mappings first:

uint256 tokenID = lpNFT.mint(msg.sender);
nftPool[tokenID] = pool;
uint256 depositValue = getPoolLPTokenValue(...);
poolsFeeData[pool][msg.sender].push(
FeeData({
tokenID: tokenID,
amount: exactBptAmountOut,
lpTokenDepositValue: depositValue,
blockTimestampDeposit: uint40(block.timestamp),
upliftFeeBps: upliftFeeBps
})
);
  1. Add an emergency recovery function (optional):

function emergencySetNftPool(uint256 tokenId, address pool) external onlyOwner {
require(lpNFT.ownerOf(tokenId) != address(0), "Invalid NFT");
require(nftPool[tokenId] == address(0), "Pool already set");
nftPool[tokenId] = pool;
}
  1. Consider implementing a more atomic deposit process by combining the NFT minting and state updates in a single operation.

Updates

Lead Judging Commences

n0kto Lead Judge 10 months ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

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