03.Uniswap V2 手续费机制

核心摘要 (Key Takeaways)

  • 流动性增长:Uniswap V2 的千三(0.3%)交易手续费会直接重新注入到流动性池中,导致池子中的资产(X和Y)缓慢增长,进而使总流动性 K 值不断增大。
  • LP 收益机制:流动性提供者(LP)通过持有 LP Token (Share) 来证明其在池中的份额。当池子总流动性 K 增长时,LP Token 的总份额不变,因此每个 LP Token 所代表的实际资产价值会提升,LP 在撤出流动性时能获得更多资产。
  • 协议手续费 (PROTOCOL_FEE):Uniswap V2 设计了一种机制,允许协议开发者收取一部分手续费(例如千三的 1/6,即万分之五)。这通过增发新的 LP Token (Share) 给协议方来实现,从而让协议方在不提供流动性的前提下,分享流动性增长带来的收益。
  • 链上状态:尽管 Uniswap V2 设计了协议手续费机制,但目前该功能并未在主网上开启。通过检查 UniswapV2Factory 合约的 feeTo 地址是否为零,可以验证其开启状态。

1. Uniswap V2 手续费机制概览

1.1 基础概念:流动性与 LP Token

  • 恒定乘积做市商 (CPMM):Uniswap V2 基于 X * Y = K 的公式进行资产交换,其中 XY 分别代表池中两种资产的数量,K 是一个常数(在无手续费和无流动性增减的情况下)。
  • 流动性 (L):在 Uniswap V2 中,流动性 L 通常指 sqrt(K)
    • 公式:L=KL = \sqrt{K}
  • LP Token (Share / LPT):当用户向 Uniswap 池中提供流动性时,会获得相应数量的 LP Token 作为凭证。这些 Token 代表了用户在池中的份额。
    • S1:在 T1 时刻,LP Token 的总数量。

1.2 千三手续费 (0.3%)

  • 收取方式:每笔交易都会收取 0.3% 的手续费。这笔费用并不是直接从交易对的另一方扣除,而是从进入池子的 Token 中扣除。
    • 例如,用户用 DX 数量的 X 资产购买 Y 资产,池子实际上只收到 (1 - 0.003) * DX = 0.997 * DX
  • 归属:这 0.3% 的手续费直接重新注入到流动性池中,而非直接分配给某个用户或地址。

2. 流动性增长的逻辑

2.1 为什么流动性会增长 (K2 > K1)?

  • 手续费的积累:由于 0.3% 的手续费被重新注入到池子中,池子里的 XY 资产总量会随着交易量的增加而缓慢增长。
    • 如果用 DX 购买 Y,池子收到 0.997 * DX。这 0.997 * DX 增加了池子里的 X 资产。
    • 如果用 DY 购买 X,池子收到 0.997 * DY。这 0.997 * DY 增加了池子里的 Y 资产。
  • 结果:只要有交易发生,池子中的 XY 资产总量就会增加,从而导致 K 值(即 X * Y)不断增大。
    • K2>K1K_2 > K_1 (在不考虑流动性增减的情况下)。

2.2 案例分析:DAI/USDT 池子

为了更好地理解流动性增长和套利机制,我们以 DAI/USDT 稳定币交易对为例:

  • 前提:DAI 和 USDT 都是稳定币,价格始终保持 1 美元。
  • 初始状态:池中有 100 DAI 和 100 USDT。
    • 此时 K=100×100=10000K = 100 \times 100 = 10000
    • L=10000=100L = \sqrt{10000} = 100
  • 单边交易:假设用户持续用 DAI 购买 USDT。
    • 每次交易都会收取 0.3% 的 DAI 手续费并注入池中。
    • 池中的 DAI 会增加,USDT 会减少(因为被换出)。
    • 池子可能暂时变为:104 DAI 和 98 USDT。
  • 套利机器人 (Arbitrage Bots) 的作用
    • 当池子变为 104 DAI 和 98 USDT 时,池子内部的 DAI 价格相对于 USDT 变得更便宜(例如 1 DAI < 1 USDT),而 USDT 价格相对于 DAI 变得更贵。
    • 套利机器人会立即发现这个价格差异:它们会用外部市场价格更低的 USDT 来购买池中相对便宜的 DAI,然后将 DAI 卖出换回 USDT,从而赚取差价。
    • 套利交易会持续进行,直到池子内部的价格与外部市场价格重新平衡。
  • 最终结果:经过交易和套利,池子最终会达到一个新的平衡点,例如:101 DAI 和 101 USDT。
    • 此时 K=101×101=10201K = 101 \times 101 = 10201
    • L=10201=101L = \sqrt{10201} = 101
  • 结论:即使经过套利平衡,最终的流动性 L(或 K)仍然会因为手续费的积累而增长(从 100 增长到 101)。

