死磕Uniswap V3(五):流动性管理与头寸
本文是「死磕Uniswap V3」系列的第五篇,深入剖析V3的流动性管理机制、Position数据结构以及mint/burn操作的完整实现。
系列导航
| 序号 | 标题 | 核心内容 |
|---|---|---|
| 01 | 概述与集中流动性 | AMM演进、集中流动性原理 |
| 02 | Tick机制与价格数学 | Tick设计、价格转换算法 |
| 03 | 架构与合约设计 | Factory、Pool合约结构 |
| 04 | 交换机制深度解析 | swap函数、价格发现 |
| 05 | 流动性管理与头寸 | Position、mint/burn |
| 06 | 费用系统与预言机 | 费用分配、TWAP |
| 07 | MEV与套利策略 | JIT、三明治攻击 |
1. 流动性管理概述
1.1 V3流动性的独特性
与V2不同,V3的流动性是非同质化的。每个流动性头寸都是独特的:
flowchart TB subgraph V2Group["V2 流动性 (ERC20)"] V2LP[LP Token] V2A[LP A: 100 LP] V2B[LP B: 100 LP] V2C[LP C: 100 LP] V2NOTE[所有LP权益相同<br/>可相互替代] V2LP --> V2A V2LP --> V2B V2LP --> V2C end subgraph V3Group["V3 流动性 (ERC721)"] V3NFT[NFT Position] V3A["LP A: [1800,2200]<br/>L=1000"] V3B["LP B: [1900,2100]<br/>L=500"] V3C["LP C: [2000,2500]<br/>L=2000"] V3NOTE[每个头寸独特<br/>不可替代] V3NFT --> V3A V3NFT --> V3B V3NFT --> V3C end style V2Group fill:#ffcdd2 style V3Group fill:#c8e6c9
1.2 核心操作流程
flowchart LR subgraph AddGroup["添加流动性"] M1[选择价格区间] M2[计算所需代币] M3[调用mint] M4[获得NFT头寸] M1 --> M2 M2 --> M3 M3 --> M4 end subgraph ManageGroup["管理流动性"] P1[增加流动性] P2[收取费用] P3[减少流动性] P1 --> P2 P2 --> P3 end subgraph RemoveGroup["移除流动性"] B1[调用burn] B2[获得代币] B3[调用collect] B4[取回费用] B1 --> B2 B2 --> B3 B3 --> B4 end AddGroup --> ManageGroup ManageGroup --> RemoveGroup style AddGroup fill:#e3f2fd style ManageGroup fill:#fff3e0 style RemoveGroup fill:#fce4ec
2. Position数据结构
2.1 Position.Info详解
每个流动性头寸由唯一的三元组标识:(owner, tickLower, tickUpper)
library Position {
struct Info {
// 此头寸的流动性数量
uint128 liquidity;
// 上次更新时的内部费用增长率
// 用于计算自上次操作以来累积的费用
uint256 feeGrowthInside0LastX128;
uint256 feeGrowthInside1LastX128;
// 待领取的费用(已结算但未提取)
uint128 tokensOwed0;
uint128 tokensOwed1;
}
}graph TB subgraph PosGroup["Position.Info 结构"] L[liquidity<br/>uint128<br/>流动性数量] FG0[feeGrowthInside0LastX128<br/>uint256<br/>token0费用增长快照] FG1[feeGrowthInside1LastX128<br/>uint256<br/>token1费用增长快照] TO0[tokensOwed0<br/>uint128<br/>待领取token0] TO1[tokensOwed1<br/>uint128<br/>待领取token1] end subgraph FuncGroup["功能分类"] 流动性管理[流动性管理] 费用追踪[费用追踪] 待领取[待领取费用] end L --> 流动性管理 FG0 --> 费用追踪 FG1 --> 费用追踪 TO0 --> 待领取 TO1 --> 待领取 style PosGroup fill:#e8f5e9
2.2 Position的唯一标识
/// @notice 获取头寸信息
function get(
mapping(bytes32 => Info) storage self,
address owner,
int24 tickLower,
int24 tickUpper
) internal view returns (Position.Info storage position) {
// 使用keccak256哈希三元组作为键
position = self[keccak256(abi.encodePacked(owner, tickLower, tickUpper))];
}flowchart LR subgraph InputGroup["输入"] O[owner地址] TL[tickLower] TU[tickUpper] end subgraph HashGroup["哈希计算"] PACK["abi.encodePacked(owner, tickLower, tickUpper)"] HASH["keccak256(...)"] end subgraph StoreGroup["存储"] KEY["bytes32 key"] POS["positions[key]"] end O --> PACK TL --> PACK TU --> PACK PACK --> HASH HASH --> KEY KEY --> POS style HashGroup fill:#fff3e0
2.3 费用快照机制
sequenceDiagram participant LP as 流动性提供者 participant Pool as 池子合约 participant Position as Position结构 LP->>Pool: mint(tickLower, tickUpper, amount) Pool->>Position: 记录当前feeGrowthInside Note over Position: feeGrowthInside0LastX128 = 当前值 Note over Pool: ... 交易发生,费用累积 ... LP->>Pool: burn(tickLower, tickUpper, 0) Pool->>Position: 计算费用差值 Note over Position: 应得费用 = <br/>(当前feeGrowthInside - Last) × liquidity Pool->>Position: 更新tokensOwed Pool-->>LP: 返回可领取金额
3. mint函数:添加流动性
3.1 函数签名与参数
function mint(
address recipient, // 头寸所有者
int24 tickLower, // 价格区间下界
int24 tickUpper, // 价格区间上界
uint128 amount, // 流动性数量
bytes calldata data // 回调数据
) external override lock returns (
uint256 amount0, // 需要的token0数量
uint256 amount1 // 需要的token1数量
);3.2 完整执行流程
flowchart TB START[mint调用] --> VALIDATE[参数验证] VALIDATE --> |amount > 0| MODIFY[_modifyPosition] subgraph _modifyPosition MP1[检查tick有效性] MP2[_updatePosition] MP3[计算所需代币数量] end MODIFY --> MP1 --> MP2 --> MP3 MP3 --> CALC{当前价格位置?} CALC -->|P < Pa| ONLY0[只需token0] CALC -->|Pa ≤ P ≤ Pb| BOTH[需要两种token] CALC -->|P > Pb| ONLY1[只需token1] ONLY0 & BOTH & ONLY1 --> BALANCE[记录余额before] BALANCE --> CALLBACK[调用mint回调] CALLBACK --> VERIFY[验证余额增加] VERIFY --> EMIT[发出Mint事件] EMIT --> END[返回代币数量] style _modifyPosition fill:#e3f2fd style CALC fill:#fff3e0
3.3 核心代码实现
function mint(
address recipient,
int24 tickLower,
int24 tickUpper,
uint128 amount,
bytes calldata data
) external override lock returns (uint256 amount0, uint256 amount1) {
require(amount > 0);
// 修改头寸并获取所需代币数量
(, int256 amount0Int, int256 amount1Int) = _modifyPosition(
ModifyPositionParams({
owner: recipient,
tickLower: tickLower,
tickUpper: tickUpper,
liquidityDelta: int256(amount).toInt128()
})
);
amount0 = uint256(amount0Int);
amount1 = uint256(amount1Int);
uint256 balance0Before;
uint256 balance1Before;
// 记录余额快照
if (amount0 > 0) balance0Before = balance0();
if (amount1 > 0) balance1Before = balance1();
// 调用回调函数,由调用者转入代币
IUniswapV3MintCallback(msg.sender).uniswapV3MintCallback(
amount0, amount1, data
);
// 验证代币已转入
if (amount0 > 0) require(balance0Before.add(amount0) <= balance0(), 'M0');
if (amount1 > 0) require(balance1Before.add(amount1) <= balance1(), 'M1');
emit Mint(msg.sender, recipient, tickLower, tickUpper, amount, amount0, amount1);
}4. _modifyPosition:核心位置修改
4.1 功能概述
_modifyPosition是流动性管理的核心函数,负责:
flowchart TB subgraph _modifyPosition职责 R1[验证tick参数] R2[更新头寸信息] R3[更新tick数据] R4[管理tick位图] R5[计算代币数量] end R1 --> R2 --> R3 --> R4 --> R5 style _modifyPosition职责 fill:#e8f5e9
4.2 参数结构
struct ModifyPositionParams {
// 头寸所有者
address owner;
// 价格区间下界tick
int24 tickLower;
// 价格区间上界tick
int24 tickUpper;
// 流动性变化量(正=添加,负=移除)
int128 liquidityDelta;
}4.3 完整代码实现
function _modifyPosition(ModifyPositionParams memory params)
private
noDelegateCall
returns (
Position.Info storage position,
int256 amount0,
int256 amount1
)
{
// 1. 验证tick参数
checkTicks(params.tickLower, params.tickUpper);
Slot0 memory _slot0 = slot0;
// 2. 更新头寸
position = _updatePosition(
params.owner,
params.tickLower,
params.tickUpper,
params.liquidityDelta,
_slot0.tick
);
// 3. 计算所需代币数量
if (params.liquidityDelta != 0) {
if (_slot0.tick < params.tickLower) {
// 当前价格在区间下方:只需要token0
amount0 = SqrtPriceMath.getAmount0Delta(
TickMath.getSqrtRatioAtTick(params.tickLower),
TickMath.getSqrtRatioAtTick(params.tickUpper),
params.liquidityDelta
);
} else if (_slot0.tick < params.tickUpper) {
// 当前价格在区间内:需要两种代币
amount0 = SqrtPriceMath.getAmount0Delta(
_slot0.sqrtPriceX96,
TickMath.getSqrtRatioAtTick(params.tickUpper),
params.liquidityDelta
);
amount1 = SqrtPriceMath.getAmount1Delta(
TickMath.getSqrtRatioAtTick(params.tickLower),
_slot0.sqrtPriceX96,
params.liquidityDelta
);
// 更新全局活跃流动性
liquidity = LiquidityMath.addDelta(liquidity, params.liquidityDelta);
} else {
// 当前价格在区间上方:只需要token1
amount1 = SqrtPriceMath.getAmount1Delta(
TickMath.getSqrtRatioAtTick(params.tickLower),
TickMath.getSqrtRatioAtTick(params.tickUpper),
params.liquidityDelta
);
}
}
}4.4 三种价格位置的代币需求
flowchart LR subgraph LowerGroup["价格 < tickLower"] A1[只需token0] A2["amount0 = L × (1/√Pa - 1/√Pb)"] A3["amount1 = 0"] A1 --> A2 A2 --> A3 end subgraph InsideGroup["tickLower ≤ 价格 < tickUpper"] B1[需要两种token] B2["amount0 = L × (1/√P - 1/√Pb)"] B3["amount1 = L × (√P - √Pa)"] B1 --> B2 B2 --> B3 end subgraph UpperGroup["价格 ≥ tickUpper"] C1[只需token1] C2["amount0 = 0"] C3["amount1 = L × (√Pb - √Pa)"] C1 --> C2 C2 --> C3 end style LowerGroup fill:#e3f2fd style InsideGroup fill:#fff3e0 style UpperGroup fill:#fce4ec
5. _updatePosition:头寸更新机制
5.1 更新流程
flowchart TB START[_updatePosition] --> GET[获取现有头寸] GET --> READ[读取全局费用增长率] READ --> CHECK{liquidityDelta != 0?} CHECK -->|是| ORACLE[更新预言机观察值] ORACLE --> UPDATE_LOWER[更新tickLower的Tick信息] UPDATE_LOWER --> UPDATE_UPPER[更新tickUpper的Tick信息] UPDATE_UPPER --> FLIP_CHECK{tick状态翻转?} FLIP_CHECK -->|是| FLIP_BITMAP[更新TickBitmap] FLIP_CHECK -->|否| CALC_FEE[计算内部费用增长率] FLIP_BITMAP --> CALC_FEE CHECK -->|否| CALC_FEE CALC_FEE --> UPDATE_POS[更新Position信息] UPDATE_POS --> CLEANUP{liquidityDelta < 0<br/>且tick已翻转?} CLEANUP -->|是| CLEAR[清理tick数据] CLEANUP -->|否| END[返回position] CLEAR --> END style FLIP_CHECK fill:#fff3e0 style UPDATE_POS fill:#c8e6c9
5.2 完整代码实现
function _updatePosition(
address owner,
int24 tickLower,
int24 tickUpper,
int128 liquidityDelta,
int24 tick
) private returns (Position.Info storage position) {
// 获取头寸引用
position = positions.get(owner, tickLower, tickUpper);
// 缓存全局费用增长率
uint256 _feeGrowthGlobal0X128 = feeGrowthGlobal0X128;
uint256 _feeGrowthGlobal1X128 = feeGrowthGlobal1X128;
bool flippedLower;
bool flippedUpper;
if (liquidityDelta != 0) {
uint32 time = _blockTimestamp();
// 获取预言机累积值
(int56 tickCumulative, uint160 secondsPerLiquidityCumulativeX128) =
observations.observeSingle(
time,
0,
slot0.tick,
slot0.observationIndex,
liquidity,
slot0.observationCardinality
);
// 更新下界tick
flippedLower = ticks.update(
tickLower,
tick,
liquidityDelta,
_feeGrowthGlobal0X128,
_feeGrowthGlobal1X128,
secondsPerLiquidityCumulativeX128,
tickCumulative,
time,
false, // 下界
maxLiquidityPerTick
);
// 更新上界tick
flippedUpper = ticks.update(
tickUpper,
tick,
liquidityDelta,
_feeGrowthGlobal0X128,
_feeGrowthGlobal1X128,
secondsPerLiquidityCumulativeX128,
tickCumulative,
time,
true, // 上界
maxLiquidityPerTick
);
// 更新位图
if (flippedLower) {
tickBitmap.flipTick(tickLower, tickSpacing);
}
if (flippedUpper) {
tickBitmap.flipTick(tickUpper, tickSpacing);
}
}
// 计算区间内的费用增长率
(uint256 feeGrowthInside0X128, uint256 feeGrowthInside1X128) =
ticks.getFeeGrowthInside(
tickLower,
tickUpper,
tick,
_feeGrowthGlobal0X128,
_feeGrowthGlobal1X128
);
// 更新头寸信息
position.update(liquidityDelta, feeGrowthInside0X128, feeGrowthInside1X128);
// 清理不再需要的tick数据
if (liquidityDelta < 0) {
if (flippedLower) {
ticks.clear(tickLower);
}
if (flippedUpper) {
ticks.clear(tickUpper);
}
}
}6. Tick更新机制
6.1 Tick.update函数
function update(
mapping(int24 => Tick.Info) storage self,
int24 tick,
int24 tickCurrent,
int128 liquidityDelta,
uint256 feeGrowthGlobal0X128,
uint256 feeGrowthGlobal1X128,
uint160 secondsPerLiquidityCumulativeX128,
int56 tickCumulative,
uint32 time,
bool upper, // true=上界,false=下界
uint128 maxLiquidity
) internal returns (bool flipped) {
Tick.Info storage info = self[tick];
uint128 liquidityGrossBefore = info.liquidityGross;
uint128 liquidityGrossAfter = LiquidityMath.addDelta(
liquidityGrossBefore,
liquidityDelta
);
require(liquidityGrossAfter <= maxLiquidity, 'LO');
// 判断是否翻转(从0变为非0,或从非0变为0)
flipped = (liquidityGrossAfter == 0) != (liquidityGrossBefore == 0);
if (liquidityGrossBefore == 0) {
// 首次初始化该tick
if (tick <= tickCurrent) {
// 初始化outside值
info.feeGrowthOutside0X128 = feeGrowthGlobal0X128;
info.feeGrowthOutside1X128 = feeGrowthGlobal1X128;
info.secondsPerLiquidityOutsideX128 = secondsPerLiquidityCumulativeX128;
info.tickCumulativeOutside = tickCumulative;
info.secondsOutside = time;
}
info.initialized = true;
}
info.liquidityGross = liquidityGrossAfter;
// 更新liquidityNet
// 下界:正向添加(从左向右跨越时增加流动性)
// 上界:反向添加(从左向右跨越时减少流动性)
info.liquidityNet = upper
? int256(info.liquidityNet).sub(liquidityDelta).toInt128()
: int256(info.liquidityNet).add(liquidityDelta).toInt128();
}6.2 liquidityNet的符号约定
flowchart TB subgraph AddGroup["添加流动性示例"] ADD[添加区间[100, 200]<br/>流动性L=1000] end subgraph TickGroup["tick更新"] T100["tick 100 (下界)<br/>liquidityNet += 1000"] T200["tick 200 (上界)<br/>liquidityNet -= 1000"] end subgraph PriceGroup["价格从左向右移动"] CROSS100[跨越tick 100<br/>活跃流动性 += 1000] CROSS200[跨越tick 200<br/>活跃流动性 -= 1000] end ADD --> T100 ADD --> T200 T100 -.-> CROSS100 T200 -.-> CROSS200 style AddGroup fill:#e3f2fd style PriceGroup fill:#c8e6c9
6.3 Tick翻转与位图管理
flowchart TD subgraph tick翻转条件 C1["liquidityGross: 0 → 非0<br/>(首次有LP进入)"] C2["liquidityGross: 非0 → 0<br/>(最后LP离开)"] end subgraph 位图操作 F1[flipTick翻转位] F2["0→1: tick变为活跃"] F3["1→0: tick变为非活跃"] end C1 --> F1 --> F2 C2 --> F1 --> F3 style tick翻转条件 fill:#fff3e0
7. Position.update:费用结算
7.1 费用计算原理
flowchart TB subgraph 费用增长率概念 G[全局费用增长率<br/>feeGrowthGlobalX128] I[内部费用增长率<br/>feeGrowthInsideX128] L[上次记录值<br/>feeGrowthInsideLastX128] end subgraph 计算公式 F["应得费用 = (Inside当前 - Inside上次) × liquidity"] end G --> I I & L --> F style 计算公式 fill:#c8e6c9
7.2 完整实现
function update(
Info storage self,
int128 liquidityDelta,
uint256 feeGrowthInside0X128,
uint256 feeGrowthInside1X128
) internal {
Info memory _self = self;
uint128 liquidityNext;
if (liquidityDelta == 0) {
// 仅收取费用,需要有流动性
require(_self.liquidity > 0, 'NP');
liquidityNext = _self.liquidity;
} else {
liquidityNext = LiquidityMath.addDelta(_self.liquidity, liquidityDelta);
}
// 计算应得费用
// 使用unchecked因为溢出是期望行为(环绕算术)
uint128 tokensOwed0 = uint128(
FullMath.mulDiv(
feeGrowthInside0X128 - _self.feeGrowthInside0LastX128,
_self.liquidity,
FixedPoint128.Q128
)
);
uint128 tokensOwed1 = uint128(
FullMath.mulDiv(
feeGrowthInside1X128 - _self.feeGrowthInside1LastX128,
_self.liquidity,
FixedPoint128.Q128
)
);
// 更新流动性
if (liquidityDelta != 0) self.liquidity = liquidityNext;
// 更新费用增长快照
self.feeGrowthInside0LastX128 = feeGrowthInside0X128;
self.feeGrowthInside1LastX128 = feeGrowthInside1X128;
// 累积待领取费用
if (tokensOwed0 > 0 || tokensOwed1 > 0) {
self.tokensOwed0 += tokensOwed0;
self.tokensOwed1 += tokensOwed1;
}
}7.3 费用计算示例
flowchart LR subgraph InitGroup["初始状态"] S1["liquidity = 1000"] S2["feeGrowthInsideLast = 100"] end subgraph CurrGroup["当前状态"] C1["feeGrowthInside = 150"] end subgraph CalcGroup["计算"] CALC["tokensOwed = (150 - 100) × 1000 / 2^128"] end subgraph ResultGroup["结果"] R["tokensOwed ≈ 50 (归一化后)"] end S1 --> CALC S2 --> CALC C1 --> CALC CALC --> R style CalcGroup fill:#fff3e0
8. burn函数:移除流动性
8.1 函数签名
function burn(
int24 tickLower,
int24 tickUpper,
uint128 amount
) external override lock returns (
uint256 amount0,
uint256 amount1
);8.2 执行流程
flowchart TB START[burn调用] --> MODIFY[_modifyPosition<br/>liquidityDelta为负] MODIFY --> UPDATE[更新头寸] UPDATE --> CALC[计算可取回代币] CALC --> ADD_OWED[累加到tokensOwed] ADD_OWED --> EMIT[发出Burn事件] EMIT --> END[返回代币数量] NOTE[注意:burn不直接转账<br/>需要调用collect取回] style NOTE fill:#ffcdd2
8.3 代码实现
function burn(
int24 tickLower,
int24 tickUpper,
uint128 amount
) external override lock returns (uint256 amount0, uint256 amount1) {
// 以负的liquidityDelta调用_modifyPosition
(Position.Info storage position, int256 amount0Int, int256 amount1Int) =
_modifyPosition(
ModifyPositionParams({
owner: msg.sender,
tickLower: tickLower,
tickUpper: tickUpper,
liquidityDelta: -int256(amount).toInt128()
})
);
// 转换为正数(burn返回的是应得的代币)
amount0 = uint256(-amount0Int);
amount1 = uint256(-amount1Int);
// 累加到待领取(position.update已经处理了费用)
if (amount0 > 0 || amount1 > 0) {
(position.tokensOwed0, position.tokensOwed1) = (
position.tokensOwed0 + uint128(amount0),
position.tokensOwed1 + uint128(amount1)
);
}
emit Burn(msg.sender, tickLower, tickUpper, amount, amount0, amount1);
}9. collect函数:领取代币
9.1 函数签名
function collect(
address recipient,
int24 tickLower,
int24 tickUpper,
uint128 amount0Requested,
uint128 amount1Requested
) external override lock returns (
uint128 amount0,
uint128 amount1
);9.2 执行流程
flowchart TB START[collect调用] --> GET[获取头寸] GET --> CALC0[计算实际可领取token0] CALC0 --> CALC1[计算实际可领取token1] CALC1 --> CHECK0{amount0 > 0?} CHECK0 -->|是| UPDATE0[更新tokensOwed0] CHECK0 -->|否| CHECK1 UPDATE0 --> TRANSFER0[转账token0] TRANSFER0 --> CHECK1{amount1 > 0?} CHECK1 -->|是| UPDATE1[更新tokensOwed1] CHECK1 -->|否| EMIT UPDATE1 --> TRANSFER1[转账token1] TRANSFER1 --> EMIT[发出Collect事件] EMIT --> END[返回实际领取数量] style CHECK0 fill:#fff3e0 style CHECK1 fill:#fff3e0
9.3 代码实现
function collect(
address recipient,
int24 tickLower,
int24 tickUpper,
uint128 amount0Requested,
uint128 amount1Requested
) external override lock returns (uint128 amount0, uint128 amount1) {
Position.Info storage position = positions.get(msg.sender, tickLower, tickUpper);
// 计算实际可领取数量(取请求和可用的最小值)
amount0 = amount0Requested > position.tokensOwed0
? position.tokensOwed0
: amount0Requested;
amount1 = amount1Requested > position.tokensOwed1
? position.tokensOwed1
: amount1Requested;
// 更新待领取余额
if (amount0 > 0) {
position.tokensOwed0 -= amount0;
TransferHelper.safeTransfer(token0, recipient, amount0);
}
if (amount1 > 0) {
position.tokensOwed1 -= amount1;
TransferHelper.safeTransfer(token1, recipient, amount1);
}
emit Collect(msg.sender, recipient, tickLower, tickUpper, amount0, amount1);
}10. NonfungiblePositionManager:NFT封装
10.1 架构关系
flowchart TB subgraph 用户层 USER[用户] end subgraph 外围合约 NPM[NonfungiblePositionManager<br/>ERC721] end subgraph 核心合约 POOL[UniswapV3Pool] end USER -->|mint/burn NFT| NPM NPM -->|mint/burn 流动性| POOL style 外围合约 fill:#e3f2fd style 核心合约 fill:#fff3e0
10.2 Position结构(NFT)
struct Position {
// 用于计算费用的临时变量
uint96 nonce;
// 允许操作此头寸的地址
address operator;
// 池子标识
address token0;
address token1;
uint24 fee;
// 价格区间
int24 tickLower;
int24 tickUpper;
// 流动性数量
uint128 liquidity;
// 费用增长快照
uint256 feeGrowthInside0LastX128;
uint256 feeGrowthInside1LastX128;
// 待领取费用
uint128 tokensOwed0;
uint128 tokensOwed1;
}10.3 NFT铸造流程
sequenceDiagram participant User as 用户 participant NPM as PositionManager participant Pool as Pool合约 participant NFT as ERC721 User->>NPM: mint(params) NPM->>Pool: mint(tickLower, tickUpper, amount) Pool-->>NPM: 回调uniswapV3MintCallback NPM->>Pool: 转入所需代币 Pool-->>NPM: 返回(amount0, amount1) NPM->>NFT: _mint(tokenId) NFT-->>User: NFT头寸代币
10.4 关键函数
// 铸造新头寸
function mint(MintParams calldata params)
external
payable
override
checkDeadline(params.deadline)
returns (
uint256 tokenId,
uint128 liquidity,
uint256 amount0,
uint256 amount1
);
// 增加流动性
function increaseLiquidity(IncreaseLiquidityParams calldata params)
external
payable
override
checkDeadline(params.deadline)
returns (
uint128 liquidity,
uint256 amount0,
uint256 amount1
);
// 减少流动性
function decreaseLiquidity(DecreaseLiquidityParams calldata params)
external
payable
override
isAuthorizedForToken(params.tokenId)
checkDeadline(params.deadline)
returns (uint256 amount0, uint256 amount1);
// 收取费用
function collect(CollectParams calldata params)
external
payable
override
isAuthorizedForToken(params.tokenId)
returns (uint256 amount0, uint256 amount1);11. 流动性数学计算
11.1 流动性与代币数量的关系
graph TB subgraph 核心公式 F1["token0数量变化:<br/>Δx = L × (1/√Pa - 1/√Pb)"] F2["token1数量变化:<br/>Δy = L × (√Pb - √Pa)"] end subgraph 等价形式 E1["Δx = L × (√Pb - √Pa) / (√Pa × √Pb)"] E2["L = Δy / (√Pb - √Pa)"] E3["L = Δx × √Pa × √Pb / (√Pb - √Pa)"] end F1 --> E1 F2 --> E2 & E3 style 核心公式 fill:#e8f5e9
11.2 SqrtPriceMath实现
/// @notice 计算价格变化所需的token0数量
function getAmount0Delta(
uint160 sqrtRatioAX96,
uint160 sqrtRatioBX96,
uint128 liquidity,
bool roundUp
) internal pure returns (uint256 amount0) {
// 确保 sqrtRatioA < sqrtRatioB
if (sqrtRatioAX96 > sqrtRatioBX96)
(sqrtRatioAX96, sqrtRatioBX96) = (sqrtRatioBX96, sqrtRatioAX96);
// amount0 = L × (√Pb - √Pa) / (√Pa × √Pb)
// = L × (sqrtRatioB - sqrtRatioA) / (sqrtRatioA × sqrtRatioB / 2^96)
uint256 numerator1 = uint256(liquidity) << FixedPoint96.RESOLUTION;
uint256 numerator2 = sqrtRatioBX96 - sqrtRatioAX96;
require(sqrtRatioAX96 > 0);
return roundUp
? UnsafeMath.divRoundingUp(
FullMath.mulDivRoundingUp(numerator1, numerator2, sqrtRatioBX96),
sqrtRatioAX96
)
: FullMath.mulDiv(numerator1, numerator2, sqrtRatioBX96) / sqrtRatioAX96;
}
/// @notice 计算价格变化所需的token1数量
function getAmount1Delta(
uint160 sqrtRatioAX96,
uint160 sqrtRatioBX96,
uint128 liquidity,
bool roundUp
) internal pure returns (uint256 amount1) {
// 确保 sqrtRatioA < sqrtRatioB
if (sqrtRatioAX96 > sqrtRatioBX96)
(sqrtRatioAX96, sqrtRatioBX96) = (sqrtRatioBX96, sqrtRatioAX96);
// amount1 = L × (√Pb - √Pa)
return roundUp
? FullMath.mulDivRoundingUp(
liquidity,
sqrtRatioBX96 - sqrtRatioAX96,
FixedPoint96.Q96
)
: FullMath.mulDiv(
liquidity,
sqrtRatioBX96 - sqrtRatioAX96,
FixedPoint96.Q96
);
}12. 本章小结
12.1 核心概念回顾
mindmap root((流动性管理)) Position 唯一标识三元组 流动性数量 费用快照机制 待领取费用 mint操作 参数验证 _modifyPosition 代币需求计算 回调验证 burn操作 负liquidityDelta 费用结算 累加tokensOwed collect操作 领取待结算代币 支持部分领取 NFT封装 NonfungiblePositionManager ERC721标准 头寸管理
12.2 关键设计要点
| 设计要点 | 实现方式 | 效果 |
|---|---|---|
| 非同质化 | Position三元组 | 每个头寸独特可追踪 |
| 费用跟踪 | 增长率快照 | O(1)费用计算 |
| 安全转账 | 回调+余额验证 | 防止代币丢失 |
| NFT封装 | PositionManager | 用户友好的接口 |
| Tick管理 | 位图+翻转检测 | 高效状态管理 |
12.3 操作流程对比
flowchart LR subgraph AddLiqGroup["添加流动性"] A1[mint] A2[_modifyPosition] A3[回调转入代币] A1 --> A2 A2 --> A3 end subgraph RemoveLiqGroup["移除流动性"] B1[burn] B2[_modifyPosition] B3[累加tokensOwed] B4[collect取回] B1 --> B2 B2 --> B3 B3 --> B4 end subgraph CollectOnlyGroup["仅收费用"] C1[burn amount=0] C2[结算费用] C3[collect取回] C1 --> C2 C2 --> C3 end style AddLiqGroup fill:#c8e6c9 style RemoveLiqGroup fill:#ffcdd2 style CollectOnlyGroup fill:#fff3e0
下一篇预告
在下一篇文章中,我们将深入探讨费用系统与预言机,包括:
- 费用增长率的完整计算机制
- 协议费用的分配与提取
- TWAP预言机的实现原理
- 观察者数组的管理与扩容