Release: Version 2.2
Name | Smart Contract Code Review And Security Analysis Methodology |
---|---|
Creators | Hacken OU |
Subject | audit; security analysis; smart contracts; |
Description | The methodology described herein provides specific guidance on how to plan and execute an audit of smart contracts in line with the Smart Contracts Audit and Analysis Requirements provided by Hacken. |
Contributor | Luciano Ciattaglia |
Date | September 13th, 2024 |
Rights | Hacken OU |
The Smart Contracts Audit and Analysis Methodology constitutes a response to the Hacken customers’ requests and is based on years of experience in conducting smart contracts audits. The purpose of this document is to explain to the Hacken customers’ and community the audit process and artifacts to increase the quality of smart contract development and save the funds of Hacken customers and their users.
The total market capitalization of crypto has reached $2.64T (>6X growth vs 2020) while the total DeFi market capitalization has passed the level of $150B (>9X growth vs 2020). In 2021, the level of crypto adoption has increased by 880% compared to the estimates for 2020. And these figures keep on growing. The mass adoption of crypto is becoming a new reality.
However, such skyrocketing crypto adoption is accompanied by growing security risks. In 2020, hackers stole crypto assets worth $1.9B and, in 2021, the majority of hacks are taking place in the DeFi segment ($361M out of $681M).
The owners and core teams behind any crypto project should realize the scope of risks associated with smart contracts and need to know how to address these threats. The most common risks related to smart contracts are operational, implementation, and design risks. The exploitation of any of these flaws by hackers can damage the competitiveness of a project or even destroy it.
No changes can be introduced into a smart contract once it is deployed. After initial exploitation, further hacks are likely to take place until all assets are stolen.
Smart contracts audit is the only effective way for projects to prevent the occurrence of serious security incidents.
Most developers need guidance on how to write secure and high-quality smart contracts. Our methodology will serve the needs of our customers, their technical teams, and their potential clients (community).
Our methodology will allow product owners to:
The value of our methodology for developers:
Preparation for an audit is as necessary as the audit itself.
During the preparation, customers have a chance to review their projects one more time thoroughly and to ensure that everything they have planned is implemented correctly and the majority of possible risks are mitigated. Good preparation saves both time and money that projects can later spend for additional audits. Also, at this stage, a Customer themself can detect many issues.
To get maximum value from the audit, we recommend our Customers follow the following steps:
To understand the needs of the Customer and to ensure that everything is developed as planned, providing functional requirements is important.
The main question that should be answered here is: what do we want to achieve via these smart contracts?
Functional requirements need to be clear, simple, and unambiguous. Here are some examples of well-written functional requirements:
Try to provide as many functional details as possible.
User stories can be useful as well.
Technical description is also required. It should contain information about the used languages and technologies, deployment instructions, instructions on how to run tests etc. Non-functional requirements can also be included in this section.
A development environment for the project should be configured by the Customer. Truffle, Hardhat, or any other comprehensive development environment can be used. Proper description of the environment should be provided. An example of the project environment configuration can be found here.
The code should be covered with unit tests. Test coverage should be 100%, with both positive and negative cases covered. Test cases that imitate usage of contracts by multiple users should be provided to ensure that contracts will not be vulnerable to DoS attacks and that operations of one user do not affect the other unless it is a part of requirements. Test should be executed on the emulated VM without the need to run local VM’s or connect to testnets.
We recommend using TDD during the development process.
We highly advise adhering to the official language code style guides and formatting code before submission for audits. Code readability and compliance with the language style guide are some of the factors that determine the code recommendations. Furthermore, to expedite the bug-fix verification process, it is essential to format the code before the initial audit. This approach reduces the time required for verifying issues and minimizes the likelihood of overlooking errors in newly added code.
Code review and analysis is performed by lead and assistant auditors across the whole process. Additional quality reviews are performed by an expert auditor to verify that all good security practices were performed during the workflow. All steps of this process:
The purpose of the pre-audit phase is to help customers understand how their project is being prepared for the audit and identify any areas that need improvement. Depending on the audit timeline, improvements can be made either before the actual audit or during the audit.
At this step, lead and assistant auditors read the code to understand its structure and purpose. Functional and technical requirements as well as other documents provided by clients are examined in details. No serious findings can be detected at this step. The main outcome of this step is a high-level description of the code provided by auditors. Those deliverables are used internally during the next steps of the audit.
Automated tools are used to search for simple issues and provide more information about smart contracts under review. The following tools are used:
During the testing stage of a smart contract audit, at least one auditor will focus on performing fuzz testing to ensure that even the most obscure edge cases are covered. Fuzz testing, or fuzzing, is especially valuable for complex projects where traditional testing methods may not uncover all potential vulnerabilities.
Invariant testing ensures that certain conditions or properties (invariants) of a smart contract remain true throughout its execution. Invariants are critical properties that must always hold, regardless of transaction sequences or state changes. Testing these ensures the contract's integrity, correctness, and security.
Traditional testing methods often need help identifying edge cases and unforeseen scenarios; This is where fuzzing shines. By injecting random or invalid data into the contract, fuzzing explores uncharted territories and exposes vulnerabilities that might otherwise remain hidden. Think of it as a stress test, pushing the contract beyond its expected boundaries to discover potential points of failure. This helps ensure that the contract behaves as expected under all possible conditions, ultimately enhancing the overall security and reliability of the smart contract.
Ensures Reliability: By verifying that invariants hold under all scenarios—including those generated by fuzzing—auditors confirm the contract behaves as intended, reducing unexpected behaviors.
Detects Logical Errors: Fuzzing provides a wide range of random or unexpected inputs that challenge the contract's logic. This stress-tests the invariants, uncovering subtle bugs or flaws not evident through standard testing.
Validates Security Properties: Invariants often represent critical security aspects. Fuzzing attempts to violate these invariants, ensuring that security requirements are consistently met.
Automates Bug Detection and Efficient Testing: Fuzzing automates the generation of diverse test cases, allowing extensive testing without exhaustive manual effort. This increases the chances of uncovering edge cases that could break invariants.
Comprehensive Coverage: Fuzzing generates a broad spectrum of inputs, including those that may not be anticipated by developers, ensuring that a wider range of potential scenarios is tested.
Discovery of Edge Cases: It excels at identifying edge cases that can lead to unexpected behaviors, such as overflows, underflows, or unexpected state changes, which are critical in the context of smart contracts.
Builds Confidence: Demonstrating that invariants hold even under the diverse conditions generated by fuzzing increases trust among users and stakeholders, showing the contract has been thoroughly vetted.
Stateless Fuzzing: Stateless fuzzing involves testing a smart contract by randomly generating inputs or transactions without considering the state of the contract or previous interactions. In this approach, each input or transaction is treated independently, and the focus is on exploring the contract's behavior under various conditions without any prior knowledge of its state. Stateless fuzzing is useful for quickly identifying vulnerabilities or edge cases that could lead to unexpected outcomes. However, it may overlook issues related to the contract's state dependencies or interactions between different transactions.
Stateful Fuzzing: Stateful fuzzing, on the other hand, takes into account the current state of the smart contract and its interactions with previous transactions. Instead of treating each input in isolation, stateful fuzzing aims to simulate realistic usage patterns by maintaining a model of the contract's state and executing transactions based on this model. This approach allows for more thorough testing of smart contracts, as it can uncover vulnerabilities related to state transitions, reentrancy, or unexpected interactions between different parts of the contract. Stateful fuzzing typically requires more computational resources and complexity compared to stateless fuzzing but can provide deeper insights into the contract's behavior and security properties.
Tool Selection: The first step is to select the appropriate fuzzing tool based on the project's protocol and programming language. For instance, Echidna is often used for Solidity-based smart contracts, while cargo-fuzz is suitable for projects written in Rust.
Identifying Invariants: Next, identify the invariants of the smart contract. In the context of smart contracts, an invariant is a condition or property that should always hold true, regardless of the contract’s state or the inputs it receives. Common invariants include conditions like “the total supply of tokens should not exceed a predefined limit” or “a contract’s balance should always be non-negative.”
Execute Fuzz Tests: Perform multiple fuzzing runs to test all identified invariants, ensuring that the contract maintains its integrity across a wide range of scenarios. Each run helps to verify that the contract behaves as expected, even when subjected to unforeseen or malicious inputs.
By incorporating fuzz testing as a standard practice in smart contract audits, auditors can significantly enhance the security and robustness of the contracts, providing greater assurance that they will perform as intended in the real world.
At this step, auditors have more information about the code and can now build funds and data flow diagrams. We visualize all possible states of the contract and all its interactions with other contracts. All changes of data and funds flow should be displayed on those diagrams.
During this step, some issues can be found and recorded by auditors.
This allows us to understand smart contracts before line-to-line review better.
BPMN and UML diagrams are used to visualize funds and data flows
During the line-to-line review, auditors thoroughly read each line of the code and record every issue detected. We review contracts for all known issues such as the ones described in SWC. We also look for possible data manipulations, access violations, flash loans, and other manipulations that can be performed through interaction with other contracts. This step also includes validation of the code according to code-style guides and best practices.
Items differ for each language and platform that we audit smart contracts for.
EVM (Solidity, Vyper, Yul): Checked Items (EVM)
Rust-based smart contract for Solala, Near, CosmWasm, and other platforms: Checked Items (RUST)
All auditors review their findings and audit artifacts, and share results internally. Auditors prepare materials for the report. An expert auditor reviews all materials prepared by the lead auditor and performs a quality review of the report. Most possible issues are already documented at this step.
During the testing phase of the audit, if unit tests are configured for the project, auditors check coverage and write their own test cases if required. Ideally, code coverage should cover all positive and negative cases. Some issues can be reproduced with invalid config of dependency contracts. Auditors create such configs and run corresponding tests.
If unit tests are not configured, auditors deploy smart contracts on the local network and check all required cases.
Some sophisticated issues require complex exploiting contracts. Usually, if it is unclear how the issue can be exploited, auditors provide the sample attacking contracts.
After all code review, analysis, and tests, auditors prepare a report. Reports have the following structure:
All issues are reported to the Customer in the preliminary report. If some points are unclear, we can provide more descriptions of issues or explain everything on a call.
The customer is entitled to one complimentary remediation check if the following conditions are met:
Fixes must be submitted in a structured list, detailing the Finding ID and the corresponding commit hash.
After all fixes are validated, a final report is provided to the customer.
During the auditing, an issue can have one of the following statuses:
Hacken assumes no liability for any security breaches or vulnerabilities ("rekt" cases) that may arise due to these unresolved or partially resolved issues.