UniSwapV3 mint new position returned unexpected value

338 Views Asked by At

I’m working on a project calling UniSwap V3 contracts. I’ve read the whitepaper, docs and everything I can find online to solve this problem, but get nothing. So I’m here to seek help, not only to solve the problem itself but to fully understand where I miss in the documentations.

First thing is initializing the pool. When I tried to mint a new position I found the comments on the function min() stating:

    /// @notice Creates a new position wrapped in a NFT
    /// @dev Call this when the pool does exist and is initialized. Note that if the pool is created but not initialized
    /// a method does not exist, i.e. the pool is assumed to be initialized.
    /// @param params The params necessary to mint a position, encoded as `MintParams` in calldata
    /// @return tokenId The ID of the token that represents the minted position
    /// @return liquidity The amount of liquidity for this position
    /// @return amount0 The amount of token0
    /// @return amount1 The amount of token1
    function mint(MintParams calldata params)
        external
        payable
        returns (
            uint256 tokenId,
            uint128 liquidity,
            uint256 amount0,
            uint256 amount1
        );

However, there’s nowhere in the docs to find how I should initialize this pool. Luckily enough, I found a github project demonstrating this feature:

// Creates the new pool
        pool = IUniswapV3Pool(
            v3Factory.createPool(address(token0), address(token1), fee)
        );


        /*
            Calculates the initial price (in sqrtPriceX96 format)
            https://docs.uniswap.org/sdk/guides/fetching-prices
            sqrtPriceX96 = sqrt(price) * 2 ** 96
        */
        // Lets set the price to be 1000 token0 = 1 token1
        uint160 sqrtPriceX96 = encodePriceSqrt(1, 1000);
        pool.initialize(sqrtPriceX96);

However, when I used this code to initialize the pool, it never worked out as expected. I’ll explain how.

The intended function is to send a pair of n ETH and m MING to the Uniswap. Here’s how I implement it:

pool = IUniswapV3Pool(
                v3Factory.createPool(Address.WETH, addressOfMing, fee)
            );


            // for testing purpose 
            // only 1 to 1 worked. 
            // uint160 sqrtPriceX96 = encodePriceSqrt(1, 1);
            uint160 sqrtPriceX96 = encodePriceSqrt(amountOfMing, amountOfETH);


            pool.initialize(sqrtPriceX96);
            tickSpacing = pool.tickSpacing();


            (uint256 _tokenId, , , ) = 
                mintNewPosition(amountOfETH, amountOfMing);

Here’s the implementation of mintNewPosition():

IERC20 ming = IERC20(addressOfMing);


        console.log("amount to mint new position: %s -> %s", amountOfETH, amountOfMing);


        require(
            ming.balanceOf(address(this)) >= amountOfMing, 
            "insufficient Ming"
        );


        require(
            weth.balanceOf(address(this)) >= amountOfETH,
            "insufficient WETH"
        );


        // Approve the position manager
        TransferHelper.safeApprove(Address.WETH, address(nonfungiblePositionManager), amountOfETH);
        TransferHelper.safeApprove(addressOfMing, address(nonfungiblePositionManager), amountOfMing);


        // Get tick spacing
        (, int24 curTick, , , , , ) = pool.slot0();
        curTick = curTick - (curTick % tickSpacing);
        int24 lowerTick = curTick - (tickSpacing * 2);
        int24 upperTick = curTick + (tickSpacing * 2);
        require(curTick % tickSpacing == 0, 'tick error');


        INonfungiblePositionManager.MintParams memory params =
            INonfungiblePositionManager.MintParams({
                token0: pool.token0(),
                token1: pool.token1(),
                fee: poolFee,
                tickLower: lowerTick,
                tickUpper: upperTick,
                amount0Desired: amountOfETH, 
                amount1Desired: amountOfMing, 
                amount0Min: 0,
                amount1Min: 0,
                recipient: address(this),
                deadline: block.timestamp
            });


        // Note that the pool defined by DAI/USDC and fee tier 0.3% must already be 
        // created and initialized in order to mint
        (tokenId, liquidity, amount0, amount1) = nonfungiblePositionManager.mint(params);
        console.log("new position minted: %s, %s", amount0, amount1);

