Skip to content

Documenting Smart Contracts: Recommendations and Security

Solid documentation is the cornerstone of any successful smart contract audit. It illuminates a smart contract system's design, functionality, and attack surface, making it easier for auditors and external contributors to evaluate it efficiently. Accessible documentation can save money since new developers and auditors have a shorter ramp-up time until they can contribute to the project, and it can be used as a guide to implement future features more securely.

This page provides developers and project managers a list of best practices on what good documentation looks like and how to document smart contract systems in a future-proof fashion, hopefully saving work along the way.

Design Overview

Every smart contract system aims to solve a specific problem. High-level documentation should always start with a problem description, why it matters, and how the system seeks to solve it. After the high-level description and approach, low-level components and their role in the overall system should be introduced. This will underline the previously mentioned design rationale and assumptions made during development. The high-level introduction of the smart contract system and subsequent explanation of its inner workings can be easily coupled with graphics built during the system design process.

With a general overview of the smart contract system's moving parts in place, the interactions and controls between all components need to be documented. The following points should be clarified for every meaningful interaction between components:

  1. Cross-contract Dependencies: Large smart contract systems often must distribute their business logic across multiple smart contracts. These interdependencies must be outlined and explained, as they can introduce trust assumptions and potentially undesired side effects. This knowledge can help auditors evaluate the impact of particular security vulnerabilities and how their findings affect the overall system. Similarly, when implementing new features, the development team can leverage this documentation to estimate the changes necessary to reflect a new feature in the existing code base.
  2. System Controls: If a smart contract system contains authorized actions and administrative capabilities, the current system's roles must be introduced and explained. This description should encompass the on-chain related aspects, e.g., what actions a specific role or authorized party can perform in the system and at what time, as well as the off-chain related aspects, such as which party owns private keys to an authorized address, how private keys are secured, or what the specific configuration of a DAO or multisig setup is. In the same spirit as the previous point, this helps outline trust relationships and surface risks that may affect system operations.
  3. Event Logging: As explained in the monitoring section, events are data stored and indexed on-chain to be consumed by off-chain infrastructure. Since events are a smart contract system's primary means for signaling its state to external entities, it is paramount to thoroughly document what events a smart contract system contains, which components they are raised by, and what data they have. This description should also include an explanation of what the event means and how it should be consumed by off-chain software.
  4. Invariants: One often overlooked aspect of smart contract documentation is that most business logic can be modeled after a set of properties that must always hold true. When these invariants are adequately documented, they can be leveraged by auditors and developers alike, guiding test suite development and manual code reviews. When invariants are written down in natural language, they can also be translated by professionals into more formal descriptions that can then be consumed by fuzzers to guide automated testing of security-related functionality.

NatSpec and Automation

Writing documentation is often unpopular with developers and project managers since its benefits are often not immediate and tangible. Whereas high-level documentation has to be manually written, for the most part, low-level technical documentation can be directly annotated in the code base. These documentation comments can be specified on a contract- and a function level. When written in a standard format like NatSpec, tools like solidity-docgen can parse the code base and automatically generate HTML and PDF documentation. The technical documentation can be generated during the CI and testing workflow and automatically uploaded to a free hosting service such as Github Pages.

The following code snippet taken from the Solidity docs (v0.8.20) is an example of NatSpec notation:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.8.2 < 0.9.0;

/// @title A simulator for trees
/// @author Larry A. Gardner
/// @notice You can use this contract for only the most basic simulation
/// @dev All function calls are currently implemented without side effects
/// @custom:experimental This is an experimental contract.
contract Tree {
    /// @notice Calculate tree age in years, rounded up, for live trees
    /// @dev The Alexandr N. Tetearing algorithm could increase precision
    /// @param rings The number of rings from dendrochronological sample
    /// @return Age in years, rounded up for partial years
    function age(uint256 rings) external virtual pure returns (uint256) {
        return rings + 1;
    }

    /// @notice Returns the amount of leaves the tree has.
    /// @dev Returns only a fixed number.
    function leaves() external virtual pure returns(uint256) {
        return 2;
    }
}

contract Plant {
    function leaves() external virtual pure returns(uint256) {
        return 3;
    }
}

contract KumquatTree is Tree, Plant {
    function age(uint256 rings) external override pure returns (uint256) {
        return rings + 2;
    }

    /// Return the amount of leaves that this specific kind of tree has
    /// @inheritdoc Tree
    function leaves() external override(Tree, Plant) pure returns(uint256) {
        return 3;
    }
}

To make the automated documentation accessible for auditors and new developers, the smart contract project's README file should elaborate on installing the system's dependencies and generating the documentation. Alternatively, when documentation is automatically generated and hosted, the respective pages for the detailed technical documentation should be referenced wherever possible to help contributors contextualize the system's components more quickly.

For further information, the solidity-docgen project's documentation and the tool's help page should be consulted as it's still under development and constantly improving. The official OpenZeppelin contracts and documentation can be consulted for detailed syntax since they also adhere to the NatSpec standard and leverage specific features provided by solidity-docgen.

A good template for starting can be found in the official OZ forums.

Deployment

When smart contract infrastructure is designed for integration with other systems, a well-documented system provides clear guidance to developers, aiding in a smooth and efficient integration process.

For that purpose, developers need to know the locations of the most recent smart contract deployment. An excellent example of good practice in this area can be seen in the documentation provided by the Compound docs. The Compound developers list their deployments on each chain in their documentation. This practice significantly reduces the risk of developers mistyping an address or accidentally initializing their system to a deprecated contract version. Moreover, with new addresses published on partial updates, this aids in maintaining the consistency and reliability of the system.

Compound Address Listing

An excellent addition for even quicker analysis is that the docs provide links adjacent to each address, directing users to the respective blockchain explorer. The contract can be read, validated, and interacted with with minimal effort. This user-friendly approach simplifies the process for developers, reducing the time and effort required for contract analysis and interaction.

Additionally, the deployment documentation clearly outlines every step leading towards production deployment, ensuring the operations' sequence and outcomes are well understood. Providing and referencing helper scripts within the documentation enhances usability, allowing developers to automate or semi-automate parts of the deployment process. Furthermore, the documentation should comprehensively list system environment variables and smart contract constructor parameters, elucidating their roles in the deployment process and overall system functioning.

Additionally, any external contracts the system interacts with must be explicitly stated. This is crucial because the system's behavior can vary depending on the blockchain environment, and contracts it integrates with on one blockchain may not function similarly on another. Thus, developers must be warned that deployment on different blockchains may not yield the expected results. This ensures that the developers understand the context within which the system operates and can make informed decisions when planning to deploy the system across multiple blockchains.

For further recommendations on smart contract deployment security best practices, check out the Deployment section.

Contributions

Establishing clear contribution guidelines is crucial for fostering an inclusive and secure development environment. The guidelines should outline the process for submitting changes and code review and acceptance standards. This clarity enhances community collaboration, reducing the potential for misunderstanding or disputes.

Running a bug bounty program or having a systematic vulnerability reporting procedure encourages responsible disclosure of any discovered security issues. More details are in the dedicated bug bounty program section.

The documentation should include any previously conducted audits and their corresponding published reports. These provide valuable insights into the system's security posture and demonstrate transparency and dedication to quality, boosting user confidence and trust in the system.