前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >如何对接 Uniswap V2 兑换代币

如何对接 Uniswap V2 兑换代币

作者头像
Tiny熊
发布2022-11-07 09:57:54
1.2K0
发布2022-11-07 09:57:54
举报

译文出自:登链翻译计划[1] 译者:翻译小组[2] 校对:Tiny 熊[3]

在上一篇文章中,我们通过 大概 100 行代码,了解了 Uniswap 的运行原理。

在本文中,我们将和正式的 Uniswap V2 交互,实现使用Uniswap[4]进行代币兑换,并通过测试验证兑换功能,通过测试验证智能合约的行为是一个很好的粉丝,测试让你相信代码以我们想要的方式执行,而不是以它不应该的方式执行。

在本文中,我们还将学习到如何 fork 主网,并冒充(模拟)一个链上账号进行交易,并编写测试。

关于 Uniswap V2

但在深入研究之前,为了本文完整,让我们再次介绍一下 Uniswap,Uniswap 是一个去中心化的交易所(DEX),运行在以太坊区块链上(主网和其他一些网络)。顾名思义,Uniswap 是用来交易 ERC20 代币的。

Uniswap 有 3 个主要功能:

  1. 在不同的代币之间进行兑换
  2. 添加代币对流动性,获得 LP ERC-20 流动性代币
  3. 销毁 LP ERC-20 流动性代币,取回配对的 ERC-20 代币

在这篇文章中,我们将重点讨论使用 fork 主网在不同的代币之间进行兑换。

所以让我们开始吧! 🥳🥳🥳

1. 创建一个项目并初始化

在命令行(CLI)上使用以下命令来初始化项目。

代码语言:javascript
复制
mkdir uni_swap && cd uni_swap
npm init -y

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

代码语言:javascript
复制
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[5]API 密钥替换 URL 中的<key>部分。

3. 编写合约实现兑换

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

在你的 CLI 中使用以下代码创建目录:

代码语言:javascript
复制
mkdir contracts && mkdir scripts && mkdir tests

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

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

它应该看起来像这样:

现在,在testSwap中,我们需要包括Uniswap Router的地址,我们使用它来完成代币兑换。

使用下面的代码:

代码语言:javascript
复制
//address of the uniswap v2 router
address private constant UNISWAP_V2_ROUTER = 0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D;

现在,定义要用来兑换的函数:

代码语言:javascript
复制
// 兑换函数
    function swap(
        address _tokenIn,
        address _tokenOut,
        uint256 _amountIn,
        address _to,
        uint256 _deadline
    ) external {}

函数命名为swap,里面有

  • _tokenIn:是我们要兑换的代币的地址。
  • _tokenOut:是我们想从这次交易中获得的代币的地址。
  • _amountIn:是我们要交易的代币的数量。
  • _to:交易兑换出的代币发送到这个地址。
  • _deadline:是交易应该被执行的时间期限。如果超过了最后期限,交易就会失败。

在兑换函数里面,我们要做的第一件事是在合约里面把所需数量的*_tokenIn* 转移到合约里,使用msg.sender

代码语言:javascript
复制
// 把 token 从用户转移到合约
IERC20(_tokenIn).transferFrom(msg.sender, address(this), _amountIn);

一旦调用执行,**_amountIn** 数量的 _tokenIn就会转入到testSwap合约中

接下来,通过调用IERC20 授权,允许 Uniswap 合约花费testSwap合约中**_amountIn**数量的代币。

代码语言:javascript
复制
//by calling IERC20 approve you allow the uniswap contract to spend the tokens in this contract
IERC20(_tokenIn).approve(UNISWAP_V2_ROUTER, _amountIn);

在使用 Uniswap Router 兑换,需要为兑换代币的设置路径,路径上第一“站”是使用的代币,最后一“站”期望收到的代币。

所以,我们将声明一个名为path的地址数组,填入 _tokenIn 的地址和_tokenOut 的地址。

代码语言:javascript
复制
address[] memory path;
path = new address[](2 "");
path[0] = _tokenIn; // DAI
path[1] = _tokenOut; // WETH

接下来,我们调用函数getAmountsOut,以预估可以兑换代币数量,对真实兑换之前预知可兑换数量是很有用的。getAmountsOut函数需要一个输入金额和一个代币地址的路径数组:

