arsalandywriter.com

<Best Practices for Solidity Coding Standards: A Comprehensive Guide>

Written on

The aim of this article is not to reiterate the official Solidity Style Guide, which should be referred to, but to highlight the frequent deviations from this guide observed during code audits or reviews. Some of the points discussed may not appear in the style guide, yet they represent common stylistic errors made by Solidity developers.

First two lines

  1. Add SPDX-License-Identifier

    While your code may compile without this line, it will trigger a warning. Simply including it will eliminate that warning.

  2. Correctly Set the Solidity Pragma Unless Creating a Library

    You may have encountered pragmas such as:

    pragma solidity ^0.8.0;

    and

    pragma solidity 0.8.26;

    Which version should you use? If you are compiling and deploying the contract, you know your Solidity version, so it’s best to specify it clearly. However, if you’re developing a library for others to use, avoid fixing the pragma, as you cannot predict the compiler version your users will choose.

Imports

  1. Specify Library Version in Import Statements

    Instead of this:

    import "@openzepplin/contracts/token/ERC20/ERC20.sol";

    Use this:

    import "@openzeppelin/[email protected]/token/ERC20/ERC20.sol";

    To find the latest version, click on the branch dropdown on GitHub, select tags, and choose the most recent release. Always opt for the latest stable version.

    Failing to version your imports may lead to your code breaking or behaving unpredictably when the underlying library changes.

  2. Utilize Named Imports Instead of Importing Entire Namespaces

    Instead of:

    import "@openzeppelin/[email protected]/token/ERC20/ERC20.sol";

    Use:

    import {ERC20} from "@openzeppelin/[email protected]/token/ERC20/ERC20.sol";

    Importing everything from a file can clutter the namespace, potentially leaving unused code that the compiler optimizer may not eliminate.

  3. Eliminate Unused Imports

    Tools like Slither can automatically flag these. Don’t hesitate to remove them; tidying up code is essential.

Contract Level

  1. Implement Contract-Level NatSpec

    NatSpec (natural language specification) serves to provide human-readable inline documentation. Here's an example:

    /// @title Liquidity token for Foo protocol

    /// @author Foo Incorporated

    /// @notice General notes for non-technical readers

    /// @dev Technical notes for Solidity developers

    contract LiquidityToken {

    }

  2. Structure Contracts According to the Style Guide

    Functions should be organized first by visibility and then by their state-modifying capabilities. The order should be: receive and fallback functions (if applicable), external functions, public functions, internal functions, and private functions. Within those categories, payable functions should be prioritized over non-payable, followed by view, and then pure functions.

    contract ProperLayout {

    // Type declarations

    address internal owner;

    uint256 internal _stateVar;

    uint256 internal _stateVar2;

    // Events

    event Foo();

    event Bar(address indexed sender);

    // Errors

    error NotOwner();

    error FooError();

    error BarError();

    // Modifiers

    modifier onlyOwner() {

    if (msg.sender != owner) {

    revert NotOwner();

    }

    _;

    }

    // Functions

    constructor() {}

    receive() external payable {}

    fallback() external payable {}

    // Function ordering by visibility

    function foo() external payable {}

    function bar() external {}

    function baz() external view {}

    function qux() external pure {}

    function fred() public {}

    function bob() public view {}

    }

Constants

  1. Substitute Magic Numbers with Constants

    If you encounter a number like 100 in your code, clarify its purpose. Generally, numbers should be defined as constants at the beginning of the contract.

  2. Use Solidity Keywords for Ether or Time Measurements

    Instead of writing:

    uint256 secondsPerDay = 60 * 60 * 24;

    Use:

    1 days

    Similarly, replace:

    require(msg.value == 10**18 / 10, "must send 0.1 ether");

    with:

    require(msg.value == 0.1 ether, "must send 0.1 ether");

  3. Utilize Underscores for Large Number Readability

    Instead of:

    uint256 private constant BASIS_POINTS_DENOMINATOR = 10000;

    Write:

    uint256 private constant BASIS_POINTS_DENOMINATOR = 10_000;

Functions

  1. Remove Virtual Modifiers from Non-Overridable Functions

    The virtual modifier indicates that a function can be overridden. If you’re certain a function won’t be overridden, it’s unnecessary; simply delete it.

  2. Order Function Modifiers Correctly

    The proper order is visibility, mutability, virtual, override, and custom modifiers. For example:

    // visibility (payability), [virtual], [override], [custom]

    function foo() public payable onlyAdmin {}

    function bar() internal view virtual override onlyAdmin {}

  3. Properly Utilize NatSpec for Functions

    Like contract-level NatSpec, function NatSpec outlines parameters and return values based on function arguments. This aids in describing argument names without lengthy variable names.

    /// @notice Deposit ERC20 tokens

    /// @param token The ERC20 token to deposit

    /// @param amount The amount of tokens to deposit

    /// @returns The amount of liquidity tokens received

    function deposit(address token, uint256 amount) public returns (uint256) {}

    You can inherit NatSpec from functions in parent contracts as well.

General Cleanliness

  1. Remove Commented Out Code

    This is straightforward; commented code adds unnecessary clutter.

  2. Choose Variable Names Wisely

    Naming is crucial for code readability. Here are some tips:

    • Avoid generic terms like "user." Instead, use specific names such as "admin," "buyer," or "seller."
    • The term "data" often lacks precision; prefer clearer names like "userAccount."
    • Use consistent terminology for the same entity; don’t mix "depositor" and "liquidityProvider."
    • Include units in variable names, e.g., "interestRatesBasisPoints" instead of just "interestRate."
    • Use verbs in state-changing function names.
    • Maintain consistency with underscores to differentiate internal variables from function arguments.
    • Following the convention of using "get" for data retrieval and "set" for data modification can enhance clarity.
    • After coding, revisit your variable and function names to ensure precision.

Additional Tricks for Organizing Large Codebases

  • For numerous storage variables, define them in a single contract and inherit from that contract.
  • Use structs to manage functions with many parameters.
  • Consolidate imports into a single Solidity file before importing it to streamline code.
  • Group related functions into libraries to reduce file size.

Mastering the organization of large codebases is an art, best learned by examining established projects.

Learn More with RareSkills

This checklist is utilized in our advanced Solidity bootcamp for code reviews.

Share the page:

Twitter Facebook Reddit LinkIn

-----------------------

Recent Post:

The Future of Finance: How AI is Transforming Investment Strategies

Discover how artificial intelligence is reshaping the financial sector and what it means for investors and institutions alike.

United in Diversity: Navigating the Hybrid Work Landscape

Explore how companies can thrive in hybrid environments by fostering inclusivity and adapting to new norms.

Best Practices for Protocol Buffers: Naming and Organization

Explore essential naming conventions and organizational strategies for Protocol Buffers to enhance maintainability and readability.

Transforming My Waistline: The Journey from Stagnation to Success

Discover how I shed over 30 pounds in six months, transforming my approach to fitness and nutrition.

My Journey through the Darkness of an Eating Disorder

A personal reflection on navigating the struggles of an eating disorder and the desire for recovery.

Is It Time to Embrace the Dating Game?

Exploring the complexities of human behavior in dating and the need to engage authentically.

Why Niels Bohr Deserves Recognition, But Not in Baseball's Hall of Fame

Niels Bohr's scientific achievements are remarkable, but they don't qualify him for the Baseball Hall of Fame alongside legendary players.

Navigating a Notary Adventure: A Journey Through Miscommunication

A humorous tale of miscommunication and navigation challenges during a notary appointment.