Hardhat
In the world of decentralized applications, the margin for error is remarkably narrow.
A single mistake in a contract can have catastrophic implications.
For those seeking an efficient method to test and refine their contracts,
this guide showcases how to utilize Hardhat and anvil-zksync
for all testing needs.
To test our contract, we are going to use Hardhat and anvil-zksync
for rapid local development.
In our tests we're going to use zksync-ethers
to interact with the Greeter
contract,
and we'll use Mocha as our test runner.
Prerequisites
zksync-cli
installed from the zksync-cli section.anvil-zksync
installed. See anvil-zksync.
Environment setup
- Create a new project with the required dependencies and boilerplate paymaster implementations:
zksync-cli create test-greeter --template hardhat_solidity
Use the private key below to use a pre-configured rich wallet for deployment and testing:? Private key of the wallet responsible for deploying contracts (optional) 0x7726827caac94a7f9e1b160f7ea819f172f7b6f9d2a97f992c38edeab82d4110
The contract for this guide exists under/contracts/Greeter.sol
. - Install the dependencies:
npm install
- Compile the contracts:
npm run compile
Run tests with Hardhat
Under the /test
directory there is a greeter.test.ts
file. The initial test checks if our Greeter
contract returns the set greeting.
import { expect } from 'chai';
import * as hre from 'hardhat';
describe('Greeter', function () {
it("Should return the new greeting once it's changed", async function () {
// Get contract factory and deploy
const Greeter = await hre.ethers.getContractFactory('Greeter');
const greeting = 'Hello world!';
const greeter = await Greeter.deploy(greeting);
await greeter.waitForDeployment();
expect(await greeter.greet()).to.eq(greeting);
const newGreeting = 'Hola, mundo!';
const setGreetingTx = await greeter.setGreeting(newGreeting);
// wait until the transaction is processed
await setGreetingTx.wait();
expect(await greeter.greet()).to.equal(newGreeting);
});
});
To run this test:
npx hardhat test test/greeter.test.ts --network hardhat
You should see the following output:
Greeter
✔ Should return the new greeting once it's changed (174ms)
1 passing (174ms)
Expand test coverage
Our aim is comprehensive coverage. Here are the test scenarios we will cover:
- Testing greet() function: Check the returned greeting.
- Testing setGreeting() function: Verify the ability to update greetings.
- Testing Insufficient Funds: Ensure transactions fail without enough funds.
Each of these test cases will rely on a common setup,
which involves creating a provider connected to the ZKsync Sepolia Testnet, initializing a wallet with a known private key,
and deploying the Greeter
contract.
Let's refactor our test file with the provided script:
To run this test:
npx hardhat test test/greeter.test.ts --network hardhat
You should see the following output:
Greeter
✔ Should return the new greeting once it's changed (211ms)
✔ Should set a new greeting and return it (2682ms)
✔ Should fail when insufficient funds (299ms)
3 passing (6s)
Understanding the test file
Have a look at the test/greeter.test.ts
file's imports:
import { expect } from 'chai';
import { Wallet, type Contract } from 'ethers';
import * as hre from 'hardhat';
This section imports all necessary utilities and configurations needed to run our tests.
expect
from Chai provides assertion functionalities for our tests.Wallet
, and theContract
type fromethers
help us creating a wallet and defining a contract's type.hre
gives us hardhat specific functionalities for deploying and interacting with our contract.
Main Test Suite
describe('Greeter', function () {
...
});
Here, we've declared our main test suite. Each test or nested suite inside provides specific scenarios or functionalities we want to test regarding the Greeter contract.
- Initialization
Before running any test, we initialize commonly used variables like the provider, wallet, deployer, and the greeter contract. - Test greet() function
We check that the greet function returns the initial greeting of 'Hi' after deployment.it("Should return the new greeting once it's changed", async function () { ... });
- Test setGreeting() function
We test that setting a new greeting updates the contract's state as expected.it("Should set a new greeting and return it", async function () { ... });
- Test insufficient funds
Here, we simulate a scenario where an empty wallet (with no funds) tries to set a new greeting. We make use of theconnect
method on yourzksync-ethers
Contract object to connect it to a different account.it("Should fail when insufficient funds", async function () { ... });