0

I am trying to build a Uniswap Liquidity pool contract, I have written the Below code for it.

CODE:

// SPDX-License-Identifier: GPL-2.0-or-later

pragma solidity =0.7.6;
pragma abicoder v2;

import '@uniswap/v3-core/contracts/interfaces/IUniswapV3Pool.sol';
import '@uniswap/v3-core/contracts/libraries/TickMath.sol';
import '@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol';
import '@uniswap/v3-periphery/contracts/interfaces/INonfungiblePositionManager.sol';
import '@uniswap/v3-periphery/contracts/libraries/TransferHelper.sol';
import '@uniswap/v3-periphery/contracts/base/LiquidityManagement.sol';

contract Liquidity is IERC721Receiver
{
    address public constant DAI = 0x6B175474E89094C44Da98b954EedeAC495271d0F ;
    address public constant USDC = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48;
    uint24 public constant poolfee = 3000;

    //Address of the INonfungiblePositionManager interface.
    INonfungiblePositionManager public immutable nonfungiblePositionManager = 
    INonfungiblePositionManager(0xC36442b4a4522E871399CD717aBDD847Ab11FE88);
    
    // Every time a user adds liquidty to the pool, he receives an NFT. And all the data regarding the Deposit is stored in the NFT.
    // NFT is created to represent a specific share or ownership stake in the liquidity pool. For example 10% share of liquidity pool.
    // Struct to hold the deposit information of a user.
    struct Deposit
    {
        address owner;
        uint128 liquidity;
        address token0;
        address token1;
    }

    //Mapping which points to the Deposit Struct. To store deposit information of each TokenID user.
    mapping(uint256 => Deposit) public deposit; // TokenID --> Deposit Structure.

    // To allow NFT's to be received by this contct we define IERC721 Receiver, and inside that we call CreateDeposit function.
    function onERC721Received (address operator, address, uint256 tokenId, bytes calldata) external override returns (bytes4) 
    {
        // get position information
        _createDeposit(operator, tokenId);
        return this.onERC721Received.selector;
    }

    // Function used to set the mapping deposit whenever someone adds liquidity using the mint function.
    function _createDeposit(address owner, uint256 tokenID) internal 
    {
        // accessing values from the position function of interface INonfungilePositionManager.
        (, , address token0, address token1, , , , uint128 liquidity, , , , ) = nonfungiblePositionManager.positions(tokenID);

        deposit[tokenID] = Deposit({owner: owner,liquidity: liquidity,token0: token0, token1: token1});
    }

    // Function to mint new tokens into the liquidity pool.
    // Function to add liquidity to the pool.
    // Every time a user adds liquidty to the pool, he receives an NFT. And all the data regarding the Deposit is stored in the NFT.
    // NFT is created to represent a specific share or ownership stake in the liquidity pool. For example 10% share of liquidity pool.
    function mintNewPosition() external returns(uint256 tokenID, uint128 liquidity, uint256 amount0,  uint256 amount1)
    {
        //User has to transfer the tokens to this contract before calling the mint function.

        uint256 amount0ToMint = 100 * 1e18; // amount of token0 we want to add in the Liquidity Pool.
        uint256 amount1ToMint = 100 * 1e18; // amount of token1 we want to add in the Liquidity Pool.

        //Transfer Tokens from User to contract Balances
        TransferHelper.safeTransferFrom(DAI, msg.sender, address(this), amount0ToMint);
        TransferHelper.safeTransferFrom(USDC, msg.sender, address(this), amount1ToMint);

        //Approve the nonfungiblePositionManger contract to use the tokens.
        TransferHelper.safeApprove(DAI,address(nonfungiblePositionManager), amount0ToMint);
        TransferHelper.safeApprove(USDC,address(nonfungiblePositionManager), amount1ToMint);

        // Populating the Mint parameters to call the mint function.
        INonfungiblePositionManager.MintParams memory params = 
            INonfungiblePositionManager.MintParams({
                token0: DAI,                        // Token0 to Mint
                token1: USDC,                       // Token1 to Mint
                fee: poolfee,                       // pool fee of the two tokens.
                tickLower: TickMath.MIN_TICK,       // Defines the lower price range in which we provided liquidity. Here its been set to MIN_TICK and MAX_Tick as liquidity is distributed to entire range of pool.
                tickUpper: TickMath.MAX_TICK,       // Defines the upper price range in which we provided liquidity.
                amount0Desired: amount0ToMint,      // Token0 to Mint
                amount1Desired: amount1ToMint,      // Token1 to Mint
                amount0Min: 0,                      // Min Token0 required to Mint.
                amount1Min: 0,                      // Min Token1 required to Mint.
                recipient: address(this),           // Address to receive the Mint tokens.
                deadline: block.timestamp           // Till when the mint should complete.
            });
        
         // Keep in mind that the pool defined by DAI/USDC and fee tier 0.3% must already be created and initialized in order to mint.
         // Mint function created an new NFT. Every time liquidity is added a new NFT is minted and given to the Owner.
         // The Mint function returns the newly created NFT's token ID.
        (tokenID, liquidity, amount0, amount1) = nonfungiblePositionManager.mint(params);
         

        //Now we add the information about the new added liquidity in the deposit mapping we created.
        _createDeposit(msg.sender, tokenID);

        //Once mint is completed we remove the allowace of NonfungiblePositionManger Contract.
        //We return the leftover tokens to the user.
        if(amount0 < amount0ToMint)
        {
            TransferHelper.safeApprove(DAI, address(nonfungiblePositionManager), 0);
            uint256 Refund0 = amount0ToMint - amount0; 
            // Before calling the mint function the user has to transfer the amount of token to add. 
            // But if some of those tokens are left and not added to the pool they will br returned to the user. More clarification on this. 
            TransferHelper.safeTransfer(DAI, msg.sender, Refund0);
        }

        if (amount1 < amount1ToMint) 
        {
            TransferHelper.safeApprove(USDC, address(nonfungiblePositionManager), 0);
            uint256 refund1 = amount1ToMint - amount1;
            TransferHelper.safeTransfer(USDC, msg.sender, refund1);
        }
    }

    //Function to collect the reward for providing funds into the liquidity pool.
    //NFT claimed by the user during providing liquidity is burned or taken from the users account.
    //The contract must have the NFT before the user can collect the reward.
    function CollectAllfee(uint256 TokenID) external returns (uint256 amount0, uint256 amount1)
    {
        //SafeTransferFrom is called: Callers NFT must be transferred to this address.
        nonfungiblePositionManager.safeTransferFrom(msg.sender, address(this),TokenID);

        //populate the collectfee params from the Position Manager.
        INonfungiblePositionManager.CollectParams memory params =
            INonfungiblePositionManager.CollectParams({
                tokenId: TokenID,                   //Token ID of the NFT
                recipient: address(this),              //Address who will receive the rewards.
                amount0Max: type(uint128).max,      //Maximum amount of Token0 to collect. Set to Max so that we collect all the fees.
                amount1Max: type(uint128).max       //Maximum amount of Token1 to collect. Set to Max so that we collect all the fees.
            });

        // Call the collectfee function from Position Manager and pass the Params.
        (amount0, amount1) = nonfungiblePositionManager.collect(params);

        // Send the fees back to the owner.
         _sendToOwner(TokenID, amount0, amount1);
    }

    function _sendToOwner(uint256 tokenId, uint256 amount0, uint256 amount1) internal 
    {
        // get owner of contract.
        address owner = deposit[tokenId].owner;
        address token0 = deposit[tokenId].token0;
        address token1 = deposit[tokenId].token1;
        // send collected fees to owner.
        TransferHelper.safeTransfer(token0, owner, amount0);
        TransferHelper.safeTransfer(token1, owner, amount1);
    }
       // When the owner collects the Reward the NFT is transferred to the contract address. This function returns it back.
    function retrieveNFT(uint256 tokenId) external 
    {
        // must be the owner of the NFT.
        require(msg.sender == deposit[tokenId].owner, 'Not the owner');
        // transfer ownership to original owner.
        nonfungiblePositionManager.safeTransferFrom(address(this), msg.sender, tokenId);
        //remove information related to tokenId.
        delete deposit[tokenId];
    }
}