代码语言:javascript
复制
uint256[] memory amountsExpected = IUniswapV2Router(UNISWAP_V2_ROUTER).getAmountsOut(
            _amountIn,
            path
);

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

代码语言:javascript
复制
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,同时,定义测试的结构和我们要使用的合约的地址。

代码语言:javascript
复制
const ERC20ABI = require("@uniswap/v2-core/build/ERC20.json").abi;

describe("Test Swap", function () {
    const DAIAddress = "0x6B175474E89094C44Da98b954EedeAC495271d0F";
    const WETHAddress = "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2";
    const MyAddress = "0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B";
    const DAIHolder = "0x5d38b4e4783e34e2301a2a36c39a03c45798c4dd";
}

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

  • DAIAddressWETHAddress分别是 Dai 合约和 WETH 合约的地址,它们将在交易中使用
  • MyAddress是交易者的地址。
  • DAIHolder是我们要冒充的地址。

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

代码语言:javascript
复制
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地址。

代码语言:javascript
复制
it("should swap", async () => {
            await hre.network.provider.request({
            method: "hardhat_impersonateAccount",
            params: [DAIHolder],
});
const impersonateSigner = await ethers.getSigner(DAIHolder);

在下一步,我们将通过使用冒充的账户获得其DAI 代币的初始余额。之后,我们将使用该余额进行兑换交易。

同样,我们也获取WETH 代币的余额,以便观察代币的兑换情况。

代码语言:javascript
复制
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("Initial WETH Balance:", ethers.utils.formatUnits(myBalance.toString()));

然后,我们将使用 DAI 合约来批准(授权)TestSwap 可使用兑换的金额:

代码语言:javascript
复制
await DAIContract.approve(TestSwapContract.address, DAIHolderBalance)

对于最后兑换截止时间,先获取最新区块的当前时间戳:

代码语言:javascript
复制
// getting current timestamp
const latestBlock = await ethers.provider.getBlockNumber();
const timestamp = (await ethers.provider.getBlock(latestBlock)).timestamp;

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

这个交易将从通过DAIHolder发起:

代码语言:javascript
复制
await TestSwapContract.connect(impersonateSigner).swap(
            DAIAddress,
            WETHAddress,
            DAIHolderBalance,
            MyAddress,
            timestamp + 1000 // adding 100 milliseconds to the current blocktime
)

最后,验证兑换交易:

代码语言:javascript
复制
const myBalance_updated = await WETHContract.balanceOf(MyAddress);
console.log("Balance after Swap:", ethers.utils.formatUnits(myBalance_updated.toString()));
const DAIHolderBalance_updated = await DAIContract.balanceOf(impersonateSigner.address);

在这里,检查了兑换功能执行后我们账户的余额。

在这下面,我们写了一些测试以检查交易是否真实完成:

代码语言:javascript
复制
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命令来运行这些测试。

结果应该是这样的:

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

而我们编写的测试也成功了!!。🎉🎉🎉

如果你一直跟到最后,那么恭喜你,你已经做得很好了。


本翻译由 Duet Protocol[6] 赞助支持。

原文:https://medium.com/uv-labs/uniswap-testing-1d88ca523bf0

参考资料

[1]

登链翻译计划: https://github.com/lbc-team/Pioneer

[2]

翻译小组: https://learnblockchain.cn/people/412

[3]

Tiny 熊: https://learnblockchain.cn/people/15

[4]

Uniswap: https://uniswap.org/

[5]

Alchemy: https://alchemy.com/?r=7d60e34c-b30a-4ffa-89d4-3c4efea4e14b

[6]

Duet Protocol: https://duet.finance/?utm_souce=learnblockchain

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2022-06-09,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 深入浅出区块链技术 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 关于 Uniswap V2
  • 1. 创建一个项目并初始化
  • 2. 初始化 Hardhat 项目
  • 3. 编写合约实现兑换
  • 4. 编写测试脚本
    • 参考资料
    相关产品与服务
    区块链
    云链聚未来,协同无边界。腾讯云区块链作为中国领先的区块链服务平台和技术提供商,致力于构建技术、数据、价值、产业互联互通的区块链基础设施,引领区块链底层技术及行业应用创新,助力传统产业转型升级,推动实体经济与数字经济深度融合。
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档