我如何在 Truffle 测试套件中与 Uniswap V2 交互?

How do I interact with Uniswap V2 in a Truffle test suite?

我正在寻找一种使用 Truffle 创建自动化测试套件的方法,它可以测试我的智能合约与 Uniswap V2 的交互。 The Uniswap docs 简要提及使用 Truffle 进行测试但未提供任何示例。我希望使用带有 ganache 的主网分支对其进行测试。

我猜这与 的已接受答案的过程类似,但我正在专门寻找一种使用 Truffle 和 web3.js.

的方法

例如,如果我正在测试以下合约:

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
    );
  }
}

我将如何创建一个单元测试来验证 swapExactETHForTokens() 将 ETH 换成 DAI? For the value of _uniswap I've been using UniswapV2Router02

如有任何帮助,我们将不胜感激。

如果您使用 Uniswap 平台兑换代币,您将有 2 个步骤。您将批准令牌,在此步骤中,metamask 将 pop-up 并且您将确认它。然后 Uniswap 将进行实际的交换,它会从你的钱包中取出代币并为你进行交换。

这是swapExactETHForTokens函数

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);
    }

最后一个函数 _swap 调用了 swap 函数:

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.');

最终 Uniswap 将 transferFrom,但在您的代币必须批准交易之前,它必须将 uniswap address 添加到其 allowance 映射中。

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

除非您设置了交换令牌并且您的交换令牌必须调用 approve.

,否则您无法测试合同的当前实施

如果你有前端应用程序,当你调用你的合约的交换功能时,会弹出元掩码,你会确认它。但是在测试环境中,您需要实际的 ERC20 合约,部署它并调用 approve。在前端你会有两个函数 swapTokenapprove。你会按这个顺序给他们打电话吗?

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

在测试套件中:

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
})})

我最终还是使用了 Hardhat/Ethers.js,只是因为设置主网分支和 运行 自动化测试套件是多么容易。我提供了一个答案 解释了设置主网分叉所需的步骤,并在这个问题中重复使用了示例合约(完成测试)。

虽然要具体回答这个问题,Hardhat has a plugin that supports testing with Truffle/Web3js,这样你仍然可以使用 Truffle/Web3js 编写你的 tests/contracts,但使用 Hardhat 的主网分叉功能与其他合约进行交互已经部署在主网上。

您能否尝试在 truffle-config.json 文件中配置一个主网端口并 运行 设置类似

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

在您的 truffle 配置文件中将端口 999 设置为 ropsten_fork,然后在单独的控制台中设置 运行

truffle console --network ropsten_fork