hardhat是一个专门用来进行Solidity智能合约开发、部署和测试的软件工具集,它既可以像ganache一样,在本地自动化部署一个以太坊网络,又可以像truffle一样,快捷的部署、测试和调试Solidity智能合约。即 hardhat = ganache + truffle。
下面,介绍hardhat的工程创建流程,这里以onehat工程为例。
1、创建文件夹并初始化
mkdir onehat
npm init -y
2、修改package.json
修改package.json,主要是添加devDependencies字段:
//package.json
{
"name": "onehat",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"@nomiclabs/hardhat-ethers": "^2.0.2",
"@nomiclabs/hardhat-waffle": "^2.0.1",
"chai": "^4.3.4",
"ethereum-waffle": "^3.4.0",
"ethers": "^5.4.7",
"hardhat": "^2.6.5",
"ethereumjs-abi": "^0.6.8"
}
}
安装依赖包
npm install
3、创建hardhat配置文件
在onehat目录下, 新建一个hardhat.config.js文件
touch hardhat.config.js
hardhat.config.js的内容如下:
// onehat/harhat.config.js
/**
* @type import('hardhat/config').HardhatUserConfig
*/
require("@nomiclabs/hardhat-waffle");
module.exports = {
solidity: "0.6.12",
};
4、编写.sol智能合约
新建一个智能合约文件onehat/contracts/Token.sol
mkdir contracts
cd contracts
touch Token.sol
Token.sol的内容如下:
// onehat/contracts/Token.sol
//SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
// This is the main building block for smart contracts.
contract Token {
// Some string type variables to identify the token.
// The `public` modifier makes a variable readable from outside the contract.
string public name = "My Hardhat Token";
string public symbol = "MBT";
// 固定发行量,保存在一个无符号整型里
uint256 public totalSupply = 1000000;
// An address type variable is used to store ethereum accounts.
address public owner;
// A mapping is a key/value map. Here we store each account balance.
mapping(address => uint256) balances;
/**
* 合约构造函数
*
* The `constructor` is executed only once when the contract is created.
*/
constructor() public {
// The totalSupply is assigned to transaction sender, which is the account
// that is deploying the contract.
balances[msg.sender] = totalSupply;
owner = msg.sender;
}
/**
* 代币转账.
*
* The `external` modifier makes a function *only* callable from outside
* the contract.
*/
function transfer(address to, uint256 amount) external {
// Check if the transaction sender has enough tokens.
// If `require`'s first argument evaluates to `false` then the
// transaction will revert.
require(balances[msg.sender] >= amount, "Not enough tokens");
// Transfer the amount.
balances[msg.sender] -= amount;
balances[to] += amount;
}
/**
* 返回账号的代币余额,只读函数。
*
* The `view` modifier indicates that it doesn't modify the contract's
* state, which allows us to call it without executing a transaction.
*/
function balanceOf(address account) external view returns (uint256) {
return balances[account];
}
}
5、编译智能合约
## 进入工程文件夹
cd onehat
## 编译智能合约
npx hardhat compile
6、工程的目录结构
![](https://img-blog.csdnimg.cn/30c3b73a330647fdbe1c74d56ec8ef35.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBAc2FucWltYQ==,size_11,color_FFFFFF,t_70,g_se,x_16)
7、测试智能合约
7.1 新建.js测试脚本
新建一个测试脚本: onehat/test/token.js
cd onehat
mkdir test
cd test
touch token.js
token.js的内容如下:
//token.js
// We import Chai to use its asserting functions here.
const { expect } = require("chai");
// `describe` is a Mocha function that allows you to organize your tests. It's
// not actually needed, but having your tests organized makes debugging them
// easier. All Mocha functions are available in the global scope.
// `describe` receives the name of a section of your test suite, and a callback.
// The callback must define the tests of that section. This callback can't be
// an async function.
describe("Token contract", function () {
// Mocha has four functions that let you hook into the the test runner's
// lifecyle. These are: `before`, `beforeEach`, `after`, `afterEach`.
// They're very useful to setup the environment for tests, and to clean it
// up after they run.
// A common pattern is to declare some variables, and assign them in the
// `before` and `beforeEach` callbacks.
let Token;
let hardhatToken;
let owner;
let addr1;
let addr2;
let addrs;
// `beforeEach` will run before each test, re-deploying the contract every
// time. It receives a callback, which can be async.
beforeEach(async function () {
// Get the ContractFactory and Signers here.
Token = await ethers.getContractFactory("Token");
[owner, addr1, addr2, ...addrs] = await ethers.getSigners();
// To deploy our contract, we just have to call Token.deploy() and await
// for it to be deployed(), which happens onces its transaction has been
// mined.
hardhatToken = await Token.deploy();
});
// You can nest describe calls to create subsections.
describe("Deployment", function () {
// `it` is another Mocha function. This is the one you use to define your
// tests. It receives the test name, and a callback function.
// If the callback function is async, Mocha will `await` it.
it("Should set the right owner", async function () {
// Expect receives a value, and wraps it in an Assertion object. These
// objects have a lot of utility methods to assert values.
// This test expects the owner variable stored in the contract to be equal
// to our Signer's owner.
expect(await hardhatToken.owner()).to.equal(owner.address);
});
it("Should assign the total supply of tokens to the owner", async function () {
const ownerBalance = await hardhatToken.balanceOf(owner.address);
expect(await hardhatToken.totalSupply()).to.equal(ownerBalance);
});
});
describe("Transactions", function () {
it("Should transfer tokens between accounts", async function () {
// Transfer 50 tokens from owner to addr1
await hardhatToken.transfer(addr1.address, 50);
const addr1Balance = await hardhatToken.balanceOf(addr1.address);
expect(addr1Balance).to.equal(50);
// Transfer 50 tokens from addr1 to addr2
// We use .connect(signer) to send a transaction from another account
await hardhatToken.connect(addr1).transfer(addr2.address, 50);
const addr2Balance = await hardhatToken.balanceOf(addr2.address);
expect(addr2Balance).to.equal(50);
});
it("Should fail if sender doesn’t have enough tokens", async function () {
const initialOwnerBalance = await hardhatToken.balanceOf(owner.address);
// Try to send 1 token from addr1 (0 tokens) to owner (1000 tokens).
// `require` will evaluate false and revert the transaction.
await expect(
hardhatToken.connect(addr1).transfer(owner.address, 1)
).to.be.revertedWith("Not enough tokens");
// Owner balance shouldn't have changed.
expect(await hardhatToken.balanceOf(owner.address)).to.equal(
initialOwnerBalance
);
});
it("Should update balances after transfers", async function () {
const initialOwnerBalance = await hardhatToken.balanceOf(owner.address);
// Transfer 100 tokens from owner to addr1.
await hardhatToken.transfer(addr1.address, 100);
// Transfer another 50 tokens from owner to addr2.
await hardhatToken.transfer(addr2.address, 50);
// Check balances.
const finalOwnerBalance = await hardhatToken.balanceOf(owner.address);
expect(finalOwnerBalance).to.equal(initialOwnerBalance - 150);
const addr1Balance = await hardhatToken.balanceOf(addr1.address);
expect(addr1Balance).to.equal(100);
const addr2Balance = await hardhatToken.balanceOf(addr2.address);
expect(addr2Balance).to.equal(50);
});
});
});
7.2 运行测试脚本
npx hardhat test test/token.js
效果如下:
![](https://img-blog.csdnimg.cn/423cdae3b62d4210834f708686a18f24.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBAc2FucWltYQ==,size_20,color_FFFFFF,t_70,g_se,x_16)
如图(2)所示,5个单元测试,都测试成功。