18.Uniswap V3 手续费计算(一)
目录
核心摘要 (Key Takeaways)
- V3手续费的复杂性根源:与V2的全范围流动性不同,Uniswap V3的集中流动性允许LP在自定义价格区间(
ticks)提供流动性,这使得手续费的计算和分配变得极其复杂。 - 全局手续费累加器
feeGrowthGlobal:Uniswap V3引入了一个全局状态变量feeGrowthGlobal,它代表每单位流动性可以获得的总手续费。这个值会随着每笔交易单调递增。 - 手续费按
token in收取:手续费只对交易中“进入”池子的代币收取。因此,系统需要为交易对中的两种代币(token0和token1)分别维护两个独立的feeGrowthGlobal变量,导致其增长呈现阶梯状。 - LP的核心关注点
feeGrowthInside:LP只关心在其提供的流动性区间内产生的手су费。因此,需要从全局feeGrowthGlobal中剥离掉区间外的部分,得到feeGrowthInside。 - 精巧的边界记录机制
feeGrowthOutside:V3通过在每个tick边界上记录一个名为feeGrowthOutside的值,来巧妙地计算任意区间的feeGrowthInside。当价格穿越一个tick时,该tick的feeGrowthOutside值会被更新,从而精确追踪内外手续费的累积情况。
1. Uniswap V2 vs. V3 手续费机制对比
1.1 Uniswap V2:简单直接
- 流动性模型:流动性平均分布在从
0到+∞的整个价格曲线上。 - 手续费收取:
- 对每笔交易收取固定的千分之三(0.3%)手续费。
- 手续费只对
token in(交易中流入池子的代币)收取。 - 案例:
- 用 X 换 Y:收取
0.003 * X,实际用于交易的是0.997 * X。 - 用 Y 换 X:收取
0.003 * Y,实际用于交易的是0.997 * Y。
- 用 X 换 Y:收取
- 手续费分配:收取的手续费会自动累积到流动性池中,为LP实现自动复投增值,无需任何额外操作。
1.2 Uniswap V3:挑战重重
- 核心挑战:集中流动性 (Concentrated Liquidity)。
- 不同的LP可以在不同的价格区间(由
tick lower和tick upper定义,视频中也称为PA和PB)提供流动性。 - 手续费档位:V3提供了多个手续费档位供流动性池选择,如
0.01%,0.05%,0.3%,1%。
- 不同的LP可以在不同的价格区间(由
- 需要解决的问题:
- 按比例分配:在同一个
tick区间内,手续费需按LP提供的流动性比例进行分配。 - 跨
tick计算:当交易价格跨越tick边界,导致总流动性发生变化时,如何精确计算每个LP在其活跃区间内应得的手续费。
- 按比例分配:在同一个
2. 手续费分配的基本原理与 feeGrowth 的引入
2.1 单次交易的分配逻辑
- 核心思想:LP在某个价格区间获得的手续费,等于该区间产生的总手续费乘以该LP提供的流动性占比。
- 案例:单次跨
tick交易- 背景:
- 一个LP在某个范围内提供了流动性,大小为
S。 - 一笔交易发生,使得价格从
tick_0移动到tick_1。 - 在区间的总流动性为,产生的总手续费为。
- 在
tick_1区间的总流动性为,产生的总手续费为。
- 一个LP在某个范围内提供了流动性,大小为
- 计算LP应得手续费:该LP获得的手续费 是其在每个
tick区间所获手续费的总和。
- 背景:
2.2 feeGrowthGlobal:全局手续费累加器
- 概念推导:将上述逻辑扩展到
N次交易,LP的总手续费可以表示为: - 定义:Uniswap V3将公式中求和的部分 定义为一个全局变量,称为
feeGrowthGlobal(视频中简称为FG)。- 物理含义:
feeGrowthGlobal代表从系统部署开始,每单位流动性 (S=1) 累计可以获得的总手续费。 - 简化公式:LP的总手续费 。
- 物理含义:
- 核心特性:
- 全局状态:
feeGrowthGlobal是一个存储在合约中的全局变量,随每次交易更新。 - 单调递增:只要有交易发生,
feeGrowthGlobal的值就会不断累积,永远不会减少。 - 按代币分离:由于手续费只对
token in收取,因此合约为交易对中的两个代币分别维护了两个feeGrowthGlobal变量:**feeGrowthGlobal0**(针对token0)**feeGrowthGlobal1**(针对token1)
- 阶梯式增长:
- 当价格上涨时(例如,用
token0换token1),feeGrowthGlobal0会增加,而feeGrowthGlobal1保持不变(呈水平线)。 - 当价格下跌时(例如,用
token1换token0),feeGrowthGlobal1会增加,而feeGrowthGlobal0保持不变。 - 这导致每个
feeGrowthGlobal变量的增长曲线都呈现出阶梯状。
- 当价格上涨时(例如,用
- 全局状态:
3. 从全局到局部:计算 feeGrowthInside
- LP的视角:一个在特定价格区间 (
tick lower,tick upper) 提供流动性的LP,只关心在这个区间内(inside)累积的手续费,区间外(outside)的与他无关。 - 核心概念:
- feeGrowthInside:在LP指定的 区间内累积的每单位流动性手续费。
- feeGrowthOutside:在该区间之外累积的手续费。
- 计算逻辑:
feeGrowthInside等于全局的feeGrowthGlobal减去所有在区间之外累积的部分。- 白皮书与视频中的公式 (变量名来自视频讲解):
- 公式解读:
- :我们想求的
feeGrowthInside(Range Fee)。 - :当前的全局
feeGrowthGlobal。 - :所有低于下边界 的手续费累积 (
Fee Below)。 - :所有高于上边界 的手续费累积 (
Fee Above)。
- :我们想求的
4. feeGrowthOutside:精巧的边界状态记录机制
为了高效计算任意区间的和,V3合约并不直接记录它们,而是采用了一个更巧妙的机制:在每个被激活的tick上记录一个状态变量 **feeGrowthOutside** (视频中简称为)。
4.1 feeGrowthOutside 的初始化
当一个LP在当前价格时,添加 区间的流动性,其边界tick的feeGrowthOutside值会按以下规则初始化:
- 规则:
- 如果一个
tick的位置小于等于当前价格 (),则 (设为当前的全局feeGrowthGlobal)。 - 如果
tick的位置大于当前价格 (),则 。
- 如果一个
- 逻辑解释:这个初始化的目的是记录“在我加入之前,已经累积了多少手续费”。对于低于当前价格的下边界,它将所有历史手续费都记录为“外部”,因为这些都与该LP无关。
4.2 feeGrowthOutside 的更新
- 触发时机:当交易价格穿越 (cross) 某个
tick时。 - 更新规则:
- 逻辑解释:这是一个巧妙的“翻转”操作。每次穿越边界时,合约会用穿越瞬间的全局减去该边界上记录的旧值。这使得
feeGrowthOutside能动态追踪边界内外的费用划分,从而可以方便地计算出任意区间的内部增长。
5. 场景推演:feeGrowthInside 的计算过程
5.1 可视化流程图 (Mermaid Diagram)
flowchart TD
A[LP在 i_l, i_u 区间添加流动性] --> B[初始化边界值];
B --> C[根据当前价格 i_c];
C --> C1[若 i_l 小于等于 i_c, F_O(i_l) = F_g0];
C --> C2[若 i_u 大于 i_c, F_O(i_u) = 0];
subgraph " "
direction LR
C1 --> D;
C2 --> D;
end
D[交易发生, 价格变动] --> E{价格是否穿越边界?};
E -- 否, 在区间内变动 --> F[feeGrowthInside = 当前F_g - F_g0];
E -- 是 --> H{穿越哪个边界?};
H -- 向右穿越 i_u --> I[更新 F_O(i_u) = F_g1 - F_O(i_u)_old];
I --> J[feeGrowthInside = F_g1 - F_g0];
H -- 向左穿越 i_l --> K[更新 F_O(i_l) = F_g1 - F_O(i_l)_old];
K --> L[feeGrowthInside = F_g1 - F_g0];
subgraph " "
direction LR
F --> M;
J --> M;
L --> M;
end
M[计算LP总手续费 = S 乘以 feeGrowthInside];
flowchart TD
A[LP在 i_l, i_u 区间添加流动性] --> B[初始化边界值];
B --> C[根据当前价格 i_c];
C --> C1[若 i_l 小于等于 i_c, F_O(i_l) = F_g0];
C --> C2[若 i_u 大于 i_c, F_O(i_u) = 0];
subgraph " "
direction LR
C1 --> D;
C2 --> D;
end
D[交易发生, 价格变动] --> E{价格是否穿越边界?};
E -- 否, 在区间内变动 --> F[feeGrowthInside = 当前F_g - F_g0];
E -- 是 --> H{穿越哪个边界?};
H -- 向右穿越 i_u --> I[更新 F_O(i_u) = F_g1 - F_O(i_u)_old];
I --> J[feeGrowthInside = F_g1 - F_g0];
H -- 向左穿越 i_l --> K[更新 F_O(i_l) = F_g1 - F_O(i_l)_old];
K --> L[feeGrowthInside = F_g1 - F_g0];
subgraph " "
direction LR
F --> M;
J --> M;
L --> M;
end
M[计算LP总手续费 = S 乘以 feeGrowthInside];
flowchart TD
A[LP在 i_l, i_u 区间添加流动性] --> B[初始化边界值];
B --> C[根据当前价格 i_c];
C --> C1[若 i_l 小于等于 i_c, F_O(i_l) = F_g0];
C --> C2[若 i_u 大于 i_c, F_O(i_u) = 0];
subgraph " "
direction LR
C1 --> D;
C2 --> D;
end
D[交易发生, 价格变动] --> E{价格是否穿越边界?};
E -- 否, 在区间内变动 --> F[feeGrowthInside = 当前F_g - F_g0];
E -- 是 --> H{穿越哪个边界?};
H -- 向右穿越 i_u --> I[更新 F_O(i_u) = F_g1 - F_O(i_u)_old];
I --> J[feeGrowthInside = F_g1 - F_g0];
H -- 向左穿越 i_l --> K[更新 F_O(i_l) = F_g1 - F_O(i_l)_old];
K --> L[feeGrowthInside = F_g1 - F_g0];
subgraph " "
direction LR
F --> M;
J --> M;
L --> M;
end
M[计算LP总手续费 = S 乘以 feeGrowthInside];flowchart TD
A[LP在 i_l, i_u 区间添加流动性] --> B[初始化边界值];
B --> C[根据当前价格 i_c];
C --> C1[若 i_l 小于等于 i_c, F_O(i_l) = F_g0];
C --> C2[若 i_u 大于 i_c, F_O(i_u) = 0];
subgraph " "
direction LR
C1 --> D;
C2 --> D;
end
D[交易发生, 价格变动] --> E{价格是否穿越边界?};
E -- 否, 在区间内变动 --> F[feeGrowthInside = 当前F_g - F_g0];
E -- 是 --> H{穿越哪个边界?};
H -- 向右穿越 i_u --> I[更新 F_O(i_u) = F_g1 - F_O(i_u)_old];
I --> J[feeGrowthInside = F_g1 - F_g0];
H -- 向左穿越 i_l --> K[更新 F_O(i_l) = F_g1 - F_O(i_l)_old];
K --> L[feeGrowthInside = F_g1 - F_g0];
subgraph " "
direction LR
F --> M;
J --> M;
L --> M;
end
M[计算LP总手续费 = S 乘以 feeGrowthInside];
流程解释:
该图展示了从LP添加流动性开始,如何根据价格变动情况(是否穿越边界)来更新feeGrowthOutside值,并最终计算出区间内的手续费增长feeGrowthInside。这是一个包含条件判断的逻辑过程,因此流程图是最佳的可视化工具。
5.2 具体计算案例
-
前提:LP在
T=0时刻添加流动性,此时全局feeGrowthGlobal为。根据初始化规则,我们得到和。 -
案例一:价格在区间内变动(未穿越边界)
- 过程:价格在和之间波动,但从未穿越它们。
- 计算:经过一段时间后,全局
feeGrowthGlobal增长到。 feeGrowthInside=当前全局feeGrowth-初始全局feeGrowth= 。
-
案例二:向右穿越上限
- 过程:价格上涨,在
T=1时刻穿越了上边界,此时的全局feeGrowthGlobal为。 - 边界更新:
- 未被穿越,保持不变。
- 被穿越,更新为:。
- 计算:在这个穿越瞬间,LP在区间内累积的
feeGrowthInside为。
- 过程:价格上涨,在
-
案例三:向左穿越下限
- 过程:价格下跌,在
T=1时刻穿越了下边界,此时的全局feeGrowthGlobal为。 - 边界更新:
- 未被穿越,保持
0不变。 - 被穿越,更新为:。
- 未被穿越,保持
- 计算:在这个穿越瞬间,LP在区间内累积的
feeGrowthInside同样是。
- 过程:价格下跌,在
通过这套精巧的机制,Uniswap V3实现了在任意价格区间内对LP手续费的精确追踪和计算。