Author: Mohamed ElBendary
Date: April 24, 2025
Status: Draft
Category: Core Protocol Enhancement
This proposal advocates for a core protocol feature that elevates the liquidity event of modifying a liquidity positionβs active price range to be treated as a first-class citizen by the pool contract, alongside its sibling operations (swap, mint, burn, donate, etc.).
The primary motivations are to:
- Enhance operational robustness through atomicity
- Improve impermanent loss (IL) management by avoiding forced crystallization
- Achieve significant gas savings compared to the current two-transaction (
burn/mint) process
We propose the addition of a new core function, updateLPPriceRange, to the Uniswap Protocol Core PoolManager.sol contract, along with corresponding hooks beforeUpdateLPPriceRange and afterUpdateLPPriceRange in the IHook interface.
This function is designed to reuse existing, optimized internal library functions (e.g., Tick.update, Position.update) for state changes and calculations.
The benefits of this feature extend to potentially realizing a more robust implementation of the widely adopted Position Manager, by exposing a wrapper function of the one proposed for this feature.
Currently, modifying the price range of a Uniswap v3/v4 concentrated liquidity position requires two separate transactions (burn/removeLiquidity followed by mint/addLiquidity). This presents several challenges:
- Operational Risk: The two-step process can fail between transactions, leaving capital unexpectedly out of the market and requiring complex recovery logic.
- Impermanent Loss (IL) Crystallization: The
burnstep forces realization of IL at the moment of withdrawal, which can be disadvantageous during temporary market volatility. - Complexity for Automated Managers: Systems must coordinate two distinct transactions and handle intermediate failures.
- High Gas Costs: Executing two separate transactions incurs significant gas costs, including two base transaction fees and potentially redundant storage reads/writes. This makes frequent active management expensive, especially on L1.
The proposed updateLPPriceRange function addresses these issues by performing the range adjustment atomically within a single transaction, leveraging core internal logic for efficiency. This provides:
- Atomicity
- IL crystallization avoidance
- Intent-revealing primitive
- Substantial gas savings
- Add
updateLPPriceRangetoPoolManager.sol.
- Add
beforeUpdateLPPriceRangeandafterUpdateLPPriceRangeto theIHookinterface. - Integrate their execution within
updateLPPriceRange.
The proposed hook functions enable critical functionality that contributes to compliance, safety, observability, and automation.
Hooks could enforce specific rules before allowing a range update, such as:
- Is the new range width acceptable?
- Is the caller authorized (beyond basic ownership)?
- Is the update happening within allowed time windows?
- Does the new range align with external oracle data (e.g., prevent updates to clearly unprofitable ranges based on volatility)?
- Does the
mustContinueTradingflag align with current pool conditions?
- Hooks can reject updates if any condition fails, reverting the transaction.
- Emit custom events to signal the successful update.
- Enables off-chain systems (e.g., automated liquidity manager backends) to listen for these events.
- Record range update data for internal hook state or external consumption via events.
- Enable follow-up processes (on-chain or off-chain) post-confirmation.
- Allow hooks to update their own internal state as necessary in sync with the pool.
β Atomicity: No half-executed LP transitions
β IL Avoidance: No forced crystallization
β Gas Savings: Estimated 30β50% reduction vs burn + mint
β Simplified Automation: One call, fewer edge cases
β Compliance/Policy Control: Via hook validation
- β Atomic adjustments reduce the risk of capital being stuck between steps.
- β Avoid forced IL crystallization during routine adjustments.
- β Significant potential gas savings for active range management.
- β Simplified automation logic β manage one atomic call instead of coordinating two.
- β Reduced operational complexity and failure handling requirements.
- β Enhanced ability to offer strategies that minimize IL realization and operate more frequently due to lower costs.
- β Lower operational costs due to reduced gas usage for rebalancing.
- β More efficient liquidity provision can lead to deeper liquidity and potentially better pricing.
- β More reliable pool operation (fewer "stuck" LP adjustments).
- β
Gain new, specific hook points (
beforeUpdateLPPriceRange,afterUpdateLPPriceRange) for validation, policy enforcement, notifications, etc. β οΈ Requires implementing the new hook callbacks if custom logic is desired.
- β Enhanced functionality addressing major LP pain points (cost, risk, IL).
- β Increased robustness for liquidity management actions.
- β Increased capital efficiency across the ecosystem due to cheaper liquidity management.
β οΈ Increased complexity of the corePoolManagercontract andIHookinterface (manageable through reuse of existing internal logic).
The function takes as input:
- A position identifier
- A new price range
- A flag to ensure continued liquidity exposure if desired
- Optional hook data
Upon success, it returns Position.Info.
function updateLPPriceRange(
PositionKey calldata key,
UpdateLPPriceRangeParams calldata params
) external override returns (Position.Info memory positionInfo);
struct UpdateLPPriceRangeParams {
int24 tickLower; // The new lower tick boundary
int24 tickUpper; // The new upper tick boundary
bool mustContinueTrading; // If true AND current tick is outside the new range, revert
bytes data; // Optional data to pass to hooks
}The function executes the following steps atomically:
- Decode Keys & Params: Extract identifiers and new range parameters.
- Load Position: Read current
Position.Info. Validate ownership/operator. Ensure liquidity > 0. - Range Check: Return early if unchanged. Validate
newTickLower < newTickUpper. - Load Pool State (
slot0): RetrievesqrtPriceX96,currentTick, andfeeGrowthGlobal. mustContinueTradingCheck: Ensure current tick falls within new range if flag is set.- Call
beforeUpdateLPPriceRangeHook: Run pre-update logic. - Fee Calculation & State Update:
- Use
Position.updateto calculate accrued fees. - Update
tokensOwedin a temporary copy ofPosition.Info.
- Use
- Tick Updates (Remove Old):
Tick.updatefor oldtickLowerwith-liquidityTick.updatefor oldtickUpperwith+liquidity
- Tick Updates (Add New):
Tick.updatefor newtickLowerwith+liquidity, storefeeGrowthOutsideTick.updatefor newtickUpperwith-liquidity, storefeeGrowthOutside
- Calculate New Snapshots:
- Use
Position.getFeeGrowthInsidewithfeeGrowthOutsideto compute new snapshot.
- Use
- Prepare Final Position State:
- Update memory copy of
Position.Infowith new tick range and fee snapshot.
- Update memory copy of
- Call
afterUpdateLPPriceRangeHook: Run post-update logic with final state. - Write Final State: Persist updated
Position.Infoto storage (SSTORE). - Emit Event:
UpdateLPPriceRange. - Return: Return updated
Position.Info.
function beforeUpdateLPPriceRange(
address sender,
PoolKey calldata key,
PoolManager.UpdateLPPriceRangeParams calldata params,
bytes calldata data
) external returns (bytes4);
function afterUpdateLPPriceRange(
address sender,
PoolKey calldata key,
PoolManager.UpdateLPPriceRangeParams calldata params,
Position.Info calldata positionInfo,
bytes calldata data
) external returns (bytes4);By reusing optimized internal functions and performing atomic execution:
- Saves one base transaction fee (~21k gas)
- Reduces
SLOADs: Readsslot0and position data once - Reduces
SSTOREs: Updates storage once instead of twice - Avoids duplicate access control checks from separate public calls
Estimated gas cost:
updateLPPriceRange: ~80k β 140k gas- Equivalent
burn + mint: ~140k β 260k+ gas
This implies a 30β50% savings in range adjustment scenarios. Benchmarks pending.
The function should revert in the following cases:
- Invalid
positionIdorPoolKey - Unauthorized caller (not owner or approved operator)
- Zero liquidity in the position
- Invalid tick range (
newTickLower >= newTickUpper) mustContinueTradingcheck fails- Hook reverts or returns failure (
beforeorafter) - Internal overflow/underflow
- Gas limit exceeded
The proposed atomic update feature would significantly enhance the Position Manager's batched operation capabilities by replacing the current burn + mint sequence with a more efficient, single-step approach.
In Uniswap v4, the Position Manager uses a batched command pattern to execute multiple actions in one transaction. However, when modifying a position's price range, it still must orchestrate two discrete core operations β burn and mint. This results in the same challenges faced by external liquidity managers:
- Increased Gas Costs: Despite batching, the core operations still involve redundant state reads/writes and two separate liquidity modifications.
- Implementation Complexity: Intermediate state must be manually handled to ensure funds from the
burnare applied to themint. - Error Handling Complexity: Failures between the two steps require additional recovery logic, even within a single transaction.
If adopted, the Position Manager could replace its two-step logic with a single atomic command. This would:
- Simplify the Command Structure: One command instead of two.
- Reduce Gas Usage: Avoids redundant operations and saves gas.
- Improve Reliability: Removes failure risk between dependent operations.
- Maintain Liquidity Continuity: The position stays live throughout the update.
This enhancement makes the Position Manager more efficient, robust, and developer-friendly, ultimately benefiting all users who interact with Uniswap through this interface.
The combination of an atomic updateLPPriceRange function and the mustContinueTrading flag offers targeted advantages for institutional and regulated liquidity providers and quantitative traders.
- The current two-step (
burn+mint) process introduces the risk thatburnsucceeds butmintfails (e.g., due to gas spikes or network congestion), leaving capital temporarily withdrawn from the market. - An atomic update ensures the adjustment is all-or-nothing, significantly reducing failure scenarios and eliminating the need for complex recovery logic.
- Avoiding IL crystallization during routine adjustments supports smoother profit and loss (P&L) profiles.
- Helps regulated entities meet reporting requirements and align with strategies focused on continuous market exposure rather than reactive liquidity repositioning.
- Institutional mandates often require precise, slippage-aware execution.
- The
mustContinueTrading = trueflag ensures that updates only execute when conditions still align with the intention to maintain an active position. - Prevents execution based on stale signals or market divergence from preconditions.
- The
mustContinueTradingflag allows programmatic enforcement of mandates that require continuous active participation. - Enables automated audit trails that show policy-aligned behavior even in volatile conditions.
- Institutions may use advanced tactics like anticipatory liquidity placement or limit-range strategies.
- The flag ensures strategies can programmatically differentiate between proactive rebalancing and passive liquidity positioning.
- A single atomic call reduces error-prone complexity in automated trading infrastructure.
- Eases integration, reconciliation, and reporting by replacing two operations with one intent-aligned action.
Together, these features deliver greater operational robustness, reduced economic risk, and improved automation clarity, all of which are especially critical in institutional DeFi environments. Significant gas cost savings are also likely to be welcomed by high-volume trading desks.
Adding the two new hook functions β beforeUpdateLPPriceRange and afterUpdateLPPriceRange β to the IHook interface requires updates to the existing hook permissions mechanism in the Uniswap protocol.
- Uniswap v4's
PoolManagermaintains aPool.HookPermissionsstructure for each pool. - This structure contains bitwise flags that determine which hook functions the
PoolManageris authorized to call on a designated hook contract. - The existing structure must be extended with new flags to control access to the proposed
beforeUpdateLPPriceRangeandafterUpdateLPPriceRangecallbacks.
- Upon pool creation, the deployer must be able to specify whether the new hook callbacks are permitted, using the updated
HookPermissionsstructure passed to theinitializefunction. - This ensures pool-level configurability of callback behavior for range updates.
- Before invoking
hook.beforeUpdateLPPriceRange(...)orhook.afterUpdateLPPriceRange(...), thePoolManagermust:- Check the corresponding permission flag.
- Skip the call if the flag is not enabled.
Without these changes:
- The
PoolManagerwould either unconditionally call the new hooks (breaking the permissions concept), or - Be unable to call them at all (limiting functionality).
By extending HookPermissions to cover the new callbacks:
- Pool deployers retain fine-grained control over allowed hook interactions.
- The system preserves gas efficiency and security isolation for unauthorized or unnecessary hook paths.
Ensuring the security and integrity of the updateLPPriceRange function β and its integration into the Uniswap Protocol ecosystem β is paramount. Security relies on:
- Robust access control
- Standard reentrancy protection
- Careful orchestration of logic and state transitions
- Correct handling of hook interactions
- Proven correctness of core Uniswap v4 libraries (
Tick.sol,Position.sol)
Rigorous auditing and comprehensive testing are essential before deployment.
- The addition of
beforeUpdateLPPriceRangeandafterUpdateLPPriceRangeintroduces new reentrancy vectors. - If a hook callback re-enters
PoolManager(e.g., viacollector evenupdateLPPriceRangeitself), it could compromise state consistency. - Mitigation: Apply Checks-Effects-Interactions and
nonReentrantpatterns. Hook developers must also implement safe patterns.
- Only the position owner or approved operator must be able to call
updateLPPriceRange. - Must strictly enforce existing
PoolManagerownership/approval logic to prevent unauthorized updates.
- The function relies heavily on
Tick.update,Position.update, andPosition.getFeeGrowthInside. - These components:
- Handle fee calculations
- Return critical accumulator values
- Update snapshots
- Any vulnerability in these libraries will propagate to
updateLPPriceRange.
β These libraries are widely used and audited, but their reuse in this new sequence must be re-verified as part of this featureβs audit scope.
- Fee calculations must be based on immutable snapshots before range change.
- Atomic execution prevents manipulation during the operation, but reading accumulator values at the wrong time could yield incorrect fee values.
- Orchestrating updates across multiple ticks and position states introduces potential for edge-case errors.
- Must test for:
MIN_TICK/MAX_TICKboundaries- Low-liquidity rounding errors
- Tick spacing misalignments
- Must check the return values of both hooks:
beforeUpdateLPPriceRange(...)afterUpdateLPPriceRange(...)
- If either hook fails or returns an unexpected selector, the entire transaction must revert.
- Gas limits within hook execution must also be considered to avoid mid-call failures.
- Validate:
newTickLower < newTickUpper- Liquidity > 0
mustContinueTradingbehavior againstcurrentTick
- Ensure invalid configurations do not waste gas or enter invalid state.
Thorough testing, edge case analysis, and full integration audits are required before this feature can be safely adopted in production.
- Requires a protocol upgrade (e.g., Uniswap v5).
- No changes to existing v4 pools.
- New hook functions remain opt-in via permission flags.
This feature introduces an implementation-level change to the core PoolManager contract and IHook interface. As such, it will likely require:
- A coordinated protocol upgrade
- Inclusion in a future major version of the Uniswap protocol (e.g., v5)
It is not deployable as a runtime feature for existing pools without a broader protocol version bump, due to breaking interface changes and required permission structure extensions.
- Apply formal methods to
updateLPPriceRangeand its interaction withTickandPositionlibraries - Prove correctness and prevent edge-case logic errors or vulnerabilities
- Analyze potential edge cases in pools using:
- Dynamic fees
- Hooks with stateful logic
- Other advanced v4 extensibility features
- Define stable interfaces for:
Tick.updatePosition.updatePosition.getFeeGrowthInside
- Consider future-proofing and minimal API surface area changes
- Explore SDK and Position Manager integration
- Design intuitive APIs and UI abstractions to surface this functionality for LPs and power users
- Improve validation error codes for:
mustContinueTradingfailures- Hook rejections
- Invalid tick range inputs
- Ensure clear revert messages and predictable contract behavior
- Run empirical benchmarks across:
- L1 and L2 deployments
- Different range widths
- Warm vs. cold tick updates
- Validate estimated 30β50% gas savings over
burn + mint
- Create comprehensive developer documentation covering:
- Function signature and parameter behavior
- Expected use cases and UX considerations
- Hook integration guidance
- Gas performance expectations
Together, these areas will help mature the proposal into a production-grade feature aligned with Uniswapβs roadmap and contributor needs.
This UIP is authored by:
Mohamed ElBendary
GitHub: https://github.com/mohamedelbendary
Uniswap Discourse: mbendary
Signed Message: I, Mohamed ElBendary, am the author of the Uniswap Improvement Proposal (UIP) titled βAtomic Update of Liquidity Position Price Range,β published at https://github.com/mohamedelbendary/uniswap-uip-atomic-lppricerange-update.
Ethereum Address: 0xf475608bBE46cBC5E9B1d42F731b5BbfC2C27930
Date: April 24, 2025
Signature (Hex): 0x710f3ccd01751a956cb28f5aad3dc435fbdcfd1d23f5caf69f823a75e53439083b2b03f9505b98ed9f320957ba090e06af60a459e2b740cf7d37ca103bafc1a11b
This proposal is shared under CC0 1.0 Public Domain Dedication. Attribution is appreciated.