2.3 可视化:流动性增长与套利平衡流程

graph TD
    A[用户发起交易] --> B{收取 0.3% 手续费};
    B --> C[手续费注入流动性池];
    C --> D[池中 X/Y 资产总量增加];
    D --> E[池子总流动性 K 增长];
    E --> F{池内价格是否偏离市场价?};
    F -- 是 --> G[套利机器人介入];
    G --> H[套利交易平衡池内价格];
    H --> D;
    F -- 否 --> I[流动性增长稳定];
    I --> J[LP Token 价值提升];
    J --> K[LP 撤出时获得更多资产];
graph TD
    A[用户发起交易] --> B{收取 0.3% 手续费};
    B --> C[手续费注入流动性池];
    C --> D[池中 X/Y 资产总量增加];
    D --> E[池子总流动性 K 增长];
    E --> F{池内价格是否偏离市场价?};
    F -- 是 --> G[套利机器人介入];
    G --> H[套利交易平衡池内价格];
    H --> D;
    F -- 否 --> I[流动性增长稳定];
    I --> J[LP Token 价值提升];
    J --> K[LP 撤出时获得更多资产];
graph TD
    A[用户发起交易] --> B{收取 0.3% 手续费};
    B --> C[手续费注入流动性池];
    C --> D[池中 X/Y 资产总量增加];
    D --> E[池子总流动性 K 增长];
    E --> F{池内价格是否偏离市场价?};
    F -- 是 --> G[套利机器人介入];
    G --> H[套利交易平衡池内价格];
    H --> D;
    F -- 否 --> I[流动性增长稳定];
    I --> J[LP Token 价值提升];
    J --> K[LP 撤出时获得更多资产];

流程解释

  1. 用户进行交易,Uniswap V2 会收取 0.3% 的手续费。
  2. 这笔手续费会被直接添加到流动性池中,导致池子中的 XY 资产总量增加。
  3. 资产总量的增加使得池子的总流动性 K 值随之增长。
  4. 随着 K 值的增长,池子内部的资产价格可能会与外部市场价格产生偏差。
  5. 套利机器人会监测到这种价格偏差,并立即进行交易以平衡价格,从而赚取差价。
  6. 套利交易本身也会产生手续费,并进一步注入池中,循环促进 K 的增长。
  7. 当池内价格与市场价格平衡后,K 值达到一个因手续费积累而增长的稳定状态。
  8. 由于 K 增长而 LP Token 数量不变,每个 LP Token 对应的资产价值提升。
  9. 当 LP 决定撤出流动性时,他们将获得比初始投入更多的资产,这就是 LP 从手续费中受益的方式。

3. LP 如何从手续费中受益

3.1 Share 价值的提升

  • 基本原理:当流动性池的总流动性 K(或 L)增长时,LP Token 的总数量 S1 保持不变(不考虑新增发或销毁)。
  • 结果:这意味着每个 LP Token 所代表的池子份额,以及其背后对应的实际资产数量,都会随之增加。LP 在撤出流动性时,会按照其 LP Token 份额,从更大的资产池中分得更多资产。
  • 手续费增加的比例:从 T1T2 时刻,手续费导致流动性增加的比例可以表示为: Fee Increase Ratio=1K1K2 \text{Fee Increase Ratio} = 1 - \frac{\sqrt{K_1}}{\sqrt{K_2}} 其中 K1K_1T1 时刻的流动性常数,K2K_2T2 时刻的流动性常数。

