Writing and Testing a Smart Contract With Hardhat and Ethers.js
A basic overview of how to write and test a smart contract using the Hardhat environment and the Ethers.js library

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:
- A function to save ETH for a minimum withdrawal date
- A function to withdraw after the specified date has passed
- 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.