Preparing for a Smart Contract Security Audit¶
Every month, hundreds of thousands of smart contracts are deployed on Ethereum. With hundreds of millions in funds handled daily and the immutable nature of smart contracts, of course, also affecting the bugs contained therein, blockchain applications are a very attractive target for malicious actors.
A smart contract audit is a comprehensive review process where a freelancer or a firm scrutinizes the smart contract code. The primary goal of this audit is to identify and fix potential security vulnerabilities, code inefficiencies, logic errors, or compliance issues before the contract is deployed onto the blockchain.
Audit customers can save time and money by being prepared for an audit beforehand. This guide presents a roadmap to help developers prepare for a smart contract audit and tips on what to expect from an audit and how to best consume it to maximize the benefits.
Smart contract audits are a cornerstone in ensuring the security and reliability of decentralized applications. They help validate the contract's correctness, check the quality of the code, and uncover potential security vulnerabilities. High-profile smart contract breaches, such as the infamous DAO, Ronin Bridge, or Wormhole hacks, highlight the necessity of such audits.
Not only does an audit safeguard a smart contract system against security threats, but it also fosters trust among stakeholders such as the community. An audit acts as another layer of security to safeguard user assets, hopefully contributing to the protocol's long-term security and helping the project developers maintain their reputation. Let's see which steps developers can take to prepare their smart contracts for an audit.
Establishing the scope of the audit before commencing the process is critical. Auditors will have difficulty concentrating on the key areas without information on what code to cover, how complex it is, and what to focus on. A well-defined scope facilitates the creation of precise cost estimates. It aligns all parties' expectations on what can and cannot be done with a specific budget and time frame. It's also an opportunity for the project's software engineers to point auditors toward particular areas of the codebase where there might be concerns or suspicions about potential weaknesses, thereby improving the effectiveness and value of the audit.
A good starting point for defining an audit's scope is the repository details, where the auditors should know the specific branch and commit hash and the file path(s) leading to the contracts intended for the audit. This ensures that the auditors' time is not wasted searching for the target, and their efforts are constrained to the in-scope components in the correct version.
As the auditors navigate the codebase, the scoping document helps inform them about key system aspects that require their focus. These could manifest as specific functions, libraries, or even entire modules within the system. Identifying these areas streamlines the audit process and ensures that critical sections are not overlooked.
Throughout the code base, there may be regions that, during the audit engagement, raise concerns. These could be areas that contain complex logic, novel techniques, or where issues have surfaced during development or testing. Highlighting these areas of concern in the audit scope document acts like a beacon, drawing the auditors' attention to components known to contain many risks.
Certain system parts may not warrant auditing for various reasons, such as previous audits, well-known code, or irrelevance to the system's security or functionality. Clear demarcation of these excluded areas ensures auditors can conserve their resources for the areas that matter.
Pre-audit communication with the auditors is crucial. Defining a scope not only sets the expectations and outlines the areas of focus but also fosters an open dialogue that can significantly improve the efficiency of the overall audit process. Ensuring auditors have a clear understanding of the system's properties, functionality, and any potential areas of concern will enable them to provide a more thorough and valuable audit in the given time frame.
After, and sometimes even during, a security audit, immediate action must be taken based on a finding. Time should be allocated for an experienced team member to understand the auditors' findings, validate whether the auditors' recommendation is the optimal way to go, and eventually fix the vulnerability. Test cases should be implemented to validate that the security issue has been mitigated effectively to prevent future regressions.
After the initial audit, it is best practice to schedule a retesting phase to verify the implemented fixes and assess their impact on the system. Suppose questions about a particular finding's nature, ambiguous working, or similar arise. In that case, most auditors are available for small follow-up questions at no extra charge. For more substantial work, like validating fixes, audit companies typically offer retainer hours or reserved time slots for subsequent retesting.
Before the auditing process starts, consider pausing active development and deploying new features. Any changes made to the code can invalidate the audit findings. The changes will not affect their process since auditors will work on a fixed code revision. However, changes to the code under test can result in auditor recommendations on fixing a particular security vulnerability becoming invalid or even insecure. These inconsistencies should be avoided whenever possible.
Freezing the code may seem counterintuitive to teams used to fast-paced development cycles. However, it is crucial for the integrity of the audit process. It reduces the risk of missing or reintroducing previously detected vulnerabilities. Furthermore, audit periods can allow the development team to focus on improving the code's documentation, building out and refactoring the test suite for better coverage and efficiency, or performing development work limited to out-of-scope components, such as unrelated smart contracts or the front end.
Clean Solidity Code¶
The quality of your smart contract code significantly impacts the efficiency and outcome of the audit. Clean, well-structured code is easier to read, understand, and reason. This helps auditors familiarize themselves with the code base faster. It can also help developers debug more efficiently and speed up development. The following sections contain some properties that make a Solidity code base clean and keep it that way.
Solidity Styleguide and Naming Conventions¶
The official Solidity documentation has a comprehensive style guide. It is inspired by Python's PEP8 and outlines the basics of structuring, naming, and formatting Solidity code. Since many code examples are given by the official style guide, they will not be repeated here. One topic, in particular, is worth highlighting, however:
Contracts, functions, and variables should have consistent, self-explanatory names. Consider creating a glossary for any terms specific to your project to help auditors understand the domain-specific vocabulary of your contract. This can also be an excellent resource for onboarding new developers.
NatSpec and Inline Comments¶
Colloquially known as NatSpec, Natural Language Specification serves as a conventional format for integrating documentation directly into Solidity code using natural language comments. These comments, composed in unambiguous, plain English, explain the functionality of a contract or function, detailing parameters, return values, and any other information that developers and auditors might require when interfacing with the code.
Notably, a variety of development tools can parse NatSpec comments. This includes the Solidity compilers, IDEs like Remix, and dedicated tools such as OpenZeppelin's solidity-docgen. This enables these tools to generate human-readable documentation that is easily accessible. By using NatSpec comments, auditors can understand the code better and streamline their development workflow, thus enhancing productivity and code quality.
Next to NatSpec comments, inline comments can supply information more granularly. They can underline the need for certain control flows and specific code constructs. In the audit context, inline comments can also highlight areas of concentrated risk, such as untrusted external calls or sensitive calculations. NatSpec explaining a contract's and function's overarching structure, as well as inline comments detailing the precise execution of the code, should go hand in hand.
Compiler and Linter Warnings¶
Addressing compiler warnings before the audit is essential. These warnings might signify potential issues in the contract. Similarly, a linter can help flag any less severe syntax- or formatting-related issues. Linters like Ethlint (formerly Solium) or Solhint can help ensure your code adheres to a consistent quality standard. To maintain code cleanliness, a pre-commit hook can be introduced. Alternatively, the continuous integration server can enforce a format check and strict failure on compilation warnings.
Comprehensive documentation is a cornerstone of a successful smart contract audit. In the bigger picture, it is so important that it warrants its own section in the Field Guide. Good documentation speeds up the initial audit phase and highlights potential security risks.
A well-documented contract should offer a thorough description of its design, the problem it solves, and any meaningful dependencies it has. It should also detail the access controls and authorization patterns to provide the auditors with a clear view of the potential attack surface.
Clear and transparent project timelines and risk disclosures are also integral to the audit process. They provide a historical roadmap for auditors, reducing the time required to familiarize themselves with the project. Transparent risk disclosure facilitates quicker incident responses and helps determine potential exploitation points.
In addition, having a contingency plan ready enhances the project's resilience. An action plan detailing strategies for bug discovery, a disclosure policy, and procedures for handling unexpected situations can ensure user confidence and the security of funds.
Check out the dedicated page for documentation.