uniswap v3 copy 自 https://liaoph.com/ (因为自己没有怎么看懂,收藏以后常看)。
官方博客(https://uniswap.org/blog/uniswap-v3/)
v3白皮书(https://uniswap.org/whitepaper-v3.pdf)
设计原理
官方的白皮书已经比较详尽的描述了 v3 的设计原理,这里仅对白皮书中的内容做一些补充,包含本人对其中一些机制的理解和思考。
Uniswap v2 版本使用 x⋅y=k 这样一个简洁的公式实现了 AMM Dex,正是由于其简洁易用性,使其在短短的一年的时间内迅速成长为 DeFi 领域的龙头项目。但是随着 DeFi 生态走过了「从无到有」的阶段,因为 v2 无法满足某些特定需求,从而诞生了 Curve, Balancer 这些针对某些功能进行改进的 AMM。简单来说,官方认为 v2 版本最大的痛点是资金利用率(Capital Efficiency)太低,v3 版本在解决这个问题的同时,还带了了新的改进,总体总结如下:
1.可灵活选择价格区间提供流动性
2.更好用的预言机
3.order book 功能
4.灵活的费率
提升资金利用率
解决资金利用问题之前,我们可以观察到大部分的交易对的价格,在大部分时间内都只是在一个固定范围内波动。例如 ETH/DAI 交易对,在近一个月时间内都是在 1300 ~ 2200 DAI/ETH 这个范围内波动。更极端的例子是 DAI/USDC 这样的稳定币交易对,在大部分时间内都只是在 1.001 ~ 1.002 DAI/USDC 范围内波动。
v2 的问题
我们先来看一看 v2 版本的资金利用率是怎样的,假设 ETH/DAI 交易对的实时价格为 1500 DAI/ETH,交易对的流动性池中共有资金:4500 DAI 和 3 ETH,根据 x⋅y=k,可以算出池内的 k 值:
k=4500×3=13500
假设 x 表示 DAI,y 表示 ETH,即初始阶段 x1=4500,y1=3,当价格下降到 1300 DAI/ETH 时:
得出 x2=4192.54,y2=3.22,资金利用率为:Δxx1=6.84%。同样的计算方式,当价格变为 2200 DAI/ETH 时,资金利用率约为 21.45%.
也就是说,在大部分的时间内池子中的资金利用与低于 25%. 这个问题对于稳定币池来说更加严重。
解决方案
v3 版本的解决方案是允许用户只在一段价格区间内提供流动性。如下图
此图展示了一个 x⋅y=k 的函数曲线图。为了满足让用户可以选择只在 [a,b] 价格区间内提供流动性。对于图中 [a,b] 区间的任意点,都有:
x=xvirtual+xreal
y=yvirtual+yreal
其中 xreal,yreal 分别表示用户提供的 x token, y token 数量,xvirtual,yvirtual 分别表示流动池虚拟出的 x token y token 数量。注意,虚拟出的 x token 和 y token 只是为了计算一致性,并不会参与真实交易,因此其数量是恒定不变的。
当流动池的价格来到用户设置的零界点时(例如图中的 a 点或者 b 点),用户实际提供的 x token 或者 y token 将为 0,x 或 y 将完全由虚拟 token 组成。为了保证虚拟 token 恒定不变,当价格进一步变动,移动到用户设定的价格区间之外时,流动池会移除这部分流动性,以保证虚拟的 x token 或 y token 数量不会减少,因此这部分虚拟的 token 只会在价格处于设定的区间内时参与价格的计算,而不会真的参与流动性提供(即虚拟 token 数为常数,并不会发生数量变化)。
例如,当价格到达 a 点时,用户的所有资金转换为 x,此时 yreal=0,y=yvirtual,当价格继续降低时,流动池将移除这部分流动性。用户的资金状态将停留在 a 点,直至价格再次回到 a 点并进入 [a,b] 价格区间。
通过这样的设计,用户的资金只会在 [a,b]价格区间内提供流动性,并且因为虚拟 token xvirtual,yvirtual 的参与,这部分流动性也满足 x⋅y=k 公式,计算价格的方式并没有产生变化。
上图展示了用户选择在价格 [a,b] 之间提供流动性时,通过虚拟 token 的参与,将曲线 f(real) (橘红色)向右上方移动至 f(virtual)(绿色),实现了价格计算的一致性(即满足x⋅y=k)。
流动性聚合
上面我们说了,通过引入虚拟 token 的概念,让用户可以在某一个价格区间内提供流动性。而每一个用户提供的流动性都可能设置不同的价格区间,这样一来一个交易对的池子中就包含了多个不同的流动性。因此从单个交易池的视角来看,Uniswap v3 实际上扮演的角色是一个交易聚合器:当发生交易时,此交易会拆分成多个,通过池中多个不同的流动性来进行交易,最后将交易结果聚合,完成最终的交易过程。
从交易聚合器的角度看,在交易发生前,池中每一个流动性中的 token price 是一致的。那么我们需要让交易结束后,池中每一个流动性中的 token price 仍然是一致的(当然这里仅包含所有在区间内的流动性)。因此 v3 的交易过程会围绕价格来进行,这样可以保证所有流动性的价格一致。事实上这就和 AMM 交易聚合器的行为一致,因此我们可以把 Uniswap V3 理解成单个交易池中不同流动性的交易聚合器。
交易过程
具体的实现,可以参考 v2 代码实现。
v3 版本
如上图最右边所示,当价格变化时,流动池中的总流动性也会随之变化。因此 v3 版本流动池中资金的关系不能像 v2 版本一样用一个平滑的 bonding curve 曲线来表示。那么如何计算交易结果呢?
在前面我们说过,V3 的行为类似一个交易聚合器,它需要保证池中所有流动性的价格在交易前后一致。因此 V3 会围绕池中的代币价格来进行。
对于一个流动性来说,流动性大小可以用 k 表示,即 k=x⋅y,用 P 表示 x 的价格,即 P=yx
对于一个流动性来说,当使用 x token 交换 y token 时,我们需要进行如下计算:
交易至指定价格(不可以超出此流动性的边界价格)P,需要的 x token 数 Δx,可以获得的 y token 数 Δy
给定 x token 数 Δx(假设不会引发价格超出此流动性的边界价格),可以获得的 y token 数 Δy,以及最终的价格 P
当 k 值不变时,根据定义:
这样一来计算过程并不需要关注流动性中的 x token 和 y token 余额,通过 k 值和价格 P 就可以完成交易过程的计算。
价格精度问题
tick 管理
灵活的手续费选择
v3 版本内置了三种梯度的手续费率(0.05%, 0.30%, and 1.00%),同时可以在未来增加更多的费率值。关于手续费的计算过程,这部分放在后文来详解(链接:交易手续费)。需要注意的是,由于需要支持多种费率,同一个代币对 v3 版本会有多个不同的流动池。例如 ETH/DAI 代币对,会分成三个池,分别对应 0.05%, 0.30%, 1.00% 的手续费。
更多的费率选择性,这样做会更加灵活,但是同时也会带来一定的流动性分裂,uni 官方表示后续可以通过治理添加更多的费率可选值,这也势必会让流动性更加分裂。那么可能会出现一种情况是,即使是只使用 uniswap v3 这单个 AMM 来完成一笔交易,但是因为代币对的流通性分散在多个池子中。那么最优的交易策略是使用交易聚合器(例如 1inch)来进行交易,即将单笔交易拆散,同时使用多个流动性池来完成交易。就目前 uniswap v3 前端代码情况来看,官方的界面是不支持这种聚合交易的,其 sdk 代码中的注释也说明了这个问题:SDK 代码。
手续费与 tick 的关系
代码架构
Uniswap v3 在代码层面的架构和 v2 基本保持一致,将合约分成了两个仓库:
- uniswap-v3-core
- uniswap-v3-periphery
core 仓库的功能主要包含在以下 2 个合约中:
- UniswapV3Factory: 提供创建 pool 的接口,并且追踪所有的 pool
- UniswapV3Pool: 实现代币交易,流动性管理,交易手续费的收取,oracle 数据管理。接口的实现粒度比较低,不适合普通用户使用,错误的调用其中的接口可能会造成经济上的损失。
peirphery 仓库的功能主要包含在以下 2 个合约:
- SwapRouter: 提供代币交易的接口,它是对 UniswapV3Pool 合约中交易相关接口的进一步封装,前端界面主要与这个合约来进行对接。
- NonfungiblePositionManager: 用来增加/移除/修改 Pool 的流动性,并且通过 NFT token 将流动性代币化。使用 ERC721 token(v2 使用的是 ERC20)的原因是同一个池的多个流动性并不能等价替换(v3 的集中流性动功能)。
这些合约间的关系大致如下图:
本系列后续会从常用的 Uniswap v3 操作入手,讲解代码调用流程。一般来说,用户的操作都是与 uniswap-v3-periphery 开始。