Skip to content
This repository has been archived by the owner on Apr 25, 2024. It is now read-only.

Uniswap JS SDK returns invalid pair address for two ERC20 tokens in hardhat network #63

Open
freebyte opened this issue Apr 29, 2021 · 3 comments

Comments

@freebyte
Copy link

I create two ERC20 tokens, deploy them with hardhat on hardhat network, then create Uniswap pair, then retrieve its address with SDK's Fetcher and with my contract. Fetcher returns different address than the one I get in my contract. If I manually substitute the address in sdk.cjs.development.js with the address from the contract, all works, otherwise Fetcher reverts.

So, here is the hardhat script:

const UniswapV2FactoryArtifact = require('@uniswap/v2-core/build/UniswapV2Factory.json');
const UniswapV2Router02Artifact = require('@uniswap/v2-periphery/build/UniswapV2Router02.json');


const moment = require('moment');

const { expect } = require("chai");
const { ethers } = require('hardhat');

// Uniswap sdk
const { Token, Fetcher } = require('@uniswap/sdk')
const hre = require('hardhat');
const chainId = hre.config.networks.hardhat.chainId;


async function testSDK(skillTokenAddress, fakeDAEAddress) {
  const SKILL = await Fetcher.fetchTokenData(chainId, skillTokenAddress,  ethers.provider);
  const FakeDAE = await Fetcher.fetchTokenData(chainId, fakeDAEAddress,  ethers.provider);
  const pair = await Fetcher.fetchPairData(FakeDAE, SKILL, ethers.provider);
  console.log(pair)
}

async function main() {

  const signers = await ethers.getSigners();
  // for (const signer of signers) {
  //   const balance = await signer.getBalance();
  //   console.log(signer.address, ethers.utils.formatUnits(balance))

  // }

  const UniswapV2Factory = await ethers.getContractFactory(
    UniswapV2FactoryArtifact.abi,
    UniswapV2FactoryArtifact.bytecode
  );

  const uniswapV2Factory = await UniswapV2Factory.deploy(signers[0].address);

  const UniswapV2Router02 = await ethers.getContractFactory(
    UniswapV2Router02Artifact.abi,
    UniswapV2Router02Artifact.bytecode
  );
  const uniswapV2Router02 = await UniswapV2Router02.deploy(uniswapV2Factory.address, signers[0].address);
  await uniswapV2Router02.deployed();
  
  const SkillToken = await ethers.getContractFactory('SkillToken');
  const skillToken = await SkillToken.deploy();
  await skillToken.deployed();
  
  const FakeDAE = await ethers.getContractFactory('FakeDAE');
  const fakeDAE = await FakeDAE.deploy();
  await fakeDAE.deployed();
  
  await fakeDAE.approve(uniswapV2Router02.address, 10000);
  await skillToken.approve(uniswapV2Router02.address, 10000);

  console.log(`Skill Address: ${skillToken.address}, FakeDAE address: ${fakeDAE.address}`)

  await expect(uniswapV2Factory.createPair(skillToken.address, fakeDAE.address))
    .to.emit(uniswapV2Factory, "PairCreated"); 

  
  await uniswapV2Router02.addLiquidity(
    skillToken.address, 
    fakeDAE.address,
    8000,
    8000,
    7999,
    7999,
    signers[0].address,
    moment().add(10, 'seconds').unix()
  );

  await uniswapV2Router02.swapTokensForExactTokens(
    1,
    2,
    [fakeDAE.address, skillToken.address],
    signers[0].address,
    moment().add(1, 'minutes').unix()
  );

  const LiqCalc = await ethers.getContractFactory('LiqCalc');
  const liqCalc = await LiqCalc.deploy(uniswapV2Factory.address);
  await liqCalc.deployed();
  const info = await liqCalc.pairInfo(skillToken.address, fakeDAE.address);
  console.log(info)
  
  await testSDK(skillToken.address, fakeDAE.address);



  
}

main()
  .then(() => process.exit(0))
  .catch(error => {
    console.error(error);
    process.exit(1);
  });

We deployed two ERC20 tokens, Skill and FakeDAE:

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/access/AccessControl.sol";

// for debug
import "hardhat/console.sol";


contract SkillToken is ERC20, AccessControl {
  uint public INITIAL_SUPPLY = 12000;
  bytes32 public constant BURNER_ROLE = keccak256("BURNER_ROLE");

  function decimals() public view override returns (uint8) {
    console.log('Skill token decimals');
    return 18;
  }

  constructor() ERC20("SkillToken", "SKILL") {
    _setupRole(DEFAULT_ADMIN_ROLE, _msgSender());
    _mint(_msgSender(), INITIAL_SUPPLY);
  }

  function burn(address from, uint256 amount) public {
    require(hasRole(BURNER_ROLE, _msgSender()), "Caller is not a burner");
    _burn(from, amount);
  }

}

and FakeDAE is analogeous.

LiqCal is a contract which reports the valid pair address:

//SPDX-License-Identifier: Unlicense

pragma solidity ^0.6.6;

