Better Programming

Advice for programmers.

Follow publication

Writing and Testing a Smart Contract With Hardhat and Ethers.js

Grace Lungu
Better Programming
Published in
4 min readMay 20, 2022
Photo by Choong Deng Xiang on Unsplash

Hardhat is an Ethereum development environment just like Truffle, which we’ll use to develop our contract and deploy it on our local network and EthersJs is a library for interacting with the Ethereum blockchain that we’ll use in our test suits to interact with the deployed contract.

Installation and setup

A complete installation guide of the Hardhat environment can be found on the official website but let’s go over the necessary step for our small project.

So let’s run the following command

npm install --save-dev hardhat && npx hardhat

This will install the package and launch the interactive interface to create the project. We’ll select > Create an advanced sample project that uses TypeScript and proceed.

Hardhat will generate a couple of files and folders, but we are only interested in a few of them.

The contracts folder will host our smart contract, the test folder our test and hardhat.config.ts the generated configuration from hardhat — for which we need to make sure that the solidity version defined here matches the specified version of our contract.

Writing the smart contract

Imagine that you have been failing to save your coins because you can’t help but spend and you end up breaking your savings goals. Well, if only you could lock your savings in a vault that only gives you back your coins after a specified date has come to pass. This is the idea behind the smart contract we’ll be writing.

Let’s go ahead and create a file named LockSave.sol inside the contracts folder.

We need three basic functions for this simple contract:

  1. A function to save ETH for a minimum withdrawal date
  2. A function to withdraw after the specified date has passed
  3. A function to retrieve all savings from the owner’s address

We’ll not focus much on the specifics of the contract but here’s what it looks like.

First, we need to define the shape of our savings

Next, we’ll need to declare a couple of variables that will help store and map the savings to their creation timestamps and owners to saving timestamps. This sort of mapping helps to manipulate and access our data.

We need a couple of custom errors to use for unwanted behaviors, such as when saving is made with an amount equal to zero, or when an address wants to withdraw before the date has passed.

The saving function

The saving function takes in the withdrawal timestamp adds a new saving to the address savings and returns the data. We use the isValidSaving modifier to check for the amount and withdrawal time.

The withdraw function

We use the saving timestamp to find the saving, remove it from savings then finally transfer back the saved amount to the owner.

But before withdrawing we first check if the address has savings as well as the withdrawal time and pass these modifiers to the withdraw function above.

Lastly, we’ll add one more function to retrieve all savings belonging to the requesting address.

Testing the smart contract

As mentioned above we’ll create a file inside the test folder as index.ts then import from chai, ethers and hardhat.

Next, we’ll declare our test suit and deploy our contract in beforeEach. This will deploy a new contract every time we run a new test case so that we always have a blank contract to work with.

Saving and retrieving the saving

To test the saving function, we’ll make a saving after defining the withdrawal timestamp and then use the getSavings function to get the saving. Afterward, we’ll use the returned saving to assert the value and timestamps.

We use ethers.getSigners to get the signer addresses and extract the first address [sender] which is the default address used by Hardhat.

ethers.utils.parseEther is a utility function to convert a value from ETH to WEI which is the value we want to send to the contract when calling the payable method save .

The response from the contract is returned as an iterator by EthersJs and by doing savings.value().next().value we are able to access the values as an array.

And finally, use expect from chai to make the assertions.

Withdrawing the savings

For the following test, we first make a saving then make a withdrawal, and finally call the getSavings method to assert that the amount was successfully withdrawn.

That will be all for our test cases, but more assertions could be done for unhappy paths such as an attempt to withdraw before the date has passed or saving for a date that already passed.

For reference, I have hosted the two files, both the smart contract and the test on GithubGist.

Sign up to discover human stories that deepen your understanding of the world.

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

Write a response