3.2 公平性与实时性

  • 公平分配:LP 收益的分配是公平的,完全基于 LP Token 的持有比例。如果 LP 持有池子 1% 的 LP Token,那么他就能获得 1% 的手续费收益。
  • 实时性:LP 可以随时加入或退出流动性池。在他们提供流动性的期间,他们对池子的贡献会通过 LP Token 价值的增长,实时地体现在他们的收益中。

4. Uniswap V2 协议手续费 (PROTOCOL_FEE) 机制

4.1 项目方的需求

  • 在 Uniswap V1 中,所有手续费都归 LP 所有,协议开发者(Uniswap 团队)自身无法从中直接获利。
  • 为了激励协议的持续开发和维护,Uniswap V2 引入了协议手续费机制,允许项目方从协议中分得一杯羹。

4.2 phi (ϕ\phi) 参数

  • 定义ϕ\phi (phi) 代表项目方希望从总手续费中分走的比例。
    • 例如,如果项目方想分走 0.3% 手续费中的 1/6,那么 ϕ=1/6\phi = 1/6
    • 实际收取的协议费率为 0.3%×16=0.05%0.3\% \times \frac{1}{6} = 0.05\% (万分之五)。

4.3 实现方式:增发 Share

  • 核心思想:项目方通常不提供流动性,因此不能直接持有 LP Token 来分享收益。为了让项目方获得收益,Uniswap V2 采取了增发新的 LP Token (Share) 给项目方的方式。
  • “做大蛋糕,分增量”:项目方增发 Share 的逻辑是,在流动性池因手续费而变大(蛋糕变大)的同时,从这个“变大的部分”中分走一小块,而不是侵占现有 LP 的初始利益。

4.4 数学推导:SM 的计算

假设:

  • S1S_1T1 时刻 LP 持有的 LP Token 总量。
  • K1K_1T1 时刻池子的流动性常数。
  • K2K_2T2 时刻池子的流动性常数(因手续费积累而增长)。
  • SMSM:项目方在 T2 时刻应增发的新 LP Token 数量。
  • ϕ\phi:项目方希望分走的手续费比例。

协议手续费的计算基于以下公式:

SMS1+SM=ϕK2K1K2 \frac{SM}{S_1 + SM} = \phi \cdot \frac{\sqrt{K_2} - \sqrt{K_1}}{\sqrt{K_2}}

这个公式的含义是:

  • 左侧 SMS1+SM\frac{SM}{S_1 + SM} 代表项目方增发的 Share 占总 Share 数量的比例。
  • 右侧 K2K1K2\frac{\sqrt{K_2} - \sqrt{K_1}}{\sqrt{K_2}} 代表从 T1T2 时刻,因手续费积累而导致的流动性(L = sqrt(K))的增长比例。
  • ϕ\phi 是项目方从这个增长比例中分走的份额。

通过对上述公式进行推导,可以求得 SM 的表达式:

