No slippage limit for Uniswap liquidity provision and removal
Description
In the _releaseFundsAndPrepareLiquidityProvisionArgs
function, the parameters for the minimum minted amounts are hardcoded to zero:
/**
* * Prepare mint parameters
*/
mintParams.token0 = tokens[0];
mintParams.token1 = tokens[1];
mintParams.fee = args.poolFee;
mintParams.tickLower = args.tickMin;
mintParams.tickUpper = args.tickMax;
mintParams.amount0Desired = amounts[0];
mintParams.amount1Desired = amounts[1];
!mintParams.amount0Min = 0;
!mintParams.amount1Min = 0;
mintParams.recipient = payable(address(this));
mintParams.deadline = block.timestamp + 2 minutes;
The minting will succeed for any amount, even zero.
Similarly, _prepareRemoveLiquidityParams
preparing liquidity removal hardcodes the minimum output amounts to zero:
function _prepareRemoveLiquidityParams(
UniswapRemoveLiquidityArgs memory args,
uint128 liquidity
)
internal
view
returns (
INonfungiblePositionManager.DecreaseLiquidityParams
memory decreaseLiquidityParams
)
{
decreaseLiquidityParams.tokenId = args.positionNote.amount;
decreaseLiquidityParams.liquidity = liquidity;
! decreaseLiquidityParams.amount0Min = 0;
! decreaseLiquidityParams.amount1Min = 0;
decreaseLiquidityParams.deadline = block.timestamp;
}
Impact
An attacker could front-run a Uniswap liquidity provision with a transaction that manipulates the pool price. The liquidity provision would then occur at an incorrect price, resulting in a lower amount of liquidity for the provider than would have been the case at the correct price. The attacker might profit from this by being able to trade at an advantageous price immediately afterwards. For example, consider a pool containing tokens X and Y:
The attacker trades on the Uniswap pool to a new price for X higher than the current (not manipulated) price . The price is the amount of tokens Y that the attacker has to pay to obtain a token X. As the attacker buys more and more, the price of X increases.
The front-run user's liquidity position from to is added, with but the current price above . This liquidity position thus only consists of tokens Y, which are now cheaper than at the unmanipulated price. The liquidity provider obtains less liquidity for their tokens than expected.
The attacker now trades back to the original price. This involves trading back through the other liquidity provider's liquidity. Additionally, there is extra liquidity by the victim between and . This allows the attacker to sell additional tokens X at a price between and . They thus obtain more tokens Y for their tokens X than they would have at the unmanipulated price.
However, the attacker also has to pay fees for their trades, including for the liquidity between and by all the other providers. Thus, this attack is profitable only if the victim's position is wide enough (so that differs significantly from ) as well as large enough compared to the other liquidity providers in that range. (Otherwise, the fees paid to trade through the other liquidity provider's position would exceed any possible profits from trading with the victim at a profitable price.)
For the case of liquidity removal, front-runners might cause an unexpected mix of the two tokens to be returned, but this should not lead to a loss of value for the user.
Recommendations
In order to use the amount0Min
and amount1Min
parameters to prevent slippage, we recommend setting those parameters to values provided by the user, which are exposed as public circuit variables in the relevant circuit and included in the data signed by the user.
Remediation
This issue has been acknowledged by Singularity, and a fix was implemented in commit 4df0140f↗.