Useful Modules for Developing/Testing Smart Contracts
By Seungwon Go, CEO & Founder at ReturnValues (seungwon.go@returnvalues.com)
truffle-flattener
truffle-flattener module takes all the files that are referenced in a solidity program and put them into one file.
You can install truffle-flattener using the command below:
npm i truffle-flattener --save
Now that you’re done with the installment, you can make a file that contains all the codes that are being referenced using truffle-flattener.
First, make a new folder with a name ‘flat’.
And run the command below in the terminal:
truffle-flattener ./contracts/SampleToken.sol > ./flat/SampleToken_flat.sol
If you run the command, it will load and show not only the contracts that are imported in SampleToken.sol but also all the contract codes that are referenced in the files that are imported and concatenate them into a file named “SampleToken_flat.sol” in a “flat” folder.
When you do “Verify and Publish” your deployed contract on etherscan.io, you’ll need all the codes in one file.
eth-gas-reporter
This modules calculates and reports how much gas is used for running the methods in the test cases and deploying the contract.
You can install it with the command below in the terminal:
npm i eth-gas-reporter --save
If you run the test case from “Truffle: Implementing Test Cases for Smart Contract”, you’ll see the report that shows how much gas is used for deploying the contract and running the methods used in the test cases.
Recently, I’ve run a program that deploys smart contract through web using web3. The gas needed exceeded the Block Gas Limit(4712388) on testnet(Ropsten) and I failed to deploy the smart contract.
By using eth-gas-reporter, you can estimate how much gas will be needed for deploying the contract and running methods used in advance, then you can modify your program before deploying.
solium
Solium is a module that checks the coding style and analyses security issues of your solidity program.
You can install it using the command below:
npm i solium --save
When you’re done with the installation, run the command below in the project root folder.
solium --init
You’ll see the two files have been created in the project root folder.
- .soliumignore : contains names of files and directories to ignore while linting
- .soliumrc.json : contains configuration that tells Solium how to lint your project. You should modify this file to configure rules, plugins and sharable configs.
.soliumrc.json file contains the code below:
{
"extends": "solium:recommended",
"plugins": ["security"],
"rules": {
"quotes": ["error", "double"],
"indentation": ["error", 4]
}
}
About the coding style rules, you can find them in the following link: https://solium.readthedocs.io/en/latest/user-guide.html#list-of-style-rules
Let’s check if our code meets the coding style rules using the command below:
solium -f ./contracts/SampleToken.sol
According to the result shown in the screen, it says we need to use 4 spaces for a tap in the solidity program.
Now open SampleToken.sol file and edit it so that the tap is four empty spaces and run the module to check if our program follows the coding style rules.
Then, you’ll see the message below:
About the security rules, you’ll find in the following link: https://www.npmjs.com/package/solium-plugin-security#list-of-rules
Using the command below, let’s check if our code follows the Security Rules:
solium -d ./contracts/
No issues have been found in our code.
solidity-coverage
Code coverage, meaning how much code has been covered, in software development is of the indexes that indicates how enough testing has been done. It means how much of the code has been actually executed when testing a software in development.
solidity-coverage is a module that measures code coverage of a solidity program. It measures in terms of Statement, Branch and Function.
Statement Coverage
- Statement Coverage is satisfied if every line of code is run at least once.
function foo (uint x, uint y) public returns (uint)
{
uint z;
if ((x > 0) && (y > 0))
{
z = x;
}
return z;
}
When you call foo(1,1), the conditions (x>0) and (y>0) are both satisfied, which will run z=x; so all the lines will be run. So the test foo(1,1) satisfies Statement Coverage. However, the test foo(0,1) does not meet if condition, so does not satisfy Statement Coverage.
Branch Coverage
- Branch Coverage is satisfied if the results of test cases are true and false.
function foo (uint x, uint y) public returns (uint)
{
uint z;
if ((x > 0) && (y > 0))
{
z = x;
}
return z;
}
foo(1,1) and foo(0,1) satisfy Branch Coverage. The first foo(1,1) will go through if statement and z=x will be run. But the foo(0,1) does not satisfy the condition (x>0) and z=x will not be run. Therefore, branch coverage is satisfied because when running foo(1,1), and foo(0,1), the results of the if statement satisfy true/false.
Function Coverage
Function Coverage checks if all the statement and the branch coverage are satisfied in the functions. So if any statement or branch coverage is not satisfied, function coverage is not satisfied.
Then, let’s install solidity-coverage using the command below:
npm i solidity-coverage --save
If you run the command below in the terminal, it will start analyzing the contract implemented in the contracts folder in four sections(Statements, Branches, Functions, and Lines) and will print out the result in the terminal.
./node_modules/.bin/solidity-coverage
And in the project root folder, you’ll see that coverage folder and coverage.json file have been created.
If you open the coverage/contracts/index.html in the browser, you’ll see the list of contracts we’ve developed in the contracts folder and the analysis result of each contract.
By clicking SampleToken.sol, you’ll see the source code and the result of the analysis. You need to be aware that it does not analyse the solidity files that are imported. So if you want a thorough analysis, you need to make one file that includes all the code used using truffle-flattener modules and analyse that file.
The image above shows the analysis result in four parts: Statement, Branches, Function and Lines. Let’s see the meaning of four parts:
- Statements : they refer to the code lines in the functions except branches. In the code above, the line 12,13 and 14 belong to statements.
- Branches : this is a section where it checks if certain conditions, such as if, assert, and require etc. are satisfied. In the code above, there is no Branches section.
- Functions : this is a section that are declared as function, modifier or constructor. In the code above, there is one function, and it is from the 9th to 11th line.
In the coverage.json find created in the project root folder, you’ll find the analysis results of Statements, Branches, Function, and Lines of each contract.
path
- The path to the file. This is an absolute path, and should be the same as the key in the report object.s
- Hash of statement counts, where keys as statement IDs.b
- Hash of branch counts, where keys are branch IDs and values are arrays of counts. For an if statement, the value would have two counts; one for the if, and one for the else. Switch statements would have an array of values for each case.f
- Hash of function counts, where keys are function IDs.fnMap
- Hash of functions where keys are function IDs, and values are{name, line, loc, skip}
, wherename
is the name of the function,line
is the line the function is declared on, andloc
is theLocation
of the function declaration (just the declaration, not the entire function body - see 'Location Objects' below.) Ifskip
is present and true, then this indicates that this function was ignored by a### instabul ignore ... ###
pragma. Note that if a function is not ignored theskip
field will be missing entirely.statementMap
- Hash where keys are statement IDs, and values areLocation
objects for each statement. TheLocation
for a function definition is really an assignment, and should include the entire function. In addition to the normal location object fields, astatementMap
entry can also have an optionalskip
field.branchMap
- Hash where keys are branch IDs, and values are{line, type, locations}
objects.line
is the line the branch starts on.type
is the type of the branch (e.g. "if", "switch").locations
is an array ofLocation
objects, one for each possible outcome of the branch. Note for anif
statement where there is noelse
clause, there will still be twolocations
generated. Istanbul does not generate coverage for thedefault
case of a switch statement ifdefault
is not explicitly present in the source code.l
- Hash of line counts, where keys are the line number.