How to write Unit tests for ERC-20 Ethereum Smart Contracts
It’s very important to write unit tests for your smart contracts, same as for any development project. However, unit testing in blockchain-based solutions is often underestimated and overlooked. Last year I performed more than 200 audits of smart contracts written mostly for Ethereum, and also for Neo, Eos, Tron and Bitcoin blockchains. From what I observed, nearly half of these projects didn’t write unit tests. Such oversight often resulted in poor contract performance and various security issues identified during audit.
Each smart contract has common parts like constructor, total supply, functions for transfer to and from, for approval, and sometimes a function for burning extra tokens. So it’s important to check your smart contract for correct initialization of all the parameters, and for reverts when you overflow or underflow total supply or other uint values. Also you need to check modifiers and proper rights usage.
Here we will consider only the Ethereum smart contracts, but this also applies to other platforms as contracts have the same structure there. First, let’s test proper initialization of token and its correct transfer to some address.
Tests for correct initialization are simple. You just need to create a sample contract and check for correctness of all values that must be initialized.
Checking transfer function is very important, because there may be issues that would cause incorrect transfers. You must make sure that recipient’s and sender’s balances will change upon transfer, try to get reverts in case function gets wrong parameters, for example, when amount being sent exceeds the sender’s balance, when contract address or invalid address is sent instead of recipient address, etc. And finally you must check that you get correct logs from transfer event.
transferFrom function is very similar to transfer, but here you also need to test that spender has enough approved balance for sending. Here are tests when spender has less amount of funds than required for transfer.
approve function is the simplest function from ERC20 standard. There is no need to check for zero address, it’s enough to check that allowance array is correctly filled. Also if you don’t have increaseApproval or decreaseApproval functions, approve will overwrite all previous values. So we recommend using these functions as protection from unnecessary overwrites. And of course it’s important to check that you get correct logs from Approval event.
Most smart contracts include a function for burning tokens left after main sale. Lots of them has a special token holder account, sometimes it’s owner account. So the best solution for burning unsold tokens is the following: get amount of tokens on holders’ address, then subtract this amount from total supply, and set amount of tokens on holders’ address to zero. This will ensure that you don’t burn all the tokens, so it’s important to lay out your token burn strategy in white paper.
It’s very important to test your smart contract before deploying it on main network in order to prevent issues in future. When you have written unit tests, they will guarantee that there won’t be any discrepancy between your white paper and smart contract, and your smart contract will not be hacked by calling functions which should have the correct rights but they don’t.
Best programming interview quote I've heard in a while: "The code's not done until the unit tests are done.
It’s not about just Smart Contracts, you need Unit tests for all your apps and codes, because it shows you all the ways how your app could be failed.