恒定乘积公式 (x*y=k):Uniswap V2 的核心公式推导与直觉理解
各位同学,今天我们来啃一块硬骨头——恒定乘积公式。说实话,这个公式看起来就一行:x * y = k。但你要是以为它就这么简单,那可就大错特错了。我在做第一个AMM项目时,就因为没吃透这个公式的边界条件,差点让池子被套利机器人薅秃。嗯,今天咱们就把这公式掰开揉碎了讲。
1. 从直觉出发:为什么是乘积?
你想想看,传统的订单簿交易所,价格是由买卖双方挂单决定的。但AMM没有订单簿,它只有两个池子——一个装Token A,一个装Token B。那怎么定价?
核心思想很简单:池子里的两种资产,总价值要保持恒定。但这里有个关键问题——价值是价格乘以数量,而价格本身又在变。所以Uniswap选择了另一种思路:保持两种资产数量的乘积不变。
为什么是乘积而不是和?我举个例子:
- 如果池子里有100个A和100个B,和是200,乘积是10000
- 有人用10个A换走了5个B,池子变成110个A和95个B
- 和变成了205,但乘积变成了10450
看到了吗?和会变,但乘积在交易前后保持不变。这就是恒定乘积的直觉——它定义了一种特殊的交易规则,让流动性提供者无论怎么交易,池子里的“乘积”永远不变。
核心直觉:恒定乘积公式的本质是——你往池子里加一种资产,就必须拿走另一种资产,且拿走后的乘积必须等于原来的乘积。这保证了池子永远不会被掏空(理论上)。
3. 公式推导:从x*y=k到价格曲线
好,咱们来点硬核的。设池子里有x个Token X和y个Token Y,恒定乘积公式就是:
x * y = k
其中k是常数。现在,假设你想用Δx个Token X换出Δy个Token Y。交易后的池子状态是:
(x + Δx) * (y - Δy) = k
注意符号:你往池子里加了Δx,所以x增加;你从池子里拿走了Δy,所以y减少。展开这个等式:
(x + Δx) * (y - Δy) = x * y
xy - xΔy + yΔx - ΔxΔy = xy
yΔx - xΔy - ΔxΔy = 0
yΔx = Δy(x + Δx)
Δy = (y * Δx) / (x + Δx)
这就是Uniswap V2的核心交易公式。你投入Δx,能得到的Δy就是上面这个值。
个人经验:我在写合约时,经常看到有人直接用Δy = y * Δx / x这个近似公式。千万别!这个近似只在Δx很小的时候成立。实际交易中,Δx可能很大,必须用完整公式,否则你会多给用户Token,造成池子损失。
4. 价格与滑点:为什么大额交易不划算?
现在我们来聊聊价格。在AMM里,价格不是固定的,它取决于你交易的数量。瞬时价格(边际价格)是:
P = y / x
但实际交易价格呢?你投入Δx,得到Δy,实际价格是Δy/Δx。代入上面的公式:
实际价格 = Δy / Δx = y / (x + Δx)
看到了吗?实际价格永远小于瞬时价格。而且Δx越大,实际价格越低。这就是滑点——你交易得越多,价格越不利。
为什么会这样?我画个图你就明白了:
图中蓝色的曲线就是恒定乘积曲线。初始点在(200, 140),瞬时价格是切线斜率。当你沿着曲线向右移动(增加x,减少y),实际价格是连接两个点的弦的斜率。弦的斜率永远小于切线的斜率——这就是滑点的几何解释。
5. 流动性提供者的数学
作为流动性提供者,你关心的是:我投进去的钱,到底怎么定价?
假设你往池子里添加流动性,你投入的两种资产比例必须和当前池子比例一致。设池子当前有x₀和y₀,你投入Δx和Δy,必须满足:
Δx / Δy = x₀ / y₀
也就是说,你投入的比例必须等于当前池子的比例。否则,你就是在改变池子的价格,会被套利者吃掉。
你获得的流动性代币数量是:
你获得的份额 = (Δx / x₀) * 总流动性代币
或者等价地:
你获得的份额 = (Δy / y₀) * 总流动性代币
避坑指南:我曾经见过一个项目,允许用户以任意比例添加流动性。结果有人用1:100的比例添加,瞬间把池子价格拉偏,然后用自己的另一个账户套利。嗯,这其实就是个变相的闪电贷攻击。所以Uniswap强制要求比例一致,不是没有道理的。
6. 无常损失:为什么你可能亏钱?
说到流动性提供,就不得不提无常损失。简单来说:如果你把Token放在池子里,而外部市场价格发生了变化,你的资产价值可能低于单纯持有。
举个例子:
| 场景 | 池子状态 | 你的资产价值 | 持有价值 | 损失 |
|---|---|---|---|---|
| 初始 | 100 ETH, 200,000 USDC | 1 ETH + 2,000 USDC | 1 ETH + 2,000 USDC | 0% |
| ETH涨到3000 USDC | ~70.7 ETH, ~282,842 USDC | ~0.707 ETH + ~2,828 USDC | 1 ETH + 2,000 USDC | ~5.7% |
| ETH跌到1000 USDC | ~141.4 ETH, ~141,421 USDC | ~1.414 ETH + ~1,414 USDC | 1 ETH + 2,000 USDC | ~5.7% |
看到了吗?无论涨跌,只要价格变动,你就会亏。这就是无常损失。但注意,这只是相对于“持有”而言的损失。如果你同时赚到了交易手续费,可能还是正的收益。
我的建议:做流动性提供者之前,先算算无常损失。有个简单的公式:损失比例 ≈ (2√r)/(1+r) - 1,其中r是价格变化倍数。比如价格翻倍(r=2),损失约5.7%。价格涨10倍,损失约24%。所以稳定币对(价格几乎不变)是最安全的。
7. 代码实现:Solidity中的恒定乘积
最后,咱们看看Uniswap V2在Solidity里是怎么实现这个公式的。核心代码在UniswapV2Pair.sol中:
// 核心交易函数
function swap(uint amount0Out, uint amount1Out, address to, bytes calldata data) external lock {
// ... 省略安全检查 ...
// 获取当前储备量
(uint112 reserve0, uint112 reserve1, ) = getReserves();
// 计算交易后的储备量
uint balance0 = IERC20(token0).balanceOf(address(this));
uint balance1 = IERC20(token1).balanceOf(address(this));
// 核心公式:确保乘积不减少
uint balance0Adjusted = balance0 * 1000 - amount0In * 3;
uint balance1Adjusted = balance1 * 1000 - amount1In * 3;
require(balance0Adjusted * balance1Adjusted >= uint(reserve0) * uint(reserve1) * (1000**2),
'UniswapV2: K');
// ... 省略转账逻辑 ...
}
注意这里有个细节:balance0Adjusted和balance1Adjusted。为什么要乘以1000再减去3倍的输入量?因为Uniswap V2收取0.3%的手续费。实际公式是:
(x - 0.003 * Δx) * (y + Δy) >= k
这个调整确保了手续费被正确计算,同时乘积不会减少。
总结一下:恒定乘积公式虽然简单,但背后有丰富的数学内涵。它定义了AMM的定价机制、滑点特性、流动性提供规则,以及无常损失。理解了这个公式,你就掌握了Uniswap V2的核心。下一章我们会讲Uniswap V3的集中流动性,那才是真正的进阶玩法。
好了,今天就到这里。记住,公式是死的,但应用是活的。我在实际项目中踩过的坑,希望你们能避开。有什么问题,欢迎在评论区讨论。
公众号:蓝海资料掘金营,微信deep3321