Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

UniswapV4PoolManagerAssertion

Git Source

Inherits: UniswapV4PoolManagerHelpers

Title: UniswapV4PoolManagerAssertion

Author: Phylax Systems

Example assertion bundle for a single Uniswap v4 pool sitting inside the singleton PoolManager.

Protects the pool’s core AMM invariants and the manager-level custody invariants:

  • swaps move price only in the requested direction and never past the caller’s price limit;
  • modifyLiquidity calls update active liquidity exactly when the position range contains the current tick;
  • protocol-fee accruals stay backed by the manager’s currency balances;
  • protocol-fee collection does not mutate any of the watched pool’s swap-critical state. Because the PoolManager is shared across every v4 pool, each call-scoped trigger must check that the call’s PoolKey matches the configured pool before evaluating the per-pool invariants. Calls to other pools no-op silently. The example intentionally combines per-pool state checks with manager-level custody checks:
  • per-pool checks protect the configured pool’s price, tick, liquidity, and fee-growth state;
  • manager-level checks protect the singleton accounting that backs protocol-fee liabilities for the currencies used by the configured pool;
  • PoolKey filtering prevents activity in unrelated pools from producing false positives while still letting one assertion deployment monitor a specific pool inside the singleton.

Functions

constructor

constructor(address manager_, IUniswapV4PoolManagerLike.PoolKey memory poolKey_)
    UniswapV4PoolManagerHelpers(manager_, poolKey_);

triggers

Registers Uniswap v4 PoolManager selectors against their protection assertions.

The PoolManager is the assertion adopter. Call-scoped triggers compare the exact pre-call and post-call snapshots for the matched manager operation. The assertion functions that receive a PoolKey return early when the call targets a different pool.

function triggers() external view override;

_registerProtocolFeeCustodyTriggers

Registers the tx-end protocol-fee custody check.

Fee custody is global per currency in v4, and swap/donate calls may accrue fees before the surrounding unlock flow settles token custody. Checking at tx end avoids tripping on ordinary mid-flow accounting while still requiring final protocol-fee liabilities to be backed by PoolManager custody for ERC-20 currencies.

function _registerProtocolFeeCustodyTriggers() internal view;

assertSwapPriceMovement

A successful swap must respect direction and caller-supplied price limits.

For zeroForOne swaps sqrtPriceX96 can only decrease; for oneForZero swaps it can only increase. The post-call price must also stay strictly inside V4’s tick-range bounds. A failure means swap execution moved price the wrong way or crossed the explicit limit that bounds user execution. This protects the user-facing swap guarantee rather than duplicating v4’s calldata validation.

function assertSwapPriceMovement() external view;

assertActiveLiquidityAccounting

modifyLiquidity must update active liquidity exactly for in-range positions.

V4’s per-pool liquidity is only the currently active liquidity. A successful modifyLiquidity whose range excludes the pre-call tick must leave it unchanged; an in-range modifyLiquidity must shift it by liquidityDelta. A failure means active liquidity no longer reflects the position range that the swap engine will use. Slot0 (price + tick) must also be unchanged because modifyLiquidity is not allowed to move the pool. This catches accounting corruption that may not be visible until a later swap prices against the wrong active liquidity.

function assertActiveLiquidityAccounting() external view;

assertProtocolFeesCoveredByCustody

Manager currency balances must keep covering accrued protocol fees.

Swaps and donates can accrue protocol fees before the surrounding unlock flow settles token custody, while collectProtocolFees withdraws them. The PoolManager singleton holds tokens for every pool, but protocolFeesAccrued is summed per currency across all pools, so the invariant manager.balanceOf(currency) >= protocolFeesAccrued(currency) must hold globally at transaction end for ERC-20 currencies used by the configured pool.

function assertProtocolFeesCoveredByCustody() external view;

assertCollectProtocolPreservesPoolState

Protocol-fee collection must not mutate swap-critical pool state.

collectProtocolFees may only reduce protocolFeesAccrued[currency] and transfer the matching token custody. A failure means owner fee collection changed the configured pool’s price, tick, fee schedule, active liquidity, or fee growth, or the protocol-fee accounting and manager balance change disagreed for the targeted currency. Calls collecting a currency that is not part of the watched pool only assert the per-pool no-mutation invariant. This protects against singleton side effects where a fee withdrawal for one currency unexpectedly contaminates the watched pool.

function assertCollectProtocolPreservesPoolState() external view;

_requireCollectProtocolCustodyMatches

When amount == 0, V4 collects the full accrued amount for the currency. We treat the actual delta as the amount taken and require the manager’s balance to drop by the same amount.

function _requireCollectProtocolCustodyMatches(
    uint256 requestedAmount,
    uint256 preAccrued,
    uint256 postAccrued,
    uint256 preBalance,
    uint256 postBalance,
    string memory currencyTag,
    bool isNativeCurrency
) internal pure;

_msg

function _msg(string memory base, string memory tag) internal pure returns (string memory);