0

I am trying to interact with Uniswap V3 contracts. More specifically, I am trying to add liquidity to DAI - USDC pool. I am doing this locally by forking the Mainnet using ganache. This is my contract -

// 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 LiquidityExamples is IERC721Receiver {
    address public constant DAI = 0x6B175474E89094C44Da98b954EedeAC495271d0F;
    address public constant USDC = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48;

    uint24 public constant poolFee = 100;

    INonfungiblePositionManager public immutable nonfungiblePositionManager;

    /// @notice Represents the deposit of an NFT
    struct Deposit {
        address owner;
        uint128 liquidity;
        address token0;
        address token1;
    }

    /// @dev deposits[tokenId] => Deposit
    mapping(uint256 => Deposit) public deposits;

    constructor(INonfungiblePositionManager _nonfungiblePositionManager) {
        nonfungiblePositionManager = _nonfungiblePositionManager;
    }

    // Implementing `onERC721Received` so this contract can receive custody of erc721 tokens
    function onERC721Received(
        address operator,
        address,
        uint256 tokenId,
        bytes calldata
    ) external override returns (bytes4) {
        // get position information

        _createDeposit(operator, tokenId);

        return this.onERC721Received.selector;
    }

    function _createDeposit(address owner, uint256 tokenId) internal {
        (
            ,
            ,
            address token0,
            address token1,
            ,
            ,
            ,
            uint128 liquidity,
            ,
            ,
            ,

        ) = nonfungiblePositionManager.positions(tokenId);

        // set the owner and data for position
        // operator is msg.sender
        deposits[tokenId] = Deposit({
            owner: owner,
            liquidity: liquidity,
            token0: token0,
            token1: token1
        });
    }

    /// @notice Calls the mint function defined in periphery, mints the same amount of each token.
    /// For this example we are providing 1000 DAI and 1000 USDC in liquidity
    /// @return tokenId The id of the newly minted ERC721
    /// @return liquidity The amount of liquidity for the position
    /// @return amount0 The amount of token0
    /// @return amount1 The amount of token1
    function mintNewPosition()
        external
        returns (
            uint256 tokenId,
            uint128 liquidity,
            uint256 amount0,
            uint256 amount1
        )
    {
        // For this example, we will provide equal amounts of liquidity in both assets.
        // Providing liquidity in both assets means liquidity will be earning fees and is considered in-range.
        uint256 amount0ToMint = 1000;
        uint256 amount1ToMint = 1000;

        // transfer tokens to contract
        TransferHelper.safeTransferFrom(
            DAI,
            msg.sender,
            address(this),
            amount0ToMint
        );
        TransferHelper.safeTransferFrom(
            USDC,
            msg.sender,
            address(this),
            amount1ToMint
        );

        // Approve the position manager
        TransferHelper.safeApprove(
            DAI,
            address(nonfungiblePositionManager),
            amount0ToMint
        );
        TransferHelper.safeApprove(
            USDC,
            address(nonfungiblePositionManager),
            amount1ToMint
        );

        INonfungiblePositionManager.MintParams
            memory params = INonfungiblePositionManager.MintParams({
                token0: DAI,
                token1: USDC,
                fee: poolFee,
                tickLower: TickMath.MIN_TICK,
                tickUpper: TickMath.MAX_TICK,
                amount0Desired: amount0ToMint,
                amount1Desired: amount1ToMint,
                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);

        // Create a deposit
        _createDeposit(msg.sender, tokenId);

        // Remove allowance and refund in both assets.
        if (amount0 < amount0ToMint) {
            TransferHelper.safeApprove(
                DAI,
                address(nonfungiblePositionManager),
                0
            );
            uint256 refund0 = amount0ToMint - amount0;
            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);
        }
    }
}

It's identical to what's given in the docs.

But, the problem is that the function mintNewPosition gets reverted. I have written some test cases to interact with the contract -

const LiquidityExamples = artifacts.require("LiquidityExamples");
const IERC20 = artifacts.require("IERC20");

