1

I am looking for a way to create an automated test suite with Truffle that can test my smart contract's interactions with Uniswap V2. The Uniswap docs briefly mention testing with Truffle but do not provide any examples. I am looking to test it using a mainnet fork with ganache.

I'm guessing it's a similar process to the accepted answer for this question, but I'm specifically looking for a way to do it using Truffle and web3.js.

As an example, if I were testing the following contract:

pragma solidity ^0.6.6;

interface IUniswap {
  function swapExactETHForTokens(
    uint amountOutMin,
    address[] calldata path,
    address to,
    uint deadline)
  external
  payable
  returns (uint[] memory amounts);
  function WETH() external pure returns (address);
}

contract MyContract {
  IUniswap uniswap;

  constructor(address _uniswap) public {
    uniswap = IUniswap(_uniswap);
  }

  function swapExactETHForTokens(uint amountOutMin, address token) external payable {
    address[] memory path = new address[](2);
    path[0] = uniswap.WETH();
    path[1] = token;
    uniswap.swapExactETHForTokens{value: msg.value}(
      amountOutMin, 
      path,
      msg.sender, 
      now
    );
  }
}

How would I create a unit test to verify that swapExactETHForTokens() swaps ETH for say, DAI? For the value of _uniswap I've been using UniswapV2Router02.

Any help would be greatly appreciated.

Jasperan
  • 2,154
  • 1
  • 16
  • 40
  • What is the contract address for `_uniswap`. which network is it deployed to? – Yilmaz Feb 06 '22 at 02:27
  • @Yilmaz I'm using UniswapV2Router02 which I believe is deployed to all networks. I've updated my question with a link to it. – Jasperan Feb 06 '22 at 06:13
  • Wthat is the token address here `function swapExactETHForTokens(uint amountOutMin, address token) ` – Yilmaz Feb 06 '22 at 10:13
  • I used this but throwing error: "0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11". are u interactign with v2 or v3 – Yilmaz Feb 06 '22 at 10:39
  • The token argument is the address of the smart contract for the ERC20 token, so for example I used `0xad6d458402f60fd3bd25163575031acdce07538d` when testing on Ropsten. You can change the example code if you want, I'm more interested in setting up a test environment using a mainnet fork in truffle. – Jasperan Feb 06 '22 at 18:27

3 Answers3

2

I ended up using Hardhat/Ethers.js anyway, just because of how easy it is to set up a mainnet fork and run an automated test suite. I provided an answer here explaining the steps required to set up a mainnet fork, and reused the example contract in this question (complete with a test).

To answer this question specifically though, Hardhat has a plugin that supports testing with Truffle/Web3js, so this way you can still use Truffle/Web3js for writing your tests/contracts, but use Hardhat's mainnet fork feature for interacting with other contracts already deployed on the mainnet.

Jasperan
  • 2,154
  • 1
  • 16
  • 40
1

If you use Uniswap platform to swap a token, you are going to have 2 steps. You are going to approve the token, in this step metamask will pop-up and you are going to confirm it. Then Uniswap will do the actual swap, it takes the tokens out of your wallet and does the exchange for you.

This is the swapExactETHForTokens function

function swapExactETHForTokens(uint amountOutMin, address[] calldata path, address to, uint deadline)
        external
        virtual
        override
        payable
        ensure(deadline)
        returns (uint[] memory amounts)
    {
        require(path[0] == WETH, 'UniswapV2Router: INVALID_PATH');
        amounts = UniswapV2Library.getAmountsOut(factory, msg.value, path);
        require(amounts[amounts.length - 1] >= amountOutMin, 'UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT');
        IWETH(WETH).deposit{value: amounts[0]}();
        assert(IWETH(WETH).transfer(UniswapV2Library.pairFor(factory, path[0], path[1]), amounts[0]));
        _swap(amounts, path, to);
    }

last function _swap calls the swap function:

From documentation

