Uniswap / v3-periphery

πŸ¦„ πŸ¦„ πŸ¦„ Peripheral smart contracts for interacting with Uniswap v3
https://uniswap.org
GNU General Public License v2.0
1.15k stars 1.06k forks source link

NonfungiblePositionManager.createAndInitializePoolIfNecessary throws EvmError: Revert #386

Open Nlferu opened 1 month ago

Nlferu commented 1 month ago

Hello,

I have just encountered some very strange bug/issue? While playing with NonfungiblePositionManager contract. I have wrote solidity code that creates new ERC20 contract and then calling function dexCoin() from another my contract that takes this newly created ERC20 contract address and calls below:

INonfungiblePositionManager(i_nftPositionManager).createAndInitializePoolIfNecessary(coinAddress, i_wrappedNativeToken, FEE, INITIAL_PRICE);

I have then wrote some simple test using foundry to check if everything works and indeed it was until I ran same code on forked Ethereum Mainnet. So the problem occurs on every single forked mainnet I tested (Ethereum Mainnet, Polygon, Avalanche), there is no issue on testnets like Sepolia.

This is contract code I'm using to start calls chain:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import {Coin} from "./Coin.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {ICoinDexer} from "./Interfaces/ICoinDexer.sol";
import {ICoinMinter} from "./Interfaces/ICoinMinter.sol";

contract CoinMinter is ICoinMinter, Ownable {
    /// @dev Constructor
    /// @notice Owner of this contract is DFM contract
    constructor() Ownable(msg.sender) {}

    /////////////////////// @notice MCM External Functions (Callable only by DFM contract) ///////////////////////

    /// @notice Deploys new ERC20  Token
    /// @param params ICoinMinter
    function mintCoinAndRequestDex(MintParams calldata params) external onlyOwner {
        /// @dev BUG HERE:
        Coin newCoin = new Coin(params);

        emit CoinMinted(address(newCoin), params.name, params.symbol);

        ICoinDexer(params.mcd).dex(address(newCoin), params.totalFunds, params.totalCoins);

        emit ICoinDexer.DexRequestReceived(address(newCoin));
    }
}

If i run my test on forked mainnet I'm getting those logs:

emit DexRequestReceived(token: Coin: [0xc3B785939FE923f3EA338Ae6793C99de2671b2cB])
    β”‚   β”‚   β”‚   β”œβ”€ [23974] WETH9::deposit{value: 11000000000000000000}()
    β”‚   β”‚   β”‚   β”‚   β”œβ”€ emit Deposit(dst: CoinDexer: [0xaBC27F4fa9dae4829575A3DF7958b9d80872c8a8], wad: 11000000000000000000 [1.1e19])
    β”‚   β”‚   β”‚   β”‚   └─ ← [Stop] 
    β”‚   β”‚   β”‚   β”œβ”€ [534] WETH9::balanceOf(CoinDexer: [0xaBC27F4fa9dae4829575A3DF7958b9d80872c8a8]) [staticcall]
    β”‚   β”‚   β”‚   β”‚   └─ ← [Return] 11000000000000000000 [1.1e19]
    β”‚   β”‚   β”‚   β”œβ”€ emit Swapped_ETH_For_WETH(amount: 11000000000000000000 [1.1e19])
    β”‚   β”‚   β”‚   β”œβ”€ [674] NonfungiblePositionManager::createAndInitializePoolIfNecessary(Coin: [0xc3B785939FE923f3EA338Ae6793C99de2671b2cB], WETH9: [0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2], 3000, 7922816251426433759354395 [7.922e24])
    β”‚   β”‚   β”‚   β”‚   └─ ← [Revert] EvmError: Revert

But if i do this in contract above:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import {Coin} from "./Coin.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {ICoinDexer} from "./Interfaces/ICoinDexer.sol";
import {ICoinMinter} from "./Interfaces/ICoinMinter.sol";

contract CoinMinter is ICoinMinter, Ownable {
    /// @dev Constructor
    /// @notice Owner of this contract is DFM contract
    constructor() Ownable(msg.sender) {}

    /////////////////////// @notice MCM External Functions (Callable only by DFM contract) ///////////////////////

    /// @notice Deploys new ERC20  Token
    /// @param params ICoinMinter
    function mintCoinAndRequestDex(MintParams calldata params) external onlyOwner {
        /// @dev BUG HERE:
        new Coin(params); /// @dev NOTCICE I JUST ADDED THIS LINE
        Coin newCoin = new Coin(params);

        emit CoinMinted(address(newCoin), params.name, params.symbol);

        ICoinDexer(params.mcd).dex(address(newCoin), params.totalFunds, params.totalCoins);

        emit ICoinDexer.DexRequestReceived(address(newCoin));
    }
}

