![]() |
VOOZH | about |
Public Blockchains like Ethereum are immutable, it is difficult to update the code of a smart contract once it has been deployed. To guarantee security, smart contracts must be tested before they are deployed to Mainnet. There are several strategies for testing contracts, but a test suite comprised of various tools and approaches is suitable for detecting both minor and significant security issues in contract code.
Smart contract testing is the process of ensuring that a smart contract's code operates as intended. Testing is important for determining whether a certain smart contract meets standards for dependability, usability, and security. Approaches may differ, but most testing methods need to run a smart contract with a subset of the data it promises to handle. It is presumed that the contract is working properly if it gives correct outcomes for sample data. Most testing tools include resources for creating and executing test cases to determine if a contract's execution matches the intended outcomes.
Testing Smart Contracts is a critical and significant process in the development phase since it involves deploying it on the network every time and determining whether it works as expected or whether it needs some fine-tuning to enhance and satisfy its requirements.
While it is feasible to update a contract if a bug is detected, upgrades are difficult and might cause issues if done incorrectly. Upgrading a contract undermines the idea of immutability and burdens users with new trust assumptions. A detailed method for testing your contract, on the other hand, mitigates smart contract security concerns and eliminates the need for sophisticated logic modifications after deployment.
Not testing smart contracts thoroughly can lead to various problems and vulnerabilities, including:
Given the potential risks associated with smart contracts, thorough testing, including unit testing, integration testing, and security auditing, is crucial to identify and mitigate these problems before deployment.
Automated and manual testing approaches for Ethereum smart contracts can be coupled to develop a strong framework for contract analysis.
Automated testing is a technology that automatically checks a smart contract's code for execution issues. It is more efficient than manual testing methods due to scripts (i.e. writing different test cases) being used to evaluate contract features. Automated testing is particularly useful when tests are repetitive and time-consuming; difficult to carry out manually; susceptible to human error; or involve evaluating critical contract functions. However, automated testing can have downsides such as missing specific vulnerabilities and producing false positives, so it is important to combine automated and human testing for smart contracts.
Unit testing examines contract functions independently and ensures that each component performs properly. If unit tests fail, they should be simple, quick to execute, and offer a clear indication of what went wrong. They may be used to ensure that functions return anticipated values, and that contract storage is appropriately updated after function execution. Running unit tests after making modifications to a contract's codebase also guarantees that no errors are introduced.
Guidelines for unit testing smart contracts:
While unit testing debugs contract operations in isolation, integration testing evaluates a smart contract's components as a whole. Integration testing can identify problems caused by cross-contract calls or interactions between various functions inside the same smart contract. Integration tests, for example, can aid in determining if inheritance and dependency injection are functioning effectively.
When it comes to smart contract testing, manual testing typically takes place in the later stages of the development cycle, following automated tests. This form of testing examines the smart contract as a fully integrated product, ensuring that it performs according to the specified technical requirements.
Testing can help validate a smart contract's expected behaviour for certain data inputs, but it cannot provide definitive proof for inputs that were not included in the tests. Therefore, testing alone cannot ensure the complete "functional correctness" of a program, meaning it cannot guarantee that the program will behave as intended for all possible input values.
In contrast, formal verification is an approach that assesses the correctness of software by comparing a formal model of the program to a formal specification. The formal model represents the program mathematically, while the formal specification defines the program's properties and logical assertions regarding its execution.
Here are some tools and libraries for Unit Testing smart contracts:
These tools and libraries provide various functionalities and options to streamline the testing process and ensure the reliability of your smart contracts.
Unit Testing is the most crucial testing method to be carried out of every other testing method; There are many frameworks, tools, and libraries that help you in testing your application one of which is Hardhat. Hardhat helps in compiling and testing smart contracts at the very core. And also has its own built-in Hardhat Network (Local Ethereum Network design for development purposes). It also allows you to deploy contracts, run tests, and debugging of code.
1. To install hardhat to the current project directory, run the following commands in your terminal.
npm init
npm install --save-dev hardhat
2. Next you need to set up your project in the current working directory in order to connect with the Hardhat development environment. For projects set up in the current directory, paste the below command in the command prompt by changing the directory to the current directory you want to set up your project.
npx hardhat
Select Create an empty hardhat.config.js with your keyboard and hit enter, once you've run the above command. hardhat.config.js file is important for a hardhat project.
3. Create three important folders in the working directory.
Below is the expected folder structure:
4. Other necessary installations (plugins) required, which help in developing a smart contract, testing, and creating an environment. To install it run this in your project working directory. Recommended for npm 7+ versions.
npm install --save-dev @nomicfoundation/hardhat-toolbox
For the npm 6 version or less, it might require installing a few more dependencies,
npm install --save-dev @nomicfoundation/hardhat-toolbox @nomicfoundation/hardhat-network-helpers @nomicfoundation/hardhat-chai-matchers @nomiclabs/hardhat-ethers @nomiclabs/hardhat-etherscan chai ethers hardhat-gas-reporter solidity-coverage @typechain/hardhat typechain @typechain/ethers-v5 @ethersproject/abi @ethersproject/providers
5. In case the above installation fails or shows errors like peer dependency conflict in the dependency tree, is most likely because of the npm version you have installed the error is most probably shown in the npm version above 7. Because npm version 7 and above treats peer dependency conflicts as errors. To tackle such a situation, install the npm version under 7.
OR
`npm install --leagacy-peer-deps`
`npm config set legacy-peer-deps true` to permanently resolve the error
6. Add the installed libraries/modules to hardhat.config.js file, add the ' require("@nomicfoundation/hardhat-toolbox"); ' statement as shown below:
Create any basic solidity smart contract under the contracts folder. For example:
Compile the smart contract to check if there are any errors or anomalies, using the hardhat compiler, use the following command,
npx hardhat compile
Important terminologies to understand:
Paste the below code into the token.js file:
In your command prompt or terminal run npx hardhat test, and the following output is expected:
Token contract
✓ Deployment should assign the total supply of tokens to the owner (654ms)
1 passing (663ms)
Which means the test has been passed.
const [owner] = await ethers.getSigners();
In ethers.js, a Signer is an object that represents an Ethereum account. Transactions are sent to contracts and other accounts using this method. We're obtaining a list of the accounts in the node we're connected to, which is Hardhat Network in this case, and we're just saving the first one. The global scope includes the ethers variable. If you prefer that your code be always explicit, you may add the following line at the top:
const { ethers } = require("hardhat");
const Token = await ethers.getContractFactory("Token");
A ContractFactory is used to create an instance of a token contract. Token here is just the instance.
const hardhatToken = await Token.deploy();
Calling the deploy() function on a ContractFactory instance will start the deployment process and returns a Promise. This object that gets created has a method for each of your smart contract functions.
const ownerBalance = await hardhatToken.balanceOf(owner.address);
After deploying the contract, contract methods on hardhatToken can be easily called. By calling the balanceOf() method we can extract the owner's account balance.
It is important to understand that the account that deploys the token gets its entire supply. And by default instances are connected to the first signer.
expect(await hardhatToken.totalSupply()).to.equal(ownerBalance);
Here it is IMPORTANT to note that the total supply and owner's balance should be equal. Therefore to check this, we wrote the above code statement, which will tell you whether the contract is correctly deployed or not.
To do this we're using Chai which is a popular JavaScript assertion library. These asserting functions are called "matchers", and the ones we're using here come from the @nomicfoundation/hardhat-chai-matchers plugin, which extends Chai with many matchers useful to test smart contracts.
To test your code by sending a transaction from an account (or Signer in ethers.js terminology) other than the default one, you can use the connect() method on your ethers.js Contract object to connect it to a different account, like this:
The below Code is the full test suite for Token.sol with other added information and the tests to be performed are structured in comprehended format.
Run npx hardhat test in your command prompt/ terminal. The output of npx hardhat test should look like this:
Note : npx hardhat test would automatically compile the last updated code.
Debugging of solidity smart contracts is possible by logging messages by calling console.log() from solidity code while running smart contracts and tests on Hardhat Network. Hardhat provides a console.sol module which makes it possible to log messages from solidity code. To do so, you just need to import hardhat/console.sol. This is how it should look like:
Then you can simply add console.log to solidity functions similar to using it in Javascript. Here we are using it in the transfer() function.
Logging outputs will be reflected when you run your tests:
After your dApp is ready, you may want to deploy it onto a live network such that others can access an instance that's not running locally on your system. The word 'Live Network' means it can be deployed on Ethereum's "mainnet" which deals with money or "testnet" that do not require actual money. A few examples of testnet are Goerli and Sepolia. Sepolia is recommended.
Create a deploy.js file inside the scripts folder and paste the following code into deploy.js:
To connect to a specific Ethereum network, you can use the --network parameter when deploying, like this:
npx hardhat run scripts/deploy.js --network <network-name>
Without using the --network parameter causes code to run against the embedded instance of the Hardhat network. Here the deployment gets lost when Hardhat finishes running, then to it is useful as it will help in testing that our deployment code works:
Output:
To deploy to testnet, simply add a network entry to your hardhat.config.js file and make the necessary changes as per the below code block. Using Goerli for this example, Sepolia or any other network is preferred as Goerli will be soon deprecating by the end of this year. In this example we will be using the Alchemy Web3 development platform in a similar way INFURA can also be used.
Grab your API Key for Goerli or Sepolia network from Infura or Alchemy development platform. To deploy on Sepolia or Goerli, send some Sepolia or Goerli ether to the address that will be doing the deployment. A faucet, is a service that distributes testing-ETH for free, is a good place to receive testnet ether. After that run the below command on your command prompt of the current working directory.
npx hardhat run scripts/deploy.js --network goerli
npx hardhat run scripts/deploy.js --network sepolia
You should see the deployed contract address if everything went fine.
1. Visit the Alchemy website - https://www.alchemy.com/
2. Login & Create your account.
3. Click on Create App on the dashboard.
4. Make the necessary changes as below image, you can take any network suitable. Remember Selecting Ethereum Mainnet Deals with REAL ETHEREUM MONEY you may lose it if you have some, so be careful and just deploy it on Goerli or Sepolia testnets only.
5. An App will get created which can be seen on the dashboard, by clicking on the VIEW KEY, you can copy-paste that URL with the alchemy key embedded in the GOERLI_URL from the above code block.
6. You can get the Metamask Private Key from Your Metamask account. First Make sure that you have enough goerli test ethers and that you are accessing the goerli test accounts and not from mainnet. You can get Goerli Test Ethers from ALCHEMY faucets. Then click on the 3 dots at the right corner of your Metamask account address and then click on the account details to get your private key.
7. For Accessing the private key either scan the QR code or Click on the export private key.