How (not) to screw up Solidity smart contract
Smart Contracts are considered to be the second generation of the blockchain. They are scripts executed and stored in the blockchain, designed to have their storage and execution calls all publicly available and verifiable by the community without the need of a single authority that controls it. Their most popular implementation is the one available on Ethereum blockchain.
Bellow, I have gathered a bunch of useful tools and practices that helped our dedicated development teams in increasing the quality of our blockchain development services.
The goal of this article is to present some of the Ethereum-specific Solidity potential vulnerabilities and implementation security flaws along with their real-world impact. It will provide you static analysis tools and best practices that will help you avoid a number of serious bugs.
By default all your data is public
Contracts are made up of state variables (with three possible visibility specifiers:
private) and functions (which can be
private). For some reason, all functions are by default
public and can be called by anyone (by outside users, by functions inside contract or by its inherited contracts).
One of the best security practices is to specify the visibility of all your data explicitly. Mark your functions
internal unless there is a need for them to be publicly accessible.
A good example of such vulnerability was the Parity wallet’s hack – public
initWallet function which changes the contract owner – allowing attackers to steal $31M worth of Ether.
Unchecked return values
Return values are often indicators of failure. Their validation is one of the most important aspects of defensive programming, especially when dealing with external libraries.
Thanks to exceptions being available in Solidity, we can often avoid this problem. However, all of low level
send() functions behave quite different. They will return boolean
false instead of propagating exception. That’s why you should prefer
send(), which – when not validated properly – can lead to fail-open.
It’s worth to check out the King of the Ether Throne investigation here to get more familiar with this particular issue.
Arithmetic overflows & underflows
A smart contract is a program. Like the ones written in java or python, it has its limitations. The Ethereum Virtual Machine (EVM) specifies fixed-size data types for integers so they can represent a certain range of numbers. Trying to set a 1-byte
uint8 variable to
256 results in
0. Such a not-so-smart contract can be easily exploited when user input remains unchecked.
Luckily, this problem is well known and now we can use open source libraries that handle such typical errors, e.g. SafeMath. If not, you can end up like 4chan’s PoWH Coin with $800k evaporated from the contract containing unsigned-overflow error.
Re-Entrancy – the infamous DAO hack
Solidity gives us the ability to interact with external contracts’ functions. Thus – there has to be a way to handle invalid calls from other contracts. That’s what fallback function is responsible for. An attacker can prepare its malicious implementation leading to unexpected code execution in the vulnerable contract (that’s where the attack name came from – one can easily “re-enter” contract leaving it really messed-up).
As Solidity documentation points out, the fallback function can only rely on 2300 gas being available. However, it hadn’t stopped the DAO attackers to steal $150M worth Ether. Long story short – this attack ultimately led to the hard-fork that created Ethereum Classic (ETC).
The first thing you have to remember when trying to prevent re-entrancy attack is the one already mentioned – whenever possible, use
transfer() when sending funds to external contracts. The second one is to avoid state changes after ether is sent out of the contract.
The Checks-Effects-Interactions Pattern introduces a good practice to perform any external call as the last function’s code operation.
A third technique is to use a mutex, locking the code execution and thus preventing re-entrancy calls.
How to test smart contracts?
There are a number of available tools to ensure your Solidity code does not contain any security pitfalls. Some of them work offline so you can use it in Continuous Integration automation.
Securify – security scanner for Ethereum smart contracts
SmartCheck – automatically checks Smart Contracts for vulnerabilities and bad practices
Solium – linter to identify and fix style & security issues in Solidity
Solhint – open source project for linting solidity code
Oyente – an Analysis Tool for Smart Contracts
Smart contracts’ code is immutable once deployed, so patching any discovered bugs is impossible. What’s more – Solidity has a number of language-specific vulnerabilities like unintended recurrence (mentioned re-entrancy attack), gas limit, an illusion of randomness, etc.
You can get even more familiar with all of these minefields here and here.
A smart contract is often handling money and, most likely, not only your money. That’s why we need to be always up to date with best Solidity practices making those assets as secure as we want them to be.
 Each Ethereum Virtual Machine instruction costs certain amount of so-called gas units. Its purpose it to limit complexity of transaction calls. You can safely consider it as a execution fee.