« 上一篇下一篇 »

UniswapV3简析(一)


Uniswap V3 是Uniswap V2的升级版, 其最大的创新在于提供了集中流动性功能, 集中流动性可以让用户自主选择做市价格区间, 这样就大大提高了资金利用的效率, 整个产品链上部分由若干个智能合约构成, 其大体分为核心合约(core contracts )与外围合约(periphery contracts)两部分组成, 外围合约主要包括 NonfungiblePositionManager, NonfungibleTokenPositionDescriptor, SwapRouter, TickLens, Quoter等合约, 核心合约主要包括 UniswapV3Pool, UniswapV3Factory等合约.

NonfungiblePositionManager: 直接与链下前端进行交互的外围合约, 主要对外提供比如创建流动池, 添加流动性, 删除流动性等API接口, 同时也会为每个流动性仓位生成一个唯一的NFT给到用户

SwapRouter: 直接与链下前端进行交互的外围合约, 主要对外提供token交易(兑换)API接口

NonfungibleTokenPositionDescriptor: NFT描述符, 记录一些NFT上有趣的信息, 这样可以在Opensea上展现出一些有意思的图形

TickLens: 外围合约, 链下前端可以通过它查询流动池中流动性仓位相关信息

Quoter: 外围合约, 链下前端可以通过它在真实交易前获取交易输出的数量

UniswapV3Factory: 核心合约, 流动池工厂, 所有的流动池合约都由这个工厂合约统一部署

UniswapV3Pool: 核心合约, 也是整个系统最核心的部分, 可以把它理解成一个流动性聚合器, 整合所有流动性, 统一处理token兑换功能

系统整体关系如下图所示:


如图所示:

1.链下前端用户调用NonfungiblePositionManager合约的流动池创建接口, 然后NonfungiblePositionManager合约内部会通过调用UniswapV3Factory工厂合约部署流动池合约UniswapV3Pool

2.链下前端用户调用NonfungiblePositionManager合约的添加流动性, 删除流动性等接口, 然后NonfungiblePositionManager合约内部会与UniswapV3Pool合约进行交互, 真正完成流动性添加删除等操作

3.链下前端用户调用SwapRouter合约的交易兑换接口, SwapRouter合约内部会再调用UniswapV3Pool合约的交易接口, 真正完成交易兑换过程

4.链下前端用户可以通过TickLens, Quoter两合约查询流动性信息和交易输出的数量, 而这两合约内部都会直接与对应的UniswapV3Pool合约进行交互, 完成真正的查询请求

在Uniswap V2版本中流动性的计算涉及K=xy, P=y/x等公式, 在V3中虽然是流动性聚合器, 更加复杂, 但是这些基本公式依然适用, 我们可以根据这些公式推导出:

这样我们在实际计算过程中不用关注x token和y token的数量, 而只需要关注K值和P值就可以完成交易输入输出数量的计算, 为了计算方便等原因, 实际代码中使用和保存的是根号P和L, L=根号K,
所以实际上从计算角度看是有两种视角:

视角一: x token和y token的数量, 如下图:

视角二:  根号P和L, 如下图:

以上两种视角是可以互相转换的

关于价格的表示问题:
因为价格是连续的, 存在精度的问题, 所以在实际计算中会非常麻烦, V3中通过一种巧妙的方式将连续的价格离散化, 然后每一个价格点位上都对应一个tick, V3中采用了等比数列的形式确定价格数列, 公比为 1.0001, 即下一个价格点为当前价格点的100.01%, 如下所示:

每个价格点位由一个tick表示, 将所有tick通过索引来表示, 定义整数i表示tick的索引:

每个流动性仓位只记录其 upper/lower tick 所包含的流动性元数据即可

所以价格有两种表示方式:
方式一: 直接用根号P来表示
方式二: 直接用tick索引i来表示价格点位

在contracts\libraries\TickMath.sol中包含两者转换的方法, 代码如下:

library TickMath {

    function getSqrtRatioAtTick(int24 tick) internal pure returns (uint160 sqrtPriceX96) { ...
    }

    function getTickAtSqrtRatio(uint160 sqrtPriceX96) internal pure returns (int24 tick) { ...
    }

}