本文详细介绍了如何测试 Uniswap 的 swap 实现,通过主网分叉创建项目并编写智能合约,最后运行测试脚本验证交换操作。重点讲解了合约的结构、所需的接口、交易逻辑以及如何进行有效的功能测试,其中包含给出具体代码示例和操作步骤。
Uniswap 以太坊
测试验证你的智能合约的行为。它们让你确信你的代码按照你所期望的方式执行,而不会按照不该执行的方式执行。
在上一篇文章中,我们学习了 主网分叉 并使用 冒充账户 玩转了 Vitalik 的账户。
现在我们决定进一步探索并测试 Uniswap 的交换实现。没错!你没听错 😎
Uniswap 是一个去中心化交易所,运行在以太坊区块链上(主网及其他几个网络)。顾名思义,Uniswap 用于 交易 ERC20 代币。
Uniswap 有 3 个主要功能:
在本文中,我们将重点关注使用分叉在不同代币之间的交换。
注意:我们建议你首先阅读 上一篇文章,然后再跟随本文,以更好地理解正在发生的事情。
那么我们开始吧! 🥳🥳
在你的 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
要初始化你的 Hardhat 项目,在 CLI 中运行 npx hardhat
命令,并创建一个空的 config.js 文件。
自定义你的 Hardhat 配置:
因为我们要 fork 主网以测试 Uniswap,因此你的 Hardhat 配置应该类似于下面这样:
注意:用你的个人 Alchemy API 密钥替换 URL 中的 <key>
部分。
创建合约、脚本和测试的目录以更好地组织代码。
在你的 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,在其中我们有:
在交换函数内部,我们要做的第一件事是使用 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
检查我们智能合约是否有任何错误。
现在,是时候 ⌛ 为我们的合约运行一些测试了!
在 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 个地址:
现在,在编写测试脚本之前,我们将部署 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;
因此,这就是我们要运行的两个测试。
sample-test.js 应该类似于以下内容。请务必注意文件开头的 require
语句。
当然,可以自由探索并进行更多测试。
现在,我们将使用命令 npx hardhat test
运行这些测试。
结果应该如下所示:
正如你所看到的,在交换完成后,我们的初始余额已增加。
而且我们编写的测试也成功通过了!! 🎉🎉🎉
如果你跟随我们完成到最后,那么祝贺你,做得很好。
当然!如果你遇到任何错误消息,可以在推特上 @uv_labs 找我们咨询。
再次声明,我们刚才运行的所有代码都在这里 👉 Github 库。如果喜欢我们的工作,请给我们一个好评和掌声。
作者(欢迎反馈):👇
- 原文链接: medium.com/buildbear/uni...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!