Uniswap 协议手续费
Uniswap V2 如何计算 mintFee
UniswapV2 的设计目标是将 1/6 的 swapFee 转入 Uniswap Protocal
由于 swapFeeRatio = 0.3%,其 1/6 = 0.05%,因此每笔 swap 交易手续费的 0.05% 将计入协议。
在交换期间收取协议费用效率低下
Uniswap 协议每笔 swap 交易收取 0.05% 的费用是低效的,因为这需要额外的代币转移。
转移 ERC20 代币需要存储更新,因此转移到协议收益地址的成本会高得多。
因此,当流动性提供者调用 burn 或 mint 时,才会计算累积的费用。 因此可以节省 gas。为了收取费用 mintFee,合约会计算自上次发生以来收取的费用金额,并向受益人地址铸造足够的 LP 代币,以使受益人有权获得 1/6 的费用。
流动性是池中代币余额乘积的平方根 liquidity = Math.sqrt(amount0.mul(amount1))
计算 mintFee 假设
为了实现这一点,Uniswap V2 依赖于以下两个不变量:
- 如果
mint()和burn()没有被调用,那么池的流动性只会增加- 多次
mint期间的swap,会增加池子的流动性。 - 每笔兑换,都会增加池子的 (token0,token1) 的代币数量
- 多次
- 流动性的增加纯粹是由于兑换手续费(或捐赠)
mintFee 计算示例
假设在
T1时,流动性池子中存在10 个 token0和10 个 token1。经过大量兑换交易,新的池余额为 40个token0 和 40个token1
此时,流动性从
10增加到40
我们希望铸造足够多的流动性代币,即 mintFee,以便协议受益人能够收到资金池全部 swapFee 的 1/6。
推导铸币费公式
在启动协议手续费的前提下,以原始流动性为起点,池子中添加了 T2-T1 时间区间的兑换手续费,我们使用以下符号:
-
s: 流动性代币基准。在T2时刻,铸造给流动性提供商的流动性代币的数量 -
$\eta$: 流动性代币基准。在
T2时刻,池子应该为协议铸造的流动性代币的数量,它应该足以赎回1/6的利润流动性 -
$\rho_{1}$: 流动性基准。在
T1时刻,池子的原始流动性Math.sqrt(_reserve0.mul(_reserve0)) -
$\rho_{2}$: 流动性基准。在
T2时刻,经过一段时间的swap手续费的积累,池子的新流动性值Math.sqrt(reserve0.mul(reserve0)) -
d: 流动性基准。 经过原始流动性和T2-T1时间区间的兑换手续费,流动性提供商获取的流动性 -
p: 流动性基准。经过T2-T1时间区间的兑换手续费, 池子应该为协议提供的流动性,它应该是时间区间内全部利润流动性的1/6
下图解答了 ,就流动性的变化而言: $\frac{s}{\eta}$ = $\frac{d}{p}$

_mintFee()Uniswap V2 的代码
考虑到这一推导,UniswapV2 _mintFee 函数的大部分内容应该是不言自明的。以下是一些符号上的变化:
T2时刻的流动性 $\rho_{2}$ 是rootKT1时刻的流动性 $\rho_{1}$ 是kLastT2时刻的流动代币数量s是totalSupply
// if fee is on, mint liquidity equivalent to 1/6th of the growth in sqrt(k)
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;
}
}
-
feeOn = false,不会为协议铸造流动性代币 -
feeOn = false,非零的kLast值置 零,不用在每次mint/burn时更新kLast的值 -
feeOn = true,但流动性没有增长,期间没有swap手续费的积累,不会为协议铸造流动性代币 -
feeOn = true, 并且存在流动性增长(期间有swap手续费的积累),因此会为协议铸造流动性代币

Where klast gets updated
在 mint/burn 引起的 流动性代币 totalSupply 变化的函数中,会进行 kLast 的更新。将 kLast 更新为当前行为的流动性。
