'How do I interact with Uniswap V2 in a Truffle test suite?
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.
Solution 1:[1]
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.
Solution 2:[2]
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:
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
})})
Solution 3:[3]
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
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | Jasper |
| Solution 2 | Jasper |
| Solution 3 | justinsacco |
