Developing smart contracts: smart contract audit best practices
When building an Ethereum Blockchain project, focus on smart contract security and code quality. Otherwise, you may lose huge money. A few impressive statistics demonstrate poor consequences.
For instance, Parity Ethereum wallet lost $30 million because of code vulnerabilities. For this reason, Satoshi Pie fond lost 32,4% of its assets. In 2016, the attacker hacked the DAO and took away $50 million.
These examples clearly show that even a slight defect or an error in the smart contract logic can lead to bad results. That’s why smart contract audit is an essential part of smart contract development.
The goal of this stage is to identify code errors and check the program logic. A high-quality thoughtful audit will allow you to remove risks, anxiety, and save your nerves and resources.
In one of our recent posts, we’ve considered smart contract structure and development. In this one, we’ll speak about the best tools and practices for smart contract testing.
Preparing for smart contract audit
The preparation step takes an important role, helping auditors clarify the project objectives, faster define vulnerabilities, and significantly improve the process of smart contract audit. When preparing for this stage, adhere to the following advice:
1. Provide a clear specification
Providing a detailed specification is always a good idea, as it helps Ethereum developers clearly understand smart contract goals, use cases, and working principles.
Though having such a document is important, many companies don’t write it. However, most engineers say that the spec enables to clarify thinking and increase project success chances.
When preparing the specification, describe the intended behavior of a smart contract: how each function should work, how shouldn’t, and how all of them should work together. Use diagrams as they also allow to see possible alternatives and see how people can misinterpret your code.
2. Document the process of smart contract deployment
Though the code of your smart contract can be great, there are some important steps to make before it’s deployed. To avoid mishaps and save the team’s efforts, provide the documentation of the deployment process.
A well-documented process, involving the order in which contracts will be deployed, which compiler versions will be used for which contracts, and so on, will help you prevent insecurities and vulnerabilities.
3. Clean the project repository
A quality smart contract code without clutter will facilitate and automate the work of auditors. When preparing the project for the testing process, remove any unused and unnecessary files, contracts, or pieces of code.
Also, specify which contracts are utilized for audit only (meaning that they aren’t the part of the end system) and which of them are reused from the tested code or inherit from the validated one.
So how to audit smart contracts?
The process of smart contract audit is similar to testing of any other code: a set of standard method calls is created in the predefined environment, and statements are written for their results. The audit is a complicated process that includes:
- Test development
- Testing of smart contract state changes
- Event testing
- Error testing
- Checking a messages’ sender
For project audit it’s convenient to use Behavior Driven Development (BDD) practices, that along with the tests allow to create documentation and use cases. Today there is a wide range of frameworks, libraries, and other tools for auditing Ethereum smart contracts.
Though the test development in Solidity is limited to the capabilities of this programming language, in our work we also use JavaScript, Truffle framework, Parity, and other proven technologies.
Smart contract audit with Truffle
Now Truffle is the most popular framework for Ethereum. Truffle is a Node.js framework used to compile, link, and deploy smart contracts. The framework is written in a completely modular way enabling engineers to choose the functionality they need.
While in Truffle v.2 tests are created on JavaScript, in Truffle v.3 developers added the ability to write tests on Solidity, whose syntax is similar to that of JavaScript.
Truffle offers a lot of useful features including binary management, library linking, custom deployment support, migrations framework, scriptable deployment, access to hundreds of external packages, external script runner, and automated contract audit with Mocha and Chai – everything to avoid failures is smart contracts.
Considering the fact that Blockchain systems don’t work very fast, auditors use Blockchain test clients, for example, TestRPC that almost completely emulates the work of the JSON RPC API of Ethereum clients.
Besides standard methods, TestRPC also implements a number of additional ones, such as evm_increaseTime and evm_mine. A good alternative to applying TestRPC is to use one of the standard clients, for instance, Parity, that runs in dev mode and has transactions instantly confirmed.
To start working with Truffle framework install it via npm:
npm install -g truffle
Then, perform the truffle init command in order to create the project structure:
$ mkdir solidity-test-example
$ cd solidity-test-example/
$ truffle init
Contracts must be located in the contracts/ directory and tests – in the test/ directory. When you compile contracts, Truffle expects that each contract is placed in a separate file, and the contract name is equal to the file name.
Test development with Truffle framework
Tests use JavaScript objects that represent abstractions for working with contracts, making the mapping between operations on objects and calls of JSON RPC methods of the Ethereum client. These objects are created automatically when you compile the source code for * .sol files.
The calls of all methods are asynchronous and return Promise, thus removing the worry about tracking transaction confirmations. To avoid large amounts of unreadable code, use async/await for writing tests.
Also, to facilitate the work it’s better not to mix migrations and the creation of test instances. Instead, create them in the test by using the code that creates a new contract instance before calling each test function. For that, you can use beforeEach async function.
const congressInitialParams = {
minimumQuorumForProposals: 3,
minutesForDebate: 5,
marginOfVotesForMajority: 1,
congressLeader: accounts[0]
};
let congress;
beforeEach(async function() {
congress = await Congress.new(…Object.values(congressInitialParams));
});
Event testing in smart contracts
Events in Ethereum can be used for the following purposes:
- To return values from methods that change the contract state
- To create a history of changing the state of smart contracts
- To be used as a cheap and convenient information repository
Let’s take the following example. You need to check that when you call the newProposal method, adding a sentence to the contract, an event record Proposal Added is created.
For that, first create a member of DAO in the test and create a proposal on its behalf. Then create a subscriber for the ProposalAdded event and check that after the newProposal method was called, the event occurred and its attributes correspond to the transmitted data. A newProposal function code:
/* Function to create a new proposal */
function newProposal(
address beneficiary,
uint etherAmount,
string JobDescription,
bytes transactionBytecode
)
onlyMembers
returns (uint proposalID)
Testing of smart contract state changes
In Ethereum, addMember method is responsible for changing the state of smart contracts. To test the method, check if it records the information about the DAO participant to the array of members structures.
/*make member*/
function addMember(address targetMember, string memberName) onlyOwner {
uint id;
if (memberId[targetMember] == 0) {
memberId[targetMember] = members.length;
id = members.length++;
members[id] = Member({member: targetMember, memberSince: now, name: memberName});
} else {
id = memberId[targetMember];
Member m = members[id];
}
MembershipChanged(targetMember, true);
}
function removeMember(address targetMember) onlyOwner {
if (memberId[targetMember] == 0) throw;
for (uint i = memberId[targetMember]; i<members.length-1; i++){
members[i] = members[i+1];
}
delete members[members.length-1];
Members.length–;
}
Using the array with test accounts, add participants in the contract in the test. Then check that the members function returns the input data. It should be noted that every time the addMember method is called, a transaction is created and the Blockchain state is changed, that is the information is recorded in the distributed ledger.
Testing of errors and checking of a messages’ sender
A general way of stopping the work of contract method is the exceptions that can be created using the throw instruction. You may need an exception if your task is to restrict access to the method.
To make it implement a modifier that would check an account address which has called a method: if it doesn’t correspond the conditions (it’s not the contract owner who calls the method), an exception is created.
Other useful recommendations for the audit to avoid smart contract mistakes:
1. Use Solidity test coverage
For smart contract audit, use Solidity-coverage. It will help you measure test coverage and define code pieces that haven’t been tested yet or need more attention. Surely, 100% test coverage isn’t a panacea, so it’s also important to write a quality test code from the beginning.
2. Use linters
Linters enable Ethereum developers to enhance the code quality by making the code easier to read and review. There are many linters you can use. For example, Solcheck is a JavaScript linter for Solidity code.
Solhint is a linter that helps engineers avoid errors and vulnerabilities in Solidity smart contracts. Solhint provides Security and Style Guide validations.
3. Make static analysis
In smart contract testing, static analysis enables to find out code vulnerabilities and insecurities. To make the code analysis, you can use such tools as Oyente that analyzes the code and detects common vulnerabilities, Manticore, that allows a dynamic binary analysis with the EVM support, and Solgraph, that enables to visualize smart contract function control flow and identify potential insecurities.
4. Prepare for failure
As despite an accurate testing a smart contract may still have some errors or vulnerabilities, you should always be prepared for failures. So, make sure your code is able to respond to smart contract bugs.
For this purpose, create an effective upgrade plan for debugging and code enhancements. Manage the amount of money at risk by establishing rate limitation and maximum usage. Also, if something goes wrong, pause your contract.
In our work, we use reliable technologies and best practices for smart contract audit, allowing us to deliver the code of the highest quality and prevent errors in smart contracts.
Having high expertise in Blockchain development, we are always glad to help you with your project. So, if you have some questions or need smart recommendations, you’re welcome to apply to us and get a consultation for free!