contract("LiquidityExample", accounts => {

    it("checks stablecoin balances of DAI_USDT_WHALE", async () => {
        const DAI_USDC_WHALE = "0x8639D7A9521AeDF18e5DC6a14c1c5CC1bfbE3BA0";
        const DAI = "0x6B175474E89094C44Da98b954EedeAC495271d0F";
        const USDC = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48";

        const DAI_Contract = await IERC20.at(DAI);
        const USDC_Contract = await IERC20.at(USDC);

        const DAI_Balance = (await DAI_Contract.balanceOf(DAI_USDC_WHALE)).toString();
        console.log(parseInt(DAI_Balance) / 10 ** 18);

        const USDC_Balance = (await USDC_Contract.balanceOf(DAI_USDC_WHALE)).toString();
        console.log(parseInt(USDC_Balance) / 10 ** 6);

        assert(parseInt(DAI_Balance) > 0);
        assert(parseInt(USDC_Balance) > 0);
    })

    it("checks mintNewPosition", async () => {
        const DAI_USDC_WHALE = "0x8639D7A9521AeDF18e5DC6a14c1c5CC1bfbE3BA0";
        const DAI = "0x6B175474E89094C44Da98b954EedeAC495271d0F";
        const USDC = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48";
        const nonfungiblePositionManagerAddress = "0xC36442b4a4522E871399CD717aBDD847Ab11FE88";

        const DAI_Contract = await IERC20.at(DAI);
        const USDC_Contract = await IERC20.at(USDC);

        const ethValue = "1000000000000000000";
        await web3.eth.sendTransaction({ from: accounts[0], to: DAI_USDC_WHALE, value: ethValue });

        ethBalance = await web3.eth.getBalance(DAI_USDC_WHALE);
        console.log("eth balance of DAI_USDC_WHALE after transfer from accounts[0]", ethBalance.toString());

        const DAI_Balance = (await DAI_Contract.balanceOf(DAI_USDC_WHALE)).toString();
        console.log("DAI balance of DAI_USD_WHALE", parseInt(DAI_Balance) / 10 ** 18);

        const USDC_Balance = (await USDC_Contract.balanceOf(DAI_USDC_WHALE)).toString();
        console.log("USDC balance of DAI_USD_WHALE", parseInt(USDC_Balance) / 10 ** 6);

        const liquidityExamples = await LiquidityExamples.new(nonfungiblePositionManagerAddress);
        console.log("LiquidityExample.address -", liquidityExamples.address);

        await DAI_Contract.approve(liquidityExamples.address, DAI_Balance.toString(), { from: DAI_USDC_WHALE });
        await USDC_Contract.approve(liquidityExamples.address, USDC_Balance.toString(), { from: DAI_USDC_WHALE });

        const DAI_allowance = await DAI_Contract.allowance(DAI_USDC_WHALE, liquidityExamples.address, { from: DAI_USDC_WHALE });
        const USDC_allowance = await USDC_Contract.allowance(DAI_USDC_WHALE, liquidityExamples.address, { from: DAI_USDC_WHALE });

        console.log("DAI allowance to LiquidityExamples =", DAI_allowance.toString());
        console.log("USDC allowance to LiquidityExamples =", USDC_allowance.toString());

        assert.equal(DAI_allowance.toString(), DAI_Balance);
        assert.equal(USDC_allowance.toString(), USDC_Balance);

        const returnValues = await liquidityExamples.mintNewPosition({ from: DAI_USDC_WHALE });
        console.log(returnValues);
    })
})

The error happens because of the last few lines in my test file. That is because it's calling the mintNewPosition in the second test. The stack trace shows me this -

  1) Contract: LiquidityExample
       checks mintNewPosition:
     Error: Improper return (may be an unexpected self-destruct)
      at unknown function [address 0x5777d92f208679DB4b9778590Fa3CAB3aC9e2168] (contracts/LiquidityExamples.sol:137:50)
      at unknown function [address 0x5777d92f208679DB4b9778590Fa3CAB3aC9e2168] (unknown location)
      at unknown function [address 0xC36442b4a4522E871399CD717aBDD847Ab11FE88] (unknown location)
      at LiquidityExamples.mintNewPosition (contracts/LiquidityExamples.sol:137:50)
      at LiquidityExamples [address 0xAd27cCe49f592A3e228256C1294dF0590588Fc86] (contracts/LiquidityExamples.sol:81:5)
      at Context.<anonymous> (test/LiquidityExampleTest.js:60:54)
      at processTicksAndRejections (internal/process/task_queues.js:95:5)

Also, this is how I am running the Mainnet fork by writing these commands in my shell.

export DAI_USDC_WHALE=0x8639D7A9521AeDF18e5DC6a14c1c5CC1bfbE3BA0
export INFURA_API_KEY=YOUR_INFURA_KEY

ganache-cli \
--fork https://mainnet.infura.io/v3/$INFURA_API_KEY \
--unlock $DAI_USDC_WHALE \
--networkId 999

Why is this happening? What am I doing wrong?

Thank you.

1 Answer 1

0

My aim here was to give an equal amount of both DAI and USDC tokens. So, changing the token amounts solved my problem. The token amounts have to be changed to the following:

uint256 amount0ToMint = 1000 * 10**18;
uint256 amount1ToMint = 1000 * 10**6;

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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