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.