Grego AI | Uniswap V4: Insufficient Slippage Protection in MINT_POSITION_FROM_DELTAS and _increaseFromDeltas

Uniswap

V4 Periphery PositionManager

Uniswap V4: Insufficient Slippage Protection in MINT_POSITION_FROM_DELTAS and _increaseFromDeltas

A confirmed Uniswap V4 finding where FromDeltas liquidity operations lacked minimum-value protection against spot-price manipulation.

Failure path

PositionManager._mintFromDeltas and the analogous _increaseFromDeltas calculate liquidity using the current spot price returned by StateLibrary.getSlot0. No minimum liquidity or minimum value protection is enforced; only a validateMaxIn check on token amounts.

Impact and conditions

Existing positions are safe. The vulnerability applies only when minting or increasing through the FromDeltas functions. Users can receive substantially less liquidity or value than expected when the price is manipulated before execution. No direct token loss occurs if TAKE actions return credits, but economic dilution happens. The path is unsupported in official Uniswap interfaces and sees very low on-chain usage.

We discovered a unique finding on the @Uniswap V4 Periphery PositionManager contract with Grego AI that allowed a sandwich attacker to manipulate the spot price and cause a user minting liquidity via MINT_POSITION_FROM_DELTAS to receive significantly less liquidity (up to ~50% position value loss in some cases) while still passing the max-in slippage checks.

The affected flow is not used in the official interfaces and sees very low on-chain usage.

Repo:

https://github.com/Uniswap/v4-periphery

Humble thanks to the Uniswap team to award us a bounty for this find. Team has acknowledged and a comment has been added to the public codebase noting this finding to make integrators aware here: https://github.com/Uniswap/v4-periphery/pull/517

If you would like to engage our AI security services for your protocol contact @0xriptide or @0xitsgreg to discuss or visit grego.ai

Bug report follows …

Summary

PositionManager._mintFromDeltas (and the analogous _increaseFromDeltas) calculates the liquidity to mint using the current spot price returned by StateLibrary.getSlot0. No minimum liquidity or minimum value protection is enforced - only a validateMaxIn check on token amounts.

An attacker can front-run a transaction to move the pool price, causing the liquidity calculation to return a materially smaller position while the token slippage check still passes (both liquidity and consumed tokens scale with the manipulated price). Unused tokens remain as credits in the PoolManager (recoverable via TAKE actions if included in the plan).

This is not generic MEV sandwich risk: the protection mechanism itself provides no lower bound on the economic outcome the user receives for their maximum input.

Finding Description

Uniswap v4’s StateLibrary.getSlot0 performs a raw extsload of the pool’s Slot0 (including sqrtPriceX96) with no validation, no TWAP, and no freshness check.

// lib/v4-core/src/libraries/StateLibrary.sol
function getSlot0(IPoolManager manager, PoolId poolId) internal view
    returns (uint160 sqrtPriceX96, int24 tick, uint24 protocolFee, uint24 lpFee) {
    bytes32 stateSlot = _getPoolStateSlot(poolId);
    bytes32 data = manager.extsload(stateSlot);  // direct, unvalidated read
    ...
}

PositionManager consumes this price directly:

// lib/v4-periphery/src/PositionManager.sol
(uint160 sqrtPriceX96,,,) = poolManager.getSlot0(poolKey.toId());
uint256 liquidity = LiquidityAmounts.getLiquidityForAmounts(sqrtPriceX96, ...);

LiquidityAmounts.getLiquidityForAmounts returns the minimum liquidity derivable from the two token amounts at the provided spot price. When the price is manipulated, the resulting liquidity changes, and the actual token consumption scales proportionally.

The only protection is validateMaxIn (or validateMaxInNegative), which solely checks that the absolute token amounts consumed do not exceed the user-specified maximums. There is no equivalent validateMinOut-style check for liquidity received or for the overall value of the minted position.

// lib/v4-periphery/src/libraries/SlippageCheck.sol
function validateMaxIn(BalanceDelta delta, uint128 amount0Max, uint128 amount1Max) internal pure {
    if (amount0 < 0 && amount0Max < uint128(uint256(-amount0))) revert ...;
    // No minimum liquidity / minimum value check exists
}

Because the user’s call does not specify an expected price or minimum liquidity, the protocol honors the max in at whatever (manipulated) price exists at execution time. This creates a mismatch between the intended guarantee and the actual outcome.

Attack Path:

  1. Attacker monitors the mempool for modifyLiquidities calls using MINT_POSITION_FROM_DELTAS (or increase) with large token credits.
  2. Attacker front-runs with a swap that shifts sqrtPriceX96 in the direction that reduces liquidity for the victim’s tick range.
  3. Victim’s transaction executes at the manipulated price → lower liquidity is minted.
  4. validateMaxIn passes because token usage scaled down proportionally.
  5. Attacker back-runs to capture profit.
  6. The victim receives a position with significantly less liquidity (up to ~5% in moderate moves; potentially much higher for extreme or asymmetric deposits) while the token check succeeds. Excess tokens stay as credits in the PoolManager.

Impact

Low severity. Existing positions are safe - the vulnerability only applies at the time of minting/increasing via the FromDeltas functions. Users of the _mintFromDeltas / _increaseFromDeltas path can receive substantially less liquidity/value than expected for their maximum token input when the price is manipulated before execution. No direct token loss occurs if TAKE actions are included (credits are returned), but economic dilution happens.

This path is not supported in official Uniswap UIs and sees very low on-chain usage, limiting practical exposure. However, the protection is incomplete: it only caps the maximum input and provides no floor on the output (liquidity or value). This was also highlighted in an earlier OpenZeppelin audit of the same code path. Unsophisticated integrators or direct users may be surprised by the outcome.

Likelihood

Low. Requires MEV searchers to specifically target MINT_FROM_DELTAS transactions and sufficient capital to move the price. Profitability depends on position size relative to fees/gas. While feasible in principle, the rarity of the code path reduces real-world likelihood.

Recommendation

Deprecate or clearly document the risks of MINT_POSITION_FROM_DELTAS and _increaseFromDeltas (they rely on spot price and lack output protection).

Consider adding a minLiquidity parameter (or equivalent minimum-value check) to these flows, similar to validateMinOut used elsewhere.

Strongly recommend using the standard MINT_POSITION action (which accepts a fixed liquidity parameter) for users who want predictable outcomes, combined with proper slippage handling via the Universal Router or custom plans.

Update documentation to warn integrators that these delta-based mint/increase paths do not protect against price movement between submission and execution.