Uniswap V3 第 3 章:数学——集中流动性方程
这一章是 V3 的数学心脏。我们推导三个关键公式:流动性 L 与价格、token 数量的关系、虚拟储备(V3 怎么”假装”自己是 V2)、以及给定区间 [Pa,Pb] 需要多少 x 和 y。掌握它们,后面的 swap 和 liquidity 章节就是水到渠成。
目录
- 1. 从 V2 到 V3:虚拟储备的思想
- 2. 流动性 L 的定义
- 3. 核心公式:x 和 y 关于 L 和 √P
- 4. 真实储备曲线:被”平移”的双曲线
- 5. 区间内需要多少 x 和 y
- 6. 案例:在 [2700, 3300] 提供流动性
- 7. 三种价格位置的资产形态
- 8. 本章小结
- 9. 动手练习
1. 从 V2 到 V3:虚拟储备的思想
V2 的池子真实持有 x 和 y,满足 x·y=k。V3 的 LP 只在区间 [Pa,Pb] 提供流动性,但在这个区间内部,池子的行为和一个 V2 池子一模一样——只是这个”等效 V2 池”的储备是虚拟的(virtual reserves)。
核心思想:
- V3 在区间内表现得像一个
x_v · y_v = L²的恒定乘积池(x_v, y_v是虚拟储备)。 - 但 LP 实际只需投入”真实储备”——也就是把虚拟双曲线平移,只保留 [Pa,Pb] 这一段所需的资产。
- 区间外的虚拟储备不需要真的存在,因为价格永远不会走到那里(走到区间边界,头寸就全变成单一资产了)。
2. 流动性 L 的定义
虚拟储备满足 x_v · y_v = L²,且:
x_v = L / √P y_v = L · √P
验证:x_v · y_v = (L/√P)·(L·√P) = L² ✓。
L 就是这个等效恒定乘积池的”流动性深度”(类比 V2 的 √k)。在一个不跨 tick 的 swap 过程中,L 保持不变,价格 √P 沿着曲线移动。跨越 tick 时 L 才会改变(下一章)。
由 x_v = L/√P、y_v = L·√P 也可反解:
L = x_v · √P (由 x 一侧)
L = y_v / √P (由 y 一侧)
3. 核心公式:x 和 y 关于 L 和 √P
把上面两式记牢,这是 V3 一切计算的基础:
x = L / √P (token0 数量与 √P 成反比)
y = L · √P (token1 数量与 √P 成正比)
直觉:
- 价格 P 上涨(√P 变大)→ x 变少、y 变多。符合”token0 涨价了,池子里 token0 被买走、token1 增多”。
- swap 的本质就是沿这条曲线移动 √P,同时按这两个公式调整 x、y。
由此还能推出 swap 的增量公式(第 4 章会用):
Δy = L · Δ(√P) (卖出 y / 买入 y 改变 √P)
Δx = L · Δ(1/√P) (改变 1/√P)
4. 真实储备曲线:被”平移”的双曲线
V2 的曲线 x·y=k 过原点附近无限延伸(两端贴近坐标轴)。V3 的真实储备曲线是把这条虚拟双曲线向内平移,使它在区间端点处正好碰到坐标轴:
- 在价格 = Pb(上边界)时,头寸全是 token0(x),y = 0。
- 在价格 = Pa(下边界)时,头寸全是 token1(y),x = 0。
- 在 Pa 和 Pb 之间,头寸是 x 和 y 的混合。
平移量正好是”区间外那部分虚拟储备”。所以 LP 只需投入”真实储备”(区间内那段),却能获得等同于一个大得多的虚拟池的深度——这就是资本效率提升的数学来源。
图像上:V3 真实储备曲线是 V2 双曲线的一小段,被平移后两端真的碰到了坐标轴(V2 永远碰不到)。碰到坐标轴 = 头寸变单一资产 = 价格走出区间。
5. 区间内需要多少 x 和 y
设当前价格 Pc,区间 [Pa, Pb](Pa < Pb),流动性 L。需要投入的真实数量:
若 Pc ≤ Pa(价格在区间下方):全是 token0
x = L · (1/√Pa − 1/√Pb), y = 0
若 Pc ≥ Pb(价格在区间上方):全是 token1
x = 0, y = L · (√Pb − √Pa)
若 Pa < Pc < Pb(价格在区间内):两种都要
x = L · (1/√Pc − 1/√Pb)
y = L · (√Pc − √Pa)
这三组公式就是 NonfungiblePositionManager.mint 背后算”你要存多少 x 和 y”的依据。记忆方法:
- x(token0)的量和”上半区间 [Pc, Pb]“有关,用
1/√的差。 - y(token1)的量和”下半区间 [Pa, Pc]“有关,用
√的差。
6. 案例:在 [2700, 3300] 提供流动性
设 ETH/USDC,当前 Pc = 3000,区间 [Pa=2700, Pb=3300],想提供 L = 1,000,000(示意值)。先算平方根:
√Pa = √2700 ≈ 51.96
√Pc = √3000 ≈ 54.77
√Pb = √3300 ≈ 57.45
价格在区间内(2700 < 3000 < 3300),两种币都要:
x = L · (1/√Pc − 1/√Pb) = 1,000,000 · (1/54.77 − 1/57.45)
= 1,000,000 · (0.018258 − 0.017407) = 1,000,000 · 0.000851 ≈ 851 (token0 单位)
y = L · (√Pc − √Pa) = 1,000,000 · (54.77 − 51.96)
= 1,000,000 · 2.81 ≈ 2,810,000 (token1 单位)
所以提供这个头寸,你大约需要按比例投入 851 单位的 token0 和 2,810,000 单位的 token1(具体单位/精度按池子代币定)。如果价格涨到 3300,x 会被换光(全变 token1);跌到 2700,y 会被换光(全变 token0)。
7. 三种价格位置的资产形态
| 当前价 Pc 相对区间 | 头寸资产形态 | 是否赚手续费 |
|---|---|---|
| Pc ≤ Pa(跌破下界) | 全是 token1(Y) | ❌ 不赚(价格不在区间内) |
| Pa < Pc < Pb(区间内) | token0 + token1 混合 | ✅ 赚手续费 |
| Pc ≥ Pb(涨破上界) | 全是 token0(X) | ❌ 不赚 |
关键认知:只有当前价格落在你的区间内时,你的流动性才被使用、才赚手续费。 价格走出区间,你的头寸”睡着了”,变成单一资产静静躺着,直到价格回来。这就是 V3 LP 要主动管理区间(甚至用机器人再平衡)的原因。
8. 本章小结
- V3 在区间内表现得像 V2 恒定乘积池,但储备是虚拟的:
x_v·y_v = L²。 - 核心公式:
x = L/√P,y = L·√P;增量Δy = L·Δ√P,Δx = L·Δ(1/√P)。 - 真实储备曲线 = 虚拟双曲线被平移,使两端碰到坐标轴(= 头寸变单一资产 = 价格出区间)。
- 区间内所需 x、y:
x = L(1/√Pc − 1/√Pb)、y = L(√Pc − √Pa)(价格在区间内时)。 - 三种位置:跌破下界全是 token1、区间内混合、涨破上界全是 token0;只有区间内才赚手续费。
9. 动手练习
目标:把第 5 节的公式用代码实现,亲手算一个头寸需要的 x 和 y。
练习:计算区间所需 token 数量
写一个脚本/测试(纯数学,可不连链):
- 输入:
L、当前价Pc、区间[Pa, Pb]。 - 先算
√Pa, √Pc, √Pb。 - 按第 5 节三种情况,分别实现并返回
(x, y)。 - 用第 6 节的数字验证:
[2700,3300]、Pc=3000、L=1e6,应得到 x≈851、y≈2.81e6。 - 测试边界:令 Pc=2700(=Pa),应得到 y=0(全是 token0);令 Pc=3300(=Pb),应得到 x=0(全是 token1)。
进阶(链上)
- 用 V3 的
LiquidityAmounts库(periphery)里的getAmountsForLiquidity验证你的手算结果。 - 反过来用
getLiquidityForAmounts:给定你愿意投入的 x、y 和区间,算出能得到的 L。
运行
forge test --evm-version cancun --fork-url $FORK_URL \
--match-path test/UniswapV3Math.t.sol -vvv
下一章(第 4 章 Swap)讲 V3 的兑换算法:单区间内怎么算、跨 tick 时 liquidity net 怎么切换流动性、swap 手续费,以及 SwapRouter 的定输入/定输出。