当前位置:网站首页>基于Hardhat编写合约测试用例
基于Hardhat编写合约测试用例
2022-07-02 09:38:00 【灬倪先森_】
基于Hardhat编写合约测试用例
为智能合约编写自动化测试至关重要,毕竟写智能合约多多少少都会跟用户资金挂钩。
场景
这里假设自己正在开发一个NFT交易平台,这个平台可以让用户售卖自己的NFT,包括ERC721和ERC1155,并且用户可以指定购买者需要支付指定的ERC20 Token
购买。
我们先确定自己的测试功能和目标,为了文章篇幅不要太长,我们就以卖家用户调用sell
,创建售卖订单功能为目标做测试。
合约代码
我们需要4个合约文件:
- ERC20
- ERC721
- ERC1155
- NFTSwap(交易平台)
前三种合约最简单的,我们不需要自己再去实现,直接引用Openzeppelin的合约代码即可。
在contracts
目录下创建一个新的文件TestDependency.sol
,并且把下面的代码粘贴进去
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;
import "@openzeppelin/contracts/token/ERC20/presets/ERC20PresetMinterPauser.sol";
import "@openzeppelin/contracts/token/ERC721/presets/ERC721PresetMinterPauserAutoId.sol";
import "@openzeppelin/contracts/token/ERC1155/presets/ERC1155PresetMinterPauser.sol";
这样需要用到的ERC20,ERC721,ERC1155合约就会被编译到项目中
NFTSwap合约代码我只展示sell
相关部分,足够测试即可
在contracts
目录下新建一个NFTSwap.sol
合约,并粘贴下面的代码
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
contract NFTSwap is Initializable {
enum AssetType {
ERC721,
ERC1155
}
struct Asset {
address Contract; // NFT Token地址
uint256 TokenId; // Token id
uint256 TokenValue; // Token Value, ERC721 为1
AssetType Type; // NFT 类型
}
function __NFTSwap_init() public initializer {
}
function sell(
Asset[] calldata assets, // 要售卖的NFT,可以同时售卖多个
address paymentToken, // 指定接受购买支付的 ERC20 代币
uint256 price // 售卖价格
) public virtual returns (uint256 goodsId) {
// 创建售卖订单逻辑
//.......
}
编译合约
* npx hardhat compile
Compiled 36 Solidity files successfully
合约编译通过,下一步
引用测试工具包
修改项目根目录下的hardhat.config.js
,添加对工具包的引用
require("@nomiclabs/hardhat-waffle");
require('@openzeppelin/hardhat-upgrades');
task("accounts", "Prints the list of accounts", async (taskArgs, hre) => {
const accounts = await hre.ethers.getSigners();
for (const account of accounts) {
console.log(account.address);
}
});
编写测试代码
这一部分是重点,我会把整个测试脚本文件先拆分讲解,并在文章最后附上完成的代码
引用
在test
目录下新建sell-test.js
文件,我们将在这里编辑测试用例代码
先添加引用
const {
expect, use } = require('chai'); //引入断言库
const {
BigNumber } = require('ethers'); // bignumber一会儿要用到
const {
deployContract, MockProvider, solidity } = require('ethereum-waffle');
const {
ethers, upgrades } = require("hardhat");
use(solidity); // 这里是跟 chai 声明使用在solidity合约测试
定义测试套件和全局变量
因为我会在这个套件内定义多个测试用例,模拟多种场景,所以可以定义全局变量,减少代码重复
describe("Test NFTSwap.sell Interface", function () {
var ERC20; // 存放要用到的ERC20
var ERC721; // 同上
var ERC1155; // 同上
var OWNER; // 这里是为了演示模拟多用户操作
var ADDR1; // 同上
}
定义beforeEach
beforeEach
会在每个测试用例运行前先运行。可以通过定义beforeEach
在每次测试前初始化环境,这样可以做到多个测试用例的数据不会相互影响,因为每次运行用例前,beforeEach
都会重置环境
beforeEach(async () => {
// 模拟不同的两个用户,比如测试完成的买卖流程就应该用 两个用户地址
[OWNER, ADDR1] = await ethers.getSigners();
// Owner 用户创建多个合约
const ERC20PresetMinterPauser = await ethers.getContractFactory("ERC20PresetMinterPauser", OWNER);
ERC20 = await ERC20PresetMinterPauser.deploy("TestERC20", "T20");
const ERC721PresetMinterPauserAutoId = await ethers.getContractFactory("ERC721PresetMinterPauserAutoId", OWNER);
ERC721 = await ERC721PresetMinterPauserAutoId.deploy("TestERC721", "T721", "https://t721.com");
const ERC1155PresetMinterPauser = await ethers.getContractFactory("ERC1155PresetMinterPauser", OWNER);
ERC1155 = await ERC1155PresetMinterPauser.deploy("https://t1155.com");
const NFTSwap = await ethers.getContractFactory("NFTSwap");
NFT_SWAP = await upgrades.deployProxy(NFTSwap, [], {
initializer: '__NFTSwap_init'
});
});
定义测试用例
这里我会定义三个测试用例,模拟售卖不同种类NFT,和同时售卖两种NFT的情况
第一个测试用例
创建售卖1个ERC721 Token
订单成功
it("Should be sale an ERC721 token successful", async function () {
// 确定 NFTSwap合约 部署完成
await NFT_SWAP.deployed();
// 确定 ERC721合约 部署完成
await ERC721.deployed();
// 增发 id=0 的token,并approve 给 NFTSwap
var mintERC721Tx = await ERC721.connect(OWNER).mint(OWNER.address);
await mintERC721Tx.wait();
var approveERC721Tx = await ERC721.connect(OWNER).setApprovalForAll(NFT_SWAP.address, true);
await approveERC721Tx.wait();
// 定义assets, assetType.ERC721 = 1
var assets = [{
Contract: ERC721.address, TokenId: BigNumber.from(0), TokenValue: BigNumber.from(1), Type: 1 }]
await ERC20.deployed();
// 发起交易
const sellTx = await NFT_SWAP.sell(assets, ERC20.address, BigNumber.from(1000000));
await sellTx.wait()
// 获取交易结果
var receipt = await ethers.provider.getTransactionReceipt(sellTx.hash);
// 判断交易最终状态,必须为1,1表示合约执行成功
expect(receipt.status).to.equal(1);
});
第二个测试用例
创建售卖1个ERC1155T oken
订单成功
it("Should be sale an ERC1155 token successful", async function () {
// 确定 NFTSwap合约 部署完成
await NFT_SWAP.deployed();
// 确定 ERC1155合约 部署完成
await ERC1155.deployed();
// 增发 id=0 的token,并approve 给 NFTSwap
var mintERC1155Tx = await ERC1155.connect(OWNER).mint(OWNER.address, 1, 10, "0x");
await mintERC1155Tx.wait();
var approveERC1155Tx = await ERC1155.connect(OWNER).setApprovalForAll(NFT_SWAP.address, true);
await approveERC1155Tx.wait();
// 定义assets, assetType.ERC1155 = 2
var assets = [{
Contract: ERC1155.address, TokenId: BigNumber.from(1), TokenValue: BigNumber.from(1), Type: 2 }]
await ERC20.deployed();
const sellTx = await NFT_SWAP.sell(assets, ERC20.address, BigNumber.from(1000000));
await sellTx.wait()
var receipt = await ethers.provider.getTransactionReceipt(sellTx.hash);
expect(receipt.status).to.equal(1);
});
第三个测试用例
创建售卖 1个ERC721 Token
+ 1个ERC1155T oken
订单成功
it("Should be packet sale an ERC721 token and an ERC1155 token successful", async function () {
// 确定 NFTSwap合约 部署完成
await NFT_SWAP.deployed();
// 确定 ERC721合约 部署完成
await ERC721.deployed();
// 增发 id=0 的ERC721 token,并approve 给 NFTSwap
var mintERC721Tx = await ERC721.connect(OWNER).mint(OWNER.address);
await mintERC721Tx.wait();
var approveERC721Tx = await ERC721.connect(OWNER).setApprovalForAll(NFT_SWAP.address, true);
await approveERC721Tx.wait();
// 确定 ERC1155合约 部署完成
await ERC1155.deployed();
// 增发 id=0 的ERC1155 token,并approve 给 NFTSwap
var mintERC1155Tx = await ERC1155.connect(OWNER).mint(OWNER.address, 1, 10, "0x");
await mintERC1155Tx.wait();
var approveERC1155Tx = await ERC1155.connect(OWNER).setApprovalForAll(NFT_SWAP.address, true);
await approveERC1155Tx.wait();
// 定义assets,这里是用两个 NFT Token的
var assets = [{
Contract: ERC721.address, TokenId: BigNumber.from(0), TokenValue: BigNumber.from(1), Type: 1 },
{
Contract: ERC1155.address, TokenId: BigNumber.from(1), TokenValue: BigNumber.from(10), Type: 2 }]
await ERC20.deployed();
const sellTx = await NFT_SWAP.sell(assets, ERC20.address, BigNumber.from(200000));
await sellTx.wait()
var receipt = await ethers.provider.getTransactionReceipt(sellTx.hash);
expect(receipt.status).to.equal(1);
});
到这里,我们的测试脚本文件已经完成了,接下来直接运行测试脚本,查看测试结果就可以了
运行测试脚本
* npx hardhat test test/sell-test.js
Test NFTSwap.sell Interface
Should be sale an ERC721 token successful (120ms)
Should be sale an ERC1155 token successful (99ms)
Should be packet sale an ERC721 token and an ERC1155 token successful (177ms)
3 passing (4s)
这里可以看到测试都通过
完整测试脚本代码
const {
expect, use } = require('chai');
const {
BigNumber } = require('ethers');
const {
deployContract, MockProvider, solidity } = require('ethereum-waffle');
const {
ethers, upgrades } = require("hardhat");
use(solidity);
describe("Test NFTSwap.sell Interface", function () {
var ERC20;
var ERC721;
var ERC1155;
var OWNER;
var ADDR1;
var NFT_SWAP;
beforeEach(async () => {
[OWNER, ADDR1] = await ethers.getSigners();
const ERC20PresetMinterPauser = await ethers.getContractFactory("ERC20PresetMinterPauser", OWNER);
ERC20 = await ERC20PresetMinterPauser.deploy("TestERC20", "T20");
const ERC721PresetMinterPauserAutoId = await ethers.getContractFactory("ERC721PresetMinterPauserAutoId", OWNER);
ERC721 = await ERC721PresetMinterPauserAutoId.deploy("TestERC721", "T721", "https://t721.com");
const ERC1155PresetMinterPauser = await ethers.getContractFactory("ERC1155PresetMinterPauser", OWNER);
ERC1155 = await ERC1155PresetMinterPauser.deploy("https://t1155.com");
const NFTSwap = await ethers.getContractFactory("NFTSwap");
NFT_SWAP = await upgrades.deployProxy(NFTSwap, {
initializer: '__NFTSwap_init'
});
});
it("Should be sale an ERC721 token successful", async function () {
await NFT_SWAP.deployed();
await ERC721.deployed();
var mintERC721Tx = await ERC721.connect(OWNER).mint(OWNER.address);
await mintERC721Tx.wait();
var approveERC721Tx = await ERC721.connect(OWNER).setApprovalForAll(NFT_SWAP.address, true);
await approveERC721Tx.wait();
var assets = [{
Contract: ERC721.address, TokenId: BigNumber.from(0), TokenValue: BigNumber.from(1), Type: 1 }]
await ERC20.deployed();
const sellTx = await NFT_SWAP.sell(assets, ERC20.address, BigNumber.from(1000000));
await sellTx.wait()
var receipt = await ethers.provider.getTransactionReceipt(sellTx.hash);
expect(receipt.status).to.equal(1);
});
it("Should be sale an ERC1155 token successful", async function () {
await NFT_SWAP.deployed();
await ERC1155.deployed();
var mintERC1155Tx = await ERC1155.connect(OWNER).mint(OWNER.address, 1, 10, "0x");
await mintERC1155Tx.wait();
var approveERC1155Tx = await ERC1155.connect(OWNER).setApprovalForAll(NFT_SWAP.address, true);
await approveERC1155Tx.wait();
var assets = [{
Contract: ERC1155.address, TokenId: BigNumber.from(1), TokenValue: BigNumber.from(1), Type: 2 }]
await ERC20.deployed();
const sellTx = await NFT_SWAP.sell(assets, ERC20.address, BigNumber.from(1000000));
await sellTx.wait()
var receipt = await ethers.provider.getTransactionReceipt(sellTx.hash);
expect(receipt.status).to.equal(1);
});
it("Should be packet sale an ERC721 token and an ERC1155 token successful", async function () {
await NFT_SWAP.deployed();
await ERC721.deployed();
var mintERC721Tx = await ERC721.connect(OWNER).mint(OWNER.address);
await mintERC721Tx.wait();
var approveERC721Tx = await ERC721.connect(OWNER).setApprovalForAll(NFT_SWAP.address, true);
await approveERC721Tx.wait();
await ERC1155.deployed();
var mintERC1155Tx = await ERC1155.connect(OWNER).mint(OWNER.address, 1, 10, "0x");
await mintERC1155Tx.wait();
var approveERC1155Tx = await ERC1155.connect(OWNER).setApprovalForAll(NFT_SWAP.address, true);
await approveERC1155Tx.wait();
var assets = [{
Contract: ERC721.address, TokenId: BigNumber.from(0), TokenValue: BigNumber.from(1), Type: 1 },
{
Contract: ERC1155.address, TokenId: BigNumber.from(1), TokenValue: BigNumber.from(10), Type: 2 }]
await ERC20.deployed();
const sellTx = await NFT_SWAP.sell(assets, ERC20.address, BigNumber.from(200000));
await sellTx.wait()
var receipt = await ethers.provider.getTransactionReceipt(sellTx.hash);
expect(receipt.status).to.equal(1);
});
});
有问题,或者建议请留言,谢谢。
边栏推荐
- TIPC Getting Started6
- Programmer growth Chapter 6: how to choose a company?
- V2x SIM dataset (Shanghai Jiaotong University & New York University)
- Functional interfaces and method references
- 函数式接口和方法引用
- 每月1号开始计算当月工作日
- Tick Data and Resampling
- Eight sorting summaries
- Mongodb learning and sorting (condition operator, $type operator, limit() method, skip() method and sort() method)
- spritejs
猜你喜欢
II Stm32f407 chip GPIO programming, register operation, library function operation and bit segment operation
RPA进阶(二)Uipath应用实践
flink二開,實現了個 batch lookup join(附源碼)
QT learning diary 8 - resource file addition
Verilog and VHDL signed and unsigned number correlation operations
mysql链表数据存储查询排序问题
【云原生】2.5 Kubernetes 核心实战(下)
JS -- take a number randomly from the array every call, and it cannot be the same as the last time
Skills of PLC recorder in quickly monitoring multiple PLC bits
RPA advanced (II) uipath application practice
随机推荐
What are the software product management systems? Inventory of 12 best product management tools
[play with FPGA learning 5 in simple terms ----- reset design]
QT learning diary 7 - qmainwindow
Verilog and VHDL signed and unsigned number correlation operations
sqlite 修改列类型
Wechat applet uses Baidu API to achieve plant recognition
Skills of PLC recorder in quickly monitoring multiple PLC bits
TIPC介绍1
金山云——2023届暑期实习
ImportError: cannot import name ‘Digraph‘ from ‘graphviz‘
JS——每次调用从数组里面随机取一个数,且不能与上一次为同一个
Array splitting (regular thinking
Appgallery connect scenario development practice - image storage and sharing
Importerror: impossible d'importer le nom « graph» de « graphviz»
在连接mysql数据库的时候一直报错
从ros1到ros2配置的一些东西
Rest (XOR) position and thinking
Mongodb learning and sorting (condition operator, $type operator, limit() method, skip() method and sort() method)
liftOver进行基因组坐标转换
Basic usage of MySQL in centos8