To my understanding, when I called pool.initialize(sqrtPriceX96), it decided the price of MING regarding ETH. Therefore, upon calling mint() using the same amount of ETH/MING pair, the amount of ETH/MING returned should be the same as there’s no slippages in this process.

However, this is what I got:

amount to mint new position: 3000000000000000000 -> 222222222222222000000000000000000
new position minted:         3000000000000000000, 25559352082992436084

Did I do anything wrong?

---------------------------update1------------------------- I found in the library PoolAddress: require(key.token0 < key.token1) therefore I edited the following code in the pool creation:

            if(Address.WETH < addressOfMing){
                console.log("init pool");
                pool = IUniswapV3Pool(
                    v3Factory.createPool(Address.WETH, addressOfMing, fee)
                );
                uint160 sqrtPriceX96 = encodePriceSqrt(amountOfMing, amountOfETH);
                pool.initialize(sqrtPriceX96);
                tickSpacing = pool.tickSpacing();
                (uint256 _tokenId, , , ) = mintNewPosition(amountOfETH, amountOfMing);
                tokenId = _tokenId;
            }else{
                console.log("init pool reversed");
                pool = IUniswapV3Pool(
                    v3Factory.createPool(addressOfMing, Address.WETH, fee)
                );
                uint160 sqrtPriceX96 = encodePriceSqrt(amountOfETH, amountOfMing);
                pool.initialize(sqrtPriceX96);
                tickSpacing = pool.tickSpacing();
                (uint256 _tokenId, , , ) = mintNewPosition(amountOfMing, amountOfETH);
                tokenId = _tokenId;
            }

The issue is still the same. However, when I increased the lowerTick&upperTick by 1000x

int24 lowerTick = curTick - (tickSpacing * 1000); 
int24 upperTick = curTick + (tickSpacing * 1000);

I'm getting a way better result as expected.

amount to mint new position 222222222222222000000000000000000, 3000000000000000000
    new position minted ->  222222222222221995864146677687271, 2999106664470400961

Yet, it's not working as expected. As long the price is determined in pool initialization, by giving the same amount of tokens pair, upper&lower ticks shouldn't affect (greatly) the amount of tokens being added to liquidity.

1

There are 1 best solutions below

0
Akshay CM On

I had the same issue as you,i've had to write code myself based on your findings to find the initial price

I've written it in solidity so you may need to reimplement it in js for your use

// src/library/UniswapV3PricingHelper.sol

library UniswapV3PricingHelper {
    uint256 private constant padding = 100;
    // Constants
    uint256 private constant Q96 = 2 ** 96;

    function _sortTokens(
        address _tokenA,
        address _tokenB
    ) internal pure returns (address _sortedTokenA, address _sortedTokenB) {
        require(_tokenA != address(0) && _tokenB != address(0), "Token addresses cannot be zero.");

        // Sort the token addresses
        (_sortedTokenA, _sortedTokenB) = _tokenA < _tokenB ? (_tokenA, _tokenB) : (_tokenB, _tokenA);
    }

    function getInitPrice(
        address _tokenBase,
        address _tokenSwap,
        uint256 tokenBaseAmt,
        uint256 tokenSwapAmt
    ) internal pure returns (address token0, address token1, uint160 initSQRTPrice) {
        (token0, token1) = _sortTokens(_tokenBase, _tokenSwap);
        if (token0 != _tokenBase) initSQRTPrice = encodePriceSqrt(tokenSwapAmt, tokenBaseAmt);
        else initSQRTPrice = encodePriceSqrt(tokenBaseAmt, tokenSwapAmt);
    }

    // Encode price square root function
    function encodePriceSqrt(uint256 reserve0, uint256 reserve1) public pure returns (uint160 sqrtPriceX96) {
        require(reserve0 > 0 && reserve1 > 0, "Reserves must be positive");
        uint256 ratio = Math.sqrt(((reserve1 * padding) / reserve0) / padding) * Q96;
        sqrtPriceX96 = uint160(ratio);
    }
}

In my library _tokenbase is the base asset,in my case it is WETH and tokenSwap is the token you want to pair against weth,say it be dai,usdc or something else

This code assumes both base and swap token is 18 decimals,you will need to handle the case either token being under 18 decimals with rounding of the amount potentially