Test smart contracts with Hardhat plugins


Test smart contracts with Hardhat plugins

This tutorial provides a step-by-step guide on testing smart contracts using the hardhat-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.

Prerequisites

  • Node.js installed (version 14.x or later)
  • Either yarn or npm installed
  • Initialized Hardhat TypeScript project

Tutorial

Integration with hardhat-zksync-node plugin

In this tutorial, the contract functionality is tested using the zkSync Era Test Node. To start local node we use a hardhat-zksync-node plugin to integrate this functionality within the Hardhat project.

zkSync Era Test Node alpha phase

During the alpha phase, zkSync Era Test Nodes are currently undergoing development, wherein certain functionalities might not be fully supported or operational.

Installation

To install the hardhat-zksync-node plugin and additional necessary packages, execute the following command:

yarn add -D @matterlabs/hardhat-zksync-node

Once installed, add the plugin at the top of your hardhat.config.ts file.

import "@matterlabs/hardhat-zksync-node";

Starting the zkSync Era Test Node

You can now safely run the zkSync Era Test Node with the following command, ensuring proper execution:

yarn hardhat node-zksync

Test if all is ok

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.

You should see output similar to this:

09:04:44  INFO Account #9: 0xe2b8Cb53a43a56d4d2AB6131C81Bd76B86D3AFe5 (1_000_000_000_000 ETH)
09:04:44  INFO Private Key: 0xb0680d66303a0163a19294f1ef8c95cd69a9d7902a4aca99c05f3e134e68a11a
09:04:44  INFO Mnemonic: increase pulp sing wood guilt cement satoshi tiny forum nuclear sudden thank
09:04:44  INFO
09:04:44  INFO ========================================
09:04:44  INFO   Node is ready at 127.0.0.1:8011
09:04:44  INFO ========================================

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.

Read more about hardhat-zksync-node here.

Integration with hardhat-chai-matchers plugin

To leverage zkSync-specific capabilities within the Chaiopen in new window assertion library for testing smart contracts, it's necessary to use the hardhat-chai-matchers plugin.

Installation

In the root directory of your project, execute this command:

yarn add -D  @nomicfoundation/hardhat-chai-matchers chai@4.3.6 @nomiclabs/hardhat-ethers

After installing it, add the plugin at the top of your hardhat.config.ts file:

import "@nomicfoundation/hardhat-chai-matchers"

Smart Contract creation

To set up the environment for using chai matchers and writing tests, you'll need to create some contracts. Follow these steps:

  1. Navigate to the root of your project.
  2. Create a folder named contracts.
  3. Inside the contracts folder, create a file named Greeter.sol.

Now we should add some code to the new Greeter.sol file:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;

contract Greeter {
    string private greeting;
    bool private greetingChanged;
    constructor(string memory _greeting) {
        greeting = _greeting;
        greetingChanged = false;
    }
    function greet() public view returns (string memory) {
        return greeting;
    }
    function setGreeting(string memory _greeting) public {
        require(bytes(_greeting).length > 0, "Greeting must not be empty");
        greeting = _greeting;
        greetingChanged = true;
    }
    function isGreetingChanged() public view returns (bool) {
        return greetingChanged;
    }
}

Compiler configuration

We'll require the hardhat-zksync-solc plugin to compile our smart contracts. To install it, execute:

yarn add -D @matterlabs/hardhat-zksync-solc

Add the plugin at the top of your hardhat.config.ts file:

import "@matterlabs/hardhat-zksync-solc";

Customize the configuration options for the hardhat-zksync-solc plugin according to your requirements.

You can find more details about available configuration options in the official documentation.

Deploy configuration

We'll use provided example contract for testing with help of hardhat-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:

yarn add -D @matterlabs/hardhat-zksync-deploy ethers zksync-ethers

Add the hardhat-zksync-deploy plugin to your hardhat.config.ts file, you can import it as follows:

import "@matterlabs/hardhat-zksync-deploy";