推导过程

  1. 初始公式: SMS1+SM=ϕK2K1K2 \frac{SM}{S_1 + SM} = \phi \cdot \frac{\sqrt{K_2} - \sqrt{K_1}}{\sqrt{K_2}}
  2. 将右侧的 K2K1K2\frac{\sqrt{K_2} - \sqrt{K_1}}{\sqrt{K_2}} 记为 FratioF_{ratio} (手续费导致的流动性增长比例)。 SMS1+SM=ϕFratio \frac{SM}{S_1 + SM} = \phi \cdot F_{ratio}
  3. 交叉相乘: SM=ϕFratio(S1+SM) SM = \phi \cdot F_{ratio} \cdot (S_1 + SM)
  4. 展开: SM=ϕFratioS1+ϕFratioSM SM = \phi \cdot F_{ratio} \cdot S_1 + \phi \cdot F_{ratio} \cdot SM
  5. 将包含 SMSM 的项移到等式左侧: SMϕFratioSM=ϕFratioS1 SM - \phi \cdot F_{ratio} \cdot SM = \phi \cdot F_{ratio} \cdot S_1
  6. 提取 SMSMSM(1ϕFratio)=ϕFratioS1 SM \cdot (1 - \phi \cdot F_{ratio}) = \phi \cdot F_{ratio} \cdot S_1
  7. 解出 SMSMSM=ϕFratio1ϕFratioS1 SM = \frac{\phi \cdot F_{ratio}}{1 - \phi \cdot F_{ratio}} \cdot S_1
  8. Fratio=K2K1K2F_{ratio} = \frac{\sqrt{K_2} - \sqrt{K_1}}{\sqrt{K_2}} 代回: SM=ϕK2K1K21ϕK2K1K2S1 SM = \frac{\phi \cdot \frac{\sqrt{K_2} - \sqrt{K_1}}{\sqrt{K_2}}}{1 - \phi \cdot \frac{\sqrt{K_2} - \sqrt{K_1}}{\sqrt{K_2}}} \cdot S_1
  9. 为了简化,分子分母同乘以 K2\sqrt{K_2}SM=ϕ(K2K1)K2ϕ(K2K1)S1 SM = \frac{\phi \cdot (\sqrt{K_2} - \sqrt{K_1})}{\sqrt{K_2} - \phi \cdot (\sqrt{K_2} - \sqrt{K_1})} \cdot S_1
  10. 进一步整理分母: K2ϕK2+ϕK1=(1ϕ)K2+ϕK1 \sqrt{K_2} - \phi\sqrt{K_2} + \phi\sqrt{K_1} = (1-\phi)\sqrt{K_2} + \phi\sqrt{K_1}
  11. 最终得到 SMSM 的表达式: SM=ϕ(K2K1)(1ϕ)K2+ϕK1S1 SM = \frac{\phi \cdot (\sqrt{K_2} - \sqrt{K_1})}{(1-\phi)\sqrt{K_2} + \phi\sqrt{K_1}} \cdot S_1
  12. 为了得到与白皮书和代码实现一致的形式,我们可以将分子分母同除以 ϕ\phiSM=ϕϕ(K2K1)(1ϕ)ϕK2+ϕϕK1S1 SM = \frac{\frac{\phi}{\phi} \cdot (\sqrt{K_2} - \sqrt{K_1})}{\frac{(1-\phi)}{\phi}\sqrt{K_2} + \frac{\phi}{\phi}\sqrt{K_1}} \cdot S_1 SM=K2K1(1ϕ1)K2+K1S1 SM = \frac{\sqrt{K_2} - \sqrt{K_1}}{(\frac{1}{\phi} - 1)\sqrt{K_2} + \sqrt{K_1}} \cdot S_1 当 Uniswap V2 协议费比例 ϕ=1/6\phi = 1/6 时,1ϕ1=11/61=61=5\frac{1}{\phi} - 1 = \frac{1}{1/6} - 1 = 6 - 1 = 5。 因此,最终用于计算的 SM 公式为: SM=K2K15K2+K1S1 SM = \frac{\sqrt{K_2} - \sqrt{K_1}}{5 \cdot \sqrt{K_2} + \sqrt{K_1}} \cdot S_1 这个公式与 Uniswap V2 白皮书和代码实现中的逻辑完全一致。

4.5 可视化:协议手续费收取流程

graph TD
    A[Uniswap V2 团队决定收取协议费] --> B{设置 feeTo 地址 (非零)};
    B --> C[用户持续进行交易];
    C --> D[手续费累积,流动性 K 增长];
    D --> E[调用 mintFee 方法 ,通常由外部触发];
    E --> F{根据 K 的增长和 phi 计算 SM};
    F --> G[增发 SM 数量的 LP Token];
    G --> H[将增发的 LP Token 发送给 feeTo 地址];
    H --> I[项目方通过 SM 分享流动性增长收益];
