Technical Audit Considerations for Smart Contracts
In this final part of multi-part blog series on auditing smart contracts, we will discuss the key security considerations when reviewing smart contracts. We will focus on public or permissionless blockchains as those have the greatest likelihood of risk exposure since deployed smart contracts can be viewed and analyzed by anyone including malicious actors.
While there may be many more security patterns to consider when reviewing smart contracts, those highlighted here have garnered attention because of successful exploits against public blockchain.
The owner(s) of the smart contract must maintain control of the contract and any critical functions such as fund transfer, pausing contract deposits, temporarily suspend application, shutdown, etc. What we look for is the constructor that initializes the main contract and whether the smart contract addresses the following:
Does the constructor specifically capture the owner's address(es)?
Does one or the majority of the addresses require to perform any critical functions? In other words, are there checks in place for ownership before executing an critical functions?
Variables in smart contract have different visibility scope, varying from public to privacy or more narrowly scoped to the contract itself, or more generously across other contracts. Malicious contracts can take advantage of the visibility property to manipulate variable states to their benefit. Zero Friction's approach to scope analysis also considers the intended usage of the variables, the criticality, and functions from which they are utilized to determine the risk from this exploit.
Reentrancy exploit seeks to take over control flow of a contract and manipulate the data to prevent the correct updating of state. Reentrancy typically targets key functions such as fund transfer in an attempt to force a recursive transfer until no funds are remained. We analyze reentrance several ways:
Does the smart contract make external calls? How is the external call safeguarded?
Does the function design to finish all internal work (e.g., state changes) first before calling the external function?
Does the smart contract use the appropriate coding syntax and structure?
Mathematical exploit occurs when an operation is performed that requires a fixed-size variable to store a number (or piece of data) that is outside the range of the variable’s data type. These can easily result in bugs, because programmers usually assume that an overflow raises an error, which is the standard behavior in high level programming languages, however, is not be the case in Solidity or other blockchain programming languages. Zero Friction looks for the usage of standard time-tested mathematical library such as SafeMath. We also examine the contract for checks to verify overflow or underflow conditions against both storage and calculated variables.
Unchecked Return Values
Certain function calls rely on one or more return values to make decision, while other may ignore the values. Accordingly, depending on the coding design, failure to verify low-level function state after call may result in incorrect variable states. What are low level functions? In Solidity these are call(), callcode(), delegatecall() and send(). What we look for in low-level calls is the verification of the return state of either success or failure, and the corresponding handling of the outcome including roll-back transaction as necessary.
A smart contract may rely on timestamp derived from the blockchain to define user position in queue, seed for random numbers, ordering for some actions, etc. However, such methods can have fatal consequences as the miners can set their block time to any period in the future. Such blocks if within certain tolerance will be accepted by the network. We examine any usage of timestamp with care and do not rely on them as advertised. We would prefer that a time oracle be utilized, however, the Oracle Problem would need to be addressed.
Blockchain systems are deterministic in that randomness does not exist. In other words, any attempt to generate a pseudo random number will likely be discovered. Zero Friction recommends that the source of randomness be obtained through an external random oracle (e.g, random.org as an example). In addition, if a decision needs to be made based on a random outcome, all critical inputs should be hashed using the respective private keys of the participant to prevent any possibility of front-running from other participants until the final time to reveal the selection for award.
In summary, it is not feasible to describe all the possible technical checks due to the varying numbers of blockchain platforms and underlying smart contract programming language. However, the most effective technical audit can be achieved with the right combination of personnel with the right skills and experience on what to look for.
Are you ready for Zero Friction to audit your smart contract? Contact us.