4

I'm creating a token which when sold on a liquidity pool, takes fees and burns a certain amount.

Given that I have a recipient address, how would I check whether it is a liquidity pool?

I think I may be able to use this: https://docs.uniswap.org/protocol/V2/reference/smart-contracts/pair-erc-20 however I'm not sure which function would work or if there's another way.

EDIT:enter image description here

Yilmaz
  • 35,338
  • 10
  • 157
  • 202
Ayudh
  • 1,673
  • 1
  • 22
  • 55

2 Answers2

4

You can test the address against the Uniswap Pair (V2) or Uniswap Pool (V3) interface, whether it returns expected values.

One step further, you can pass these returned values back to the Uniswap Factory contract (address can be found in V2 docs and V3 docs), which tells you a pool address based on the input values. This way you can be certain that the queried address is in fact a Uniswap Pool, and not just some other contract returning values from same-named functions.

pragma solidity ^0.8;

import "https://github.com/Uniswap/v2-core/blob/master/contracts/interfaces/IUniswapV2Factory.sol";
import "https://github.com/Uniswap/v3-core/blob/main/contracts/interfaces/IUniswapV3Factory.sol";
import "https://github.com/Uniswap/v2-core/blob/master/contracts/interfaces/IUniswapV2Pair.sol";
import "https://github.com/Uniswap/v3-core/blob/main/contracts/interfaces/IUniswapV3Pool.sol";

contract MyContract {
    IUniswapV2Factory constant v2Factory = IUniswapV2Factory(address(0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f));
    IUniswapV3Factory constant v3Factory = IUniswapV3Factory(address(0x1F98431c8aD98523631AE4a59f267346ea31F984));

    /**
     * true on Ethereum mainnet - 0x0d4a11d5EEaaC28EC3F61d100daF4d40471f1852
     * false on Ethereum mainnet - 0xdAC17F958D2ee523a2206206994597C13D831ec7
     */
    function isUniswapV2Pair(address target) external view returns (bool) {
        if (target.code.length == 0) {
            return false;
        }

        IUniswapV2Pair pairContract = IUniswapV2Pair(target);

        address token0;
        address token1;

        try pairContract.token0() returns (address _token0) {
            token0 = _token0;
        } catch (bytes memory) {
            return false;
        }

        try pairContract.token1() returns (address _token1) {
            token1 = _token1;
        } catch (bytes memory) {
            return false;
        }

        return target == v2Factory.getPair(token0, token1);
    }

    /**
     * true on Ethereum mainnet - 0x4e68Ccd3E89f51C3074ca5072bbAC773960dFa36
     * false on Ethereum mainnet - 0xdAC17F958D2ee523a2206206994597C13D831ec7
     */
    function isUniswapV3Pool(address target) external view returns (bool) {
        if (target.code.length == 0) {
            return false;
        }

        IUniswapV3Pool poolContract = IUniswapV3Pool(target);

        address token0;
        address token1;
        uint24 fee;

        try poolContract.token0() returns (address _token0) {
            token0 = _token0;
        } catch (bytes memory) {
            return false;
        }

        try poolContract.token1() returns (address _token1) {
            token1 = _token1;
        } catch (bytes memory) {
            return false;
        }

        try poolContract.fee() returns (uint24 _fee) {
            fee = _fee;
        } catch (bytes memory) {
            return false;
        }

        return target == v3Factory.getPool(token0, token1, fee);
    }
}

Note that this snippet works only on networks where Uniswap is deployed (e.g. your local fork of the mainnet, or some of the testnets). On other networks (such as the Remix VM emulator), the Uniswap contracts are unreachable, which results in revert of the call.