graph TD
    A[Uniswap V2 团队决定收取协议费] --> B{设置 feeTo 地址 (非零)};
    B --> C[用户持续进行交易];
    C --> D[手续费累积,流动性 K 增长];
    D --> E[调用 mintFee 方法 ,通常由外部触发];
    E --> F{根据 K 的增长和 phi 计算 SM};
    F --> G[增发 SM 数量的 LP Token];
    G --> H[将增发的 LP Token 发送给 feeTo 地址];
    H --> I[项目方通过 SM 分享流动性增长收益];
graph TD
    A[Uniswap V2 团队决定收取协议费] --> B{设置 feeTo 地址 (非零)};
    B --> C[用户持续进行交易];
    C --> D[手续费累积,流动性 K 增长];
    D --> E[调用 mintFee 方法 ,通常由外部触发];
    E --> F{根据 K 的增长和 phi 计算 SM};
    F --> G[增发 SM 数量的 LP Token];
    G --> H[将增发的 LP Token 发送给 feeTo 地址];
    H --> I[项目方通过 SM 分享流动性增长收益];

流程解释

  1. Uniswap V2 团队决定开启协议手续费功能。
  2. 团队通过设置 UniswapV2Factory 合约中的 feeTo 地址为一个非零地址来激活此功能。
  3. 用户继续在 Uniswap V2 池中进行交易。
  4. 交易产生的手续费会不断累积,使得池子的总流动性 K 值持续增长。
  5. 在某个时间点,mintFee 方法会被调用(通常由外部触发,例如在添加/移除流动性时检查并触发)。
  6. mintFee 方法会根据当前池子的流动性 K 值 (K2) 与上次计算协议费时的 K 值 (K1) 的差额,以及预设的协议费比例 phi,计算出应该增发给协议方的 LP Token 数量 SM
  7. 系统增发 SM 数量的 LP Token。
  8. 这些新发行的 LP Token 会被发送到预设的 feeTo 地址。
  9. 项目方通过持有这些 SM LP Token,可以分享流动性池因手续费积累而带来的增长收益。

5. 代码实现与链上验证

5.1 mintFee 方法

function _mintFee(uint112 _reserve0, uint112 _reserve1) private returns (bool feeOn) {
    address feeTo = IUniswapV2Factory(factory).feeTo();
    feeOn = feeTo != address(0);
    uint _kLast = kLast; // gas savings
    if (feeOn) {
        if (_kLast != 0) {
            uint rootK = Math.sqrt(uint(_reserve0).mul(_reserve1));
            uint rootKLast = Math.sqrt(_kLast);
            if (rootK > rootKLast) {
                uint numerator = totalSupply.mul(rootK.sub(rootKLast));
                uint denominator = rootK.mul(5).add(rootKLast);
                uint liquidity = numerator / denominator;
                if (liquidity > 0) _mint(feeTo, liquidity);
            }
        }
    } else if (_kLast != 0) {
        kLast = 0;
    }
}
  • 位置:协议手续费的逻辑主要实现在 UniswapV2Pair 合约的 mintFee 方法中。
  • 功能:该方法负责计算并增发协议方应得的 LP Token。
  • 代码逻辑
    • 计算 K2K1(当前和上次的流动性常数)。
    • 使用上述推导的公式计算 SM(增发数量)。
    • 如果 SM > 0,则调用 _mint 函数增发 SM 数量的 LP Token,并发送给 feeTo 地址。
    • 代码中 _mintFee 的分母是 5 * sqrt(K2) + sqrt(K1),这与白皮书的 ϕ=1/6\phi = 1/6 时的公式一致。

5.2 feeTo 开关

  • 控制机制:协议手续费的开启与否由 UniswapV2Factory 合约中的 feeTo 地址决定。
  • 判断逻辑
    • 如果 feeTo 地址为 0x00...00 (零地址),表示协议手续费未开启。
    • 如果 feeTo 地址不为 0x00...00,表示协议手续费已开启。
  • 代码中的 feeOn 变量mintFee 方法内部会检查 feeTo 地址是否为零,并设置一个布尔变量 feeOn。只有当 feeOntrue 时,协议手续费的逻辑才会被触发。