Hardhat Notes
Contents
TL;DR
The execution environment for the console is the same as for tasks, scripts and tests.
Basics
Hardhat is a development environment for Ethereum software.
Hardhat Runner is the main component you interact with when using Hardhat.
- It’s a flexible and extensible task runner** that helps you manage and automate the recurring tasks inherent to developing smart contracts and dApps.
- Hardhat Runner is designed around the concepts of tasks and plugins.
- Every time you’re running Hardhat from the command-line, you’re running a task.
- For example,
npx hardhat compile
runs the built-in compile task. - The list of available tasks includes the built-in ones and also those that came with any installed plugins.
- For example,
- Tasks can call other tasks, allowing complex workflows to be defined.
- Users and plugins can override existing tasks, making those workflows customizable and extendable.
- Every time you’re running Hardhat from the command-line, you’re running a task.
The Hardhat Runtime Environment, or HRE for short, is an object in the global scope containing all the functionality that Hardhat exposes when running a task, test or script.
- When you require Hardhat (
const hardhat = require("hardhat")
) you’re getting an instance of the HRE. - By default, the HRE gives you programmatic access to the task runner and the config system, and exports an EIP1193-compatible Ethereum provider.
- Plugins can extend the HRE.
- For example,
hardhat-ethers
adds aEthers.js
instance to it, making it available to tasks, tests and scripts.
- For example,
- During initialization, the Hardhat configuration file essentially constructs a list of things to be added to the HRE. This includes tasks, configs and plugins.
- Plugins can extend the HRE.
- Before running a task, test or script, Hardhat injects the HRE into the global scope, turning all of its fields into global variables. When the task execution is completed, these global variables are removed, restoring their original value, if they had one.
- You can import the config API explicitly when defining your tasks, and receive the HRE explicitly as an argument to your actions.
- The HRE can be used from any JavaScript or TypeScript file. To do so, you only have to import it with
require("hardhat")
.- You can do this to keep more control over your development workflow, create your own tools, or to use Hardhat with other dev tools from the node.js ecosystem.
- Hardhat has been designed as a library, allowing you to get creative and build standalone CLI tools that access your development environment.
- Using HRE explicitly can take advantage of your editor’s autocomplete.
- Extend HRE can be done by adding
extendEnvironment((hre) => {})
tohardhat.config.js
. - In reality, Hardhat is the HRE.
Basic workflows:
-
Initialize a new project using
yarn hardhat
.# structure of the initialized project contracts/ scripts/ test/ hardhat.config.js
-
Compile contracts
-
Test contracts
-
Deploying contracts
-
Verify contracts
-
Verify contracts
Configuration
When Hardhat is run, it searches for the closest hardhat.config.js
file starting from the Current Working Directory.
- This file normally lives in the root of your project.
- An empty
hardhat.config.js
is enough for Hardhat to work.
module.exports = {
defaultNetwork: "rinkeby",
networks: {
goerli: {
url: "...",
accounts: [privateKey1, privateKey2]
}
},
solidity: {},
paths: {},
mocha: {}
}
- The
networks
config field is an optional object where network names map to their configuration.accounts
field controls which accounts Hardhat uses.- It can use the node’s accounts (by setting it to
"remote"
), a list of local accounts (by setting it to an array of hex-encoded private keys), or use an HD Wallet. - Default value:
"remote"
.
- It can use the node’s accounts (by setting it to
The configuration file is always executed on startup before anything else happens.
- Hardhat’s config file will always run before any task, so you can use it to integrate with other tools, like importing
@babel/register
.
Hardhat Network
There are two kinds of networks in Hardhat: JSON-RPC based networks, and the built-in Hardhat Network.
- You can customize which network is used by default when running Hardhat by setting the config’s
defaultNetwork
field. If you omit this config, its default value is"hardhat"
.
By default, Hardhat will spin up a new in-memory instance of Hardhat Network on startup. It’s also possible to run Hardhat Network in a standalone fashion (using yarn hardhat node
) so that external clients can connect to it.
{
localhost: {
url: "http://127.0.0.1:8545"
},
hardhat: {
// See its defaults
}
}
- The built-in with Hardhat Network runs as either an in-process (
hardhat
) or stand-alone (localhost
) daemon, servicing JSON-RPC and WebSocket requests. - It’s backed by the
@ethereumjs/vm
EVM implementation. - When Hardhat executes your tests, scripts or tasks, an in-process Hardhat Network node is started automatically, and all of Hardhat’s plugins (ethers.js, web3.js, Waffle, Truffle, etc) will connect directly to this node’s provider.
Compile
yarn hardhat compile
yarn hardhat clean
Hardhat always runs the compile
task when it’s invoked via yarn hardhat run
.
- In a standalone fashion you may want to call compile (
hre.run("compile")
) manually to make sure everything is compiled.
Test
Some useful functionality:
- The built-in Hardhat Network as the development network to test on, along with the Hardhat Network Helpers library to manipulate this network.
- Mocha as the test runner, Chai as the assertion library, and the Hardhat Chai Matchers to extend Chai with contracts-related functionality.
- The
ethers.js
library to interact with the network and with contracts.
GAS_REPORT=true yarn hardhat test
# run specific tests
yarn hardhat test --grep store
yarn hardhat test test/my-tests.js
yarn hardhat coverage
In the case of await expect(...)
, we were comparing two values in a synchronous way; the inner
await is just there to wait for the value to be retrieved.
In the case of await expect(lock.withdraw()).to.be.revertedWith("You can't withdraw yet");
the whole assertion is async because it has to wait until the transaction is mined.
- This means that the
expect
call returns a promise that we have to await.
By default, deployments and function calls are done with the first configured account. We can use the .connect
method of the contract to call the function with a different account.
A fixture is a function that sets up the chain to some desired state.
The loadFixture
helper in the Hardhat Network Helpers receives a fixture as the its argument.
- The first time
loadFixture
is called, the fixture is executed. - The second time, instead of executing the fixture again,
loadFixture
will reset the state of the network to the point where it was right after the fixture was executed. - This is faster, and it undoes any state changes done by the previous test.
Deploy
There are no official plugins that implement a deployment system for Hardhat yet.
hardhat deploy
: Deploy contracts using hardhat-deploy
plugin.
- 执行所有或符合条件的部署脚本。
# module.exports.tags = ["all", "storage"]
yarn hardhat deploy --tags all --network goerli
yarn hardhat run --network goerli scripts/deploy.js
deploys contracts using scripts.
Verify Contracts
Verifying a contract means making its source code public, along with the compiler settings you used, which allows anyone to compile it and compare the generated bytecode with the one that is deployed on-chain.
Tasks and Scripts
At its core, Hardhat is a task runner that allows you to automate your development workflow. It comes with some
built-in tasks
, like compile
and test
.
-
Tasks in Hardhat are asynchronous JavaScript functions that get access to the Hardhat Runtime Environment, which exposes its configuration and parameters, as well as programmatic access to other tasks and any plugin objects that may have been injected.
-
You can add your own custom tasks as well.
task("block_number", "Prints the current block").setAction( async (taskArgs, hre) => { const blockNumber = await hre.ethers.provider.getBlockNumber() console.log(`current block number: ${blockNumber}`) } ) // yarn hardhat block_number
- Task creation code can go in
hardhat.config.js
, or whatever your configuration file is called.- 如果不需要利用配置文件会被首先执行这一性质,task 也可以定义在任意的文件中。
- Task creation code can go in
-
You can also override existing tasks, which allows you to change how different parts of Hardhat work.
Scripts can take advantage of the Hardhat Runtime Environment (HRE) to access all of Hardhat’s functionality, including the task runner.
- Everything that is available in the Hardhat Runtime Environment is also globally available in the scripts.
// scripts/block_number.js
async function main() {
const blockNumber = await ethers.provider.getBlockNumber()
console.log(`current block number: ${blockNumber}`)
}
main()
.then(() => process.exit(0))
.catch((e) => {
console.error(e)
process.exit(1)
})
// yarn hardhat run scripts/block_number.js --network goerli
Choosing between tasks and scripts:
- If you want to automate a workflow that requires no parameters, a script is probably the best choice.
- If the workflow you are automating requires some parameters, consider creating a Hardhat task.
- If you feel Hardhat’s parameter handling is falling short of your needs, you should write a script.
- Just import the Hardhat runtime environment explicitly, use your own argument parsing logic (e.g. using
yargs
), and run it as a standalone Node.js script.
- Just import the Hardhat runtime environment explicitly, use your own argument parsing logic (e.g. using
- If you need to access the Hardhat Runtime Environment from another tool which has its own CLI, like
jest
orndb
, you should write a script.- Make sure to import the Hardhat runtime environment explicitly, so it can be run with that tool instead of Hardhat’s CLI.
Hardhat Console
Hardhat comes built-in with an interactive JavaScript console.
-
The
compile
task will be called before opening the console prompt, but you can skip this with the--no-compile
parameter. -
The configuration has been processed, and the Hardhat Runtime Environment has been initialized and injected into the global scope.
yarn hardhat console // Welcome to Node.js v18.12.1. // Type ".help" for more information. > config // access the config object > ethers // access the ethers object; provided by @nomiclabs/hardhat-ethers
-
The console has the handy history feature.
- The Hardhat console is just an instance of a Node.js console, so anything you use in Node.js will also work.
-
To make things easier, Hardhat’s console supports top-level
await
statements (e.g.console.log(await ethers.getSigners()
).
Local Development
# starts a JSON-RPC server on top of Hardhat Network
# 会自动执行部署脚本
yarn hardhat node
# opens a hardhat console
yarn hardhat console --network local
Plugins
Plugins are bits of reusable configuration.
- Anything that you can do in a plugin can also be done in your config file.
- You can test your ideas in a config file and then move them into a plugin when ready.
When developing a plugin the main tools available to integrate new functionality are extending the Hardhat Runtime Environment, extending the Hardhat config, defining new tasks and overriding existing ones, which are all configuration actions achieved through code.
- Some examples of things you could achieve by creating a plugin are: running a linter when the
check
task runs, using different compiler versions for different files or generating an UML diagram for your contracts.
Keeping startup time short is vital to give a good user experience. To do so, Hardhat and its plugins delay any slow import or initialization until the very last moment.
yarn
Running yarn <command> [<args>]
will run the command, if it is matching a locally installed CLI.
-
包括当前路径下的
node_modules/.bin
下面的二进制。rm node_modules/.bin/hardhat yarn bin hardhat # error Couldn't find a binary named "hardhat" # version from yarn.lock yarn add hardhat@^2.10.2 yarn bin hardhat # node_modules/.bin/hardhat
Cheat Sheet
- This plugin extends the Hardhat Runtime Environment by adding 4 fields
getNamedAccounts: () => Promise<{ [name: string]: string }>
: a function returning an object whose keys are names and values are addresses. It is parsed from thenamedAccounts
configuration.
hardhat-deploy-ethers
is a fork of@nomiclabs/hardhat-ethers
.- Other plugin might have an hardcoded dependency on
@nomiclabs/hardhat-ethers
. The best way to installhardhat-deploy-ethers
and ensure compatibility is using alias.yarn add --dev @nomiclabs/hardhat-ethers@npm:hardhat-deploy-ethers
- 其它 plugin 可能硬编码了对
@nomiclabs/hardhat-ethers
引用,所以要保持@nomiclabs/hardhat-ethers
对外的引用方式不变,但实际的实现指向的是hardhat-deploy-ethers
。
- This plugins adds an
ethers
object to the Hardhat Runtime Environment. This object has add some extra hardhat-deploy specific functionalities.
References