|
| 1 | +--- |
| 2 | +head: |
| 3 | + - - meta |
| 4 | + - name: "twitter:title" |
| 5 | + content: Test Contracts on zkSync with Hardhat | zkSync Docs |
| 6 | +--- |
| 7 | + |
| 8 | +# Test smart contracts with Hardhat plugins |
| 9 | + |
| 10 | +This tutorial provides a step-by-step guide on testing smart contracts using the **hardhat-zksync-chai-matchers** plugin in conjunction with the **zkSync Era Test Node** on your local machine. To facilitate this process of running tests on the **zkSync Era Test Node**, you'll also utilize the **hardhat-zksync-node** plugin. |
| 11 | + |
| 12 | +# Prerequisites |
| 13 | + |
| 14 | +- Node.js installed (version 14.x or later) |
| 15 | +- Either yarn or npm installed |
| 16 | +- Initialized Hardhat TypeScript project |
| 17 | + |
| 18 | +# Tutorial |
| 19 | + |
| 20 | +## Integration with hardhat-zksync-node plugin |
| 21 | + |
| 22 | +In this tutorial, the contract functionality is tested using the [zkSync Era Test Node](../../test-and-debug/era-test-node.md). To start local node we use a **hardhat-zksync-node** plugin to integrate this functionality within the Hardhat project. |
| 23 | + |
| 24 | +:::warning zkSync Era Test Node alpha phase |
| 25 | + |
| 26 | +During the alpha phase, zkSync Era Test Nodes are currently undergoing development, wherein certain functionalities might not be fully supported or operational. |
| 27 | + |
| 28 | +::: |
| 29 | + |
| 30 | +### Installation |
| 31 | + |
| 32 | +To install the **hardhat-zksync-node** plugin and additional necessary packages, execute the following command: |
| 33 | + |
| 34 | +::: code-tabs |
| 35 | + |
| 36 | +@tab:active yarn |
| 37 | + |
| 38 | +```bash |
| 39 | +yarn add -D @matterlabs/hardhat-zksync-node |
| 40 | +``` |
| 41 | + |
| 42 | +@tab npm |
| 43 | + |
| 44 | +```bash |
| 45 | +npm i -D @matterlabs/hardhat-zksync-node |
| 46 | +``` |
| 47 | + |
| 48 | +::: |
| 49 | + |
| 50 | +Once installed, add the plugin at the top of your **hardhat.config.ts** file. |
| 51 | + |
| 52 | +```bash |
| 53 | +import "@matterlabs/hardhat-zksync-node"; |
| 54 | +``` |
| 55 | + |
| 56 | +### Starting the zkSync Era Test Node |
| 57 | + |
| 58 | +You can now safely run the **zkSync Era Test Node** with the following command, ensuring proper execution: |
| 59 | + |
| 60 | +```bash |
| 61 | +yarn hardhat node-zksync |
| 62 | +``` |
| 63 | + |
| 64 | +::: note Test if all is ok |
| 65 | + |
| 66 | +We'll want to verify the correctness of our installations and test if we can run a **zkSync Era Test Node**, without further use of this command in the tutorial. |
| 67 | + |
| 68 | +::: |
| 69 | + |
| 70 | +You should see output similar to this: |
| 71 | + |
| 72 | +```log |
| 73 | +09:04:44 INFO Account #9: 0xe2b8Cb53a43a56d4d2AB6131C81Bd76B86D3AFe5 (1_000_000_000_000 ETH) |
| 74 | +09:04:44 INFO Private Key: 0xb0680d66303a0163a19294f1ef8c95cd69a9d7902a4aca99c05f3e134e68a11a |
| 75 | +09:04:44 INFO Mnemonic: increase pulp sing wood guilt cement satoshi tiny forum nuclear sudden thank |
| 76 | +09:04:44 INFO |
| 77 | +09:04:44 INFO ======================================== |
| 78 | +09:04:44 INFO Node is ready at 127.0.0.1:8011 |
| 79 | +09:04:44 INFO ======================================== |
| 80 | +``` |
| 81 | + |
| 82 | +Since we've confirmed that the **zkSync Era Test Node** is functioning properly with the help of the **hardhat-zksync-node** plugin, we can shut it down and continue with the tutorial. |
| 83 | + |
| 84 | +Read more about **hardhat-zksync-node** [here.](../../tooling/hardhat/hardhat-zksync-node.md) |
| 85 | + |
| 86 | +## Integration with hardhat-zksync-chai-matchers plugin |
| 87 | + |
| 88 | +To leverage zkSync-specific capabilities within the [Chai](https://www.chaijs.com/) assertion library for testing smart contracts, it's necessary to use the **hardhat-zksync-chai-matchers** plugin. |
| 89 | + |
| 90 | +### Installation |
| 91 | + |
| 92 | +In the root directory of your project, execute this command: |
| 93 | + |
| 94 | +::: code-tabs |
| 95 | + |
| 96 | +@tab:active yarn |
| 97 | + |
| 98 | +```bash |
| 99 | +yarn add -D @matterlabs/hardhat-zksync-chai-matchers @nomicfoundation/hardhat-chai-matchers chai @nomiclabs/hardhat-ethers ethers |
| 100 | +``` |
| 101 | + |
| 102 | +@tab npm |
| 103 | + |
| 104 | +```bash |
| 105 | +npm i -D @matterlabs/hardhat-zksync-chai-matchers |
| 106 | +``` |
| 107 | + |
| 108 | +::: |
| 109 | + |
| 110 | +After installing it, add the plugin at the top of your **hardhat.config.ts** file: |
| 111 | + |
| 112 | +```bash |
| 113 | +import "@nomicfoundation/hardhat-chai-matchers" |
| 114 | +import "@matterlabs/hardhat-zksync-chai-matchers"; |
| 115 | +``` |
| 116 | + |
| 117 | +::: note Import order matters |
| 118 | + |
| 119 | +To enable the **@matterlabs/hardhat-zksync-chai-matchers** plugin to override **@nomicfoundation/hardhat-chai-matchers**, you must import the latter first, ensuring that all zkSync-specific matchers take override the original ones. |
| 120 | + |
| 121 | +::: |
| 122 | + |
| 123 | +Read more about **hardhat-zksync-chai-matchers** [here.](../../tooling/hardhat/hardhat-zksync-chai-matchers.md) |
| 124 | + |
| 125 | +### Smart Contract creation |
| 126 | + |
| 127 | +To set up the environment for using chai matchers and writing tests, you'll need to create some contracts. Follow these steps: |
| 128 | + |
| 129 | +1. Navigate to the root of your project. |
| 130 | +2. Create a folder named **contracts**. |
| 131 | +3. Inside the **contracts** folder, create a file named **Greeter.sol**. |
| 132 | + |
| 133 | +Now we should add some code to the new **Greeter.sol** file: |
| 134 | + |
| 135 | +```solidity |
| 136 | +// SPDX-License-Identifier: MIT |
| 137 | +pragma solidity ^0.8.17; |
| 138 | +
|
| 139 | +contract Greeter { |
| 140 | + string private greeting; |
| 141 | + bool private greetingChanged; |
| 142 | + constructor(string memory _greeting) { |
| 143 | + greeting = _greeting; |
| 144 | + greetingChanged = false; |
| 145 | + } |
| 146 | + function greet() public view returns (string memory) { |
| 147 | + return greeting; |
| 148 | + } |
| 149 | + function setGreeting(string memory _greeting) public { |
| 150 | + require(bytes(_greeting).length > 0, "Greeting must not be empty"); |
| 151 | + greeting = _greeting; |
| 152 | + greetingChanged = true; |
| 153 | + } |
| 154 | + function isGreetingChanged() public view returns (bool) { |
| 155 | + return greetingChanged; |
| 156 | + } |
| 157 | +} |
| 158 | +``` |
| 159 | + |
| 160 | +### Compiler configuration |
| 161 | + |
| 162 | +We'll require the **hardhat-zksync-solc** plugin to compile our smart contracts. To install it, execute: |
| 163 | + |
| 164 | +::: code-tabs |
| 165 | + |
| 166 | +@tab:active yarn |
| 167 | + |
| 168 | +```bash |
| 169 | +yarn add -D @matterlabs/hardhat-zksync-solc |
| 170 | +``` |
| 171 | + |
| 172 | +@tab npm |
| 173 | + |
| 174 | +```bash |
| 175 | +npm i -D @matterlabs/hardhat-zksync-solc |
| 176 | +``` |
| 177 | + |
| 178 | +::: |
| 179 | + |
| 180 | +Add the plugin at the top of your **hardhat.config.ts** file: |
| 181 | + |
| 182 | +```bash |
| 183 | +import "@matterlabs/hardhat-zksync-solc"; |
| 184 | +``` |
| 185 | + |
| 186 | +Customize the configuration options for the **hardhat-zksync-solc** plugin according to your requirements. |
| 187 | + |
| 188 | +You can find more details about available configuration options in the [official documentation.](../../tooling/hardhat/hardhat-zksync-solc.md) |
| 189 | + |
| 190 | +### Deploy configuration |
| 191 | + |
| 192 | +We'll use provided example contract for testing with help of **hardhat-zksync-chai-matchers** plugin and deploying it on **zkSync Era Test Node**. Since deployment is involved, we'll also require the **hardhat-zksync-deploy** plugin for assistance. In the same directory, execute the command to install **hardhat-zksync-deploy** plugin: |
| 193 | + |
| 194 | +::: code-tabs |
| 195 | + |
| 196 | +@tab:active yarn |
| 197 | + |
| 198 | +```bash |
| 199 | +yarn add -D @matterlabs/hardhat-zksync-deploy ethers zksync-ethers |
| 200 | +``` |
| 201 | + |
| 202 | +@tab npm |
| 203 | + |
| 204 | +```bash |
| 205 | +npm i -D @matterlabs/hardhat-zksync-deploy |
| 206 | +``` |
| 207 | + |
| 208 | +::: |
| 209 | + |
| 210 | +Add the **hardhat-zksync-deploy** plugin to your hardhat.config.ts file, you can import it as follows: |
| 211 | + |
| 212 | +```bash |
| 213 | +import "@matterlabs/hardhat-zksync-deploy"; |
| 214 | +``` |
| 215 | + |
| 216 | +Learn more about the **hardhat-zksync-deploy** plugin [here.](../../tooling/hardhat/hardhat-zksync-deploy.md) |
| 217 | + |
| 218 | +## Write Test Cases |
| 219 | + |
| 220 | +With the previous steps completed, your **hardhat.config.ts** file should now be properly configured to include settings for local testing. |
| 221 | + |
| 222 | +```javascript |
| 223 | +import { HardhatUserConfig } from "hardhat/config"; |
| 224 | + |
| 225 | +import "@matterlabs/hardhat-zksync-solc"; |
| 226 | +import "@matterlabs/hardhat-zksync-deploy"; |
| 227 | +import "@matterlabs/hardhat-zksync-node"; |
| 228 | +import "@nomicfoundation/hardhat-chai-matchers"; |
| 229 | +import "@matterlabs/hardhat-zksync-chai-matchers"; |
| 230 | + |
| 231 | +const config: HardhatUserConfig = { |
| 232 | + zksolc: { |
| 233 | + version: "latest", |
| 234 | + }, |
| 235 | + solidity: "0.8.19", |
| 236 | + networks: { |
| 237 | + hardhat: { |
| 238 | + zksync: true, |
| 239 | + }, |
| 240 | + }, |
| 241 | +}; |
| 242 | +export default config; |
| 243 | +``` |
| 244 | + |
| 245 | +Here are the steps to create test cases with the **hardhat-zksync-chai-matchers** plugin: |
| 246 | + |
| 247 | +1. Navigate to your project's root directory. |
| 248 | +2. Create a new folder named **test**. |
| 249 | +3. Inside the **test** folder, create a file named **test.ts**. |
| 250 | + |
| 251 | +Once you've completed these steps, you'll be ready to write your tests using the **hardhat-zksync-chai-matchers** plugin. |
| 252 | + |
| 253 | +Here's a brief example showcasing the functionalities of our contract: |
| 254 | + |
| 255 | +```typescript |
| 256 | +import * as hre from "hardhat"; |
| 257 | +import { expect } from "chai"; |
| 258 | +import { Wallet, Provider, Contract } from "zksync-ethers"; |
| 259 | +import { Deployer } from "@matterlabs/hardhat-zksync-deploy"; |
| 260 | +import { ZkSyncArtifact } from "@matterlabs/hardhat-zksync-deploy/src/types"; |
| 261 | +import { ZkSyncProviderAdapter } from "@matterlabs/hardhat-zksync-node"; |
| 262 | +import "@matterlabs/hardhat-zksync-node/dist/type-extensions"; |
| 263 | + |
| 264 | +const RICH_PRIVATE_KEY = "0x7726827caac94a7f9e1b160f7ea819f172f7b6f9d2a97f992c38edeab82d4110"; |
| 265 | + |
| 266 | +describe("Greeter", function () { |
| 267 | + let provider: Provider; |
| 268 | + let deployer: Deployer; |
| 269 | + let artifact: ZkSyncArtifact; |
| 270 | + let contract: Contract; |
| 271 | + |
| 272 | + before(async function () { |
| 273 | + // Creation of a provider from a network URL adjusted specifically for the zkSync Era Test Node. |
| 274 | + provider = new Provider(hre.network.config.url); |
| 275 | + // To ensure proper testing, we need to deploy our contract on the zkSync Era Test Node, for more info check hardhat-zksync-deploy plugin documentation. |
| 276 | + deployer = new Deployer(hre, new Wallet(RICH_PRIVATE_KEY)); |
| 277 | + artifact = await deployer.loadArtifact("Greeter"); |
| 278 | + contract = await deployer.deploy(artifact, ["Hello, world!"]); |
| 279 | + }); |
| 280 | + it("should work on Era Test node", async function () { |
| 281 | + const netVersion = await provider.send("net_version", []); |
| 282 | + expect(netVersion === 260); |
| 283 | + }); |
| 284 | + it("greet should return a string", async function () { |
| 285 | + expect(await contract.greet()).to.be.a("string"); |
| 286 | + }); |
| 287 | + it("is deployed address valid", async function () { |
| 288 | + expect(await contract.getAddress()).to.be.properAddress; |
| 289 | + }); |
| 290 | + it("greet should say Hello", async function () { |
| 291 | + expect(await contract.greet()).to.match(/^Hello/); |
| 292 | + }); |
| 293 | + it("setGreeting should throw when passed an invalid argument", async function () { |
| 294 | + await expect(contract.setGreeting("")).to.be.revertedWith("Greeting must not be empty"); |
| 295 | + }); |
| 296 | + it("isGreetingChanged should return true after setting greeting", async function () { |
| 297 | + expect(await contract.isGreetingChanged()).to.be.false; |
| 298 | + const tx = await contract.setGreeting("Changed"); |
| 299 | + await tx.wait(); |
| 300 | + expect(await contract.greet()).to.match(/^Changed/); |
| 301 | + expect(await contract.isGreetingChanged()).to.be.true; |
| 302 | + }); |
| 303 | +}); |
| 304 | +``` |
| 305 | + |
| 306 | +Execute the following command in your terminal to run the tests: |
| 307 | + |
| 308 | +```bash |
| 309 | +yarn hardhat test |
| 310 | +``` |
| 311 | + |
| 312 | +::: note When you execute this command, the contract will be automatically compiled, eliminating the need for manual compilation. However, if you prefer to compile manually, simply run the following command: |
| 313 | + |
| 314 | +```bash |
| 315 | +yarn hardhat compile |
| 316 | +``` |
| 317 | + |
| 318 | +::: |
| 319 | + |
| 320 | +The **hardhat-zksync-node** plugin overrides the default behavior of the Hardhat **test** task. It starts the **zkSync Era Test Node** before running tests, executes the tests, and then automatically shuts down the node after the test cases are completed. Additionally, the plugin generates a log file named `era_test_node.log`, which indicates the node's activity and transactions made during the tests. Whenever you re-run the **test** command, the content of `era_test_node.log` is refreshed. |
| 321 | +This setup ensures that your tests are executed against a controlled environment, mimicking the behavior of a live network but in a local sandboxed context. It's a common practice to ensure that smart contracts behave as expected under various conditions before deploying them to a live network. |
| 322 | + |
| 323 | +`era_test_node.log` file example: |
| 324 | + |
| 325 | +``` |
| 326 | +10:53:11 INFO |
| 327 | +10:53:11 INFO EthToken System Contract |
| 328 | +10:53:11 INFO Topics: |
| 329 | +10:53:11 INFO 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef |
| 330 | +10:53:11 INFO 0x0000000000000000000000000000000000000000000000000000000000008001 |
| 331 | +10:53:11 INFO 0x00000000000000000000000036615cf349d7f6344891b1e7ca7c72883f5dc049 |
| 332 | +10:53:11 INFO Data (Hex): 0x000000000000000000000000000000000000000000000000000028e0ec2a9900 |
| 333 | +10:53:11 INFO |
| 334 | +10:53:11 INFO Call: SUCCESS |
| 335 | +10:53:11 INFO Output: "0x0000000000000000000000000000000000000000000000000000000000000001" |
| 336 | +10:53:11 INFO === Console Logs: |
| 337 | +10:53:11 INFO === Call traces: |
| 338 | +``` |
0 commit comments