Introduction
The Celo blockchain is an open-source, mobile-first blockchain platform that enables fast, secure, and efficient decentralized applications and crypto assets. Like Ethereum, it also allows developers to write and deploy smart contracts using the Solidity programming language.
In this article, we will guide you through the process of testing smart contracts on Celo using the Remix IDE, an open-source web application for developing, testing, and deploying smart contracts in Solidity.
Prerequisites
These tutorials assume that you have some basic knowledge of solidity
This is a list of what we’ll cover
- Step 1: Creating a new project
- Step 2: Testing the smart contract
- Step 3: Compiling and deploying the smart contract
Unit Testing
Unit testing is a testing method performed to verify the functionality and reliability of smart contracts. Smart contracts are computer programs that run autonomously on the blockchain. Unit testing involves writing test code that calls individual functions or methods in the contract code with appropriate inputs and verifies the expected outputs.
This type of testing is often used by developers to ensure that their smart contracts are working as intended before they are deployed to the blockchain. It can also be used by auditors to verify the security and reliability of smart contracts before they are used by users. Unit testing is a valuable tool for ensuring the quality of smart contracts. It can help to prevent errors and vulnerabilities that could lead to loss of funds or other problems.
Step 1: Creating a new project
-
Open the Remix IDE in your browser
-
Click on the File Explorer icon on the left side of the screen, then create a new workspace by clicking on the + icon
-
Chose a template for your project. For this tutorial, we will use the Basic template and name our project Unit Testing, then click on Ok
-
You will see a structure project like this
-
Now, we will create a new file named
MyToken.sol
by right-clicking on the contracts folder and selecting New FileIn this tutorial, we will create simple smart contracts named
MyToken.sol
. These contracts will be used in unit testing. The code for these contracts can be found below:MyToken.sol
contract will be used openzeppelin library to create a simple ERC20 token// SPDX-License-Identifier: MIT pragma solidity 0.8.15; import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import "@openzeppelin/contracts/access/Ownable.sol"; contract MyToken is ERC20, Ownable { uint256 public rate; event Buy(address indexed buyer, uint256 amount); event Withdraw(address indexed owner, uint256 amount); constructor() ERC20("Test Token", "TT") { rate = 100; } function buy() external payable { require(msg.value > 0); uint256 amount = msg.value * rate; _mint(msg.sender, amount); emit Buy(msg.sender, amount); } // Owner withdraw fund from token sale function withdraw() external onlyOwner { uint256 amount = address(this).balance; (bool status, ) = msg.sender.call{value:amount}(""); require(status); emit Withdraw(msg.sender, amount); } }
Step 2: Testing the Smart Contract
-
First, we need to activate the Solidity Unit Testing plugin by clicking on the Plugins Manager icon on the left side of the screen
-
Next, create a new file named
myToken.test.js
by right-clicking on the test folder and selecting New File -
Now, we will write a unit test for the
MyToken.sol
contract.const { expect } = require("chai"); const { ethers } = require("hardhat");
First, we need to import the modules that we will use in our unit tests. We will use
chai
for assertions,ethers
fromhardhat
to connect to the blockchain and send transactions to contracts.```javascript describe("MyToken", function(){ // Code for test }) ```
Next, we will create a test suite for the
MyToken
contract. A test suite is a collection of test cases that are related to each other. In this case, we will create a test suite for theMyToken
contract.it("Owner is developer address", async function () { const account = await ethers.getSigners(); const MyToken = await ethers.getContractFactory("MyToken"); const token = await MyToken.deploy(); await token.deployed(); expect((await token.owner())).to.be.equal(account[0].address); });
Next, we will create a test case for the
owner
function of theMyToken
contract. This function returns the address of the owner of the contract. We will use theexpect
function fromchai
to check if the address returned by theowner
function is equal to the address of the developer.it("Owner transfer ownership", async function () { const account = await ethers.getSigners(); const MyToken = await ethers.getContractFactory("MyToken"); const token = await MyToken.deploy(); await token.deployed(); await token.transferOwnership(account[1].address); expect( (await token.owner()) ).to.be.equal(account[1].address); });
Next, we will create a test case for the
transferOwnership
function of theMyToken
contract. This function transfers the ownership of the contract to another address. We will use theexpect
function fromchai
to check if the address returned by theowner
function is equal to the address of the developer.it("Buy token", async function(){ const account = await ethers.getSigners(); const MyToken = await ethers.getContractFactory("MyToken"); const token = await MyToken.deploy(); await token.deployed(); await token.connect(account[1]).buy({value:ethers.utils.parseEther("0.5")}); expect((await token.balanceOf(account[1].address)).toString()).to.be.equal("50000000000000000000"); });
Next, we will create a test case for the
buy
function of theMyToken
contract. This function allows users to buy tokens from the contract. We will use theexpect
function fromchai
to check if the balance of the user is equal to the amount of tokens they bought.it("Owner withdraw fee", async function(){ const account = await ethers.getSigners(); const MyToken = await ethers.getContractFactory("MyToken"); const token = await MyToken.deploy(); await token.deployed(); let amount = ethers.utils.parseEther("0.5"); // Buy token await token.connect(account[1]).buy({value:amount}); // Dev claim fee await token.withdraw(); // Get contract balance let contractBalance = await ethers.provider.getBalance(token.address); expect(contractBalance.toString()).to.be.equal("0"); });
Next, we will create a test case for the
withdraw
function of theMyToken
contract. This function allows the owner of the contract to withdraw the fee from the contract. We will use theexpect
function fromchai
to check if the balance of the contract is equal to 0 after the owner withdraws the fee.Complete code for
myToken.test.js
fileconst { expect } = require("chai"); const { ethers } = require("hardhat"); describe("MyToken", function () { it("Owner is developer address", async function () { const account = await ethers.getSigners(); const MyToken = await ethers.getContractFactory("MyToken"); const token = await MyToken.deploy(); await token.deployed(); expect((await token.owner())).to.be.equal(account[0].address); }); it("Owner transfer ownership", async function () { const account = await ethers.getSigners(); const MyToken = await ethers.getContractFactory("MyToken"); const token = await MyToken.deploy(); await token.deployed(); await token.transferOwnership(account[1].address); expect( (await token.owner()) ).to.be.equal(account[1].address) }); it("Buy token", async function(){ const account = await ethers.getSigners(); const MyToken = await ethers.getContractFactory("MyToken"); const token = await MyToken.deploy(); await token.deployed(); await token.connect(account[1]).buy({value:ethers.utils.parseEther("0.5")}); expect((await token.balanceOf(account[1].address)).toString()).to.be.equal("50000000000000000000"); }); it("Owner withdraw fee", async function(){ const account = await ethers.getSigners(); const MyToken = await ethers.getContractFactory("MyToken"); const token = await MyToken.deploy(); await token.deployed(); let amount = ethers.utils.parseEther("0.5") // Buy token await token.connect(account[1]).buy({value:amount}); // Dev claim fee await token.withdraw(); // Get contract balance let contractBalance = await ethers.provider.getBalance(token.address) expect(contractBalance.toString()).to.be.equal("0"); }) });
-
Now, we will run the unit tests, right-click on the
mytoken.test.js
file and select Run
Step 3: Compiling and deploying the smart contract
-
Now, we will compile the smart contract. Open
MyToken.sol
file, and look compiler icon on the left side of the editor.Choose the compiler version
0.8.15
and in the contract dropdown, chooseMyToken.sol
. Then click on the compile button. -
To deploy the smart contract, click on the Ethereum icon in the left sidebar of the editor.
In the environment dropdown, select Injected Web3. Then, in the contract dropdown, select
MyToken.sol
. Finally, click on the Deploy button.You should see the contract deployed in the Deployed Contracts section.
Now, we can interact with the smart contract using the Remix IDE UI.
Conclusion
Testing smart contracts is a vital step in the development process. It ensures that your contracts are secure, efficient, and function as expected. Remix IDE provides an excellent environment for writing, deploying, and testing Solidity smart contracts for Celo and other Ethereum-like blockchains.
However, while Remix provides an excellent environment for development and initial testing, it is important to use thorough and automated testing strategies as your contract complexity grows. This will help to ensure maximum security and reliability.
It is also important to remember that blockchain transactions are irreversible, and any bug in a live smart contract could result in significant financial losses. Therefore, it is essential to test your smart contracts thoroughly before deploying them to the blockchain.
Next steps
You can try creating your own smart contract using the Remix IDE. To learn more about the Remix IDE, you can read the official Remix IDE documentation here You can also try some other smart contract frameworks, such as Truffle and Hardhat
About the Author
Joel Obafemi
A marketer, copywriter, and collab manager for web3 brands. You can connect with me on LinkedIn.