It is also important to ensure that your contract controls enough ETH/tokens to make the swap, and has granted approval to the router to withdraw this many tokens.

Imagine you want to swap 50 DAI for as much ETH as possible from your smart contract.

transferFrom

Before swapping, our smart contracts needs to be in control of 50 DAI. The easiest way to accomplish this is by calling transferFrom on DAI with the owner set to msg.sender:

uint amountIn = 50 * 10 ** DAI.decimals();
require(DAI.transferFrom(msg.sender, address(this), amountIn), 'transferFrom failed.');

Eventually Uniswap will transferFrom, but before your token has to approve the transaction, it has to add uniswap address to its allowance mapping.

mapping(address=>mapping(address=>uint)) public allowance;
// token address is allowign uniswap address for this much token

You cannot test the current implementation of your contract unless you have a swap token set and your swap token has to call approve.

If you had front end app, when you call your contract's swap function, metamask would pop up and you would confirm it. However in a test environment you need the actual ERC20 contract, you deploy it and you call the approve. In front end you would have two functions swapToken and approve. You would call them in this order?

const startSwap = async () => {
    await approve()
    await swapToken()
}

In test suite:

const MyContract = artifacts.require("MyContract");
const Dai = artifacts.require("Dai");

// ganache provides an array of accounts
contract("Uniswap", (ganachProvidedAccounts) => { 
  let myContract,dai;
  // intialize the contracts before each test
  before(async () => {
    myContract = await myContract.new();
    dai = await Dai.new();
  })

  describe("Swapping", async () => {
    it("swap tokens", async () => {
      let result;
     
      // first ask for approval of 100 token transfer
      await dai.approve(myContract.address, tokens("100"), {
        from:ganachProvidedAccounts[0] ,
      });
      // // check staking for customer
      await myContract.swapExactETHForTokens("100"), { from: ganachProvidedAccounts[0] });
      // make your assetion
})})
Jasperan
  • 2,154
  • 1
  • 16
  • 40
Yilmaz
  • 35,338
  • 10
  • 157
  • 202
  • Hi @Yilmaz, thank you for the answer, but it's not exactly what I'm looking for. I just added the smart contract as an example to test, I'm more looking for exact steps on how to set up the test environment in Truffle so I can use a mainnet fork for my unit tests. – Jasperan Feb 07 '22 at 17:29
  • And I thought for `swapExactETHForTokens()` I didn't have to call `approve`, since it uses the ETH I've sent it when I call this function. I used it to successfully swap ETH for DAI on Ropsten so I'm not sure why it can't be tested, given I have the right testing environment set up? – Jasperan Feb 07 '22 at 17:37
  • @Jasper when you test it , metamask pops up and you confirm the transaction, right? – Yilmaz Feb 07 '22 at 17:54
  • https://help.uniswap.org/en/articles/5392390-what-is-an-approve – Yilmaz Feb 07 '22 at 17:59
  • I do get a metamask confirmation yes. I get now why `approve` is needed. Ignoring the smart contract though, how do I set up a test environment so I can interact with Uniswap in my test suite? That's mainly what I'm looking for. – Jasperan Feb 07 '22 at 18:09
  • @Jasper updated – Yilmaz Feb 07 '22 at 18:19
  • 1
    Thank you @Yilmaz, I've awarded the bounty. I think I'll end up using hardhat anyway but your answer is still useful – Jasperan Feb 12 '22 at 00:21
0

Could you have tried configuring a mainnet port in you truffle-config.json file and running something like

INFURA_API_KEY=A45..
MY_ACCOUNT=0x...
ganache-cli --fork https://ropsten.infura.io/v3/$INFURA_API_KEY \ --unlock $MY_ACCOUNT \ --networkId 999

Setting port 999 to ropsten_fork in your truffle config file andnd then in a separate console run

truffle console --network ropsten_fork
travelerrrrrrr
  • 193
  • 3
  • 14