很多博文都只是浅尝辄止,或者干脆复制黏贴,也没有考虑手续费的变量。
这里以 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
。
amountIn
A
的数量
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 |
经过验证,上面是正确的。