so i just added this line new Coin(params); and i run same test without changing anything else in code it works perfectly fine:

emit DexRequestReceived(token: Coin: [0x2758Cea4179785155297c7C795B881D8677Dd18B])
    β”‚   β”‚   β”‚   β”œβ”€ [23974] WETH9::deposit{value: 11000000000000000000}()
    β”‚   β”‚   β”‚   β”‚   β”œβ”€ emit Deposit(dst: CoinDexer: [0xaBC27F4fa9dae4829575A3DF7958b9d80872c8a8], wad: 11000000000000000000 [1.1e19])
    β”‚   β”‚   β”‚   β”‚   └─ ← [Stop] 
    β”‚   β”‚   β”‚   β”œβ”€ [534] WETH9::balanceOf(CoinDexer: [0xaBC27F4fa9dae4829575A3DF7958b9d80872c8a8]) [staticcall]
    β”‚   β”‚   β”‚   β”‚   └─ ← [Return] 11000000000000000000 [1.1e19]
    β”‚   β”‚   β”‚   β”œβ”€ emit Swapped_ETH_For_WETH(amount: 11000000000000000000 [1.1e19])
    β”‚   β”‚   β”‚   β”œβ”€ [4649797] NonfungiblePositionManager::createAndInitializePoolIfNecessary(Coin: [0x2758Cea4179785155297c7C795B881D8677Dd18B], WETH9: [0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2], 3000, 7922816251426433759354395 [7.922e24])
    β”‚   β”‚   β”‚   β”‚   β”œβ”€ [2666] UniswapV3Factory::getPool(Coin: [0x2758Cea4179785155297c7C795B881D8677Dd18B], WETH9: [0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2], 3000) [staticcall]
    β”‚   β”‚   β”‚   β”‚   β”‚   └─ ← [Return] 0x0000000000000000000000000000000000000000
    β”‚   β”‚   β”‚   β”‚   β”œβ”€ [4593296] UniswapV3Factory::createPool(Coin: [0x2758Cea4179785155297c7C795B881D8677Dd18B], WETH9: [0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2], 3000)
    β”‚   β”‚   β”‚   β”‚   β”‚   β”œβ”€ [4435593] β†’ new <unknown>@0xecC6B9DA20A905e5e37bd6f10D8911EA8659f05d
    β”‚   β”‚   β”‚   β”‚   β”‚   β”‚   β”œβ”€ [734] UniswapV3Factory::parameters() [staticcall]
    β”‚   β”‚   β”‚   β”‚   β”‚   β”‚   β”‚   └─ ← [Return] UniswapV3Factory: [0x1F98431c8aD98523631AE4a59f267346ea31F984], Coin: [0x2758Cea4179785155297c7C795B881D8677Dd18B], WETH9: [0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2], 3000, 60
    β”‚   β”‚   β”‚   β”‚   β”‚   β”‚   └─ ← [Return] 22142 bytes of code
    β”‚   β”‚   β”‚   β”‚   β”‚   β”œβ”€ emit PoolCreated(token0: Coin: [0x2758Cea4179785155297c7C795B881D8677Dd18B], token1: WETH9: [0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2], fee: 3000, tickSpacing: 60, pool: 0xecC6B9DA20A905e5e37bd6f10D8911EA8659f05d)
    β”‚   β”‚   β”‚   β”‚   β”‚   └─ ← [Return] 0xecC6B9DA20A905e5e37bd6f10D8911EA8659f05d

So to sum up the only thing that changed is that ERC20 contract address. I have noticed that createAndInitializePoolIfNecessary has problem with some certain addresses and only while testing on mainnets. I have even reproduced this error using different private key and creating additional unnecessary coin as above but it didnt solve issue, so i guess address generated was incorrect for some reason. Solution I showed works only for some private keys and if it pass on ethereum mainnet it will also pass on any other mainnet and testnet. Am I missing some additional requirements to create and initialize pool? Maybe it is just foundry bug and it is creating some corrupted addresses (I highly doubt)? Or it just needs some specific ERC20 address format.

If anyone has any idea how this can be fixed or can explain to me where is the problem I will be very grateful!