UniswapV4PoolManagerAssertion
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);