Hero Image by DESPOINA MATSINOPOULOU from Pixabay
OpenZeppelin - A Hands-On Learning Experience
Today I decided to have a new learning experience.
It's called OpenZeppelin, which is a set of libraries and tools to code, deploy and operate Smart Contracts.
OpenZeppelin - Learn
There is a page called Learn which takes you through the following activities:
- Setting up a Node Project
- Developing Smart Contracts
- Deploying and Interacting
- Writing Automated Tests
- Connecting to Public Test Networks
- Upgrading Smart Contracts
- Preparing for Mainnet
I would like to give you some hints and present you with my notes and findings for each one of these sections.
Here we are:
Show me the code: This is the link to the source of my work.
Setting up a Node Project
Version Management with asdf
I use asdf to manage the versions of the tools I use in a project.
For this particular learning experience I used:
- Node.js 18.17.0
- Yarn 1.22.19
- direnv 2.34.0
About the direnv, I am going to tell you, in a while, how I use it.
Truffle vs Hardhat
The tutorial is suggesting that I use one of Truffle or Hardhat.
According to Truffle website, the Truffle Suite is being sunset. I can confirm that from the relevant Consensys blog post. Consensys is announcing partnership with Hardhat.
Hence, I went with Hardhat. See next section about which version.
Developing Smart Contracts
Hardhat
Which version of hardhat
to install? At the time of writing this one, the latest
version was 2.22.5
. This is the version I went with. That was not a problem.
Watch out, that the suggested npx hardhat
command is deprecated and it is suggested that you use:
$ npx hardhat init
to initialize your project.
This presents a list of options to choose from. I chose
Create an empty hardhat.config.js
.
OpenZeppelin Contracts
This is where the good stuff starts. I am introduced to one of OpenZeppelin Contracts, Ownable
.
I am being asked to install @openzeppelin/contracts
. The latest version at the time
of writing was 5.0.2
. I used that one, with no problems.
Deploying and Interacting
Local Blockchain
Thanks to Hardhat Network I am able to use a local Ethereum-like testnet for development purposes.
This is excellent!
Deploying a Contract
The script that is used to deploy the contract, scripts/deploy.js
requires ethers and hardhat-ethers.
Problem: Versions
The latest versions of these packages are:
- Ethers:
6.13.0
- Hardhat Ethers:
3.0.6
. In fact the latest version of this one is coming in the package@nomicfoundation/hardhat-ethers
These versions do not work with the script/deploy.js
as defined by the OpenZeppelin tutorial.
I used these:
- Ethers:
5.7.2
withyarn add ethers@5.7.2
and - Hardhat Ethers:
2.2.3
withyarn add @nomiclabs/hardhat-ethers@2.2.3
.
See how the version 2.x.y
of hardhat-ethers
is in different namespace: @nomiclabs
and not @nomicfoundation
.
Hardhat Config for Deployment
Which takes us to the script hardhat.config.js
which needs to require
the correct
hardhat-ethers
package:
require("@nomiclabs/hardhat-ethers");
...
instead of
require("@nomicfoundation/hardhat-ethers");
Interacting
That went smoothly. Nothing to note here.
Writing Automated Tests
This is one of my favourite parts in software engineering. Automated tests.
Chai
Which version?
The latest, at the time of writing, was 5.5.1
. But I had to go back to 4.4.1
to work
well with my current setup. It seems that 5.5.1
doesn't play well with module loading with the current set up of the hardhat project.
So, do:
yarn add --dev chai@4.4.1
instead of .yarn add --dev chai
Location of Test Script
The tutorial is suggesting test/Box.test.js
. I went with test/contracts/Box.test.js
to mirror the location of the contract source file.
OpenZeppelin Test Helpers vs Hardhat Chai Matchers
As the tutorial is suggesting, I went with Hardhat Chai Matchers rather than OpenZeppelin Test Helpers, because the latter is web3.js based. The former is ethers based.
So, yes to Hardhat Chai Matchers, not to OpenZeppelin Test Helpers for this project. I am also guessing that OpenZeppelin will soon have to adapt their tutorial to
suggest using Hardhat only since Truffle (which uses web3.js) is becoming obsolete.
Hardhat Chai Matchers Version
But again, which version of Hardhat Chai Matchers?
At the time of this writing, the latest version was 2.0.7
published in the @nomicfoundation
namespace.
But, this doesn't work well with the chai
and the ethers
version that we have gone so far.
So, I had to install 1.0.6
.
Hence:
yarn add --dev @nomicfoundation/hardhat-chai-matchers@1.0.6
instead of .yarn add --dev @nomicfoundation/hardhat-chai-matchers
Correct test/contracts/Box.test.js
So, the matching version of the test/contracts/Box.test.js
is the following:
Note that I have also reorganized the outline of the tests. I like to nest and use describe
and context
more than it is suggested in the original
Box.test.js
script by the tutorial. My approach makes the tests easier to read and deliver their testing purpose to the reader.
Connecting to Public Test Networks
It's amazing to be able to see your contracts deployed to the public, even if it is a public test network and not the real Ethereum Mainnet.
I went with Sepolia.
But, first one needs to use a service via which they will be able to connect to an Ethereum network.
I went with Alchemy. Alchemy is a suite of tools to build web3 apps and deploy them to the network, the network being any test or real network.
Important Create an Alchemy account and a new app inside it. For the app you create, you are going to get an API Key.
Adding Sepolia to Hardhat Networks
I then added Sepolia to the list of Hardhat networks supported:
File: hardhat.config.js
require("@nomiclabs/hardhat-ethers");
// Ensure your configuration variables are set before executing the script
const { vars } = require("hardhat/config");
const ALCHEMY_API_KEY = vars.get("ALCHEMY_API_KEY");
// This is the private key of my SEPOLIA Account I use for testing.
// I save the value inside the ".envrc" which is not checked-in.
// +direnv+ makes sure that the environment variable is set accordingly.
//
const SEPOLIA_TEST_NET_PRIVATE_KEY = vars.get("SEPOLIA_TEST_NET_PRIVATE_KEY");
/** @type import('hardhat/config').HardhatUserConfig */
module.exports = {
solidity: "0.8.24",
networks: {
sepolia: {
url: `https://eth-sepolia.g.alchemy.com/v2/${ALCHEMY_API_KEY}`,
accounts: [SEPOLIA_TEST_NET_PRIVATE_KEY],
},
},
};
Look how the url
value is matching the https
endpoint suggested by the Alchemy API dialogue (screenshot above).
Hardhat Configuration
Hardhat is configured using configuration variables. It is all explained here: Configuration Variables.
I personally like to use environment variables. Hence, I am following the instructions outlined here: Overriding configuration variables with environment variables, in combination with direnv.
Hence, inside my .envrc
file I have something like this:
export HARDHAT_VAR_ALCHEMY_API_KEY='...'
Hence, the hardhat.config.js
script can read the value of it.
Important: do not commit the .envrc
file to git
. Add .envrc
to you .gitignore
file.
But what about the SEPOLIA_TEST_NET_PRIVATE_KEY
? Here it is:
Signing Requests
In order for you to be able to commit transactions to the network you need to sign them and tell who you are. You need an Ethereum account. For testing purposes I am not using my real account. I am a little cautious. I have created another account to play with.
And since each account has a public and a private key, here, we have to declare the private key. Because this is needed to sign requests for transactions.
We have to declare it as accounts
value in the sepolia
Hardhat network configuration.
Now, Hardhat is ready to send requests to Sepolia, via Alchemy.
Note that the tutorial is suggesting another approach to specifying how the transaction requests will be signed. The tutorial uses the package mnemonics to create the Ethereum account. I prefer to create my accounts with MetaMask.
Adding Funds to Testnet Account
In order to execute transactions in an Ethereum network, your account needs to have some Ether in order to pay for the gas necessary for the transaction.
This is true even if you are about to carry out a transaction on a testnet, like Sepolia. The only difference is that the account balance that will carry out transactions on a testnet can have fake/test ETH and not real.
Maybe this is the most cumbersome step in the process? Maybe, but it does worth trying.
The testnets usually have a way to add test ETHs using a process called faucet. For example, this is the Sepolia Faucet.
But, as you can see from the message on their site
To prevent bots and abuse, this faucet requires a minimum mainnet balance of 0.001ETH on the wallet address being used.
This is what I did. I made sure that the Ethereum account that I use for testing and development (read earlier about the account with the private key in the environment variable SEPOLIA_TEST_NET_PRIVATE_KEY
) it had some real ETH in the mainnet. With some real ETH, I managed to activate the Ethereum Sepolia Faucet to give me some test ETH.
Remember Ethereum account balances are per network. So the same account can have different balances in different networks. Here I am talking about network-native currencies, the currency needed to pay for gas and executing transactions. If we were to talk about tokens, then an account/wallet could potentially have multiple balances in the same network, one balance per token the account holds. But, here, I am not talking about tokens. I am talking about the native currency of the network.
With test funds in the testnet, I can do transactions. It works.
Upgrading Smart Contracts
I can't imagine anyone deploying production contracts without them having the ability to be upgraded.
So, this section was fascinating.
Which Library Version?
The tutorial is asking you to install @openzeppelin/hardhat-upgrades the latest version of which is 3.1.1
, at the time of writing.
That was another compatibility problem for me.
So, I had to downgrade to 1.28.0
.
Hence:
yarn add --dev @openzeppelin/hardhat-upgrades@1.28.0
Upgradeable Contracts Cannot Have a Constructor
That part of the tutorial required the library @openzeppelin/contracts-upgradeable, version 5.0.2
, at the time of writing.
So, install that as a normal dependency, not a dev one.
yarn add @openzeppelin/contracts-upgradeable
Otherwise, I didn't have any other surprises in this section.
Preparing for Mainnet
Verifying Source Code
This was super interesting too. I should verify the source code of my contract after deployment to Mainnet.
I followed the instructions to verify using the Hardhat Etherscan plugin.
Just a note here. The tutorial is suggesting that we use the @nomiclabs/hardhat-etherscan
library which is now deprecated. The deprecation warning is pointing to better using the @nomicfoundation/hardhat-verify
instead. Anyway. Current tutorial experience worked with the deprecated package without problem.
Closing Note
These were my notes while I practised on OpenZeppelin Learn.
They may be helpful to you if you decide to practise too.
Thanks for reading. You can always reach out to me on: