Testing
Discover how to effectively test smart contracts on ZKsync Era ecosystem.
Welcome back to our ZKsync 101 series, your fast-track to ZKsync development! In this
third guide, we transition from deploying and managing contracts to the critical phase
of testing. This guide will walk you through the steps to ensure your CrowdfundingCampaign
contracts, introduced in our first guide and efficiently deployed through contract factories
in the second, work flawlessly.
Elevate your ZKsync toolkit with robust contract testing techniques.
Craft comprehensive tests for your CrowdfundingCampaign
to ensure reliability and security.
Use Hardhat or Foundry to write and run tests, ensuring your campaigns are ready.
Dive into the world of smart contract testing and solidify the foundation of your ZKsync projects.
Setup the project
Run the following command in your terminal to initialize the project.
npx zksync-cli@latest create --template qs-testing contract-testing-quickstart
cd contract-testing-quickstart
Local Era Node
While setting up a local development environment was previously optional, testing contracts requires
a more structured setup. We'll use hardhat-zksync
to run tests against an In-memory node,
which operates seamlessly within a separate process for an optimized testing workflow.
If you have not set up your local era node yet, follow the instructions in the Getting Started section.
Within the hardhat.config.ts
, you'll observe the zksync
flag set to true
under the
hardhat
network, indicating the integration with ZKsync's testing environment.
hardhat: {
zksync: true,
},
To use the In-memory node for testing, ensure the hardhat
network is selected with
the zksync
flag enabled. This setup initiates the node alongside your tests and ensures
it terminates once testing is complete. The node's port allocation starts at the default
8011
, facilitating smooth and isolated test execution.
Secondly within the hardhat.config.ts
, you'll observe the importing of
@nomicfoundation/hardhat-chai-matchers
. This plugin provides Hardhat with an extended
suite of assertion methods tailored for contract testing, significantly improving the testing
toolkit available for your project.
import "@nomicfoundation/hardhat-chai-matchers";
Test Wallet Configuration
For testing purposes, we use pre-configured, well-funded wallets. During this testing guide, we will use the following pre-configured wallet, which eliminates the need for manual funding or setup:
- Account Address:
0x36615Cf349d7F6344891B1e7CA7C72883F5dc049
- Private Key:
0x7726827caac94a7f9e1b160f7ea819f172f7b6f9d2a97f992c38edeab82d4110
This streamlined approach allows us to focus on writing and running effective tests.
Compile the CrowdfundingCampaign
contract
Now that our setup is complete, it's time to focus on the core of this
guide - testing our CrowdfundingCampaign.sol
contract. Here's a quick
refresher on its structure:
Thorough testing involves scrutinizing every function and aspect of our contract,
including potential failure scenarios. In this guide, we'll focus in on the contribute
method to ensure it's tested.
As a challenge to hone your testing skills further,
consider writing additional tests for the withdrawFunds
, getTotalFundsRaised
,
and getFundingGoal
methods, expanding your test coverage and reinforcing the
reliability of the contract.
Smart contracts deployed to ZKsync must be compiled using our custom compiler.
zksolc
is the compiler used for Solidity.
To compile the contracts in a project, run the following command:
npm run compile
Upon successful compilation, you'll receive output detailing the
zksolc
and solc
versions used during compiling and the number
of Solidity files compiled.
Compiling contracts for ZKsync Era with zksolc v1.4.1 and solc v0.8.17
Compiling 15 Solidity files
Successfully compiled 15 Solidity files
The compiled artifacts will be located in the /artifacts-zk
folder.
Test CrowdfundingCampaign
This section describes testing the CrowdfundingCampaign.sol
contract. Let's
start by reviewing the tests for CrowdfundingCampaign.sol
contract provided
during the initialization step in the /tests
directory, specifically the
crowdFunding.test.ts
file.
import "@nomicfoundation/hardhat-chai-matchers";
import { expect } from "chai";
import { ethers } from "ethers";
import { getWallet, LOCAL_RICH_WALLETS, deployContract } from "../deploy/utils";
describe("CrowdfundingCampaign", function () {
let campaign;
let owner, addr1, addr2;
beforeEach(async function () {
owner = getWallet(LOCAL_RICH_WALLETS[0].privateKey);
addr1 = getWallet(LOCAL_RICH_WALLETS[1].privateKey);
addr2 = getWallet(LOCAL_RICH_WALLETS[2].privateKey);
const fundingGoalInWei = ethers.parseEther('1').toString();
campaign = await deployContract("CrowdfundingCampaign", [fundingGoalInWei], { wallet: owner, silent: true });
});
describe("Contribute", function () {
it("should reject contributions of 0", async function () {
await expect(campaign.connect(addr1).contribute({ value: ethers.parseEther("0") })).to.be.revertedWith("Contribution must be greater than 0");
});
it("should aggregate contributions in totalFundsRaised", async function () {
await campaign.connect(addr1).contribute({ value: ethers.parseEther("0.5") });
await campaign.connect(addr2).contribute({ value: ethers.parseEther("0.3") });
expect(await campaign.getTotalFundsRaised()).to.equal(ethers.parseEther("0.8"));
});
it("should emit GoalReached event when funding goal is met", async function () {
await expect(campaign.connect(addr1).contribute({ value: ethers.parseEther("1") }))
.to.emit(campaign, "GoalReached")
.withArgs(ethers.parseEther("1"));
});
});
});
- Initialization: Each test case initializes with fresh contract instances and predefined rich wallet accounts to simulate various contributors and the contract owner.
- Deployment: The
CrowdfundingCampaign
contract is deployed using thedeployContract
utility, setting a specific funding goal for each test scenario.
contribute
Method Tests:
- Zero Contributions: Verifies that the contract correctly rejects contribution attempts with zero value, ensuring the integrity of the contribution process.
- Funds Aggregation: Tests the contract's ability to accurately aggregate contributions from
multiple addresses and update the
totalFundsRaised
accordingly. - Goal Achievement: Checks for the
GoalReached
event emission upon meeting the funding goal, confirming the contract's responsiveness to achieving its set target.
Execute the test command corresponding to your package manager:
npx hardhat test --network hardhat
Upon completion, the test suite will provide a summary of all executed tests, indicating their success or failure:
CrowdfundingCampaign
Contribute
✔ should reject contributions of 0 (45ms)
✔ should aggregate contributions in totalFundsRaised (213ms)
✔ should emit GoalReached event when funding goal is met (113ms)
3 passing (1s)
🎉 Congratulations! The contribute
method of the CrowdfundingCampaign
contract
has been thoroughly tested and is ready for action.
Takeaways
- Testing: Understanding contract testing is important for ensuring the reliability and security of your smart contracts on ZKsync. Proper testing safeguards against unforeseen errors and vulnerabilities.
- Comprehensive Coverage: Achieving comprehensive test coverage, including both positive and negative testing
scenarios, is essential for a robust smart contract. This guide emphasized the
contribute
method, but testing should encompass all aspects of your contract. - Tooling Efficiency: Leveraging Hardhat or Foundry for testing provides a streamlined and efficient workflow.
These tools offer powerful features and plugins, like
@nomicfoundation/hardhat-chai-matchers
, that enhance the testing process.
Next Steps
With a solid foundation in contract testing, you're well-equipped to advance your ZKsync development journey. Consider the following steps to further your expertise:
- Upgradeability: Dive into the Upgradeability article focusing on contract upgradeability. Learning to make your contracts upgradeable will enable you to update and improve your smart contracts over time without losing state or funds.
- Advanced ZKsync Integrations: Explore deeper into ZKsync's ecosystem by implementing features like account abstraction and paymasters to enhance user experience and contract flexibility.
- Community Engagement and Contribution: Join the vibrant ZKsync community. Participate in forums, Discord, or GitHub discussions. Sharing insights, asking queries, and contributing can enrich the ecosystem and your understanding of ZKsync.