Uniswap V3分析(一)

UniswapV3分析(一)本文将对复杂的Uniswap3进行分析。准备官方博客白皮书coreperiphery架构v3在代码层面的架构和v2基本保持一致。core的功能主要包含在以下2个合约中:UniswapV3Factory:提供创建pool的接口,并且追踪所有的pool

Uniswap V3分析(一)

本文将对复杂的Uniswap3进行分析。

准备

官方博客

白皮书

core

periphery

架构

v3在代码层面的架构和v2基本保持一致。

core的功能主要包含在以下2个合约中:

UniswapV3Factory: 提供创建pool的接口,并且追踪所有的pool。

UniswapV3Pool: 负责核心逻包括swap/mint/burn等。

peirphery的功能主要包含在以下2个合约:

SwapRouter: 提供代币交易的接口,它是对UniswapV3Pool合约中交易相关接口的进一步封装,用来提升用户体验。

NonfungiblePositionManager: 用来增加/移除/修改Pool的流动性,并且通过 NFT token 将流动性代币化。使用ERC721 token (v2 使用的是 ERC20) 的原因是同一个池的多个流动性并不能等价替换(v3 的集中流性动功能)。

设计原理

LP的权衡

Uniswap v2版本使用x⋅y=k这样一个简洁的公式实现了AMM Dex。但是这么做并不是毫无代价。LP要为所有的价格都需要提供流动性。那么就意味着即使在毫无可能的价格上,LP也需要负责提供流动性。导致资金利用率太低。

例如当前 1ETH=1800 USDT。按照Uniswap V2的设计,从1 ETH = 0 USDT 到 1 ETH = 正无穷大个USDT这条曲线上的任一点,LP都有义务提供流动性。例如在1 ETH= 10USDT 这样荒谬的价格上,每个LP也需要按比例在ETH:USDT池中需要持有一定数量的ETH。

为了解决资金利用率太低的问题,Uniswap v3版本的AMM曲线从无限变成局部。通过引入虚拟流动性,允许用户只在一段价格区间内提供流动性。

在 x⋅y=k 的函数曲线图中,为了满足让用户可以选择只在[a,b]价格区间内提供流动性。对于图中 [a,b] 区间的任意点,都有

(X_virtual+X_real) * (Y_virtual+Y_real) = k = L*L

这里的virtual变量只和L, a,b有关,和X_real,Y_real没有关系。X_real,Y_real为用户提供的Xtoken,Ytoken数量。注意,X_virtual和Y_virtual虚拟出的只是为了计算一致性,并不会参与真实交易,因此其数量是恒定不变的。当价格变动,移动到用户设定的价格区间之外时,流动池会移除这部分流动性。

Tick

UniswapV3将连续的价格范围,分割成有限个离散的价格点。每一个价格对应一个 tick,用户在设置流动性的价格区间时,只能选择这些离散的价格点中的某一个作为流动性的边界价格。

tick有如下特征:

tick组成的价格序列既为一串等比数列,公比为 1.0001,即p_i = 1.0001^i

为了计算方便,实际计算过程使用的是√P,即√p_i = (√1.0001)^i

tick的序号是固定的整数集合,即 区间 [-887272, 887272] 的整数。

V3的交易

因为每一个用户提供的流动性都可能设置不同的价格区间,这样一来一个交易对的池子中就包含了多个不同的流动性。因此从单个交易池的视角来看,Uniswap v3实际上扮演的角色是一个交易聚合器。当发生交易时,此交易会拆分成多个,通过池中多个不同的流动性来进行交易,最后将交易结果聚合,完成最终的交易过程。

而交易的具体过程又类似于LOB,每对Tick都可以看做一个具有流动性L的池。这个池的功能就是流动性不变的情况下,完成x和y的兑换功能。所以tick i和tick i+1可以看做一组限价单。交易的过程不停进行x和y的买入卖出,直到这对tick的流动性L被耗尽。此时系统再转移到下一组tick 继续执行订单。

V3的手续费

在v1和v2中,每个交易对对应一个独立的流动性池,对所有池收取0.30%的手续费。但对于部分池子可能太高了(稳定币池),而对于另一部分池子又太低了(高波动性)。

为了解决这个问题,v3为每个交易对引入了多种池,允许分别设置不同的交易手续费。默认允许创建三个手续费等级:0.05%,0.30%和1%。可以通过UNI治理添加更多手续费等级。

手续费

当价格来到一个tick,我们只要记录这个tick上的交易量,根据费率算出该区间的总手续费,然后找出所有包含该 tick 流动性头寸 position,先将他们的数量汇总,再根据出资比例逐个分配手续费,将数值累加到待每个头寸的待收取手续费的变量上。

这是一个很常见的反法,但是十分消耗gas。因为一笔交易可能会横跨很多个tick,且单个tick 的计算,就有可能涉及到非常多的流动性头寸 position,这不但需要一个耗时的遍历查找的过程,更严重的问题是,每个流动性头寸的待收取手续费肯定是要写入一个storage变量。

V3中为了解决这个问题使用了如下方法:

先看要用到的变量:

feeGrowthGlobal:表示全局累计的手续费总额

feeGrowthOutside:表示发生在此tick外侧的手续费总额

feeGrowthInside:表示此position内的手续费总额(注:只会在position发生变动或者用户提取手续费时更新)

V3定义为与当前价格所对应的 tick 相对于 tick i 的相反侧。

此时便可以计算出position内的手续费总额,注意根据 i_current, a, b 三者位置关系不同,需要判断above和below的计算方式,

每当有流动性注入的时候,在价格的边界对应的 tick 上有如下初始化规则:

当 i_current < i 则 feeGrowthOutside = feeGrowthGlobal
当 i_current >= i 则 feeGrowthOutside = 0

在交易过程中,feeGrowthOutside 有如下更新规则:

当价格穿过某个已初始化的 tick 时,该 tick 上的 feeGrowthOutside 需要翻转,因为外侧手续费永远要在当前价格的另一侧,固 feeGrowthOutside = feeGrowthGlobal - feeGrowthOutside

点赞 0
收藏 2
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
不可思议之人
不可思议之人
0x46b7...98ee
江湖只有他的大名,没有他的介绍。