Generalized Minting
Now, we’re ready to update the mint
function so we don’t need to hard code values anymore and can calculate them instead.
Indexing Initialized Ticks
Recall that, in the mint
function, we update the TickInfo mapping to store information about available liquidity at ticks. Now, we also need to index newly initialized ticks in the bitmap index–we’ll later use this index to find the next initialized tick during swapping.
First, we need to update the Tick.update
function:
// src/lib/Tick.sol
function update(
mapping(int24 => Tick.Info) storage self,
int24 tick,
uint128 liquidityDelta
) internal returns (bool flipped) {
...
flipped = (liquidityAfter == 0) != (liquidityBefore == 0);
...
}
It now returns a flipped
flag, which is set to true when liquidity is added to an empty tick or when entire liquidity is removed from a tick.
Then, in the mint
function, we update the bitmap index:
// src/UniswapV3Pool.sol
...
bool flippedLower = ticks.update(lowerTick, amount);
bool flippedUpper = ticks.update(upperTick, amount);
if (flippedLower) {
tickBitmap.flipTick(lowerTick, 1);
}
if (flippedUpper) {
tickBitmap.flipTick(upperTick, 1);
}
...
Again, we’re setting tick spacing to 1 until we introduce different values in Milestone 4.
Token Amounts Calculation
The biggest change in the mint
function is switching to tokens amount calculation. In Milestone 1, we hard-coded these values:
amount0 = 0.998976618347425280 ether;
amount1 = 5000 ether;
And now we’re going to calculate them in Solidity using formulas from Milestone 1. Let’s recall those formulas:
is the amount of token0
, or token . Let’s implement it in Solidity:
// src/lib/Math.sol
function calcAmount0Delta(
uint160 sqrtPriceAX96,
uint160 sqrtPriceBX96,
uint128 liquidity
) internal pure returns (uint256 amount0) {
if (sqrtPriceAX96 > sqrtPriceBX96)
(sqrtPriceAX96, sqrtPriceBX96) = (sqrtPriceBX96, sqrtPriceAX96);
require(sqrtPriceAX96 > 0);
amount0 = divRoundingUp(
mulDivRoundingUp(
(uint256(liquidity) << FixedPoint96.RESOLUTION),
(sqrtPriceBX96 - sqrtPriceAX96),
sqrtPriceBX96
),
sqrtPriceAX96
);
}
This function is identical to
calc_amount0
in our Python script.
The first step is to sort the prices to ensure we don’t underflow when subtracting. Next, we convert liquidity
to a Q96.64 number by multiplying it by 2**96. Next, according to the formula, we multiply it by the difference of the prices and divide it by the bigger price. Then, we divide by the smaller price. The order of division doesn’t matter, but we want to have two divisions because the multiplication of prices can overflow.
We’re using mulDivRoundingUp
to multiply and divide in one operation. This function is based on mulDiv
from PRBMath
:
function mulDivRoundingUp(
uint256 a,
uint256 b,
uint256 denominator
) internal pure returns (uint256 result) {
result = PRBMath.mulDiv(a, b, denominator);
if (mulmod(a, b, denominator) > 0) {
require(result < type(uint256).max);
result++;
}
}
mulmod
is a Solidity function that multiplies two numbers (a
and b
), divides the result by denominator
, and returns the remainder. If the remainder is positive, we round the result up.
Next, :
function calcAmount1Delta(
uint160 sqrtPriceAX96,
uint160 sqrtPriceBX96,
uint128 liquidity
) internal pure returns (uint256 amount1) {
if (sqrtPriceAX96 > sqrtPriceBX96)
(sqrtPriceAX96, sqrtPriceBX96) = (sqrtPriceBX96, sqrtPriceAX96);
amount1 = mulDivRoundingUp(
liquidity,
(sqrtPriceBX96 - sqrtPriceAX96),
FixedPoint96.Q96
);
}
This function is identical to
calc_amount1
in our Python script.
Again, we’re using mulDivRoundingUp
to avoid overflows during multiplication.
And that’s it! We can now use the functions to calculate token amounts:
// src/UniswapV3Pool.sol
function mint(...) {
...
Slot0 memory slot0_ = slot0;
amount0 = Math.calcAmount0Delta(
slot0_.sqrtPriceX96,
TickMath.getSqrtRatioAtTick(upperTick),
amount
);
amount1 = Math.calcAmount1Delta(
slot0_.sqrtPriceX96,
TickMath.getSqrtRatioAtTick(lowerTick),
amount
);
...
}
Everything else remains the same. You’ll need to update the amounts in the pool tests, they’ll be slightly different due to rounding.