Summary
A user can perform a uninteded self-transfer of their liquidity-position NFT, leading to a state inconsistency that prevents them from subsequently withdrawing their liquidity.
Vulnerability Details
If a user mistakenly transfers the NFT to themselves, they will not be able to remove liquidity. This happens because the initial addition and removal of FeeData in afterUpdate deletes their record from the array but does not pop the array entry, leading to an inconsistency while processing the liquidity removal.
function testSelfTransfer() public {
uint256[] memory maxAmountsIn = [uint256(2e18), uint256(2e18)].toMemoryArray();
vm.startPrank(bob);
uint256 bptAmountDeposit = 2e18;
upliftOnlyRouter.addLiquidityProportional(pool, maxAmountsIn, bptAmountDeposit, false, bytes(""));
upliftOnlyRouter.addLiquidityProportional(pool, maxAmountsIn, bptAmountDeposit, false, bytes(""));
vm.stopPrank();
LPNFT lpNft = upliftOnlyRouter.lpNFT();
vm.startPrank(bob);
lpNft.transferFrom(bob, bob, 1);
vm.stopPrank();
uint256[] memory minAmountsOut = [uint256(0), uint256(0)].toMemoryArray();
vm.expectRevert();
vm.startPrank(bob);
upliftOnlyRouter.removeLiquidityProportional(bptAmountDeposit, minAmountsOut, false, pool);
vm.stopPrank();
vm.expectRevert();
vm.startPrank(bob);
upliftOnlyRouter.removeLiquidityProportional(bptAmountDeposit, minAmountsOut, false, pool);
vm.stopPrank();
}
Impact
create DoS Preventing User from Removing Liquidity.
Tools Used
Manual Review
Recommendations
Add a check to revert or disallow transfers if the from and to addresses are same
@@ -189,6 +189,8 @@ contract UpliftOnlyExample is MinimalRouter, BaseHooks, Ownable {
error TransferUpdateTokenIDInvaid(address from, address to, uint256 tokenId);
error ZeroAmountProvided(uint256 amount);
+
+ error SelfTransfer();
modifier onlySelfRouter(address router) {
_ensureSelfRouter(router);
@@ -585,7 +587,9 @@ contract UpliftOnlyExample is MinimalRouter, BaseHooks, Ownable {
if (msg.sender != address(lpNFT)) {
revert TransferUpdateNonNft(_from, _to, msg.sender, _tokenID);
}
-
+ if (_from == _to){
+ revert SelfTransfer();
+ }
If self-transfers must be allowed, ensure the contract logic correctly updates ownership and internal state even when the NFT is transferred to the same address.