基于Hardhat编写合约测试用例
Posted 灬倪先森_
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了基于Hardhat编写合约测试用例相关的知识,希望对你有一定的参考价值。
基于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(1000000Web3与智能合约:开发一个简单的DApp并部署到以太坊测试网(Solidity+Hardhat+React)① 环境搭建
前言
智能合约(Smart Contract)存在于以太坊区块链中,任何人都可以通过支付一定量的Gas fee与之交互,这个系列文章将介绍作为开发者,如何构建一个DApp:
- 使用Solidity编写一个智能合约。
- 使用Hardhat将智能合约部署到以太坊网络中。
- 基于Reac构建一个客户端网站来与区块链上的智能合约进行交互。
一、环境搭建
1.安装hardhat
安装node/npm,如果没有安装的话点这里。
新建一个你的DApp工作目录例如myweb3,cd到该目录下执行如下命令:
cd myweb3
npm init -y
npm install --save-dev hardhat
2.测试是否安装成功
在工作目录下运行:
npx hardhat
在这里如果你将yarn和npm一起安装,可能会收到像npm ERR! could not determine executable to run
这样的错误,可以运行以下命令来继续:
yarn add hardhat
npx hardhat
选择创建一个示例项目,全都选择“是”,示例项目会要求你安装hardhat-waffle
和hardhat-ethers
,可用一下命令安装:
npm install --save-dev @nomiclabs/hardhat-waffle ethereum-waffle chai @nomiclabs/hardhat-ethers ethers
运行以下命令,在控制台应输出像0xa0Ee7A142d267C1f36714E4a8F75612F20a79720
的字符串,这是hardhat为我们生成的用于模拟真实用户的以太坊地址。
npx hardhat accounts
最后,为了确定一切都安装就绪,运行:
npx hardhat compile
然后运行:
npx hardhat test
你应在控制台看到:
以上是关于基于Hardhat编写合约测试用例的主要内容,如果未能解决你的问题,请参考以下文章
Web3 系列开发教程——创建你的第一个 NFT使用 Ethers.js 铸造 NFT | 测试用例
开源区块链系统aelf性能测试系列文章 AELF vs ETH - 01
Web3与智能合约:开发一个简单的DApp并部署到以太坊测试网(Solidity+Hardhat+React)① 环境搭建