2

我正在编写一个自动化测试套件,需要针对 Uniswap v2 风格的自动市场标记测试功能:进行掉期并使用不同的订单路由。因此,需要部署路由器。

是否有任何现有示例说明如何在 Brownie 中部署可测试的 Uniswap v2 样式交换?因为 Brownie 是少数智能合约开发者,有 Truffle 或 Hardhat 的例子吗?

我也在探索使用主网分叉的选项,但我不确定这个操作是否太昂贵(太慢)而无法用于单元测试。

4

3 回答 3

2

使用本地测试网可以让您在测试期间非常精确地控制区块链的状态。但是,它将要求您手动部署所需的每个合同。

主网的分叉将使您不必部署已经部署在主网上的每个合约。但是,您将牺牲对环境的控制,并且需要连接到节点。

我在测试网上部署了 Uniswap 2V 几次。为此,您将需要以下合约的字节码和 ABI:UniswapV2Factory、UniswapV2Pair、UniswapV2Router02(我想您想要路由器的第二个版本)。Uniswap 文档很好地解释了如何从 NPM 下载它们。为了让路由器正常工作,您还需要部署 WETH 合约。我建议从这个 github页面部署一个。

在运行此代码之前,只需确保您的链正在运行。对于安全帽,运行以下命令:

npx hardhat node

首先将您的签名者连接到您的开发链:

var provider = new ethers.providers.WebSocketProvider("ws://localhost:8545");
var signer = provider.getSigner();

使用 ethers.js 库,您首先部署工厂:

const compiledUniswapFactory = require("@uniswap/v2-core/build/UniswapV2Factory.json");
var uniswapFactory = await new ethers.ContractFactory(compiledUniswapFactory.interface,compiledUniswapFactory.bytecode,signer).deploy(await signer.getAddress());

然后是 WETH 合约:

const compiledWETH = require("canonical-weth/build/conrtacts/WETH.json";
var WETH = await new ethers.ContractFactory(WETH.interface,WETH.bytecode,signer).deploy();

您现在可以部署路由器。

const compiledUniswapRouter = require("@uniswap/v2-periphery/build/UniswapV2Router02");
var router = await new ethers.ContractFactory(compiledUniswapRouter.abi,compiledUniswapRouter.bytecode,signer).deploy(uniswapFactory.address,WETH.address);

您还需要部署所需的 ERC20 代币(这是我编写的代币示例):

const compiledERC20 = require("../../../Ethereum/Ethereum/sources/ERC20.sol/Token.json");
var erc20Factory = new ethers.ContractFactory(compiledERC20.abi,compiledERC20.bytecode,signer);

var erc20_0 = await erc20Factory.deploy("1000000", "Token 0", "5", "T0");
var erc20_1 = await erc20Factory.deploy("1000000", "Token 1", "5", "T1");

部署函数的参数将取决于您要部署的令牌的构造函数。

您还需要使用 Uniswap 工厂的 createPair 方法创建对。

uniswapFactory.createPair(erc20_0.address,erc20_1.address);

请记住,在该对中,代币将由合约任意排序。ERC20_0 可能不是两者中的第一个。

之后,只需等待所有交易完成,您就可以开始测试了。

于 2022-01-28T05:19:48.400 回答
0

为了快速简单的设置,我会使用 Hardhat 的主网分叉。

就像@Xavier 所说,使用主网分叉意味着您不必部署要与之交互的每个合约。如果您想针对多个交易所测试您的合约,这将是最简单的方法。但是,它确实需要连接到节点,因此您的单元测试会运行得更慢。

swapExactETHForTokens例如,假设我想测试以下合约,它使用该方法将 ETH 换成 Uniswap 上的 ERC20 代币。

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 UniswapTradeExample {
  IUniswap uniswap;
  
  // Pass in address of UniswapV2Router02
  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
    );
  }
}

在 Hardhat 文档中,要做的第一件事是使用免费使用的Alchemy建立与存档节点的连接。

获得节点的 url 后,将其作为网络添加到 hardhat.config.js 文件中:

networks: {
  hardhat: {
    forking: {
      url: "https://eth-mainnet.alchemyapi.io/v2/<key>",
      blockNumber: 14189520
    }
  }
}

如果你设置blockNumber,Hardhat 每次都会分叉这个块。如果您正在使用测试套件并希望您的测试具有确定性,建议您这样做。

最后,这是测试类,它测试swapExactETHForTokens上述合同中的 。例如,我将 1 ETH 换成 DAI。

const { assert } = require("chai");
const { ethers } = require("hardhat");
const ERC20ABI = require('./ERC20.json');

const UNISWAPV2ROUTER02_ADDRESS = "0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D";
const DAI_ADDRESS = "0x6b175474e89094c44da98b954eedeac495271d0f";

describe("UniswapTradeExample", function () {
    it("Swap ETH for DAI", async function () {
        const provider = ethers.provider;
        const [owner, addr1] = await ethers.getSigners();
        const DAI = new ethers.Contract(DAI_ADDRESS, ERC20ABI, provider);

        // Assert addr1 has 1000 ETH to start
        addr1Balance = await provider.getBalance(addr1.address);
        expectedBalance = ethers.BigNumber.from("10000000000000000000000");
        assert(addr1Balance.eq(expectedBalance));

        // Assert addr1 DAI balance is 0
        addr1Dai = await DAI.balanceOf(addr1.address);
        assert(addr1Dai.isZero());

        // Deploy UniswapTradeExample
        const uniswapTradeExample =
            await ethers.getContractFactory("UniswapTradeExample")
                .then(contract => contract.deploy(UNISWAPV2ROUTER02_ADDRESS));
        await uniswapTradeExample.deployed();

        // Swap 1 ETH for DAI
        await uniswapTradeExample.connect(addr1).swapExactETHForTokens(
            0,
            DAI_ADDRESS,
            { value: ethers.utils.parseEther("1") }
        );

        // Assert addr1Balance contains one less ETH
        expectedBalance = addr1Balance.sub(ethers.utils.parseEther("1"));
        addr1Balance = await provider.getBalance(addr1.address);
        assert(addr1Balance.lt(expectedBalance));

        // Assert DAI balance increased
        addr1Dai = await DAI.balanceOf(addr1.address);
        assert(addr1Dai.gt(ethers.BigNumber.from("0")));
    });
});

注意const ERC20ABI = require('./ERC20.json');顶部的,它导入了获取 DAI 合约并使用它的方法所需的ERC20ABI 。balanceOf()

就这样。运行npx hardhat test应该表明这个测试通过了。

于 2022-02-20T01:59:57.250 回答
0

Xavier Hamel 的回答让我朝着正确的方向前进。

不幸的是,Uniswap v2 及其克隆无法在不编辑源代码的情况下重新编译和重新部署。这是因为

  • 路由器依赖于UniswapV2Library
  • UniswapV2Library在 pairFor()函数中硬编码 UniswapV2Factory的初始代码哈希
  • 任何重新编译都可能导致UniswapV2Factory不同的哈希值
  • 路由器将无法路由任何交易,pairFor因为它会生成无效的配对合约地址而失败

这是一个令人讨厌的问题。我最终构建了整个库和工具来解决它:请与Smart 联系人进行测试。它基于 Sushiswap v2 repo,因为他们的合约维护得最好。

于 2022-01-31T13:51:02.257 回答