Reputation: 1
I've been trying to mint new position with Hardhat and a fork of Arbitrum Mainnet but when NonfungiblePositionManager.mint() is called, the transaction is reverted with:
Error: Transaction reverted without a reason string
I'm probably doing something wrong as it is the first time I use the fork features and I work with Uniswap V3 environments.
I thought the error was maybe with WETH, so I tried with a USDC/DAI pool but I still got the same error.
I've taken the example contrat from the Uniswap V3 official docs
Here's my smart contract
contract Ranger is IERC721Receiver {
address public constant WETH = 0x82aF49447D8a07e3bd95BD0d56f35241523fBab1;
address public constant USDC = 0xaf88d065e77c8cC2239327C5EDb3A432268e5831;
uint24 public constant poolFee = 500;
INonfungiblePositionManager public constant nonfungiblePositionManager =
INonfungiblePositionManager(0xC36442b4a4522E871399CD717aBDD847Ab11FE88);
struct Deposit {
address owner;
uint128 liquidity;
address token0;
address token1;
}
mapping(uint256 => Deposit) public deposits;
function onERC721Received(
address operator,
address,
uint256 tokenId,
bytes calldata
) external override returns (bytes4) {
_createDeposit(operator, tokenId);
return this.onERC721Received.selector;
}
function _createDeposit(address owner, uint256 tokenId) internal {
(
,
,
address token0,
address token1,
,
,
,
uint128 liquidity,
,
,
,
) = nonfungiblePositionManager.positions(tokenId);
deposits[tokenId] = Deposit({
owner: owner,
liquidity: liquidity,
token0: token0,
token1: token1
});
console.log("Token ID: ", tokenId);
console.log("Liquidity: ", liquidity);
}
function mintNewPosition(
uint256 amount0ToMint,
uint256 amount1ToMint
)
external
returns (
uint256 tokenId,
uint128 liquidity,
uint256 amount0,
uint256 amount1
)
{
TransferHelper.safeApprove(
USDC,
address(nonfungiblePositionManager),
amount0ToMint
);
TransferHelper.safeApprove(
WETH,
address(nonfungiblePositionManager),
amount1ToMint
);
INonfungiblePositionManager.MintParams
memory params = INonfungiblePositionManager.MintParams({
token0: USDC,
token1: WETH,
fee: poolFee,
tickLower: TickMath.MIN_TICK,
tickUpper: TickMath.MAX_TICK,
amount0Desired: amount0ToMint,
amount1Desired: amount1ToMint,
amount0Min: 0,
amount1Min: 0,
recipient: address(this),
deadline: block.timestamp
});
(tokenId, liquidity, amount0, amount1) = nonfungiblePositionManager
.mint(params);
_createDeposit(msg.sender, tokenId);
if (amount1 < amount1ToMint) {
TransferHelper.safeApprove(
USDC,
address(nonfungiblePositionManager),
0
);
uint256 refund1 = amount1ToMint - amount1;
TransferHelper.safeTransfer(USDC, msg.sender, refund1);
}
if (amount0 < amount0ToMint) {
TransferHelper.safeApprove(
WETH,
address(nonfungiblePositionManager),
0
);
uint256 refund0 = amount0ToMint - amount0;
TransferHelper.safeTransfer(WETH, msg.sender, refund0);
}
}
}
Here's my Hardhat test file
const WETH = "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1";
const USDC = "0xaf88d065e77c8cC2239327C5EDb3A432268e5831";
const FEE = 500;
const WHALE = "0x090ee598777CaDDAd04Df4271B167F38E73a9Bf0";
const AMOUNT0 = 10000n;
const AMOUNT1 = 10n;
describe("Ranger", async () => {
let contract: Contract;
let accounts: HardhatEthersSigner[];
let weth: IERC20;
let usdc: IERC20;
// let dai: IERC20;
before(async () => {
await deployments.fixture(["all"]);
accounts = await ethers.getSigners();
const tmp = await deployments.get("Ranger");
contract = await ethers.getContractAt(tmp.abi, tmp.address);
usdc = await ethers.getContractAt("IERC20", USDC);
weth = await ethers.getContractAt("IERC20", WETH);
await network.provider.request({
method: "hardhat_impersonateAccount",
params: [WHALE],
});
const whale = await ethers.getSigner(WHALE);
const token0amount = AMOUNT0 * 10n ** 6n;
const token1amount = AMOUNT1 * 10n ** 18n;
expect(await usdc.balanceOf(whale.address)).to.gte(token0amount);
expect(await weth.balanceOf(whale.address)).to.gte(token1amount);
await usdc.connect(whale).transfer(accounts[0].address, token0amount);
await weth.connect(whale).transfer(accounts[0].address, token1amount);
});
it("Pool with given parameters exist", async () => {
const factory = await ethers.getContractAt(
"IUniswapV3Factory",
"0x1F98431c8aD98523631AE4a59f267346ea31F984"
);
const pool = await factory.getPool(USDC, WETH, FEE);
expect(pool).to.not.equal("0x0000000000000000000000000000000000000000");
});
it("Mint new position", async () => {
const token0amount = AMOUNT0 * 10n ** 6n;
const token1amount = AMOUNT1 * 10n ** 18n;
const contractAddress = await contract.getAddress();
await usdc.connect(accounts[0]).transfer(contractAddress, token0amount);
await weth.connect(accounts[0]).transfer(contractAddress, token1amount);
expect(await usdc.balanceOf(contractAddress)).to.gte(token0amount);
expect(await weth.balanceOf(contractAddress)).to.gte(token1amount);
await contract.mintNewPosition(token0amount, token1amount);
});
});
Here's my hardhat test output:
Ranger
✔ Pool with given parameters exist (578ms)
1) Mint new position
1 passing (7s)
1 failing
1) Ranger
Mint new position:
Error: Transaction reverted without a reason string
at <UnrecognizedContract>.<unknown> (0xc36442b4a4522e871399cd717abdd847ab11fe88)
at Ranger.mintNewPosition (contracts/Ranger.sol:132)
at EdrProviderWrapper.request (node_modules/hardhat/src/internal/hardhat-network/provider/provider.ts:429:41)
at async HardhatEthersSigner.sendTransaction (node_modules/@nomicfoundation/hardhat-ethers/src/signers.ts:125:18)
at async send (node_modules/ethers/src.ts/contract/contract.ts:313:20)
at async Proxy.mintNewPosition (node_modules/ethers/src.ts/contract/contract.ts:352:16)
at async Context.<anonymous> (test/ranger.test.ts:84:5)
Thank you for your help and your time, if you need anything else let me know.
Kind regards, Julian
Upvotes: 0
Views: 671
Reputation: 1
For anyone using the same documentation code example here
You have to change TickMath.MIN_TICK and TickMath.MAX_TICK to ticks with the good tickSpacing
For example here the pool I used has 500 for fee value, which mean 10 tick spacing but MAX_TICK is equal to 887272 you could do
tickUpper: MAX_TICK - MAX_TICK % tickSpacing
You can get tick spacing by calling feeAmountTickSpacing in UniswapV3Factory contract or by calling tickSpacing of any pool contract
INonfungiblePositionManager.MintParams
memory params = INonfungiblePositionManager.MintParams({
token0: USDC,
token1: WETH,
fee: poolFee,
tickLower: TickMath.MIN_TICK,
tickUpper: TickMath.MAX_TICK,
amount0Desired: amount0ToMint,
amount1Desired: amount1ToMint,
amount0Min: 0,
amount1Min: 0,
recipient: address(this),
deadline: block.timestamp
});
Upvotes: 0
Reputation: 49551
your code and logic seems correct. However, the error "Transaction reverted without a reason string" typically means that a transaction has failed, but the revert operation did not include a specific error message. in simple terms, solidity evm does not figure out what is going on.
i checked those addresses:
const WETH = "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1";
const USDC = "0xaf88d065e77c8cC2239327C5EDb3A432268e5831";
INonfungiblePositionManager(0xC36442b4a4522E871399CD717aBDD847Ab11FE88);
they all are valid addresses on Goerli TestNet
. but whale account is not
const WHALE = "0x090ee598777CaDDAd04Df4271B167F38E73a9Bf0";
In your test setup, you are impersonating a whale account:
await network.provider.request({
method: "hardhat_impersonateAccount",
params: [WHALE],
});
according to your logic, whale
account must hold a significant amount of both USDC
and WETH
, but it does not even exist:
Upvotes: 0