Liquidity Calculation

Of the whole math of Uniswap V3, what we haven’t yet implemented in Solidity is liquidity calculation. In the Python script, we have these functions:

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)

Let’s implement them in Solidity so we can calculate liquidity in the Manager.mint() function.

Implementing Liquidity Calculation for Token X

The functions we’re going to implement allow us to calculate liquidity () when token amounts and price ranges are known. Luckily, we already know all the formulas. Let’s recall this one:

In a previous chapter, we used this formula to calculate swap amounts ( in this case) and now we’re going to use it to find :

Or, after simplifying it:

We derived this formula in Liquidity Amount Calculation.

In Solidity, we’ll again use PRBMath to handle overflows when multiplying and then dividing:

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)
    );
}

Implementing Liquidity Calculation for Token Y

Similarly, we’ll use the other formula from Liquidity Amount Calculation to find when the amount of and the price range is known:

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
        )
    );
}

I hope this is clear!

Finding Fair Liquidity

You might be wondering why there are two ways of calculating while we have always had only one , which is calculated as , and which of these ways is correct? The answer is: they’re both correct.

In the above formulas, we calculate based on different parameters: price range and the amount of either token. Different price ranges and different token amounts will result in different values of . And there’s a scenario where we need to calculate both of the ’s and pick one of them. Recall this piece from the mint function:

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(...);
}

It turns out, we also need to follow this logic when calculating liquidity:

  1. if we’re calculating liquidity for a range that’s above the current price, we use the version on the formula;
  2. when calculating liquidity for a range that’s below the current price, we use the one;
  3. when a price range includes the current price, we calculate both and pick the smaller of them.

Again, we discussed these ideas in Liquidity Amount Calculation.

Let’s implement this logic now.

When the current price is below the lower bound of a price range:

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
        );

When the current price is within a range, we’re picking the smaller :

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

    liquidity = liquidity0 < liquidity1 ? liquidity0 : liquidity1;

And finally:

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

Done.