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) { ...
}
}