import '@uniswap/v2-periphery/contracts/libraries/UniswapV2Library.sol';
import '@uniswap/v2-core/contracts/interfaces/IUniswapV2Pair.sol';

import "hardhat/console.sol";


contract LiqCalc  {
    address public factory;
    constructor(address factory_) public {
        factory = factory_;
    }

    function pairInfo(address tokenA, address tokenB) public view returns (uint reserveA, uint reserveB, uint totalSupply) {
        IUniswapV2Pair pair = IUniswapV2Pair(UniswapV2Library.pairFor(factory, tokenA, tokenB));
        console.log('LiqCal, pair addr:', address(pair));
        totalSupply = pair.totalSupply();
        (uint reserves0, uint reserves1,) = pair.getReserves();
        (reserveA, reserveB) = tokenA == pair.token0() ? (reserves0, reserves1) : (reserves1, reserves0);
    }
 
}

which is 0x21e7b0ced250469051a249252a33c7c5eb13a1f6

However, Fetcher returns invalid pair:

Fetcher.fetchPairData = function fetchPairData(tokenA, tokenB, provider) {
    console.log(`====> chainA=${tokenA.chainId}, addrA: ${tokenA.address}, chainB =${tokenB.chainId}, addrB: ${tokenB.address}`)
    try {
      console.log(`typeof provider: ${typeof provider}`)
      if (provider === undefined) provider = providers.getDefaultProvider(networks.getNetwork(tokenA.chainId));
      !(tokenA.chainId === tokenB.chainId) ? "development" !== "production" ? invariant(false, 'CHAIN_ID') : invariant(false) : void 0;
      var address = Pair.getAddress(tokenA, tokenB);
      console.log(`Pair address in Fetcher: ${address}, substituted: 0x21e7b0ced250469051a249252a33c7c5eb13a1f6`)

      address = '0x21e7b0ced250469051a249252a33c7c5eb13a1f6';
      return Promise.resolve(new contracts.Contract(address, IUniswapV2Pair.abi, provider).getReserves()).then(function (_ref) {
        var reserves0 = _ref[0],
            reserves1 = _ref[1];
        var balances = tokenA.sortsBefore(tokenB) ? [reserves0, reserves1] : [reserves1, reserves0];
        return new Pair(new TokenAmount(tokenA, balances[0]), new TokenAmount(tokenB, balances[1]));
      });
    } catch (e) {
      return Promise.reject(e);
    }
  };

from console.log:

Pair address in Fetcher: 0x7818b27560325CD29600237a5a14880b6f392Ea8, substituted: 0x21e7b0ced250469051a249252a33c7c5eb13a1f6

When I don't not substitute inside Fetcher my pair, getReserves() reverts obviously, because no such pair exists.

The entire log from the run with substituted pair address (all works):

npx hardhat run scripts/uniswap-script.js 
Skill Address: 0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0, FakeDAE address: 0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9
LiqCal, pair addr: 0x21e7b0ced250469051a249252a33c7c5eb13a1f6
[
  BigNumber { _hex: '0x1f3f', _isBigNumber: true },
  BigNumber { _hex: '0x1f42', _isBigNumber: true },
  BigNumber { _hex: '0x1f40', _isBigNumber: true },
  reserveA: BigNumber { _hex: '0x1f3f', _isBigNumber: true },
  reserveB: BigNumber { _hex: '0x1f42', _isBigNumber: true },
  totalSupply: BigNumber { _hex: '0x1f40', _isBigNumber: true }
]
Skill token decimals
Fake token decmals
====> chainA=31337, addrA: 0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9, chainB =31337, addrB: 0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0
typeof provider: object
Calling Pair get address for A: 0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9, B: 0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0
Pair address in Fetcher: 0x7818b27560325CD29600237a5a14880b6f392Ea8, substituted: 0x21e7b0ced250469051a249252a33c7c5eb13a1f6
Calling Pair get address for A: 0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0, B: 0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9
Pair {
  liquidityToken: Token {
    decimals: 18,
    symbol: 'UNI-V2',
    name: 'Uniswap V2',
    chainId: 31337,
    address: '0x7818b27560325CD29600237a5a14880b6f392Ea8'
  },
  tokenAmounts: [
    TokenAmount {
      numerator: [JSBI],
      denominator: [JSBI],
      currency: [Token],
      token: [Token]
    },
    TokenAmount {
      numerator: [JSBI],
      denominator: [JSBI],
      currency: [Token],
      token: [Token]
    }
  ]
}

What am I doing incorrect?

@freebyte
Copy link
Author

Seems like Pair.getAddress does something wrong.

@weixin3046
Copy link

我也遇到了相同的问题,如何解决它

@Florian-S-A-W
Copy link

Hi @weixin3046 @freebyte ,
The result of the create2 function depends on the deploy address of the factory, the token addresses and the init_code_hash.
The deploy address of the factory depends on the wallet address that deploys it, the nonce, and the bytecode of the contract. In your case you are getting the factory address from the Factory contract you deployed, the SDK has the correct Factory address for mainnet hardcoded in constants.
You can read more about that here.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants