很多博文都只是浅尝辄止,或者干脆复制黏贴,也没有考虑手续费的变量。
这里以 pancakeswap 的 getAmountsOut 为例,探究,如何计算 AMM 价格。
写在前面
经常看到,AMM 的乘积是一个常数,即池子的深度
$$ x * y = k $$
其中,K 是常数。然后,x 增加和 y 减少之后
$$ (x - \Delta{x}) * (y + \Delta{y}) = k $$
其里面有一个前提,所谓的 k 不变,是不存在加池子和撤池子的情况,才是恒定 k。仅仅只是交易的话,k 是恒定的,但是,如果进行加池子或者减少池子,则 K 是变化的。
代码分析
getAmountOut
这里参考的是 pancakeswap。
1 | // given an input amount of an asset and pair reserves, returns the maximum output amount of the other asset |
这里解释一下参数
假设,池子是 A , B 两个 token 组成的,这个时候,你想用 A 来换 B。
amountInA的数量
reserveIn- 池子中
A的数量
- 池子中
reserveOut- 池子中
B的数量
- 池子中
方法中 998,是因为,pancakeswap 的收取费用是 0.2%。
但是,pancakeswap 现在的手续费调整为 0.25%,即上面的 998 要变成 997.5 。但是,看公开的代码,这里的手续费用是写死的,不知道 pancakeswap 是怎么变得。
这里按照代码中的 0.2% 的手续费进行计算。
公式代码如下面所示
$$ amountOut = \frac{amountIn * 998 * reserveOut}{reserveIn * 1000 + amountIn * 998} $$
公式推导
这里直接带手续费进行推导。
$$ reserveIn * reserveOut = (reserveIn + amountIn * (1- f)) * (reserveOut - amountOut) $$
$$ \frac{reserveIn * reserveOut}{(reserveIn + amountIn * (1- f))} = (reserveOut - amountOut) $$
$$ amountOut = reserveOut - \frac{reserveIn * reserveOut}{(reserveIn + amountIn * (1- f))} $$
$$ amountOut = \frac{reserveOut * (reserveIn + amountIn * (1-f)) - reserveIn * reserveOut}{(reserveIn + amountIn * (1- f))} $$
$$ amountOut = \frac{reserveOut * reserveIn + reserveOut * amountIn * (1-f)) - reserveIn * reserveOut}{(reserveIn + amountIn * (1- f))} $$
$$ amountOut = \frac{reserveOut * amountIn * (1-f)}{(reserveIn + amountIn * (1- f))} $$
这里要说一下,为什么 $ (reserveIn + amountIn * (1- f)) $ ,这里的 $ 1 - f $ 是因为,pancakeswap 要拿走一部分的币作为手续费。
getAmountIn
1 | function getAmountIn(uint amountOut, uint reserveIn, uint reserveOut) internal pure returns (uint amountIn) { |
结合上面的公式,最后推导为
$$ amountIn = \frac{reserveIn * amountOut}{(reserveOut - amountOut) * (1- f)} $$
细节
这里要特别注意一个经常被忽略的点。
假设一个池子,A 有 50 个,B 有 100 个。
如果,你卖出 50 个 A,只能获得 50 个 B「不考虑手续费」,而获得不了 100 个 B。这就是恒定乘积做市商。也就是滑点。
使用
一般调用上面两个方法都是用下面的用法,下面两个方法的底层就是上面的解析。
1 | function getAmountsOut(uint amountIn, address[] memory path) public view override returns (uint[] memory amounts) { |
getAmountsOut(10000,[path_A,path_B])- 意思是
10000个A能兑换多少个B
- 意思是
getAmountsIn(10000,[path_A,path_B])- 意思是 借
10000个B需要多少个A
- 意思是 借
自定义计算
1 | import math |
经过验证,上面是正确的。