Write contract test cases based on Hardhat
Writing automated tests for smart contracts is crucial, after all, writing smart contracts is more or less tied to user funds.
Scenes
It is assumed here that I am developing an NFT trading platform. This platform allows users to sell their own NFTs, including ERC721 and ERC1155, and users can specify that buyers need to pay for specified purchases ERC20 Token
.
We first determine our test function and goal. In order not to make the article too long, we will sell
test it with the function of calling and creating sales orders by seller users.
contract code
We need 4 contract files:
- ERC20
- ERC721
- ERC1155
- NFTSwap (trading platform)
The first three contracts are the simplest, we don’t need to implement them ourselves, we just need to directly quote the contract code of Openzeppelin. Create a new file in the directory
and paste the following code into itcontracts
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";
In this way, the ERC20, ERC721, and ERC1155 contracts that need to be used will be compiled into the project
I only show sell
the relevant part of the NFTSwap contract code, enough to test it. Create a new contract
in the directory and paste the following codecontracts
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) {
// 创建售卖订单逻辑
//.......
}
Compile the contract
➜ npx hardhat compile
Compiled 36 Solidity files successfully
The contract is compiled and passed, the next step
Reference Test Kit
Modify the project root directory hardhat.config.js
and add a reference to the toolkit
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);
}
});
write test code
This part is the key point. I will split and explain the entire test script file first, and attach the completed code at the end of the article.
quote
test
Create a new file in the directory , sell-test.js
where we will edit the test case code
and add references first
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合约测试
Define test suites and global variables
Because I will define multiple test cases in this suite to simulate multiple scenarios, global variables can be defined to reduce code duplication
describe("Test NFTSwap.sell Interface", function () {
var ERC20; // 存放要用到的ERC20
var ERC721; // 同上
var ERC1155; // 同上
var OWNER; // 这里是为了演示模拟多用户操作
var ADDR1; // 同上
}
definitionbeforeEach
beforeEach
will be run before each test case is run. beforeEach
The environment can be initialized before each test by definition , so that the data of multiple test cases will not affect each other, because beforeEach
the environment will be reset before each test case is run
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'
});
});
Define test cases
Here I will define three test cases, simulating the sale of different types of NFTs, and the sale of two NFTs at the same time
first test case
1个ERC721 Token
Successfully created sales order
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);
});
second test case
1个ERC1155T oken
Successfully created sales order
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);
});
third test case
Create sales 1个ERC721 Token
+ 1个ERC1155T oken
order success
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);
});
At this point, our test script file has been completed, and then run the test script directly to view the test results
run test script
➜ 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)
Here you can see that the tests pass
Complete test script code
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);
});
});
If you have any questions or suggestions, please leave a message, thank you.