Petr Hejda
  • 40,554
  • 8
  • 72
  • 100
  • Is this only for pools that are on uniswap? Or also pools that are on another DEX for example? I see that uniswap makes a distinction between Pair and Pair (ERC-20) contracts https://docs.uniswap.org/protocol/V2/reference/smart-contracts/pair . What's the difference? – Ayudh Apr 21 '22 at 06:29
  • 1
    @Ayudh This example works only with Uniswap (both V2 and V3) pools. Other DEXes usually implement the Uniswap interface but their factory contracts are deployed on a different address. So to expand this example to e.g. Sushiswap (which uses the Uniswap V2 interface), you'll need to create a new function, that practically copies the existing `isUniswapV2Pair()` - except it queries the [Sushiswap factory](https://etherscan.io/address/0xC0AEe478e3658e2610c5F7A4A2E1777cE9e4f2Ac#code) address (`0xC0AE...`) instead of the Uniswap factory (`0x5C69...`). – Petr Hejda Apr 21 '22 at 06:37
  • I see, thank you. Lastly, could you explain the difference between Pair (ERC-20) and Pair contracts? docs.uniswap.org/protocol/V2/reference/smart-contracts/pair & https://docs.uniswap.org/protocol/V2/reference/smart-contracts/pair-erc-20 – Ayudh Apr 21 '22 at 06:40
  • 1
    @Ayudh A "Pair contract" is their V2 wording for what is a Pool in V3 - a contract that holds liquidity of two tokens and allows users to swap them ... A "Pair (ERC-20)" is, in the context of Uniswap V2, a token representing liquidity in such "Pair contract". For example, you provide liquidity to a Pair contract A/B, and you are minted this ERC-20 token in exchange, representing your % stake in this total liquidity. When you decide to remove the liquidity, they burn these "Pair ERC20" tokens and send you back the original A and B tokens. – Petr Hejda Apr 21 '22 at 06:46
  • 1
    This added logic of the token, representing your liquidity stake, allows for trading the liquidity token as well. Same way as you could sell a a debt someone ows you to a third party in a regular finance. – Petr Hejda Apr 21 '22 at 06:48
  • I see. Thank you very much. I get it now! – Ayudh Apr 21 '22 at 06:49
  • The try catch is throwing an error instead of returning false. Why is that? – Ayudh Apr 21 '22 at 07:58
  • 1
    @Ayudh Most likely you've deployed the contract on a network where Uniswap is not available (e.g. in the Remix VM emulator). See my last paragraph in the answer. – Petr Hejda Apr 21 '22 at 08:58
  • I've tested it in mainnet fork too actually. I've attached an image of the error. It results after I do not call it on a liquidity pool address but a user address. I would think that it should return false in that case but it still throws? I must be missing something – Ayudh Apr 21 '22 at 09:52
  • @Ayudh Oh that seems like my mistake. I tested it only on contract addresses. I'll try to find some solution using more low-level approach. – Petr Hejda Apr 21 '22 at 10:09
  • God bless your Petr, you've been so patient and kind with your time. What a wonderful example... – Ayudh Apr 21 '22 at 10:17
  • 2
    @Ayudh So instead of refactoring the whole thing, I just added a condition checking if the target address is a contract: `if (target.code.length == 0)`. And if it is an end user address, it performs an early return. See the updated code. – Petr Hejda Apr 21 '22 at 11:20
  • That's lovely, thanks! I didn't know that we can call .code.length on an address. It works like a charm~ – Ayudh Apr 21 '22 at 12:00
2

In Uniswap V3

 import "@uniswap/v3-core/contracts/interfaces/IUniswapV3Factory.sol";

 address poolAddress = IUniswapV3Factory(_factory).getPool(
        _token0,
        _token1,
        _fee
    );

you can get the _factory address from here https://docs.uniswap.org/protocol/reference/deployments.

getPool is a mapping.

mapping(address => mapping(address => mapping(uint24 => address))) public override getPool;

when you call IUniswapV3Factory(_factory).getPool, if the key does not exist it will return the default address type which is address(0). so you should add an require condition

require(poolAddress!=address(0))

If this condition passes, that means you got a valid pool address from the mapping.

Yilmaz
  • 35,338
  • 10
  • 157
  • 202