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.
    • Tasks can call other tasks, allowing complex workflows to be defined.
    • Users and plugins can override existing tasks, making those workflows customizable and extendable.

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 a Ethers.js instance to it, making it available to tasks, tests and scripts.
    • During initialization, the Hardhat configuration file essentially constructs a list of things to be added to the HRE. This includes tasks, configs and plugins.
  • 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) => {}) to hardhat.config.js.
  • In reality, Hardhat is the HRE.

Basic workflows:

  1. Initialize a new project using yarn hardhat.

    # structure of the initialized project
    contracts/
    scripts/
    test/
    hardhat.config.js
    
  2. Compile contracts

  3. Test contracts

  4. Deploying contracts

  5. Verify contracts

  6. 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".

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.

https://hardhat.org/hardhat-network/docs/reference

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 也可以定义在任意的文件中。
  • 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.
  • If you need to access the Hardhat Runtime Environment from another tool which has its own CLI, like jest or ndb , 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

hardhat-deploy :

  • 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 the namedAccounts configuration.

hardhat-deploy-ethers

  • 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 install hardhat-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