加密日记【016】uniswap V3 tick
April 16th, 2023

ticks

用bitmap来建立一个 ticks 的索引

contract UniswapV3Pool {
    using TickBitmap for mapping(int16 => uint256);
    mapping(int16 => uint256) public tickBitmap;
    ...
}

这里的存储方式是一个mapping,key 的类型是 int16,value 的类型是 uint256。想象一个无穷的 0/1 数组:

举例计算某个tick的位置:

tick = 85176
word_pos = tick >> 8 # or tick // 2**8
bit_pos = tick % 256
print(f"Word {word_pos}, bit {bit_pos}")
# Word 332, bit 184

也就是说位于85176的tick,在用bitmap存储的时候是tickBitmap[332]=184;

在uniswap V3用position表示这个tick的位置

function position(int24 tick) private pure returns (int16 wordPos, uint8 bitPos) {
    wordPos = int16(tick >> 8);
    bitPos = uint8(uint24(tick % 256));
}

翻转flag

function flipTick(
    mapping(int16 => uint256) storage self,
    int24 tick,
    int24 tickSpacing
) internal {
    require(tick % tickSpacing == 0); // ensure that the tick is spaced
    (int16 wordPos, uint8 bitPos) = position(tick / tickSpacing);
    uint256 mask = 1 << bitPos;
    self[wordPos] ^= mask;
}
//mask 二进制表示 从右到左的184位为1
10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000

tickBitmap[332]的初始值全为0的情况下,self[wordPos] ^= mask;之后的结果是第184位变成1。

找下一个tick

在 swap 过程中,我们需要找到现在 tick 左边或者右边的下一个有流动性的 tick。因为同一个堆栈里面左边是高位,右边是低位,所以在同一个word内,左边的价格比右边高。

  • 当卖出 token x (在这里即 ETH)时,找到在同一个 word 内当前tick的右边下一个有流动性的tick。

  • 当卖出 token y (在这里即 USDC)时,找到在同一个 word 内当前tick的左边下一个有流动性的tick。

在nextInitializedTickWithinOneWord函数里面,

if (lte) { //lte为true表示卖出x
    (int16 wordPos, uint8 bitPos) = position(compressed);
     //(1 << bitPos) - 1 表示类似于1000-1=111,(1 << bitPos)=1000,相加为1111,即当前位及所有右边的位为 1
    uint256 mask = (1 << bitPos) - 1 + (1 << bitPos);
    //将掩码覆盖到当前 word 上,得出右边的所有位
    uint256 masked = self[wordPos] & mask;  
    //如果右边有为1的tick,那么表示右边有流动性。
    initialized = masked != 0;
    //下一个有流动性的 tick 位置,或者下一个 word 的最左边一位
    next = initialized
        ? (compressed - int24(uint24(bitPos - BitMath.mostSignificantBit(masked)))) * tickSpacing
        : (compressed - int24(uint24(bitPos))) * tickSpacing;
       
} else { //买入x的时候方向相反
    (int16 wordPos, uint8 bitPos) = position(compressed + 1);
    //当前位置所有左边的位为 1;
    uint256 mask = ~((1 << bitPos) - 1);
    uint256 masked = self[wordPos] & mask;
    ...

跨tick交易

按照图中的点a-f,假设在进入a之前没有流动性,那么从左到右:

  • 进入a的时候,流动性Liquidity= 10ETH

  • 到达b的时候,流动性Liquidity+20ETH=30ETH

  • 到达c的时候,流动性Liquidity-10ETH=20ETH

  • 到达d的时候,流动性Liquidity+20ETH=40ETH

  • 到达e的时候,流动性Liquidity-20ETH=20ETH

  • 到达f的时候,流动性Liquidity-20ETH=20ETH

如果我是a-c的10个ETH的提供者,那么在a点我有10个eth,在c点全部换成了USDC,如果我是在ac的重点开始提供流动性,那么我将提供一半eth,一半USDC。

function update(
    mapping(int24 => Tick.Info) storage self,
    int24 tick,
    int128 liquidityDelta,
    bool upper
) internal returns (bool flipped) {
    ...

    tickInfo.liquidityNet = upper
        ? int128(int256(tickInfo.liquidityNet) - liquidityDelta)
        : int128(int256(tickInfo.liquidityNet) + liquidityDelta);
}//当经过上限时,是增加还是减少取决于方向

tick 间隔

在做swap的时候需要循环遍历找到两个tick之间的所有tick,十分消耗gas。为了节省gas,用tickSpacing来调节间隔,tickSpacing越大,越节省gas。但是tick 间隔越大,精度越低。价格波动大的交易对可以容忍价格精度低,但是价格波动小的交易对就需要高的价格精度。所以Uniswap设置了10,60,200作为tickSpacing的选项。

Subscribe to 0x3c
Receive the latest updates directly to your inbox.
Nft graphic
Mint this entry as an NFT to add it to your collection.
Verification
This entry has been permanently stored onchain and signed by its creator.
More from 0x3c

Skeleton

Skeleton

Skeleton