Summary
When a user removes liquidity from the pool he pays fees and a portion of the fees is liquidity added back for QuantAMMAdmin
Those funds are stuck in, and can't be removed as there is no mapping or NFT minted for those shares so any attempt to removeliquidity will revert
Vulnerability Details
Here is the call inside onAfterREmoveLiquidity to add fees to quantAMMAdmin
File: UpliftOnlyExample.sol
536: if (localData.adminFeePercent > 0) {
537: _vault.addLiquidity(
538: AddLiquidityParams({
539: pool: localData.pool,
540: to: IUpdateWeightRunner(_updateWeightRunner).getQuantAMMAdmin(),
541: maxAmountsIn: localData.accruedQuantAMMFees,
542: minBptAmountOut: localData.feeAmount.mulDown(localData.adminFeePercent) / 1e18,
543: kind: AddLiquidityKind.PROPORTIONAL,
544: userData: bytes("")
545: })
In removeLiquidityProportional there is a check for the length of poolsFeeData in this case, would be zero and any attempt to remove will revert
File: UpliftOnlyExample.sol
265: function removeLiquidityProportional(
266: uint256 bptAmountIn,
267: uint256[] memory minAmountsOut,
268: bool wethIsEth,
269: address pool
270: ) external payable saveSender(msg.sender) returns (uint256[] memory amountsOut) {
271: uint depositLength = poolsFeeData[pool][msg.sender].length;
272:
273: if (depositLength == 0) {
274: revert WithdrawalByNonOwner(msg.sender, pool, bptAmountIn);
275: }
POC
Add this anywhere in UpliftExample.t.sol
function testRemoveLiquidity_Stuck_Admin_Fees() public {
vm.prank(address(vaultAdmin));
updateWeightRunner.setQuantAMMUpliftFeeTake(0.5e18);
vm.stopPrank();
uint256[] memory maxAmountsIn = [dai.balanceOf(bob), usdc.balanceOf(bob)].toMemoryArray();
vm.prank(bob);
upliftOnlyRouter.addLiquidityProportional(pool, maxAmountsIn, bptAmount, false, bytes(""));
vm.stopPrank();
uint256[] memory minAmountsOut = [uint256(0), uint256(0)].toMemoryArray();
vm.startPrank(bob);
upliftOnlyRouter.removeLiquidityProportional(bptAmount, minAmountsOut, false, pool);
vm.stopPrank();
BaseVaultTest.Balances memory balancesAfter = getBalances(updateWeightRunner.getQuantAMMAdmin());
vm.prank(updateWeightRunner.getQuantAMMAdmin());
vm.expectRevert();
upliftOnlyRouter.removeLiquidityProportional(500000000000000000, minAmountsOut, false, pool);
vm.stopPrank();
}
Impact
Tools Used
manual review
Recommendations