Quoting the official documentation, Solidity “is a contract-oriented, high-level language for implementing smart contracts.” It was proposed back in 2014 by Gavin Wood and developed by several people, most of them being core contributors to the Ethereum platform, to enable writing smart contracts on blockchain platforms such as Ethereum. Solidity was designed around the ECMAScript syntax to make something web developers would be familiar with, but it is statically typed like C++, with support for inheritance, libraries, and user-defined data types. At the time Solidity was proposed, it had significant differences to other languages also targeting the EVM (e.g., Serpent, LLL, Viper, and Mutan) such as mappings, structs, inheritance, and even a natural language specification NatSpec. Like other programming languages targeting a Virtual Machine (VM), Solidity is compiled into bytecode using a compiler: solc. Smart Contracts can be seen as a computer protocol intended to complete some task according to the contract rules. In the cryptocurrencies context, smart contracts enforce transactions’ traceability and irreversibility, avoiding the need of a third-party regulator like banks. This concept was suggested by Nick Szabo back in 1994. This article is an introduction to Solidity from a security standpoint, created by the Checkmarx Security Research Team. As more and more people/organizations look to blockchain as a promising technology, and being willing to build on top of it, it is mandatory to apply software development best practices such as code review, testing, and auditing while creating smart contracts. These practices become even more critical as smart contracts execution happens in public with source code generally available. It is hard to ensure that software can’t be used in a way that was not anticipated, so it is essential to be aware of the most common issues as well as the exploitability of the environment where the smart contract runs on. An exploit may not target the smart contract itself, but the compiler or the virtual machine (e.g., EVM) instead. We cover that in the next sections, providing a Proof-of-Concept that demonstrates the discussed topics.
PreambleIn the context of Ethereum (abbreviated Eth), Smart Contracts are scripts that can handle money. These contracts are enforced and certified by Miners (multiple computers) who are responsible for adding a transaction (execution of a Smart Contract or payment of cryptocurrency) to a public ledger (a block). Multiple blocks are called blockchain. Miners spend “Gas” to do their work (e.g., publish a smart contract, run a smart contract function, or transfer money between accounts). This “Gas” is paid using Eth.
PrivacyIn Solidity, private may be far from what you may expect, mainly if you’re used to Object-Oriented Programming using languages like Java. A private variable doesn’t mean that someone can’t read its content, it just means that it can be accessed only from within the contract. You should remember that the blockchain is stored on many computers, making it possible for others to see what’s stored in such “private” variables. Note that private functions are not inherited by other contracts. To enable private functions inheritance, Solidity offers the internal keyword.
pure/view functionsPreventing functions from reading the state at the level of the EVM is not possible, but it is possible to prevent them from writing to the state ( i.e., view can be enforced at the EVM level, while pure cannot). The compiler started enforcing that pure is not reading the state in version 0.4.17. Source
ReentrancyReentrancy is a well-known computing concept, and also the cause of a $70M hack back in June 2016 called the DAO (Decentralized Autonomous Organization) Attack. David Siegel authored “Understanding The DAO Hack for Journalists” a complete events timeline and comprehensive explanation of what happened. “In computing, a computer program or subroutine is called reentrant if it can be interrupted in the middle of its execution and then safely be called again (“re-entered”) before its previous invocations complete execution.” (Wikipedia). By using a common computing pattern, it was possible to exploit a Smart Contract. It is still possible. The call() function is the heart of this attack, and it is worth noting that it:
- is used to invoke a function in the same contract (or of another contract) to transfer data or Ethereum;
- does not throw, it just returns true/false;
- triggers the execution of code and spends all the available Gas for this purpose; there’s no Gas limit unless we specify one;
- “be prepared” - any function running external code is a threat;
- These functions: <address>.transfer(uint256 amount)/ <address>.send(uint256 amount) return (bool) are safe against Reentrancy as they currently have a limit of 2300 Gas;
- if you cannot avoid using call(), update the internal state before making an external call.