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:

Confront Your Fears: Achieve Your Goals with Confidence

Discover how to confront your fears and achieve your goals with confidence. Learn effective strategies for personal growth.

A Comprehensive 6-Day Bodybuilding Regimen for Growth

Discover a 6-day workout plan designed for muscle growth and strength enhancement, tailored for optimal results.

Unlocking the Potential of ChatGPT Plugins: A Comprehensive Guide

Explore how to effectively install and utilize ChatGPT plugins to enhance your AI experience, including step-by-step instructions and video resources.

Einstein's Unexpected Take on UFOs: A Glimpse into His Mind

Discover Albert Einstein's surprising response to UFO sightings in 1952, revealing his thoughts on life beyond Earth.

Harnessing the Power of Affirmations: Words That Transform Lives

Discover how affirmations can enhance your confidence and transform your life through the power of words.

The Chaotic Genius of Young Steve Jobs: A Deep Dive

Explore the early life of Steve Jobs, his pranks, and the lessons they teach about ambition and questioning societal norms.

Exploring Free Will: The Intersection of Science and Philosophy

A deep dive into how science and philosophy intertwine in the debate over free will, featuring insights from quantum mechanics and neuroscience.

Transform Your Life in Just One Year: A Simple Guide

Discover a two-step process to transform your life in one year, focusing on breaking bad habits and building new, healthy routines.