本文章用简洁通俗的语言带你理解UniswapV2,推导Uniswap恒等公式,并讲解了Uniswap的核心代码。
uniswap是一个智能合约,实施基于“恒定乘积公式”的自动流动性协议。每个Uniswap交易对上都存储着对于两种资产的储备。可以理解为每个国家都会对其他国家有一定的外汇储备,在兑换对应资产时使用。
介绍V2之前,我们先介绍一下uniswapV1。
uniswapV1主要提供各种ERC20 token与ETH互相兑换的途径,以ETH为交易中心来实现ERC20 token与ERC20 token之间的兑换。
可以理解成你拿黄金去换白银,Uniswap V1的兑换方式就是先计算出来1g黄金能换多少美元,再把换出的美元拿去购买白银。
UniSwapV2与V1是基于相同的公式实现,但它提出了以下三点创新:
在开始解释前,我们通过这张图可以了解到Uniswap在做什么。
简单来说就是流动性提供者提供token pair并获得LP shares,可以产生持续收益。
交易者将自己想交易的token用uniswap交换到对应token。
要理解Uniswap,就必须理解恒定乘积公式,接下来我会带你推导一遍恒定乘积公式,并试图让你理解为什么这么做。
恒定乘积公式$x\times y=k$,
$x$和$y$分别为交易时token $X$和token $Y$的数量。代币兑换价格由$x$和$y$的比率决定,从而保留$x\times y$的乘积。
也就是说,当你出售了$\Delta x$代币时,你将获得$\Delta y$代币,使得
$x\times y = k = (x+\Delta x)\times(y-\Delta y)$
当使用$\Delta y$交易$\Delta x$时,交易代币的储备更新公式如下:
$x' = x+\Delta x = (1+\alpha)x = \frac{1}{1-\beta}x$
$y' = y-\Delta y = \frac{1}{1+a}y = (1-\beta)y$
其中,$\alpha = \frac{\Delta x}{x},\beta=\frac{\Delta y}{y}$。因此我们有了:
$\Delta x = \frac{\beta}{1-\beta}x$
$\Delta y = \frac{\alpha}{1+\alpha}y$
我们这里解释一下上述公式,其中$\alpha=\frac{\Delta x}{x}$以及$\beta = \frac{\Delta y}{y}$的含义是你所交易的token/该token总数。价格$\frac{\Delta x}{\Delta y}是\frac{x}{y}$的函数,我们交易的时候只会给出其中一方的token,假设是$\Delta x$,通过这样的公式计算得出我们应该获得多少对应的$\Delta y$。可以看出,在$\Delta y = \frac{\alpha}{1+\alpha}y$中,计算出$\Delta y$所需要的变量只有$x,y,\Delta x$,这样可以保证你输入$\Delta x$时就能得到相应的$\Delta y$
紧接着,我们考虑每一次token交易时,我们都需要给流动性提供者一笔手续费。手续费在UniswapV2中设置为$\rho = 0.003$。那么我们在有手续费的情况下,交易公式变成了以下:
$x'_\rho = x+\Delta x = (1+\alpha) x = \frac{1+\beta(\frac{1}{\gamma}-1)}{1-\beta}x$
$y'_\rho = y-\Delta y = \frac{1}{1+\alpha\gamma}y = (1-\beta)y$
这一步(1)中,表示的是你存入$\Delta x$个token0,这一步和没有手续费是一样的计算过程。但是下一步给你$\Delta y$个token1,这一步已经扣除了手续费,体现在$\frac{1}{1+\alpha\gamma}$。为什么会变成$\frac{1}{1+\alpha\gamma}$? 这部分论文中没有给出一个详细的解释,因此我的解释可能不完全正确。总之你可以理解成$\alpha=\frac{\Delta x}{x}$, $y-\Delta y = \frac{1}{1+a}y$是挂钩着兑换比例,而加入手续费后,兑换给你的$\Delta y$就变少了,少的这部分由$\gamma = 1-\rho$在分母作为乘积来表示。当然你也可以参考论文https://github.com/runtimeverification/verified-smart-contracts/blob/uniswap/uniswap/x-y-k.pdf 来获得自己的理解。欢迎在评论区中留言或加我微信沟通
其中,$\alpha = \frac{\Delta x}{x},\beta=\frac{\Delta y}{y}$,$\gamma = 1-\rho$,因此我们能得出在有手续费情况下的计算公式为
$\Delta x = \frac{\beta}{1-\beta}\cdot\frac{1}{\gamma}\cdot x$
$\Delta y = \frac{\alpha\gamma}{1+\alpha\gamma}\cdot y$
当存在手续费时,即$\rho>0$时,有$x'\rho\times y'\rho > x\times y$。
让我们来代入一个实际的例子,假设你建立一个池子时$x=10,y=20,k=200$。此时你要加入$\Delta x=1$,原本要给你的$\Delta y$应该是$\Delta y = \frac{\alpha}{1+\alpha}y$为1.818。而加上手续费后$\Delta y$ 为1.813。
这里我们完成了基础的恒定乘积公式的推导,后续在核心代码中更详细的部分我们会在下文分析代码时继续详细推导。
在V1中,每次流动性交换需要用ETH作为交换桥梁,这样使得交易路由更简单,并减少了流动性的碎片化。但缺点就是对流动性提供者的要求变高了,流动性提供者必须提供ETH。
在V2中,你可以创建任何ERC20 Pair的流动性池,以达到可以直接通过ERC20 token兑换ERC20 token。
在Uniswap代码中,每次swap都会调用该函数:
function swap(uint amount0Out, uint amount1Out, address to, bytes calldata data) ck {
函数接受参数中可以看出,uniswap要求swap
的调用者通过amount{0,1}Out
参数指定他们希望接收多少个想要的代币,这些输出的代币数量取决于swap
调用者输入的token{0,1}
。
swap的完整代码如下:
上述代码主要完成的任务就是利用乘积恒等式在你用swap交易token时在余额中更新token数量,并给你对应的token。
值得注意的有两点
每个Uniswap流动性池都是一堆ERC20 token的交易场所。pool合约创建时,各个代币的余额为0。为了促进交易,必须有人用每个代币的初始存款作为种子。第一个流动性提供者时设定该池初始价格的人。也就是说,第一流动性提供者提供的$x$和$y$会得到最初的$x\times y = k$,后续的流动性波动都是在这基础之上进行波动的。
每当有流动性存入池子中,流动性提供者就会获得liquidity token作为奖励。每当发生交易时,该合约都会收取$0.3$%的费用,作为流动性提供者的奖励。
对于最初的流动性提供者,它可以获得$\sqrt{x\times y}$的liquidity token作为奖励,其中一部分奖励以minimum_liquidity的形式永久锁在最初的流动性提供者手中。后续的流动性提供者获得的奖励计算公式为$\min(\frac{x\times totalSupply}{X},\frac{y\times totalSupply}{Y})$.
在代码中体现为:
=- 首先我们先关注池子中已经存在流动性的情况。
为什么Uniswap要采取乘积的平方根来计算铸造的份额?以下是白皮书的理由
Uniswap v2 最初铸造的份额等于数量的几何平均值,流动性 = sqrt(xy)。该公式确保任何时候流动性池份额的价值本质上与最初存入流动性的比率无关......上述公式确保流动性池份额的价值永远不会低于该储备金的几何平均值水池。
我们用一个例子来看这个事情。
uniswapV2的生命周期是有人第一次mint了LP token,最终销毁时是在流动性提供者销毁他们的LP token来赎回自己的ERC 20 token。
140行用紫色表示。流动性时通过pool合约拥有的LP代币数量来衡量的。用户发送到合约的金额会被销毁。
142行和154行的红框表示费用,我们可以暂时跳过这些费用,因为Uniswap不对流动性提供者收取费用
144行和145行。用于计算LP提供者将收回多少金额。如果流动性代币的总供应量是1000个,他们燃烧100个LP代币,那么他们将获得池中持有的token0和token1的10%。流动性/总供应量是他们在LP代币总供应量中燃烧的份额。
147到149行。是LP token被销毁,token0和token1被发送回流动性提供者的账户上
150行到151行。更新余额变量。
153行,调用_update函数更新TWAP和_reverse变量
价格预言机时用于查看给定资产价格相关信息的工具,比如你在手机上看股票价格的时候,手机就可以被称作为价格预言机。然而在链上时没有办法实时感知外部世界的,因此不同的链上的价格预言机都带有不同的设计与不同的去中心化程度。
UniswapV2给出的办法是,在任何交易发生之前,都会计算每个区块开始时的市场价格。由于这个价格是由前一个区块中的最后一笔交易决定的,因此这个价格的操纵成本很高。攻击者必须在前一个区块的最后一笔交易进行恶意交易,但是这也不能保证攻击者能正好在下个区块获利,除非攻击者拥有的算力能够连续开采两个区块。
但是单凭这样还是有风险,UniswapV2将结束价格添加到核心合约中的单个累计价格变量中,并按照价格存在的时间进行加权,该变量代表合约的历史记录中每秒Uniswap价格的总和。
外部合约可以使用该变量来跟踪任何时间间隔内准确的时间加权平均价格(TWAP)。
TWAP 是通过在所需时间间隔的开始和结束时读取 ERC20 代币对的累积价格来构建的。然后,可以将该累积价格的差异除以间隔长度,以创建该期间的 TWAP。
也就是说,这样可以通过时间来把攻击者的篡改造成的危害降到最低,除非攻击者能够每次都篡改成功,不然uniswap的价格不会产生大幅波动。
TWAP 可以直接使用,也可以根据需要作为移动平均线(EMA 和 SMA)的基础。
这部分在代码中体现为:
Uniswap Factory对每一个ERC20 token pair 池创建一个智能合约,其核心逻辑如下,代码比较简洁,这部分不做过多介绍
Uniswap 闪电掉期允许您提取 Uniswap 上任何 ERC20 代币的全部储备,并无需预付费用即可执行任意逻辑,前提是在交易结束时您可以:
任何人都可以通过闪电贷的方式在去中心化的交易所中套利,获得套利空间。这样可以更好的让每个token pair的汇率保持在他们应有的水平。因为只要出现token pair的汇率与大众预期不符,或者与其他交易所相差过大,就会有人用闪电贷的方式套利,促使价格回归正常区间。
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!