|
| 1 | +--- |
| 2 | +prev: false |
| 3 | +--- |
| 4 | + |
| 5 | +# Hardhat 3 Alpha |
| 6 | + |
| 7 | +## Overview |
| 8 | + |
| 9 | +\<Intro to the public alpha tutorial. Summary of the content of this tutorial. Familiarity with HH2 assumptions. State of HH3.> |
| 10 | + |
| 11 | +\<Feedback welcome> |
| 12 | + |
| 13 | +## Initializing a project |
| 14 | + |
| 15 | +- Create an empty directory |
| 16 | +- Initialize an npm/pnpm project |
| 17 | +- Make it ESM. |
| 18 | + - Short explanation about ESM and link to deep dive |
| 19 | +- Run hardhat init |
| 20 | +- Select the first option, Node test runner + viem |
| 21 | + - Mocha and ethers will continue to be supported |
| 22 | + - Short explanation about node test runner and link to deep dive |
| 23 | +- Install the dependencies |
| 24 | +- All set up now, let’s see what new features are in store |
| 25 | + |
| 26 | + |
| 27 | +## Solidity tests |
| 28 | + |
| 29 | +One of Hardhat 3's new features is support for writing tests in Solidity. You can run this project's Solidity tests with the `test solidity` task: |
| 30 | + |
| 31 | +``` |
| 32 | +$ npx hardhat test solidity |
| 33 | +<output> |
| 34 | +``` |
| 35 | + |
| 36 | +A Solidity tests is a normal Solidity contract that will be executed in a special way. Let's see the example in this project. The contract we want to test is in the `contracts/Counter.sol` file: |
| 37 | + |
| 38 | +```solidity |
| 39 | +contract Counter { |
| 40 | + uint public x; |
| 41 | +
|
| 42 | + function inc() public { |
| 43 | + x++; |
| 44 | + } |
| 45 | +
|
| 46 | + function incBy(uint by) public { |
| 47 | + require(by > 0, "Counter: by must be greater than 0"); |
| 48 | + x += by; |
| 49 | + } |
| 50 | +} |
| 51 | +``` |
| 52 | + |
| 53 | +And this is the content of the `contracts/Counter.t.sol` Solidity test file: |
| 54 | + |
| 55 | +```solidity |
| 56 | +import { Test } from "forge-std/Test.sol"; |
| 57 | +
|
| 58 | +import "./Counter.sol"; |
| 59 | +
|
| 60 | +contract CounterTest is Test { |
| 61 | + Counter counter; |
| 62 | +
|
| 63 | + function setUp() public { |
| 64 | + counter = new Counter(); |
| 65 | + } |
| 66 | +
|
| 67 | + function test_InitialValue() public view { |
| 68 | + require(counter.x() == 0, "Initial value should be 0"); |
| 69 | + } |
| 70 | +
|
| 71 | + function test_Inc() public { |
| 72 | + counter.inc(); |
| 73 | + require(counter.x() == 1, "Value after calling inc should be 1"); |
| 74 | + } |
| 75 | +
|
| 76 | + function testFuzz_Inc(uint8 x) public { |
| 77 | + for (uint8 i = 0; i < x; i++) { |
| 78 | + counter.inc(); |
| 79 | + } |
| 80 | + require(counter.x() == x, "Value after calling inc x times should be x"); |
| 81 | + } |
| 82 | +
|
| 83 | + function test_IncBy() public { |
| 84 | + counter.incBy(5); |
| 85 | + require(counter.x() == 5, "Value after calling incBy(5) should be 5"); |
| 86 | + } |
| 87 | +
|
| 88 | + function test_IncByZero() public { |
| 89 | + vm.expectRevert("Counter: 'by' must be greater than 0"); |
| 90 | + counter.incBy(0); |
| 91 | + } |
| 92 | +} |
| 93 | +``` |
| 94 | + |
| 95 | +The `CounterTest` contract represents a suite of tests. All functions that start with `test` will be considered test functions. This contract will be deployed and all its tests functions will be called. If a test function reverts, that test will be considered failed. There's also a `setUp` function which will be called before each test. |
| 96 | + |
| 97 | +Functions that start with `test` and don't have any parameters are unit tests. But if a test function has parameters, it's considered a fuzz test. These tests will be called multiple times with different, random values as arguments. If any of these calls reverts, the test will be considered failed and the arguments that produced the failure will be printed. |
| 98 | + |
| 99 | +Another thing that is different between normal Solidity code and Solidity tests is the possibility of using cheatcodes. These are special functions that can be called during a test execution to modify the state or behavior of the EVM in non-standard ways. For example, in `test_IncByZero` we are using the `vm.expectRevert` cheatcode. When used, the next call will be expected to revert and, if it doesn't, the test will fail. There are many other cheatcodes. For example, you can change the value of `block.number` by using the `vm.roll` cheatcode. |
| 100 | + |
| 101 | +Hardhat 3's Solidity tests are compatible with Foundry-style tests. You can write unit, fuzz, and invariant tests, and all testing-related cheatcodes are supported. You can write tests using any Solidity testing library, like forge-std or PRBTest. |
| 102 | + |
| 103 | +You can learn more about Hardhat 3's Solidity tests [here](/hh3/under-the-hood/solidity-tests). |
| 104 | + |
| 105 | +## TypeScript tests |
| 106 | + |
| 107 | +While Solidity tests are excellent for unit-like testing, there are scenarios where they fall short. For example: |
| 108 | + |
| 109 | +- Complex test setups can become unwieldy in Solidity due to language constraints. |
| 110 | +- Some tests require interaction with a real blockchain and real transactions. Although you can simulate this using cheatcodes, doing so can be cumbersome and error-prone. |
| 111 | + |
| 112 | +To address these limitations, Hardhat 3, like its predecessor, supports writing tests in TypeScript (or JavaScript). In the sample project, you can run TypeScript-based tests using the `test js` task. |
| 113 | + |
| 114 | +\<example> |
| 115 | + |
| 116 | +With TypeScript, you can easily manage advanced test setups, perform assertions, and leverage the full capabilities of modern JavaScript tooling, making it the preferred choice for integration and more complex tests. |
| 117 | + |
| 118 | +Another major improvement in Hardhat 3 is its flexibility with test runners. The sample project uses the **Node.js test runner**, but you can now use any JavaScript test runner, including Mocha (with our plugin for backward compatibility), Jest, or Vitest. Writing plugins for additional test runners is also feasible, and we may release official support for them in the future. |
| 119 | + |
| 120 | +To run all your tests—both Solidity and TypeScript—simply execute the `test` task: |
| 121 | + |
| 122 | +\<output> |
| 123 | + |
| 124 | +## Multichain support |
| 125 | + |
| 126 | +- There is an implicit assumption across the Ethereum development ecosystem that you are developing for mainnet, but then you might actually deploy in some other chain, like Optimism or Arbitrum, and hope for the best. This is not ideal. |
| 127 | +- Hardhat 3 lets you select for which chain you are developing, by selecting a certain chain type that you want to simulate locally. Initially it supports three chain types: ethereum mainnet, optimism, and a generic chain type that works as a sort of common denominator and it’s the closest one to the Hardhat 2 behavior. More chain types will be added in the future. |
| 128 | +- Example: run a script that estimates the Optimism L1 fee. |
| 129 | +- That script also illustrates another change in HH3. In Hardhat 2, each execution connected automatically to one, and only one, network. There wasn’t a good way to connect to multiple networks, or to change which network you are connected to. In Hardhat 3, on the other hand, you create network connections explicitly, you can have as many as you want, and you can create and close them at any point. |
| 130 | +- Example, explanation about the `network.connect` API |
| 131 | +- Show that this combines with viem’s powerful typescript support: if you change the chain type to mainnet, you’ll get compilation errors. |
| 132 | + |
| 133 | +## Deploying contracts |
| 134 | + |
| 135 | +- The default deployment solution in Hardhat 3 is Ignition. |
| 136 | +- Short description about Ignition. |
| 137 | +- While this is our recommended approach for deploying contracts, you can choose to use anything else: scripts, or any other deployment plugins that could be developed by the community in the future |
| 138 | +- Example: ignition module |
| 139 | +- Run an ignition deploy in process |
| 140 | +- Run an ignition deploy in a hardhat node. Run it again. You can try adding a new step and checking that only that is executed. |
| 141 | +- Ignition is already available and being used in Hardhat 2. Its docs assume Hardhat 2, but most of it should be compatible with Hardhat 3. |
| 142 | + |
| 143 | +## Build profiles |
| 144 | + |
| 145 | +- HH3 introduces support for build profiles. |
| 146 | +- Build profiles let you have compile your project in different ways depending on what you are doing. For example, you can have a development profile that disables the optimizer and a production profile that enables it. The development profile is then used when running tests locally, while the production profile is used for deployments or when running tests in the CI |
| 147 | +- Some profiles are already used by Hardhat tasks, but you can create new ones and use them in your project however you want. For example: \<example> |
| 148 | +- In the sample project, we \<description> |
| 149 | +- You don't need to define build profiles if you don't need them, you can define a single configuration and that will be used for everything, \<example> |
| 150 | + |
| 151 | +## Managing dependencies |
| 152 | + |
| 153 | +- Like Hardhat 2, Hardhat 3 uses npm to manage dependencies. For simple use cases, the user experience will be the same. But Hardhat 3 revamps how npm is used under the hood to handle advanced use cases much better. |
| 154 | +- One such use case relates to transitive dependencies. \<Short explanation of the problem. HH3 handles this under the hood so that it just works. Link to explainer.> |
| 155 | +- Hardhat 3 also has built-in support for remappings, but they are not necessary under any circumstance: you use it only if you want to. |
| 156 | + |
| 157 | +## Configuration |
| 158 | + |
| 159 | +- Like Hardhat 2, Hardhat 3 is configured with a JavaScript file, but in Hardhat 3 the configuration is completely declarative. This contrasts with Hardhat 2, where part of the configuration was done by importing modules or calling functions that had side effects. |
| 160 | +- Most things should look and feel the same though. |
| 161 | +- Example: configuration in sample project. |
| 162 | +- This approach allows us to have much faster load times, even if you have a lot of plugins. |
| 163 | +- This also allows creating Hardhat environments in runtime, for advanced use cases. |
| 164 | + |
| 165 | +## Extensibility |
| 166 | + |
| 167 | +- The main extensibility point of Hardhat 3 is the same as in Hardhat 2: the ability to create custom tasks. These haven't changed a lot. |
| 168 | +- Example: \<TBD>. |
| 169 | +- This is pretty much the same as in Hardhat 2, except for two differences: the task needs to be included in the configuration object, and you need to call a `build` function at the end. |
| 170 | +- Hardhat 3 also includes a new and powerful hook system that lets you easily extend its functionality, and lets plugin authors add their own extensibility functions for their plugins. \<Link to explainer> |
| 171 | + |
| 172 | +## Closing words |
| 173 | + |
| 174 | +- This tutorial was an overview of the most important things included in the HH3 alpha. |
| 175 | +- This is an alpha, things can and will change. If there’s anything here that you don’t like, an API that is confusing, or anything else, we’d love you hear your feedback. |
| 176 | + - Join our telegram group |
| 177 | + - Open an issue |
| 178 | +- Wen HH3 stable |
| 179 | + - More features will be gradually added to the alpha, stay in tune by following us on Twitter, watching our releases page, or joining the public alpha telegram group. |
| 180 | + - Eventually we’ll have a feature-complete beta version and with a stable API. There won’t be breaking changes unless it’s unavoidable. At this point, you should be able to start using Hardhat 3 for real projects, or migrate existing projects to it, if you are comfortable with using beta-stage software. |
0 commit comments