流动性计算
\[ \]

流动性计算 #

在 Uniswap V3 的所有数学公式中,只剩流动性计算我们还没在 Solidity 里面实现。在 Python 脚本中我们有这两个函数:

def liquidity0(amount, pa, pb):
    if pa > pb:
        pa, pb = pb, pa
    return (amount * (pa * pb) / q96) / (pb - pa)


def liquidity1(amount, pa, pb):
    if pa > pb:
        pa, pb = pb, pa
    return amount * q96 / (pb - pa)

接下来我们在 Solidity 中实现这个计算,以便于在 Manager.mint() 中使用。

实现 Token X 的流动性计算 #

我们要实现的函数是在已知 token 数量和价格区间的情况下计算流动性($L = \sqrt{xy}$)。我们之前已经知道了所有相关的公式,让我们回顾一下:

$$\Delta x = \Delta \frac{1}{\sqrt{P}}L$$

在上一章,我们使用这个函数来计算交易数量(这里的 $\Delta x$),现在我们用它来计算 $L$:

$$L = \frac{\Delta x}{\Delta \frac{1}{\sqrt{P}}}$$

简化之后: $$L = \frac{\Delta x \sqrt{P_u} \sqrt{P_l}}{\sqrt{P_u} - \sqrt{P_l}}$$

这个公式来自于计算流动性一章。

在 Solidity 中,我们仍然使用 PRBMath 来处理乘除过程中可能出现的溢出:

function getLiquidityForAmount0(
    uint160 sqrtPriceAX96,
    uint160 sqrtPriceBX96,
    uint256 amount0
) internal pure returns (uint128 liquidity) {
    if (sqrtPriceAX96 > sqrtPriceBX96)
        (sqrtPriceAX96, sqrtPriceBX96) = (sqrtPriceBX96, sqrtPriceAX96);

    uint256 intermediate = PRBMath.mulDiv(
        sqrtPriceAX96,
        sqrtPriceBX96,
        FixedPoint96.Q96
    );
    liquidity = uint128(
        PRBMath.mulDiv(amount0, intermediate, sqrtPriceBX96 - sqrtPriceAX96)
    );
}

实现 Token Y 的流动性计算 #

类似得,我们将使用计算流动性中出现的另一个公式来在给定 $y$ 的数量和价格区间的前提下计算流动性:

$$\Delta y = \Delta\sqrt{P} L$$ $$L = \frac{\Delta y}{\sqrt{P_u}-\sqrt{P_l}}$$

function getLiquidityForAmount1(
    uint160 sqrtPriceAX96,
    uint160 sqrtPriceBX96,
    uint256 amount1
) internal pure returns (uint128 liquidity) {
    if (sqrtPriceAX96 > sqrtPriceBX96)
        (sqrtPriceAX96, sqrtPriceBX96) = (sqrtPriceBX96, sqrtPriceAX96);

    liquidity = uint128(
        PRBMath.mulDiv(
            amount1,
            FixedPoint96.Q96,
            sqrtPriceBX96 - sqrtPriceAX96
        )
    );
}

希望这些代码足够清晰易懂。

找到公平的流动性 #

你可能会问为什么我们有两种方式来计算 $L$,但是我们实际上只有一个 $L$,即 $L = \sqrt{xy}$,究竟哪种方法是正确的?答案是:两种方法都是正确的。

在上面的公式中,我们基于不同的参数来计算$L$:价格区间和其中某种 token 的数量。不同的价格区间和不同的 token 数量会导致不同的 $L$。有一个场景是我们需要计算两个$L$并且从中挑选出一个的,回顾一下之前的 mint 函数:

if (slot0_.tick < lowerTick) {
    amount0 = Math.calcAmount0Delta(...);
} else if (slot0_.tick < upperTick) {
    amount0 = Math.calcAmount0Delta(...);

    amount1 = Math.calcAmount1Delta(...);

    liquidity = LiquidityMath.addLiquidity(liquidity, int128(amount));
} else {
    amount1 = Math.calcAmount1Delta(...);
}

在计算流动性时有如下逻辑:

  1. 如果我们在一个低于现价的价格区间计算流动性,我们使用 $\Delta y$ 版本的公式;
  2. 如果我们在一个高于现价的价格区间计算流动性,我们使用 $\Delta x$ 版本的公式;
  3. 如果现价在价格区间内,我们两个都计算并且挑选较小的一个。

同样,这些也在计算流动性一章中讨论过。

我们现在来实现其中的逻辑。 当现价低于价格区间下界时:

function getLiquidityForAmounts(
    uint160 sqrtPriceX96,
    uint160 sqrtPriceAX96,
    uint160 sqrtPriceBX96,
    uint256 amount0,
    uint256 amount1
) internal pure returns (uint128 liquidity) {
    if (sqrtPriceAX96 > sqrtPriceBX96)
        (sqrtPriceAX96, sqrtPriceBX96) = (sqrtPriceBX96, sqrtPriceAX96);

    if (sqrtPriceX96 <= sqrtPriceAX96) {
        liquidity = getLiquidityForAmount0(
            sqrtPriceAX96,
            sqrtPriceBX96,
            amount0
        );

当现价位于价格区间中,我们挑选较小的一个:

} else if (sqrtPriceX96 <= sqrtPriceBX96) {
    uint128 liquidity0 = getLiquidityForAmount0(
        sqrtPriceX96,
        sqrtPriceBX96,
        amount0
    );
    uint128 liquidity1 = getLiquidityForAmount1(
        sqrtPriceAX96,
        sqrtPriceX96,
        amount1
    );

    liquidity = liquidity0 < liquidity1 ? liquidity0 : liquidity1;

最后一种情况:

} else {
    liquidity = getLiquidityForAmount1(
        sqrtPriceAX96,
        sqrtPriceBX96,
        amount1
    );
}

Done.