Skip to main content

uniswap-v4-expert

Use when building on, integrating with, or analyzing Uniswap V4. Covers PoolManager singleton architecture, flash accounting via EIP-1153 transient storage, hook lifecycle, PoolKey structure, Currency type, dynamic fees, custom accounting, native ETH support, PositionManager (ERC-721 positions), and production deployment addresses.

跳到安装

来源信息

仓库
ccashwell/evm-cortex
最近来源活动
2026年4月10日 16:31
检测到的 SKILL.md 语言
英语
星标
131
分支
18

安装方式

默认使用会先检查来源的 Prompt;你也可以切换为直接命令,或下载本地副本。

检查来源文件

决定是否安装前,请先阅读 SKILL.md,以及 SkillsMP 当前展示的配套文件。

正在显示 SKILL.md

SKILL.md
来源说明 · 只读预览
name
uniswap-v4-expert
description
Use when building on, integrating with, or analyzing Uniswap V4. Covers PoolManager singleton architecture, flash accounting via EIP-1153 transient storage, hook lifecycle, PoolKey structure, Currency type, dynamic fees, custom accounting, native ETH support, PositionManager (ERC-721 positions), and production deployment addresses.
# Uniswap V4 Expert ## Architecture Overview Uniswap V4 replaces V3's factory-per-pool model with a **singleton PoolManager** — every pool lives inside a single contract. This eliminates redundant bytecode deployments and enables multi-hop swaps to settle only net token transfers. All state-changing operations use **flash accounting** via EIP-1153 transient storage: callers accumulate deltas during an `unlock()` callback and must zero out all balances before the callback returns. ### Singleton Design ``` ┌────────────────────────────────────────────┐ │ PoolManager │ │ ┌──────────┐ ┌───────────┐ ┌──────────┐ │ │ │ Pool A │ │ Pool B │ │ Pool C │ │ │ │ ETH/USDC │ │ WBTC/USDC │ │ ETH/DAI │ │ │ └──────────┘ └───────────┘ └──────────┘ │ │ │ │ Transient Storage (EIP-1153) │ │ ┌──────────────────────────────────────┐ │ │ │ currency → delta mapping (per lock) │ │ │ └──────────────────────────────────────┘ │ └────────────────────────────────────────────┘ ``` ### Flash Accounting Flow 1. Caller invokes `poolManager.unlock(data)` 2. PoolManager calls `IUnlockCallback(msg.sender).unlockCallback(data)` 3. Inside the callback, caller executes operations (swap, modifyLiquidity, donate) 4. Each operation updates transient storage deltas — no token transfers yet 5. Caller resolves deltas via `settle()` (pay tokens in) and `take()` (withdraw tokens out) 6. On return from `unlockCallback`, PoolManager verifies all currency deltas are zero 7. If any delta is nonzero, the transaction reverts with `CurrencyNotSettled()` This means multi-hop swaps (e.g., A→B→C) only require net token movements for A and C, saving gas on intermediate transfers. ### Functions Callable Outside unlock() Only two functions do NOT require the unlock context: - `initialize()` — creates a new pool (no balance changes) - `updateDynamicLPFee()` — called by hook contracts to set the current dynamic fee Everything else (`swap`, `modifyLiquidity`, `donate`, `take`, `settle`, `mint`, `burn`, `sync`, `clear`) requires being inside an active `unlockCallback`. ## Core Types ### PoolKey The unique identifier for a pool. Defined in `v4-core/src/types/PoolKey.sol`: ```solidity import {Currency} from "v4-core/src/types/Currency.sol"; import {IHooks} from "v4-core/src/interfaces/IHooks.sol"; struct PoolKey { /// @notice The lower currency of the pool, sorted numerically Currency currency0; /// @notice The higher currency of the pool, sorted numerically Currency currency1; /// @notice The pool LP fee, capped at 1_000_000. If the highest bit is 1, the pool has a dynamic fee and must be exactly equal to 0x800000 uint24 fee; /// @notice Ticks that involve positions must be a multiple of tick spacing int24 tickSpacing; /// @notice The hooks of the pool IHooks hooks; } ``` **Sorting invariant**: `currency0 < currency1` is enforced. The PoolManager reverts with `CurrenciesOutOfOrderOrEqual` if violated. When constructing a PoolKey, always sort currencies by address value. ### PoolId A `bytes32` hash of the PoolKey, used as the storage key for pool state. Defined in `v4-core/src/types/PoolId.sol`: ```solidity type PoolId is bytes32; library PoolIdLibrary { function toId(PoolKey memory poolKey) internal pure returns (PoolId poolId) { assembly ("memory-safe") { // 0xa0 = 5 slots × 32 bytes (total size of PoolKey struct) poolId := keccak256(poolKey, 0xa0) } } } ``` Usage: `using PoolIdLibrary for PoolKey;` then `key.toId()`. ### Currency An address wrapper where `address(0)` represents native ETH. Defined in `v4-core/src/types/Currency.sol`: ```solidity type Currency is address; library CurrencyLibrary { Currency public constant ADDRESS_ZERO = Currency.wrap(address(0)); function isAddressZero(Currency currency) internal pure returns (bool) { return Currency.unwrap(currency) == Currency.unwrap(ADDRESS_ZERO); } function transfer(Currency currency, address to, uint256 amount) internal { /* handles ETH vs ERC-20 */ } function balanceOfSelf(Currency currency) internal view returns (uint256) { /* handles ETH vs ERC-20 */ } } ``` Native ETH pools use `Currency.wrap(address(0))` as one of the currencies. No WETH wrapping required. ### BalanceDelta Two `int128` values packed into a single `int256`. Upper 128 bits = amount0, lower 128 bits = amount1. Defined in `v4-core/src/types/BalanceDelta.sol`: ```solidity type BalanceDelta is int256; library BalanceDeltaLibrary { BalanceDelta public constant ZERO_DELTA = BalanceDelta.wrap(0); function amount0(BalanceDelta balanceDelta) internal pure returns (int128 _amount0) { assembly ("memory-safe") { _amount0 := sar(128, balanceDelta) } } function amount1(BalanceDelta balanceDelta) internal pure returns (int128 _amount1) { assembly ("memory-safe") { _amount1 := signextend(15, balanceDelta) } } } ``` Delta semantics from the caller's perspective: - **Negative** delta = caller owes tokens to PoolManager (must `settle()`) - **Positive** delta = PoolManager owes tokens to caller (can `take()`) ### BeforeSwapDelta Return type of the `beforeSwap` hook. Upper 128 bits = delta in **specified** tokens, lower 128 bits = delta in **unspecified** tokens. Defined in `v4-core/src/types/BeforeSwapDelta.sol`: ```solidity type BeforeSwapDelta is int256; function toBeforeSwapDelta(int128 deltaSpecified, int128 deltaUnspecified) pure returns (BeforeSwapDelta beforeSwapDelta) { assembly ("memory-safe") { beforeSwapDelta := or(shl(128, deltaSpecified), and(sub(shl(128, 1), 1), deltaUnspecified)) } } library BeforeSwapDeltaLibrary { BeforeSwapDelta public constant ZERO_DELTA = BeforeSwapDelta.wrap(0); function getSpecifiedDelta(BeforeSwapDelta delta) internal pure returns (int128); function getUnspecifiedDelta(BeforeSwapDelta delta) internal pure returns (int128); } ``` ## PoolManager Interface Full interface from `v4-core/src/interfaces/IPoolManager.sol`. The PoolManager inherits `IProtocolFees`, `IERC6909Claims`, `IExtsload`, and `IExttload`. ### initialize ```solidity function initialize(PoolKey memory key, uint160 sqrtPriceX96) external returns (int24 tick); ``` Creates a new pool. Does NOT require the unlock context. Reverts if `currency0 >= currency1`, if `tickSpacing` is zero or exceeds `type(int16).max`, or if the pool already exists. Emits `Initialize` event. ### unlock ```solidity function unlock(bytes calldata data) external returns (bytes memory); ``` Entry point for all delta-accounting operations. Calls `IUnlockCallback(msg.sender).unlockCallback(data)`. After the callback returns, asserts all currency deltas are zero. ### swap ```solidity function swap(PoolKey memory key, SwapParams memory params, bytes calldata hookData) external returns (BalanceDelta swapDelta); ``` Executes a swap. Only callable inside `unlockCallback`. Invokes `beforeSwap` and `afterSwap` hooks if the pool's hook contract has those permissions. ### modifyLiquidity ```solidity function modifyLiquidity(PoolKey memory key, ModifyLiquidityParams memory params, bytes calldata hookData) external returns (BalanceDelta callerDelta, BalanceDelta feesAccrued); ``` Adds or removes liquidity. Returns both the principal delta and fees accrued. A zero `liquidityDelta` "pokes" the position to collect fees without changing liquidity. ### donate ```solidity function donate(PoolKey memory key, uint256 amount0, uint256 amount1, bytes calldata hookData) external returns (BalanceDelta); ``` Distributes tokens to in-range liquidity providers. Useful for hook-driven fee distribution or protocol reward injection. ### Settlement Functions ```solidity function settle() external payable returns (uint256 paid); function settleFor(address recipient) external payable returns (uint256 paid); function sync(Currency currency) external; function take(Currency currency, address to, uint256 amount) external; function clear(Currency currency, uint256 amount) external; ``` **settle()**: Pays what the caller owes. For ERC-20 tokens, the caller must first call `sync(currency)`, transfer tokens to the PoolManager, then call `settle()`. For native ETH, send value directly with `settle{value: amount}()`. Returns the amount credited. **sync(currency)**: Snapshots the PoolManager's current ERC-20 balance into transient storage. MUST be called before transferring ERC-20 tokens for settlement. Not needed for native ETH. **take(currency, to, amount)**: Withdraws tokens the PoolManager owes to the caller. Reverts if the caller's delta for that currency is insufficient. **clear(currency, amount)**: Zeros out a positive delta WITHOUT transferring tokens. The tokens are permanently locked in the PoolManager. Use only for dust amounts. ### ERC-6909 Claims ```solidity function mint(address to, uint256 id, uint256 amount) external; function burn(address from, uint256 id, uint256 amount) external; ``` Converts currency deltas into ERC-6909 claim tokens (and vice versa). The `id` is the currency address cast to `uint256`. Useful for holding balances inside the PoolManager across transactions without actual token transfers. ## Parameter Structs ### SwapParams Defined in `v4-core/src/types/PoolOperation.sol`: ```solidity struct SwapParams { /// Whether to swap token0 for token1 or vice versa bool zeroForOne; /// The desired input amount if negative (exactIn), or the desired output amount if positive (exactOut) int256 amountSpecified; /// The sqrt price at which, if reached, the swap will stop executing uint160 sqrtPriceLimitX96; } ``` **CRITICAL**: `amountSpecified` sign convention: - **Negative** = exact input (caller specifies how much to spend) - **Positive** = exact output (caller specifies how much to receive) Price limits: - `zeroForOne = true`: set `sqrtPriceLimitX96` to a value **less than** the current price (price decreases) - `zeroForOne = false`: set `sqrtPriceLimitX96` to a value **greater than** the current price (price increases) - Use `TickMath.MIN_SQRT_PRICE + 1` or `TickMath.MAX_SQRT_PRICE - 1` for unlimited slippage ### ModifyLiquidityParams Defined in `v4-core/src/types/PoolOperation.sol`: ```solidity struct ModifyLiquidityParams { int24 tickLower; int24 tickUpper; int256 liquidityDelta; bytes32 salt; } ``` - `liquidityDelta > 0`: add liquidity - `liquidityDelta < 0`: remove liquidity - `liquidityDelta == 0`: poke (collect accrued fees only) - `salt`: differentiates multiple positions at the same tick range from the same address ## Fee System ### Static Fees Set at pool creation via `PoolKey.fee`. Denominated in **hundredths of a basis point** (1/100th of 1/10000th): | PoolKey.fee | Effective Fee | |-------------|--------------| | 100 | 0.01% | | 500 | 0.05% | | 3000 | 0.30% | | 10000 | 1.00% | | 1000000 | 100% (MAX) | ### Dynamic Fees From `v4-core/src/libraries/LPFeeLibrary.sol`: ```solidity library LPFeeLibrary { uint24 public constant DYNAMIC_FEE_FLAG = 0x800000; uint24 public constant OVERRIDE_FEE_FLAG = 0x400000; uint24 public constant REMOVE_OVERRIDE_MASK = 0xBFFFFF; uint24 public constant MAX_LP_FEE = 1000000; // 100% } ``` To create a dynamic fee pool, set `PoolKey.fee = LPFeeLibrary.DYNAMIC_FEE_FLAG` (exactly `0x800000`). Two mechanisms for dynamic fee updates: 1. **Persistent update**: Hook calls `poolManager.updateDynamicLPFee(key, newFee)` (e.g., in `afterInitialize` or periodically). This sets the stored fee for subsequent swaps. 2. **Per-swap override**: `beforeSwap` returns a fee with the override flag set in the third return value (`uint24`). The returned fee is `desiredFee | LPFeeLibrary.OVERRIDE_FEE_FLAG`. This overrides the stored fee for that single swap only. ```solidity function beforeSwap(address, PoolKey calldata, IPoolManager.SwapParams calldata, bytes calldata) external override returns (bytes4, BeforeSwapDelta, uint24) { uint24 dynamicFee = _computeFee(); return (this.beforeSwap.selector, BeforeSwapDeltaLibrary.ZERO_DELTA, dynamicFee | LPFeeLibrary.OVERRIDE_FEE_FLAG); } ``` ### Protocol Fees Set by the PoolManager owner via `IProtocolFees.setProtocolFee(PoolKey, uint24)`. Protocol fees are taken as a percentage of LP fees. The protocol fee is a `uint24` where the upper 12 bits are the fee for token0 and the lower 12 bits are the fee for token1. ## PositionManager (Periphery) The `PositionManager` is the canonical periphery contract for managing liquidity positions as ERC-721 NFTs. Source: `v4-periphery/src/PositionManager.sol`. ```solidity contract PositionManager is IPositionManager, ERC721Permit_v4, // ERC-721 + EIP-4494 permit PoolInitializer_v4, Multicall_v4, DeltaResolver, ReentrancyLock, BaseActionsRouter, // action dispatch via unlock Notifier, // subscriber/notification pattern Permit2Forwarder, // Permit2 integration NativeWrapper // WETH wrapping/unwrapping { ... } ``` **NFT metadata**: Name = `"Uniswap v4 Positions NFT"`, Symbol = `"UNI-V4-POSM"`. ### Entry Point ```solidity function modifyLiquidities(bytes calldata unlockData, uint256 deadline) external payable; ``` The standard entry point. Encodes a sequence of actions and their parameters. The `unlockData` is ABI-encoded as `(bytes actions, bytes[] params)` where `actions` is a packed byte array of action codes. ### Action Codes From `v4-periphery/src/libraries/Actions.sol`: ```solidity library Actions { uint256 internal constant INCREASE_LIQUIDITY = 0x00; uint256 internal constant DECREASE_LIQUIDITY = 0x01; uint256 internal constant MINT_POSITION = 0x02; uint256 internal constant BURN_POSITION = 0x03; uint256 internal constant SWAP_EXACT_IN_SINGLE = 0x06; uint256 internal constant SWAP_EXACT_IN = 0x07; uint256 internal constant SWAP_EXACT_OUT_SINGLE = 0x08; uint256 internal constant SWAP_EXACT_OUT = 0x09; uint256 internal constant SETTLE = 0x0b; uint256 internal constant SETTLE_ALL = 0x0c; uint256 internal constant SETTLE_PAIR = 0x0d; uint256 internal constant TAKE = 0x0e; uint256 internal constant TAKE_ALL = 0x0f; uint256 internal constant TAKE_PORTION = 0x10; uint256 internal constant TAKE_PAIR = 0x11; uint256 internal constant CLOSE_CURRENCY = 0x12; uint256 internal constant CLEAR_OR_TAKE = 0x13; uint256 internal constant SWEEP = 0x14; uint256 internal constant WRAP = 0x15; uint256 internal constant UNWRAP = 0x16; } ``` **DEPRECATED** (vulnerable to sandwich attacks — lack slippage protection): - `INCREASE_LIQUIDITY_FROM_DELTAS` (0x04) - `MINT_POSITION_FROM_DELTAS` (0x05) ### Typical Action Sequences Mint a new position: ``` [MINT_POSITION, SETTLE_PAIR, SWEEP] // or CLOSE_CURRENCY for each ``` Increase liquidity on existing position: ``` [INCREASE_LIQUIDITY, SETTLE_PAIR, SWEEP] ``` Decrease liquidity and collect: ``` [DECREASE_LIQUIDITY, TAKE_PAIR] ``` Burn an empty position: ``` [BURN_POSITION] // position must have zero liquidity ``` ### Subscriber/Notification Pattern The `Notifier` base enables position subscribers — external contracts that receive callbacks when a position is modified. Subscribers implement `ISubscriber`: ```solidity interface ISubscriber { function notifySubscribe(uint256 tokenId, bytes memory data) external; function notifyUnsubscribe(uint256 tokenId) external; function notifyModifyLiquidity(uint256 tokenId, int256 liquidityChange, BalanceDelta feesAccrued) external; function notifyBurn(uint256 tokenId) external; } ``` Subscribe via `positionManager.subscribe(tokenId, subscriber, data)`. The subscriber is notified on every liquidity modification or burn. ## Production Deployment Addresses ### Ethereum Mainnet (Chain ID: 1) | Contract | Address | |----------|---------| | PoolManager | `0x000000000004444c5dc75cB358380D2e3dE08A90` | | Universal Router | `0x66a9893cC07D91D95644AEDD05D03f95e1dBA8Af` | | PositionManager | `0xbD216513d74C8cf14cf4747E6AaA6420FF64ee9e` |
在 GitHub 查看
这个 SKILL.md 很大,SkillsMP 这里只预览前一段内容。 在 GitHub 查看