一、简介
智能合约的开发、部署工具。
(一) 优势:
- 自动提供一个虚拟区块链以及虚拟私钥。
- 一系列开发工具,如单测,自定义任务等完善的本地环境。
- 非常适合运行测试。
(二) Hardhat官方教程
- 教程包含项目新建,合约编写,单测,编译部署等。
- 教程中的 npx 可以替换成yarn
- 教程中的编译部署和demo hardhat-simple-storage-fcc的部署方法不同
二、hardhat-simple-storage-fcc学习
(一) hardhat-ethers
hardhat-ethers 和 ethers 的区别,前者知道 contracts文件架的存在、也知道合约已经被编译好了。
所以要这么引入:
// imports
const { ethers, run, network } = require("hardhat")
(二) 编译合约
yarn hardhat compile
与我们用 ethers js 和 solc js所做的事情非常相似。
生成cache文件夹,加速访问solidity文件。
生成 artifacts文件夹,包含所有我们编译的代码信息。
yarn hardhat clearn
- 删除artifacts。
- 清除缓存。
(三)部署合约
yarn hardhat run scripts/deploy.js
1.部署时 默认 hardhat 本地虚拟网络。
2.执行该脚本时,hardhat都会自动为你重新编译一次。
module.exports = {
//如果没有特别指定某个网络,它都会自动使用这个虚拟机的hardhat network,
//并且这个虚拟的network,会自动为你提供一个RPC URL 和一个私钥
//
defaultNetwork: "hardhat",
}
1) 默认网络
有了这个网络标识,在不同链,不同私钥之间切换就会变得非常容易。
不传,默认就是传入hardhat
# npx 可以替换成yarn
yarn hardhat run scripts/deploy.js --network hardhat
2) sepolia网络
添加一个网络,主要是三要数: RPC URL、PRIVATE_KEY、CHAINID
sepolia 的chainId: 11155111,
每一个EVM基础网络都有一个新的chainID
EVM网络: Solidity可以在其上面运行。
这里使用了 alchemy 提供EVM节点。
const SEPOLIA_RPC_URL =
process.env.SEPOLIA_RPC_URL ||
"https://eth-sepolia.g.alchemy.com/v2/your-api-key"
const PRIVATE_KEY =
process.env.PRIVATE_KEY ||
"0x11ee3108a03081fe260ecdc106554d09d9d1209bcafd46942b10e02943effc4a"
module.exports = {
defaultNetwork: "hardhat",
networks: {
hardhat: {},
sepolia: {
url: SEPOLIA_RPC_URL,
accounts: [PRIVATE_KEY],
chainId: 11155111,
},
},
}
.env添加配置:
必须有一个真实的测试网络上的RPC_URL 以及私钥。
PRIVATE_KEY=234523425asdfasdfa
SEPOLIA_RPC_URL=https://eth-sepolia.g.alchemy.com/v2/your-api-key
然后就可以运行时指定:
yarn hardhat run scripts/deploy.js --network sepolia
(四) 部署后自动验证
基于 etherscan api,编写 的hardhat 插件,进行自动验证。
效果等同于 etherscan 网页上 对合约进行 verify and publish 。
1). hardhat-verify
插件安装后,使用文档
npm install --save-dev @nomicfoundation/hardhat-verify
yarn add --dev @nomicfoundation/hardhat-verify
2).etherscan apiKey
注册 etherscan--> Create API Key -->填入 .env文件。
3.)命令行执行测试(可选)
#npx 等于 yarn
npx hardhat verify --network mainnet DEPLOYED_CONTRACT_ADDRESS "Constructor argument 1"
4).程序化验证
对scripts/deploy.js 进行适当改造
a. 创建验证函数
const verify = async (contractAddress, args) => {
console.log("Verifying contract...")
try {
await run("verify:verify", {
address: contractAddress,
constructorArguments: args,
})
} catch (e) {
if (e.message.toLowerCase().includes("already verified")) {
console.log("Already Verified!")
} else {
console.log(e)
}
}
}
run允许我们运行所有hardhat任务
b. 测试网络时执行验证
同 etherscan上 手工进行 verify and publish 环节。
验证与发布
// 如果在本地 hardhat网络部署时,去执行etherscan是没有意义的。
// what happens when we deploy to our hardhat network?
if (network.config.chainId === 11155111 && process.env.ETHERSCAN_API_KEY) {
console.log("Waiting for block confirmations...")
// Not functionable in version 6^ ethers ----->
await simpleStorage.deploymentTransaction().wait(6)
await verify(simpleStorage.target, [])
//______________________________________________
}
5). 与合约交互
const currentValue = await simpleStorage.retrieve()
console.log(`Current Value is: ${currentValue}`)
// Update the current value
const transactionResponse = await simpleStorage.store(7)
await transactionResponse.wait(1)
const updatedValue = await simpleStorage.retrieve()
console.log(`Updated Value is: ${updatedValue}`)
1.部署 在 hardhat:
yarn hardhat run scripts/deploy.js
执行结果正常,且没有验证相关的信息。
2.部署 在 sepolia :
yarn hardhat run scripts/deploy.js --network sepolia
(五) 自定义hardhat任务
1)查看任务
$ yarn hardhat
yarn run v1.22.22
$ D:\code_solidity\hh-fcc\hardhat-simple-storage-fcc\node_modules\.bin\hardhat
Hardhat version 2.8.3
Usage: hardhat [GLOBAL OPTIONS] <TASK> [TASK OPTIONS]
GLOBAL OPTIONS:
--config A Hardhat config file.
--emoji Use emoji in messages.
--help Shows this message, or a task's help if its name is provided
--max-memory The maximum amount of memory that Hardhat can use.
--network The network to connect to.
--show-stack-traces Show stack traces.
--tsconfig A TypeScript config file.
--verbose Enables Hardhat verbose logging
--version Shows hardhat's version.
AVAILABLE TASKS:
block-number Prints the current block number
check Check whatever you need
clean Clears the cache and deletes all artifacts
compile Compiles the entire project, building all artifacts
console Opens a hardhat console
coverage Generates a code coverage report for tests
flatten Flattens and prints contracts and their dependencies
gas-reporter:merge
help Prints this message
node Starts a JSON-RPC server on top of Hardhat Network
run Runs a user-defined script after compiling the project
test Runs mocha tests
verify Verifies a contract on Etherscan or Sourcify
To get help for a specific task run: npx hardhat help [task]
Done in 3.19s.
2)block-number任务实现
1.任务源码
tasks/block-number.js
const { task } = require("hardhat/config")
task("block-number", "Prints the current block number").setAction(
// const blockTask = async function() => {}
// async function blockTask() {}
async (taskArgs, hre) => {
const blockNumber = await hre.ethers.provider.getBlockNumber()
console.log(`Current block number: ${blockNumber}`)
}
)
module.exports = {}
2.导入:
hardhat.config.js
//导入后才能在yarn hardhat任务列表中显示
require("./tasks/block-number")
3.输出:
hardhat每次运行都会重置,所以 blocknumber为0,测试网会是很大的数字。
$ yarn hardhat block-number
yarn run v1.22.22
$ D:\code_solidity\hh-fcc\hardhat-simple-storage-fcc\node_modules\.bin\hardhat block-number
Current block number: 0
Done in 3.99s.
三、hardhat本地节点
1)启动本地网络节点
与ganache完全相同,只不过运行于我们的终端1里:
yarn hardhat node
Started HTTP and WebSocket JSON-RPC server at http://127.0.0.1:8545/
Accounts
========
WARNING: These accounts, and their private keys, are publicly known.
Any funds sent to them on Mainnet or any other live network WILL BE LOST.
Account #0: 0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266 (10000 ETH)
Private Key: 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
2)部署合约到本地节点
这个节点 并没有运行于 hardhat network,可以新建个终端2和它交互
1.添加本地网络
把localhost的url和chainid (和hardhat一样) 填上。
不需要象 sepolia 一样传入 accounts,因为本地网络都是自己生成accounts,类似ganache。
module.exports = {
defaultNetwork: "hardhat",
networks: {
hardhat: {},
sepolia: {
url: SEPOLIA_RPC_URL,
accounts: [PRIVATE_KEY],
chainId: 11155111,
},
localhost: {
url: "http://localhost:8545",
chainId: 31337,
},
},
...
}
yarn hardhat run scripts/deploy.js --network localhost
2.终端1
可以看到如下输出:
从from 地址看出 使用了 account0,进行合约的部署
Account #0: 0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266 (10000 ETH)
web3_clientVersion
eth_chainId (2)
eth_accounts
eth_blockNumber
eth_chainId (2)
eth_estimateGas
eth_getBlockByNumber
eth_feeHistory
eth_sendTransaction
Contract deployment: SimpleStorage
Contract address: 0x5fbdb2315678afecb367f032d93f642f64180aa3
Transaction: 0x25cb0a6fcef2fda9e8e5041d3fa58d3ee6766a32f8ab49ce328b4ea853fe069e
From: 0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266
Value: 0 ETH
Gas used: 463682 of 463682
Block #1: 0x51387bb4997512bf6bfc1a4a0eb6f459cd93ca9155562e25cf7fe882c03752a0
eth_chainId
eth_getTransactionByHash
eth_chainId
eth_getTransactionReceipt
eth_chainId
eth_call
Contract call: SimpleStorage#retrieve
From: 0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266
To: 0x5fbdb2315678afecb367f032d93f642f64180aa3
eth_chainId
eth_estimateGas
eth_feeHistory
eth_sendTransaction
Contract call: SimpleStorage#store
Transaction: 0x7705d6421a42c95a0e34825319c8d7d6d632512a564a27f94f91c343a652357f
From: 0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266
To: 0x5fbdb2315678afecb367f032d93f642f64180aa3
Value: 0 ETH
Gas used: 43724 of 43724
Block #2: 0x495b4190b154cc11387c09bf93e22e339eb19a441d1bcd3384263a2c04139fdd
eth_chainId
eth_getTransactionByHash
eth_chainId
eth_getTransactionReceipt
eth_chainId
eth_call
Contract call: SimpleStorage#retrieve
From: 0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266
To: 0x5fbdb2315678afecb367f032d93f642f64180aa3
3.终端2
可以看到如下输出:
Deploying contract...
Current Value is: 0
Updated Value is: 7
Done in 17.64s.
四、hardhat控制台
1)连入本地节点
#有时会有问题,不建议
#yarn hardhat console --network localhost
#改成如下
npx hardhat console --network localhost
之后就可以在控制台里执行 deploy.js 里面的所有操作,
不需要require(”hardhat“)这些导入操作,因为hardhat已经自动导入控制台。
临时节点
npx hardhat console --network hardhat
测试节点
npx hardhat console --network sepolia
2)执行操作
把之前deploy.js 里的部署操作,依次复制进控制台如下:
$ npx hardhat console --network localhost
Welcome to Node.js v16.20.0.
Type ".help" for more information.
> const SimpleStorageFactory = await ethers.getContractFactory("SimpleStorage")
undefined
> const simpleStorage = await SimpleStorageFactory.deploy()
undefined
> const currentValue = await simpleStorage.retrieve()
undefined
> currentValue
BigNumber { value: "0" }
> const transactionResponse = await simpleStorage.store(7)
undefined
> currentValue = await simpleStorage.retrieve()
BigNumber { value: "7" }
五、单元测试
hardhat测试使用的是Mocha框架,一个基于js 的框架,用于运行我们的测试。
你甚至可以直接用Solidity来编写测试,如果你想的话。主流的还是用js编写测试代码
1)源码范例
test/test-deploy.js
const { ethers } = require("hardhat")
const { expect, assert } = require("chai")
// describe("SimpleStorage", () => {})
describe("SimpleStorage", function () {
// let simpleStorageFactory
// let simpleStorage
let simpleStorageFactory, simpleStorage
beforeEach(async function () {
simpleStorageFactory = await ethers.getContractFactory("SimpleStorage")
simpleStorage = await simpleStorageFactory.deploy()
})
it("Should start with a favorite number of 0", async function () {
const currentValue = await simpleStorage.retrieve()
const expectedValue = "0"
// assert
// expect
assert.equal(currentValue.toString(), expectedValue)
// expect(currentValue.toString()).to.equal(expectedValue)
})
it("Should update when we call store", async function () {
const expectedValue = "7"
const transactionResponse = await simpleStorage.store(expectedValue)
await transactionResponse.wait(1)
const currentValue = await simpleStorage.retrieve()
assert.equal(currentValue.toString(), expectedValue)
})
// Extra - this is not in the video
it("Should work correctly with the people struct and array", async function () {
const expectedPersonName = "Patrick"
const expectedFavoriteNumber = "16"
const transactionResponse = await simpleStorage.addPerson(
expectedPersonName,
expectedFavoriteNumber
)
await transactionResponse.wait(1)
const { favoriteNumber, name } = await simpleStorage.people(0)
// We could also do it like this
// const person = await simpleStorage.people(0)
// const favNumber = person.favoriteNumber
// const pName = person.name
assert.equal(name, expectedPersonName)
assert.equal(favoriteNumber, expectedFavoriteNumber)
})
})
2)运行测试
yarn hardhat test
#或者
npx hardhat test
3)输出如下测试:
$ yarn hardhat test
yarn run v1.22.22
$ D:\code_solidity\hh-fcc\hardhat-simple-storage-fcc\node_modules\.bin\hardhat test
SimpleStorage
√ Should start with a favorite number of 0
√ Should update when we call store
√ Should work correctly with the people struct and array
3 passing (2s)
Done in 11.06s.
4)运行指定测试
1.only()
it.only()
describe.only()
2.npx hardhat test --grep xx
https://ethereum.stackexchange.com/questions/113296/run-a-single-hardhat-test
只适用于 hardhat 2.9 以上
#单独执行这个测试 it("Should update when we call store"
npx hardhat test --grep store
5)gas reporter
1.安装扩展
yarn add hardhat-gas-reporter --dev
2.添加配置
hardhat.config.js
为了把gas "Gwei" 转换成 "USD", 需要去https://pro.coinmarketcap.com/申请这个key,配到.env.
如果key不合法,则usd 那一列 没有数据.COINMARKETCAP_API_KEY=asdfasdfasdfasdfasdfasdfasdf
require("hardhat-gas-reporter")
module.exports = {
gasReporter: {
//这行可以注释调,不估算 gas
enabled: true,
currency: "USD",
outputFile: "gas-report.txt",
noColors: true,
//这行可以注释调,不处理美元
coinmarketcap: COINMARKETCAP_API_KEY,
//可以估算多种EVM链的gas
//token:"MATIC"
},
}
3.输出报告
yarn hardhat test
在运行完测试之后,它会自动运行这个"gas reporter",
并输出报告在 gas-report.txt
6)solidity-coverage(HardHat插件)
npm install --save-dev solidity-coverage
添加到配置:
require("solidity-coverage")
执行
yarn hardhat coverage
输出:
--------------------|----------|----------|----------|----------|----------------|
File | % Stmts | % Branch | % Funcs | % Lines |Uncovered Lines |
--------------------|----------|----------|----------|----------|----------------|
contracts\ | 100 | 100 | 100 | 100 | |
SimpleStorage.sol | 100 | 100 | 100 | 100 | |
--------------------|----------|----------|----------|----------|----------------|
All files | 100 | 100 | 100 | 100 | |
--------------------|----------|----------|----------|----------|----------------|
六、Hardhat waffle
Hardhat waffle是一个用于waffle 测试框架的一个插件,有更多的测试,断言功能.