I am getting the following error when I compile the contract.

Stack too deep when compiling inline assembly: Variable headStart is 1 slot(s) too deep inside the stack.

Can anyone suggest any possible solutions? Thank You!!!

1
  • When I compile the contract no error gets thrown. Can you elaborate further which function is the problem?
    – Nal Luksic
    May 30, 2023 at 13:32

2 Answers 2

0

This problem usually arises, when you use more than 16 variables in a function at once. To fix the stack too-deep error, you should delete your variables after you do not need them this way:

function example() {
  uint var1;
  {
    (uint varA, uint varB) = getVars();
    var1 = varA + varB;
  }
...//varA and varB get deleted here
}

This way var1 will be cleared from the stack after the section is finished.

2
  • Thanks, Nal, for the explanation, I understand now why I am getting the error. Can you suggest where in the code I can do this. May 30, 2023 at 15:11
  • The compiler should point out where the problem is located at, but I would assume mintNewPosition is the problematic one.
    – Nal Luksic
    May 31, 2023 at 7:14
0

this article may help. https://medium.com/@sandnc.eth/uniswap-v3-stack-too-deep-error-message-d268dc8b57db NonfungiblePositionManager needs an other external function position1, which returns less values

in INonfungiblePositionManager.sol add :

function positions1(uint256 tokenId)
        external
        view
        returns (
            address token0,                
            address token1,
            uint128 liquidity
        );

in createDeposit :

(address token0, address token1, uint128 liquidity ) =
            nonfungiblePositionManager.positions1(positionId);

//"position1" instead of "positions"

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.