环境:
-
Node.js v16.14.2
-
创建hardhat 项目
mkdir hardhat-tutorial
cd hardhat-tutorial
npm init --yes
npm install --save-dev hardhat
运行
npx hardhat
使用键盘选择"创建一个新的hardhat.config.js
安装 Ethers.js和Waffle插件
npm install --save-dev @nomiclabs/hardhat-ethers ethers @nomiclabs/hardhat-waffle ethereum-waffle chai
将高亮行require("@nomiclabs/hardhat-waffle");
添加到你的hardhat.config.js
中,
require("@nomiclabs/hardhat-waffle");
/**
* @type import('hardhat/config').HardhatUserConfig
*/
module.exports = {
solidity: "0.7.3",
};
这里引入hardhat-waffle
,因为它依赖于hardhat-ethers
,因此不需要同时添加两个。
编写合约
首先创建一个名为 contracts
的新目录,然后在目录内创建一个名为Token.sol
的文件。
// Solidity files have to start with this pragma.
// It will be used by the Solidity compiler to validate its version.
pragma solidity ^0.7.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() {
// 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];
}
}
编译合约
要编译合约,请在终端中运行 npx hardhat compile
。 compile
任务是内置任务之一
$ npx hardhat compile
Compiling 1 file with 0.7.3
Compilation finished successfully
合约已成功编译了。
5. 测试合约
为智能合约编写自动化测试至关重要,因为事关用户资金。 为此,我们将使用Hardhat Network,这是一个内置的以太坊网络,专门为开发设计,并且是Hardhat中的默认网络。 无需进行任何设置即可使用它。 在我们的测试中,我们将使用ethers.js与前面构建的合约进行交互,并使用 Mocha 作为测试运行器。
编写测试用例
在项目根目录中创建一个名为test
的新目录,并创建一个名为Token.js
的新文件。
const { ethers } = require("hardhat");
describe("Token contract", function() {
let Token ;
let hardhatToken ;
let owner;
let add1 ;
let add2 ;
let addrs;
beforeEach(async function(){
Token = await ethers.getContractFactory("Token");
[owner ,add1 ,add2 , ...addrs] = await ethers.getSigners();
hardhatToken = await Token.deploy();
});
describe("部署", function(){
it("检查owner是否正确", async function(){
expect(await hardhatToken.owner()).to.equal(owner.address);
});
it("检查总量是否正确", async function(){
const blanceCount = await hardhatToken.balanceOf(owner.address);
expect(await hardhatToken.totalSupply()).to.equal(blanceCount);
});
});
describe("交易",function(){
it("转账账户",async function(){
hardhatToken.transfer(add1.address,50);
expect(await hardhatToken.balanceOf(add1.address)).to.equal(50)
await hardhatToken.connect(add1).transfer(add2.address,50);
expect(await hardhatToken.balanceOf(add2.address)).to.equal(50)
})
// it("转账余额不足",async function(){
// const initAmount = await hardhatToken.balanceOf(owner.address);
// await expect( hardhatToken.connect(add1).transfer(owner.address , 100)).to.be.revertedWith("not engouth");
// expect(hardhatToken.balanceOf(owner.address)).to.equal(initAmount);
// })
});
describe("转账更新交易金额",function(){
it("交易逻辑测试",async function(){
const initAmount = await hardhatToken.balanceOf(owner.address);
await hardhatToken.transfer(add1.address , 50);
await hardhatToken.transfer(add2.address , 100);
expect(await hardhatToken.balanceOf(add1.address)).to.equal(50);
expect(await hardhatToken.balanceOf(add2.address)).to.equal(100);
expect(await hardhatToken.balanceOf(owner.address)).to.equal(initAmount-150);
});
});
it("Deployment should assign the total supply of tokens to the owner", async function() {
const [owner] = await ethers.getSigners();
const Token = await ethers.getContractFactory("Token");
const hardhatToken = await Token.deploy();
const ownerBalance = await hardhatToken.balanceOf(owner.address);
expect(await hardhatToken.totalSupply()).to.equal(ownerBalance);
});
});
测试成功
admin@XZA000428468 hardhat-tutorial % npx hardhat test
Token contract
totalSupply is 1000000
totalSupply is 1000000
✔ Deployment should assign the total supply of tokens to the owner (129ms)
部署
totalSupply is 1000000
✔ 检查owner是否正确
totalSupply is 1000000
✔ 检查总量是否正确
交易
totalSupply is 1000000
sender balance is 1000000 token
try to send 50 token to 0x70997970c51812dc3a010c7d01b50e0d17dc79c8
sender balance is 50 token
try to send 50 token to 0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc
✔ 转账账户 (55ms)
转账更新交易金额
totalSupply is 1000000
sender balance is 1000000 token
try to send 50 token to 0x70997970c51812dc3a010c7d01b50e0d17dc79c8
sender balance is 999950 token
try to send 100 token to 0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc
✔ 交易逻辑测试 (64ms)
5 passing (1s)
6. 用 Hardhat Network 调试
Solidity console.log
在Hardhat Network上运行合约和测试时,你可以在Solidity代码中调用console.log()
打印日志信息和合约变量。 你必须先从合约代码中导入**Hardhat **的console.log
再使用它。
pragma solidity ^0.6.0;
import "hardhat/console.sol";
contract Token {
//...
}
7. 部署到真实网络
在项目根目录的目录下创建一个新的目录scripts
,并将以下内容粘贴到 deploy.js
文件中:
async function main() {
const [deployer] = await ethers.getSigners();
console.log(
"Deploying contracts with the account:",
deployer.address
);
console.log("Account balance:", (await deployer.getBalance()).toString());
const Token = await ethers.getContractFactory("Token");
const token = await Token.deploy();
console.log("Token address:", token.address);
}
main()
.then(() => process.exit(0))
.catch(error => {
console.error(error);
process.exit(1);
});
运行到hardhat 内置网络
$ npx hardhat run scripts/deploy.js
Deploying contracts with the account: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266
Account balance: 10000000000000000000000
Token address: 0x5FbDB2315678afecb367f032d93F642f64180aa3
部署到远程网络
要部署到诸如主网或任何测试网之类的线上网络,你需要在hardhat.config.js
文件中添加一个network
条目。 在此示例中,我们将使用Ropsten,但你可以类似地添加其他网络:
require("@nomiclabs/hardhat-waffle");
// Go to https://www.alchemyapi.io, sign up, create
// a new App in its dashboard, and replace "KEY" with its key
const ALCHEMY_API_KEY = "KEY";
// Replace this private key with your Ropsten account private key
// To export your private key from Metamask, open Metamask and
// go to Account Details > Export Private Key
// Be aware of NEVER putting real Ether into testing accounts
const ROPSTEN_PRIVATE_KEY = "YOUR ROPSTEN PRIVATE KEY";
module.exports = {
solidity: "0.7.3",
networks: {
ropsten: {
url: `https://eth-ropsten.alchemyapi.io/v2/${ALCHEMY_API_KEY}`,
accounts: [`0x${ROPSTEN_PRIVATE_KEY}`]
}
}
};
ALCHEMY_API_KEY :在Alchemy网络上注册自己的api_key ROPSTEN_PRIVATE_KEY:你账号的私钥
最后运行:
npx hardhat run scripts/deploy.js --network ropsten
部署成功:
admin@XZA000428468 hardhat-tutorial % npx hardhat run scripts/deploy.js --network ropsten
Deploying contracts with the account: 0xb2D709be86ADc8520192872197c36886A553cd30
Account balance: 1091208822845060326
Token address: 0xA94AE7e3b03Dd4d5F3a346DFFB3CFbACf847e877
去ether测试网络上查看部署的代币