Learn more about the hardhat-zksync-deploy plugin here.

Write Test Cases

With the previous steps completed, your hardhat.config.ts file should now be properly configured to include settings for local testing.

import { HardhatUserConfig } from "hardhat/config";

import "@matterlabs/hardhat-zksync-solc";
import "@matterlabs/hardhat-zksync-deploy";
import "@matterlabs/hardhat-zksync-node";
import "@nomicfoundation/hardhat-chai-matchers";

const config: HardhatUserConfig = {
  zksolc: {
    version: "latest",
  },
  solidity: "0.8.19",
  networks: {
    hardhat: {
      zksync: true,
    },
  },
};
export default config;

Here are the steps to create test cases with the hardhat-chai-matchers plugin:

  1. Navigate to your project's root directory.
  2. Create a new folder named test.
  3. Inside the test folder, create a file named test.ts.

Once you've completed these steps, you'll be ready to write your tests using the hardhat-chai-matchers plugin.

Here's a brief example showcasing the functionalities of our contract:

import * as hre from "hardhat";
import { expect } from "chai";
import { Wallet, Provider, Contract } from "zksync-ethers";
import { Deployer } from "@matterlabs/hardhat-zksync-deploy";
import { ZkSyncArtifact } from "@matterlabs/hardhat-zksync-deploy/src/types";
import "@matterlabs/hardhat-zksync-node/dist/type-extensions";

const RICH_PRIVATE_KEY = "0x7726827caac94a7f9e1b160f7ea819f172f7b6f9d2a97f992c38edeab82d4110";

describe("Greeter", function () {
  let provider: Provider;
  let deployer: Deployer;
  let artifact: ZkSyncArtifact;
  let contract: Contract;

  before(async function () {
    // Creation of a provider from a network URL adjusted specifically for the zkSync Era Test Node.
    provider = new Provider(hre.network.config.url);
    // 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.
    deployer = new Deployer(hre, new Wallet(RICH_PRIVATE_KEY));
    artifact = await deployer.loadArtifact("Greeter");
    contract = await deployer.deploy(artifact, ["Hello, world!"]);
  });
  it("should work on Era Test node", async function () {
    const netVersion = await provider.send("net_version", []);
    expect(netVersion === 260);
  });
  it("greet should return a string", async function () {
    expect(await contract.greet()).to.be.a("string");
  });
  it("is deployed address valid", async function () {
    expect(await contract.getAddress()).to.be.properAddress;
  });
  it("greet should say Hello", async function () {
    expect(await contract.greet()).to.match(/^Hello/);
  });
  it("setGreeting should throw when passed an invalid argument", async function () {
    await expect(contract.setGreeting("")).to.be.revertedWith("Greeting must not be empty");
  });
  it("isGreetingChanged should return true after setting greeting", async function () {
    expect(await contract.isGreetingChanged()).to.be.false;
    const tx = await contract.setGreeting("Changed");
    await tx.wait();
    expect(await contract.greet()).to.match(/^Changed/);
    expect(await contract.isGreetingChanged()).to.be.true;
  });
});

Execute the following command in your terminal to run the tests:

yarn hardhat test

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:

yarn hardhat compile

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

era_test_node.log file example:

10:53:11  INFO
10:53:11  INFO EthToken System Contract
10:53:11  INFO   Topics:
10:53:11  INFO     0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef
10:53:11  INFO     0x0000000000000000000000000000000000000000000000000000000000008001
10:53:11  INFO     0x00000000000000000000000036615cf349d7f6344891b1e7ca7c72883f5dc049
10:53:11  INFO   Data (Hex): 0x000000000000000000000000000000000000000000000000000028e0ec2a9900
10:53:11  INFO
10:53:11  INFO Call: SUCCESS
10:53:11  INFO Output: "0x0000000000000000000000000000000000000000000000000000000000000001"
10:53:11  INFO === Console Logs:
10:53:11  INFO === Call traces: