Introduction
As a web3 developer, you know you move up the ladder when your productivity speed increases. Being productive is not only measured in how fast you can deliver but in conjunction with how secure and usable your application is, which largely depends on how well you are familiar with using sophisticated tools. While Celo unpacks and unfolds the web3 mysteries enabling you to focus on logic to build user-centric applications, your part as the developer is knowing how to use these tools to increase your productivity level.
Prerequisitesâ
This tutorial focuses on advance practical methods of configuring and using Hardhat. so you should:
- Know how to set up a basic Hardhat project. Please complete the previous example tutorial here as weâll also make use of the previous example here. Clone the project, and navigate to the project directory.
git clone https://github.com/bobeu/advanced-hardhat-for-celo-dev.git
cd advanced-hardhat-for-celo-dev
Requirementsâ
To get started, ensure you have the following tools installed on the machine:
- Code editor. VSCode recommended.
- NodeJs version >=14.0.0.
Note: I am using Node version â18.12.1â
Hardhat plugins
Continuing from the last tutorial, we will install a few plugins to improve the deployment of the Lock contract to Celo networks and solidify testing.
An important highlight of Hardhat is the plugins that are external to Hardhat itself. Even though Hardhat comes with some default plugins, you can always override them to use whatâs best for your needs. We will discuss some of the external programs - relevant plugins, or the plugins you most likely need. However, this does not include plugins that werenât built before this article was written. During Hardhat installation, If you choose to install the sample project to enable support for waffle tests, âhardhat-waffleâ and âhardhat-ethersâ are installed alongside â@nomicfoundation/hardhat-toolboxâ. As a Celo developer using hardhat, youâll mostly be running tasks which is a function of the âHardhat Runnerâ - a CLI for interacting with Hardhat. Each time you run a Hardhat command from the CLI e.g. ânpx hardhat compileâ, youâre simply running a task.
Using plugins with Hardhat is one flexibility advantage over Truffle, which simply extends the Hardhat Runtime Environment - HRE . Some of them are in-built, while others are built by the community. Now, letâs examine some of the higher-level libraries rather called plugins, and how to integrate and use them.
- @nomicfoundation/hardhat-toolbox
- @nomicfoundation/hardhat-chai-matchers
- @openzeppelin/hardhat-upgrades
- @nomiclabs/hardhat-ethers
- @typechain/hardhat
- Hardhat-deploy
- Hardhat-react
Under the hood, these plugins extend Hardhat functionalities by extending the config file.
Example:
extendEnvironment((hre) => {
const Web3 = require("ethers");
hre.Web3 = Web3;
hre.web3 = new Web3(hre.network.provider);
});
Hardhat lets you add instances of these tools to its environment object by installing and initializing them once in âhardhatconfig.jsâ. Soon as they feature in the Hardhat global variables, theyâll be available for use across every Hardhat component. Firstly, we will install and configure the plugins we need, then we will apply the usage.
Note: Hardhat components are files/folders within Hardhat context i.e. within the scope of its reach, such as âtestâ folder.
@nomicfoundation/hardhat-toolbox
A set of coordinated tools developed and recommended by the hardhat officials that bundles the commonly used packages and plugins. When you initialize a hardhat project, â@nomicfoundation/hardhat-toolboxâ will be the default umbrella package that houses other tools such as â@nomiclabs/hardhat-etherscanâ, â@nomiclabs/hardhat-ethersâ etc. Importing the library alongside the pre-installed tools will only make them redundant.
Even though you can do a lot with this plugin, yet youâre limited to the extent of its scope. When you need to make use of other plugins, it is advisable to remove â@nomicfoundation/hardhat-toolboxâ from the configuration before proceeding to avoid dependency conflicts.
@nomicfoundation/hardhat-chai-matchers
This is a Hardhat component that blends Ethereum-specific functionalities with the Chai assertion library to make testing smart contracts more easy, readable, and concise. The recent version of Hardhat comes with â@nomicfoundation/hardhat-toolboxâ so you do not need to install it. If you like to set things up independently, please continue with this section.
This plugin depends on other libraries or packages to function as intended. You will install it as a peer/dev dependency along with the peer packages if youâre using an older version of Node.
Peer dependencies
- chai
- @nomiclabs/hardhat-ethers
- ethers
npm install --save-dev @nomicfoundation/hardhat-chai-matchers chai @nomiclabs/hardhat-ethers ethers
Or
yarn add --dev @nomicfoundation/hardhat-chai-matchers chai @nomiclabs/hardhat-ethers ethers
Include it in the Hardhat config file:
require("@nomicfoundation/hardhat-chai-matchers")
import "@nomicfoundation/hardhat-chai-matchers";
@openzeppelin/hardhat-upgrades
This tool reduces your tasks when writing upgradeable contracts by modifying Hardhat scripts adding new functions that enable you to deploy and manage proxied contracts, so anytime you release a new version of your dApp, you only need a minimal command to update it, and your users can start interacting with the newer version.
If youâre looking for a tutorial on how to write upgradeable contracts, this blogpost might be helpful.
Openzeppelin/hardhat-upgrades depend on etherjs library as peer dependency to function properly. We will install it as a dev dependency. alongside the peer.
npm install --save-dev @nomiclabs/hardhat-ethers ethers
Activate it in âhardhatconfig.jsâ:
require("@nomicfoundation/hardhat-chai-matchers");
require("@openzeppelin/hardhat-upgrades"); /* < ======== */
@nomiclab/hardhat-ethers
This is an official plugin from the Nomic Foundation that wraps the Ethereum library (Etherjs), abstracts the complexities and provides you with a hardhat-configured version for easy interaction with Ethereum-compatible blockchains without creating additional tasks. It depends on the âether.jsâ library and works very well with versions from v5.0.0 and above, it adds ethers objects to the Hardhat environment using the same APIs as ether.js .
It adds helpful functionalities to the ethers object which you need to be aware of, such as
- Libraries interface,
- FactoryOptions interface,
- deployContract()
- getContractFactory(),
- getSigners() etc.
The return value of each of the helper functions (Contracts and ContractFactory) are by default connected to the first signer returned by the âgetSigners()â API.
npm install --save-dev "@nomiclabs/hardhat-ethers" "ethers@^5.0.0"
Include it in the configuration file:
require('@nomicfoundation/hardhat-chai-matchers')
require('@openzeppelin/hardhat-upgrades')
require('@nomiclabs/hardhat-ethers') /* < ======== */
@typechain/hardhat
Of all mentioned plugins, this is the easiest as it adds Typescript support for smart contracts at compile time. It generates Typescript binding you will need in your test and/or frontend if you use Typescript in your project. This might be very helpful if your project features Typescript, but since this tutorial is based on Javascript, we will only jump over it.
npm install --save-dev typechain "@typechain/hardhat" "@typechain/ethers-v5"
Hardhat-deploy
If you need a more elegant deployment method (aside from using the scripts/<somefile>
method) that keeps track of all the networks your contracts are deployed to, can also replicate same in your testing environment, âHardhat-deployâ is the tool you need. In fact, it is one of the plugins I love using. It has interesting features that make your work much easier by adding and modifying existing hardhat tasks. An example task is âdeployâ. Before installing hardhat-deploy, if you try to run ânpx hardhat deployâ, you get a warning that there is no such task. By adding the tool, the task becomes available.
With this tool, you can write more straightforward tests, assign custom names to addresses to make tracking the address deployed that contract easier, track networks, etc.
Here are some of its features, all for a better developer experience as itemized in the documentation:
- Chain configuration export.
- It enables deterministic deployments across selected networks.
- It enhances library linking at the time of deployment.
- Developers are able to create specific deploy scripts per network.
- Deployment retrying (by saving pending transactions): and you can feel confident when making deployment since you can always recover them.
- It lists deployed contractsâ information i.e. contractsâ addresses and abis. This is useful for web applications.
- It allows you to only deploy what is needed.
- Support for openzeppelin transparent proxies. You are able to make proxy deployments with the ability to upgrade them transparently, only if code changes.
For more of the features, please refer to the documentation.
The following command installs âhardhat-deployâ together with peer dependencies - âethers.jsâ and âhardhat-ethersâ and ensures compatibility:
npm install --save-dev @nomiclabs/hardhat-ethers@npm:hardhat-deploy-ethers ethers
If you use the above command, in your config file, you will need to write require(â@nomiclabs/hardhat-ethersâ) instead of require(âhardhat-deploy-ethersâ)
Since weâd already installed âetherâ and ânomiclabs/hardhat-ethersâ, we only need to install âhardhat-deploy-ethersâ.
npm install -D hardhat-deploy
Or
yarn add --dev hardhat-deploy
Then activate in hardhat file:
require("@nomicfoundation/hardhat-chai-matchers");
require('@openzeppelin/hardhat-upgrades');
require("@nomiclabs/hardhat-ethers");
require("hardhat-deploy"); /* < ======== */
Hardhat-react
A hardhat plugin for react application. When run, it generates react hook components from your smart contract that is hot-loaded into your React application. It could also be helpful where your application uses Typescript, as everything is typed and initialized for you. It automatically hooks into âhardhat-deployâ when you run ânpx hardhat node --watchâ and you can alway run it manually with ânpx hardhat reactâ .
It uses the following peer plugins to maximize results:
- hardhat
- hardhat-deploy
- hardhat-deploy-ethers
- hardhat-typechain
- ts-morph
- ts-node
- typescript
- ts-generator
- typechain@4.0.0
- @typechain/ethers-v5
Interestingly, you can install all of the dependencies with just one command.
npm install --save-dev hardhat hardhat-deploy hardhat-deploy-ethers hardhat-typechain hardhat-typechain ts-morph ts-node typescript ts-generator typechain@4.0.0 @typechain/ethers-v5
Yarn
yarn add --dev hardhat hardhat-deploy hardhat-deploy-ethers hardhat-typechain hardhat-typechain ts-morph ts-node typescript ts-generator typechain@4.0.0 @typechain/ethers-v5
Import them to hardhat.
require("@nomicfoundation/hardhat-chai-matchers");
require('@openzeppelin/hardhat-upgrades');
require("@nomiclabs/hardhat-ethers");
require('hardhat-deploy');
require("@nomiclabs/hardhat-ethers");
require("hardhat-deploy-ethers");
require("hardhat-deploy");
require("@symfoni/hardhat-react");
require("hardhat-typechain");
require("@typechain/ethers-v5");
Now that we have installed all the tools we need, letâs discuss how we can use them in our Celo application. At the top of your âhardhatconfig.jsâ file, you should have the following set of imports and configurations.
require("@nomicfoundation/hardhat-toolbox");
require("@nomicfoundation/hardhat-chai-matchers");
require("@openzeppelin/hardhat-upgrades");
require("@nomiclabs/hardhat-ethers");
require("hardhat-deploy");
require("@nomiclabs/hardhat-ethers");
require("hardhat-deploy-ethers");
require("hardhat-deploy");
require("@symfoni/hardhat-react");
require("hardhat-typechain");
require("@typechain/ethers-v5");
const {config} = require("dotenv");
const { extendEnvironment } = require("hardhat/config");
config();
/** @type import("hardhat/config").HardhatUserConfig */
module.exports = {
networks: {
localhost: {
url: "http://127.0.0.1:8545",
},
alfajores: {
url: "https://alfajores-forno.celo-testnet.org",
accounts: [`${process.env.PRIVATE_KEY}`],
chainId: 44787,
},
celo: {
url: "https://forno.celo.org",
accounts: [`${process.env.PRIVATE_KEY}`],
chainId: 42220,
},
},
solidity: {
version: "0.8.9",
settings: { // See the solidity docs for advice about optimization and evmVersion
optimizer: {
enabled: true,
runs: 200
},
evmVersion: "byzantium"
}
},
};
To make a more advanced deployment and testing, we will leverage some of the tools we have installed.
Modifying âLock.solâ
For this task, we will modify the Lock contract to make it look more advanced with another dependency contract, and use âhardhat-deployâ to deploy the contracts to the âalfajoresâ network and save the deployment information anywhere we want for later use which is not available to us in the previous deployment method. Secondly, we want the deployment to run only if changes are made to the contracts. In the contract folder, create a new â.solâ file and name it âBeneficiary.solâ.
Make a new folder as âinterfaceâ. Add a new file named âBeneficiary.solâ.
Rename âLock.solâ to âBank.solâ, then paste the following code to their respective destination.
Bank.sol
This is a simple time-lock contract that allows only approved child address to withdraw from the bank. The Bank makes an external gasless call to the âBeneficiariesâ contract to verify if the caller is an approved beneficiary. The former depends on the latter.
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.9;
import "./interfaces/IBeneficiary.sol";
contract Bank {
address public immutable beneficiary;
event Withdrawal(uint amount, address indexed who);
constructor(address _beneficiary) payable {
require(
_beneficiary != address(0),
"Beneficiary is zero address"
);
beneficiary = _beneficiary;
}
function withdraw() external {
require(IBeneficiary(beneficiary).getApproval(msg.sender), "You're not a beneficiary");
emit Withdrawal(address(this).balance, msg.sender);
payable(msg.sender).transfer(address(this).balance);
}
function getBalance(address who) external view returns(uint256) {
return address(who).balance;
}
}
Beneficiary.sol
An independent contract that returns whether the queried address is a beneficiary or not. Owner is able to approve or disapprove the beneficiary.
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.9;
contract Beneficiaries {
address payable immutable owner;
address payable immutable child_1;
address payable immutable child_2;
mapping (address => bool) public approval;
constructor (address _child_1, address _child_2) {
require(_child_1 != address(0) && _child_2 != address(0), "Children addresses are empty");
child_1 = _child_2;
child_2 = _child_2;
}
function getApproval(address child) external view returns(bool) {
return approval[child];
}
function approve(address childToApprove, bool _approval) public {
require(msg.sender == owner, "Caller not owner");
require(childToApprove == child_1 || childToApprove == child_2, "Child not recognized");
approval[child] = _approval;
}
}
IBeneficiary.sol
path: âinterface/IBeneficiary.solâ
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
interface IBeneficiary {
function getApproval(address) external view returns(bool);
}
Note: The above contracts are for testing purposes and may contain potential bugs. Do not use in production.
In the project directory, make a new folder called âdeployâ, navigate into it and create a new file â00_deploy.jsâ. You could as well name it whatever you want as long as it is â.jsâ extended and inside the âdeployâ folder, Hardhat-deploy will find it. Inside this file, we will write a script that executes the task we want.
mkdir deploy
cd deploy
touch 00_deploy.js
Output => âdeploy/00_deploy.jsâ
Run compile:
Before you compile, comment out these dependencies.
// require("@nomicfoundation/hardhat-toolbox");
// require("hardhat-typechain");
With ârequire(â@nomicfoundation/hardhat-toolboxâ)â active you may get âunresolved dependency errorâ and may be required to install the following dependencies:
npm install --save-dev "@nomicfoundation/hardhat-network-helpers@^1.0.0" "@nomicfoundation/hardhat-chai-matchers@^1.0.0" "@nomiclabs/hardhat-ethers@^2.0.0" "@nomiclabs/hardhat-etherscan@^3.0.0" "@types/chai@^4.2.0" "@types/mocha@^9.1.0" "@typechain/ethers-v5@^10.1.0" "@typechain/hardhat@^6.1.2" "chai@^4.2.0" "hardhat-gas-reporter@^1.0.8" "solidity-coverage@^0.8.1" "ts-node@>=8.0.0" "typechain@^8.1.0"
If you get a prompt in the terminal to install the above dependencies, install them using Yarn. With Yarn, you do not have to worry about incompatible dependencies. Yarnâs installation is smooth.
yarn add --dev "@nomicfoundation/hardhat-network-helpers@^1.0.0" "@nomicfoundation/hardhat-chai-matchers@^1.0.0" "@nomiclabs/hardhat-ethers@^2.0.0" "@nomiclabs/hardhat-etherscan@^3.0.0" "@types/chai@^4.2.0" "@types/mocha@^9.1.0" "@typechain/ethers-v5@^10.1.0" "@typechain/hardhat@^6.1.2" "chai@^4.2.0" "hardhat-gas-reporter@^1.0.8" "solidity-coverage@^0.8.1" "ts-node@>=8.0.0" "typechain@^8.1.0"
Also, you get the following error if you try to compile while â@nomicfoundation/hardhat-toolboxâ is imported alongside previous dependencies. This is because we had manually installed some of the dependencies which also exist in the toolbox. Most times you get typechain error.
There is more than one copy of the Typechain plugin all competing for usage hence the naming conflict. To resolve this issue, we simply remove â@nomicfoundation/hardhat-toolboxâ from the hardhatconfig.js so we can have only the manually installed dependencies.
You will also get an error like this if âhardhat-typechainâ is present because it works plainly with Typescript. Since we are not using Typescript we simply remove it.
Now we can compile
npx hardhat compile
Deploy to Alfajores:
Remember we had earlier created a file inside the deploy folder - â00_deploy.jsâ. Copy/paste this codeâ
module.exports = async ({ getNamedAccounts, deployments}) => {
const {deploy} = deployments;
const {deployer, child_1, child_2} = await getNamedAccounts();
console.log(child_1, child_2)
const beneficiaries = await deploy('Beneficiaries', {
from: deployer,
gasLimit: 4000000,
args: [child_1, child_2],
});
const bank = await deploy('Bank', {
from: deployer,
gasLimit: 4000000,
value: "1000000000000000000",
args: [beneficiaries.address],
});
console.log("Beneficiaries address:", beneficiaries.address)
console.log("Bank address", bank.address,)
console.log("Deployer", deployer)
console.log("Child_1 ", child_1)
console.log("Child_2 ", child_2)
};
module.exports.tags = ['Beneficiaries', 'Bank'];
The â00_deploy.jsâ is a module that accepts two parameters from the hardhat environment (hardhatconfig) when invoked:
deployments (line 2): This function returns a couple of utility functions, but we only need the âdeploy()â so we extract by destructuring.
getNamedAccount : A function that returns predefined named accounts from the âhardhatconfig.jsâ. For our contract, we are required to supply two arguments (a)child_1 (b) child_2 and a deployer address. By default, hardhat generates 10 default accounts with signers that enable us to make transactions without providing private keys or mnemonic phrases. We extracted the first 3 signers by setting the key/value pair in the âhardhatconfig.jsâ as below.
Line 9: âdeploy()â function accepts two arguments - Contract name and an optional argument of type object where we specified âfromâ, âgaslimitâ and âargsâ. The function resolves to an object from which we extracted the address of the deployed contracts. What we just did is a local deployment. Now letâs deploy to a live network, i.e Alfajores (Celoâs testnet).
We will need to make some changes in the config file under the ânamedAcccountsâ object so we can perform deployment either to âHardhatâ or âTestnetâ. However, we will need accounts with signing capability to deploy to Alfajores since the default 10 accounts are meant to be used locally, and an account to sign transaction during testing. To achieve this, one thing we could do is to make a reference to the âenvâ file. Perhaps we need accounts with private key access. Since public keys are often derived from private keys, we can set the private key in the secret file i.e â.envâ, then import it to the point it is needed. We will as well keep the rest two accounts we need as parameters in the same â.envâ file. Add âCHILD_1â and âCHILD_2â private keys.
In the âhardhatconfig.jsâ, modify the ânamedAccountâ object as shown below. Pay attention to how I configured each of the accounts. The âdefaultâ key points to accounts generated by Hardhat. To set accounts for Alfajores, we must use the networkâs chainId as the key otherwise we get errors. The same method applies if you are deploying to the Celo mainnet.
Notice I prefixed the value for the deployer under â44787â with âprivatekey://â. This is to enable Hardhat to know that we are using a full signer account since the deployer will need to sign transactions while deploying to the live network. Lastly, we loaded the values programmatically using âprocess.envâ .
We can then run:
npx hardhat deploy --network alfajores
You should see the above messages printed to the console. If you get any error, please go over it again to ensure you follow all the steps as itemized.
If you perhaps would need deployment information, paste the following in the âhardhatconfig.jsâ and make a folder called âdeploymentsâ. It should be in the projectâs root.
paths: {
deploy: "deploy",
deployments: "deployments",
imports: "imports"
},
When the deployment is done, âhardhat-deployâ generates the following outputs.
If you check the content of the .chainId file, youâll see 44787 which represents the network we just deployed to i.e Alfajores.
The code in your âhardhatconfig.jsâ file should look like this:
require("@nomicfoundation/hardhat-chai-matchers");
require("@openzeppelin/hardhat-upgrades");
require("@nomiclabs/hardhat-ethers");
require("hardhat-deploy");
require("@nomiclabs/hardhat-ethers");
require("hardhat-deploy-ethers");
require("hardhat-deploy");
require("@typechain/ethers-v5");
const {config} = require("dotenv");
config();
/** @type import("hardhat/config").HardhatUserConfig */
module.exports = {
networks: {
localhost: {
url: "http://127.0.0.1:8545",
live: false,
saveDeployments: true,
tags: ["local"]
},
hardhat: {
live: false,
saveDeployments: true,
tags: ["test", "local"]
},
alfajores: {
url: "https://alfajores-forno.celo-testnet.org",
accounts: [`${process.env.PRIVATE_KEY}`],
chainId: 44787,
},
celo: {
url: "https://forno.celo.org",
accounts: [`${process.env.PRIVATE_KEY}`],
chainId: 42220,
},
},
solidity: {
version: "0.8.9",
settings: { // See the solidity docs for advice about optimization and evmVersion
optimizer: {
enabled: true,
runs: 200
},
}
},
paths: {
deploy: 'deploy',
deployments: 'deployments',
imports: 'imports'
},
namedAccounts: {
deployer: {
default: 0,
44787: `privatekey://${process.env.PRIVATE_KEY}`,
},
child_1: {
default: 1,
44787: process.env.CHILD_1,
},
child_2: {
default: 2,
44787 : process.env.CHILD_2,
}
}
};
Testing
Testing smart contracts enables us to be sure our solidity code is working as intended. We have 2 contracts to test: âBeneficiaries.solâ and âBank.solâ.
Remove the existing âLock.jsâ test file. Make a new file under the test folder and name it âBeneficiaries.jsâ. We will write a short, concise, simple unit test using the script we wrote in âdeploy/00_deploy.jsâ. Since we are testing locally, we will require deployments to be done locally. Here, we are leveraging âhardhat-deployâ and âhardhat-deploy-ethersâ.
In the new test file, paste the following code:
const { expect } = require("chai");
const { BigNumber } = require("bignumber.js");
const {ethers, getNamedAccounts} = require("hardhat");
const toBN = (x) => {
return new BigNumber(x);
}
describe('Beneficiaries', () => {
it("Test1: Should confirm child_1 status as false", async function () {
await deployments.fixture(["Beneficiaries"]);
const { deployer, child_1 } = await getNamedAccounts();
const Instance = await ethers.getContract("Beneficiaries", deployer);
// Note: For approval, it used default account i.e account[0] which is also the deployer
await Instance.approval(child_1).then((tx) =>
expect(tx).to.equal(false)
);
});
it("Test2: Should approve child_2", async function () {
await deployments.fixture(["Beneficiaries"]);
const { deployer, child_2 } = await getNamedAccounts();
const Instance = await ethers.getContract("Beneficiaries", deployer);
// At this point, child_2 is not approved
await Instance.approval(child_2).then((tx) =>
expect(tx).to.equal(false)
);
// Here the owner approve child_2
await Instance.approve(child_2, true);
// And we can verify if child_2 is truly approved.
await Instance.approval(child_2).then((tx) =>
expect(tx).to.equal(true)
);
});
it("Test3: Should withdraw successfully", async function () {
await deployments.fixture(["Beneficiaries", "Bank"]);
const { deployer, child_1, child_2 } = await getNamedAccounts();
const Beneficiaries = await ethers.getContract("Beneficiaries", deployer, child_1, child_2);
const Bank = await ethers.getContract("Bank", deployer);
const initialBalance = await Bank.getBalance(child_2);
const bankBalance = await Bank.getBalance(Bank.address);
expect(initialBalance).to.be.gt(BigNumber(0));
// Here the owner approve child_2
await Beneficiaries.approve(child_2, true);
const signer = await ethers.getSigner(child_2);
await Bank.connect(signer).withdraw();
expect(await Bank.getBalance(child_2)).to.be.gt(bankBalance);
expect(await Bank.getBalance(Bank.address)).to.equal(BigNumber(0));
});
});
The above set of tests cover for âBeneficiaries.solâ and âBank.solâ contracts. Letâs examine the code so you can better understand the structure.
We are using the deployments information from the â00_deploy.jsâ when we call âawait deployments.fixtures([âBeneficiariesâ])â, the script is run and it returns deployment information for which of the valid tags we specify. In this case âBeneficiariesâ. We also have access to the ânamedAccountâ we used earlier by destructuring the returned values.
const { deployer, child_1, child_2 } = await getNamedAccounts();
Thereafter, we get a single contract object by calling the âgetContractâ method provided by the plugin.
const Instance = await ethers.getContract("Beneficiaries", deployer);
Notice how we generate a signer for the âchild_2â account?
const signer = await ethers.getSigner(child_2);
Since we had set names to reference different accounts in the âhardhatconfig.jsâ by setting default keys, we get the same set of accounts while ânpx hardhat testâ is invoked
The default private key/paired accounts generated by Hardhat are not compatible with âsignersâ of âethersâ library. So we will encounter an error while trying to connect âchild_2â to sign the transaction in âTest 3â. To fix this, weâll use the âethersâ library since we want to make the âChild_2â address compatible with ethersâ signers. âethersâ exposes a method called âgetSigner()â that accepts optional string arguments and creates a signing capacity for it. This is exactly what we did to make child_2 ethers-compatible-signer.
const signer = await ethers.getSigner(child_2);
await Bank.connect(signer).withdraw();
We can now run the test.
npx hardhat test
Boom! We made it and our test passed as expected. The complete code for this tutorial can be found here.
_Deployments folder
If you expand the deployment folder, you should have the following structure generated by the plugin.
Conclusionâ
Congratulations on completing this tutorial. What we have learned so far:
- Hardhat plugins
- What they are.
- How they work under the hood.
- How we can manipulate them to achieve desired results.
- Advanced hardhat configuration using plugins with examples.
- Lastly, we learn how to harmonize deployment to write clear and concise tests.
What next?â
You can go over this tutorial as many times as you can to fully understand all of the concepts we have discussed so far. Practice makes perfect. Make some time to try out new things on your own and you will be able to deploy your own dApp on Celo. If youâre looking for related tutorials, I recommend browsing through the Celo Academy.
About the Authorâ
Isaac Jesse , aka Bobelr is a smart contract/Web3 developer. He has been in the field since 2018, worked as an ambassador with several projects like Algorand and so on as a content producer. He has also contributed to Web3 projects as a developer.