Uniswap 🔀 测试

  • BuildBear
  • 发布于 2022-04-26 19:35
  • 阅读 49

本文详细介绍了如何测试 Uniswap 的 swap 实现,通过主网分叉创建项目并编写智能合约,最后运行测试脚本验证交换操作。重点讲解了合约的结构、所需的接口、交易逻辑以及如何进行有效的功能测试,其中包含给出具体代码示例和操作步骤。

Uniswap 以太坊

测试验证你的智能合约的行为。它们让你确信你的代码按照你所期望的方式执行,而不会按照不该执行的方式执行。

在上一篇文章中,我们学习了 主网分叉 并使用 冒充账户 玩转了 Vitalik 的账户。

现在我们决定进一步探索并测试 Uniswap 的交换实现。没错!你没听错 😎

但在深入之前,我们先了解更多关于 Uniswap 的信息

Uniswap 是一个去中心化交易所,运行在以太坊区块链上(主网及其他几个网络)。顾名思义,Uniswap 用于 交易 ERC20 代币

Uniswap 有 3 个主要功能:

  1. 不同代币之间的交换
  2. 向市场添加流动性,并通过配对交换 ERC-20 流动性代币获得奖励
  3. 销毁 ERC-20 流动性代币并兑换回允许交易者交换的 ERC-20 代币

在本文中,我们将重点关注使用分叉在不同代币之间的交换。

注意:我们建议你首先阅读 上一篇文章,然后再跟随本文,以更好地理解正在发生的事情。

那么我们开始吧! 🥳🥳

1. 创建项目并初始化

在你的 CLI 上使用以下命令初始化你的项目。

mkdir uni_swap && cd uni_swap
npm init -y

安装项目所需的依赖,运行

npm install --save hardhat @nomiclabs/hardhat-ethers @nomiclabs/hardhat-waffle ethers @uniswap/v2-core dotenv

2. 初始化你的 Hardhat 项目

要初始化你的 Hardhat 项目,在 CLI 中运行 npx hardhat 命令,并创建一个空的 config.js 文件。

自定义你的 Hardhat 配置:

因为我们要 fork 主网以测试 Uniswap,因此你的 Hardhat 配置应该类似于下面这样:

注意:用你的个人 Alchemy API 密钥替换 URL 中的 <key> 部分。

3. 编写交换的智能合约

创建合约、脚本和测试的目录以更好地组织代码。

在你的 CLI 中使用以下代码。

mkdir contracts && mkdir scripts && mkdir tests

为了编写交换合约,在合约目录中创建一个文件,并命名为 testSwap.sol

为了我们的合约,我们需要包含一个接口:Uniswap,以使用它们的函数。

编写智能合约:

在你的 testSwap.sol 文件中导入接口,并创建一个名为 testSwap 的合约。

它应该类似于下面这样:

现在,在 testSwap 内,我们需要包括 Uniswap Router 的地址。它是我们在代币之间进行交易所必需的。

使用以下代码:

// Uniswap V2 路由器的地址
address private constant UNISWAP_V2_ROUTER = 0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D;

现在,定义我们将用于交换的函数:

// 交换函数
    function swap(
        address _tokenIn,
        address _tokenOut,
        uint256 _amountIn,
        address _to,
        uint256 _deadline
    ) external {}

我们将函数命名为 swap,在其中我们有:

  • _tokenIn 是我们想交易的代币的地址
  • _tokenOut 是我们想从此交易中获得的代币的地址
  • _amountIn 是我们正在交易的代币数量
  • _to 是我们发送输出代币的地址
  • _deadline 是交易应该执行的时间。如果超出截止时间,交易将失效。

在交换函数内部,我们要做的第一件事是使用 msg.sender_tokenIn 地址的金额转移到合约中。

// 从 msg.sender 转移金额的代币到此合约
IERC20(_tokenIn).transferFrom(msg.sender, address(this), _amountIn);

一旦调用此操作,_tokenIn 地址将拥有 _amountIn 中的金额。

接下来,通过调用 IERC20 的 approve,你允许 Uniswap 合约支出此合约中的 _amountIn 代币。

// 通过调用 IERC20 的 approve,你允许 Uniswap 合约支出此合约中的代币
IERC20(_tokenIn).approve(UNISWAP_V2_ROUTER, _amountIn);

现在,我们需要为交换代币调用的一个参数是 path

因此,我们将声明一个名为 path 的地址数组。

_tokenIn 的地址和 _tokenOut 的地址。

address[] memory path;
path = new address[](2);
path[0] = _tokenIn; // DAI
path[1] = _tokenOut; // WETH

接下来,我们将调用 getAmountsOut 函数,它对于计算在进行交换时我们应该预期的代币数量非常有用。它接受一个输入金额和一个代币地址数组。正如你猜到的,数组是我们上面定义的 path

uint256[] memory amountsExpected = IUniswapV2Router(UNISWAP_V2_ROUTER).getAmountsOut(
            _amountIn,
            path
);

最后,我们将调用 Uniswap Router 的 swapExactTokensForTokens 函数,并传入参数。

uint256[] memory amountsReceived = IUniswapV2Router(UNISWAP_V2_ROUTER).swapExactTokensForTokens(
            amountsExpected[0],
            (amountsExpected[1]*990)/1000, // 接受 1% 的滑点
            path,
            _to,
            _deadline
);

祝贺你!我们的合约准备好了。 🎉

它应该类似于下面这样: 👇

使用命令 npx hardhat compile 检查我们智能合约是否有任何错误。

现在,是时候 ⌛ 为我们的合约运行一些测试了!

4. 编写测试脚本的时间

tests 文件夹内创建一个文件,并命名为 sample-test.js

首先,我们将从 Uniswap 导入 ERC20 合约的 ABI。

还要定义我们将使用的合约地址的测试结构。

const ERC20ABI = require("@uniswap/v2-core/build/ERC20.json").abi;describe("测试交换", function () {
    const DAIAddress = "0x6B175474E89094C44Da98b954EedeAC495271d0F";
    const WETHAddress = "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2";
    const MyAddress = "0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B";
    const DAIHolder = "0x5d38b4e4783e34e2301a2a36c39a03c45798c4dd";
}

在这里,我们使用了 4 个地址:

  • DAIAddressWETHAddress 是 Dai 合约和 WETH 合约的地址,分别用于交易
  • MyAddress 是交易金额的地址
  • DAIHolder 是我们将要 冒充 的地址。

现在,在编写测试脚本之前,我们将部署 testSwap 智能合约。为此,我们有以下代码:

let TestSwapContract;
beforeEach(async () => {
        const TestSwapFactory = await ethers.getContractFactory("testSwap");
        TestSwapContract = await TestSwapFactory.deploy();
        await TestSwapContract.deployed();
})beforeEach(async () => {
        const TestSwapFactory = await ethers.getContractFactory("testSwap");
        TestSwapContract = await TestSwapFactory.deploy();
        await TestSwapContract.deployed();
})

创建测试脚本的结构。并 冒充 我们之前定义的 DAIHolder 地址。

it("应该交换", async () => {
            await hre.network.provider.request({
            method: "hardhat_impersonateAccount",
            params: [DAIHolder],
});
const impersonateSigner = await ethers.getSigner(DAIHolder);

在下一步中,我们将使用冒充账户获取 DAI 代币 的初始余额。然后,我们将交换地址上的总余额。

同样,我们也会获取 WETH 代币 的余额,以观察代币的交换。

const DAIContract = new ethers.Contract(DAIAddress, ERC20ABI, impersonateSigner)
const DAIHolderBalance = await DAIContract.balanceOf(impersonateSigner.address)
const WETHContract = new ethers.Contract(WETHAddress, ERC20ABI, impersonateSigner)
const myBalance = await WETHContract.balanceOf(MyAddress);
console.log("初始 WETH 余额:", ethers.utils.formatUnits(myBalance.toString()));

然后,我们将使用 DAI 合约批准交换其中的总余额。

await DAIContract.approve(TestSwapContract.address, DAIHolderBalance)

对于截止时间,我们将使用区块的当前时间戳。

// 获取当前时间戳
const latestBlock = await ethers.provider.getBlockNumber();
const timestamp = (await ethers.provider.getBlock(latestBlock)).timestamp;

我们将通过调用我们编写的 swap 函数进行交易。传递我们上面配置的参数。

该交易将由 DAIHolder 发起。

await TestSwapContract.connect(impersonateSigner).swap(
            DAIAddress,
            WETHAddress,
            DAIHolderBalance,
            MyAddress,
            timestamp + 1000 // 将当前区块时间加上 100 毫秒
)

最后,是时候测试交换交易了! 😬

const myBalance_updated = await WETHContract.balanceOf(MyAddress);
console.log("交换后的余额:", ethers.utils.formatUnits(myBalance_updated.toString()));
const DAIHolderBalance_updated = await DAIContract.balanceOf(impersonateSigner.address);

在这里,我们首先检查了交换函数执行后的我们账户的余额。

在此之下,我们编写了一些测试来检查交易是否真实。

expect(DAIHolderBalance_updated.eq(BigNumber.from(0))).to.be.true
expect(myBalance_updated.gt(myBalance)).to.be.true;
  • 由于我们交换了所有余额,因此在第一个测试中,我们期望 DAI 地址余额 应为 0
  • 在第二个测试中,我们检查我们账户中的 余额 现在是否 大于 之前。

因此,这就是我们要运行的两个测试。

sample-test.js 应该类似于以下内容。请务必注意文件开头的 require 语句。

当然,可以自由探索并进行更多测试。

现在,我们将使用命令 npx hardhat test 运行这些测试。

结果应该如下所示:

正如你所看到的,在交换完成后,我们的初始余额已增加。

而且我们编写的测试也成功通过了!! 🎉🎉🎉

如果你跟随我们完成到最后,那么祝贺你,做得很好。

当然!如果你遇到任何错误消息,可以在推特上 @uv_labs 找我们咨询。

再次声明,我们刚才运行的所有代码都在这里 👉 Github 库。如果喜欢我们的工作,请给我们一个好评和掌声。

作者(欢迎反馈):👇

Amateur-DevPari Tomar

  • 原文链接: medium.com/buildbear/uni...
  • 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
BuildBear
BuildBear
https://medium.com/buildbear