Strong Gauze Hawk
Medium
Strategy:withdrawPartial()
function removes partial liquidity from the main and secondary positions by calling the _removeFromPosition
function.
function withdrawPartial(uint256 shares, uint256 totalSupply)
external
onlyVault
returns (uint256 amount0Out, uint256 amount1Out)
{
(uint256 bal0, uint256 bal1) = idleBalances(); // before withdrawing any liquidity, but after collecting fees.
uint256 liqToRemove = mainPosition.liquidity * shares / totalSupply;
(uint256 m0, uint256 m1) =
_removeFromPosition(uint128(liqToRemove), mainPosition.tickLower, mainPosition.tickUpper);
mainPosition.liquidity -= uint128(liqToRemove);
liqToRemove = secondaryPosition.liquidity * shares / totalSupply;
(uint256 s0, uint256 s1) =
_removeFromPosition(uint128(liqToRemove), secondaryPosition.tickLower, secondaryPosition.tickUpper);
secondaryPosition.liquidity -= uint128(liqToRemove);
amount0Out = m0 + s0 + (bal0 * shares / totalSupply);
amount1Out = m1 + s1 + (bal1 * shares / totalSupply);
return (amount0Out, amount1Out);
}
Because the _removeFromPosition()
function does not have slippage controls for bal0
and bal1
returned by the IUniswapV3Pool.burn()
function call, the withdrawPartial()
function call can suffer from price manipulation on the associated Uniswap V3 pool.
function _removeFromPosition(uint128 liquidity, int24 tickLower, int24 tickUpper)
internal
returns (uint256 bal0, uint256 bal1)
{
if (liquidity != 0) {
(bal0, bal1) = IUniswapV3Pool(pool).burn(tickLower, tickUpper, liquidity);
IUniswapV3Pool(pool).collect(address(this), tickLower, tickUpper, type(uint128).max, type(uint128).max);
return (bal0, bal1);
}
}
If a price manipulation frontruns the withdrawPartial()
transaction, the withdrawn token amounts (amount0Out
and amount1Out
) can be much less than what they should be.
Without slippage controls, an attacker can manipulate the Uniswap V3 pool price just before a withdrawal, causing the IUniswapV3Pool.burn()
call to return significantly fewer tokens than expected. This means that when a user withdraws liquidity via withdrawPartial()
, they may end up receiving much less than their rightful share
The withdrawPartial()
function can be updated to include slippage controls for amount0Out
and amount1Out
returned by the IUniswapV3Pool().burn
function call
https://github.com/sherlock-audit/2025-02-yieldoor/blob/main/yieldoor/src/Strategy.sol#L134-L155 https://github.com/sherlock-audit/2025-02-yieldoor/blob/main/yieldoor/src/Strategy.sol#L486-L495