Git Product home page Git Product logo

solidity-style-guide's Introduction

Coinbase Solidity Style Guide

This is a guide for Coinbase engineers developing EVM-based smart contracts. We use Solidity when developing such contracts, so we call it a "Solidity Style Guide." This guide also covers development and testing practices. We are sharing this publicly in case it is useful to others.

Why?

We should be as specific and thorough as possible when defining our style, testing, and development practices. Any time we save not having to debate these things on pull requests is productive time that can go into other discussion and review. Following the style guide is evidence of care.

tom-sachs-gq-style-spring-2019-05

1. Style

A. Unless an exception or addition is specifically noted, we follow the Solidity Style Guide.

B. Exceptions

1. Names of internal functions in a library should not have an underscore prefix.

The style guide states

Underscore Prefix for Non-external Functions and Variables

One of the motivations for this rule is that it is a helpful visual clue.

Leading underscores allow you to immediately recognize the intent of such functions...

We agree that a leading underscore is a useful visual clue, and this is why we oppose using them for internal library functions that can be called from other contracts. Visually, it looks wrong.

Library._function()

or

using Library for bytes
bytes._function()

Note, we cannot remedy this by insisting on the use public functions. Whether a library functions are internal or external has important implications. From the Solidity documentation

... the code of internal library functions that are called from a contract and all functions called from therein will at compile time be included in the calling contract, and a regular JUMP call will be used instead of a DELEGATECALL.

Developers may prefer internal functions because they are more gas efficient to call.

If a function should never be called from another contract, it should be marked private and its name should have a leading underscore.

C. Additions

1. Errors

A. Prefer custom errors.

Custom errors are in some cases more gas efficient and allow passing useful information.

B. Custom error names should be CapWords style.

For example, InsufficientBalance.

2. Events

A. Events names should be past tense.

Events should track things that happened and so should be past tense. Using past tense also helps avoid naming collisions with structs or functions.

We are aware this does not follow precedent from early ERCs, like ERC-20. However it does align with some more recent high profile Solidity, e.g. 1, 2, 3.

YES:

event OwnerUpdated(address newOwner);

NO:

event OwnerUpdate(address newOwner);
B. Prefer SubjectVerb naming format.

YES:

event OwnerUpdated(address newOwner);

NO:

event UpdatedOwner(address newOwner);

3. Named arguments and parameters

A. Avoid unnecessary named return arguments.

In short functions, named return arguments are unnecessary.

NO:

function add(uint a, uint b) public returns (uint result) {
  result = a + b;
}

Named return arguments can be helpful in functions with multiple returned values.

function validate(UserOperation calldata userOp) external returns (bytes memory context, uint256 validationData)

However, it is important to be explicit when returning early.

YES:

function validate(UserOperation calldata userOp) external returns (bytes memory context, uint256 validationData) {
  context = "";
  validationData = 1;

  if (condition) {
    return (context, validationData);
  }
}

NO:

function validate(UserOperation calldata userOp) external returns (bytes memory context, uint256 validationData) {
  context = "";
  validationData = 1;
  if (condition) {
    return;
  }
}
B. Prefer named arguments.

Passing arguments to functions, events, and errors with explicit naming is helpful for clarity, especially when the name of the variable passed does not match the parameter name.

YES:

pow({base: x, exponent: y, scalar: v})

NO:

pow(x, y, v)
C. Prefer named parameters in mapping types.

Explicit naming parameters in mapping types is helpful for clarity, especially when nesting is used.

YES:

mapping(address account => mapping(address asset => uint256 amount)) public balances;

NO:

mapping(uint256 => mapping(address => uint256)) public balances;

4. Structure of a Contract

A. Prefer composition over inheritance.

If a function or set of functions could reasonably be defined as its own contract or as a part of a larger contract, prefer defining it as part of a larger contract. This makes the code easier to understand and audit.

Note this does not mean that we should avoid inheritance, in general. Inheritance is useful at times, most especially when building on existing, trusted contracts. For example, do not reimplement Ownable functionality to avoid inheritance. Inherit Ownable from a trusted vendor, such as OpenZeppelin or Solady.

B. Avoid writing interfaces.

Interfaces separate NatSpec from contract logic, requiring readers to do more work to understand the code. For this reason, they should be avoided.

C. Avoid using assembly.

Assembly code is hard to read and audit. We should avoid it unless the gas savings are very consequential, e.g. > 25%.

4. Versioning

A. Avoid unnecessary version Pragma constraints.

While the main contracts we deploy should specify a single Solidity version, all supporting contracts and libraries should have as open a Pragma as possible. A good rule of thumb is to the next major version. For example

pragma solidity ^0.8.0;

5. Struct and Error Definitions

A. Prefer declaring structs and errors within the interface, contract, or library where they are used.
B. If a struct or error is used across many files, with no interface, contract, or library reasonably being the "owner," then define them in their own file. Multiple structs and errors can be defined together in one file.

6. Imports

A. Use named imports.

Named imports help readers understand what exactly is being used and where it is originally declared.

YES:

import {Contract} from "./contract.sol"

NO:

import "./contract.sol"

For convenience, named imports do not have to be used in test files.

B. Order imports alphabetically (A to Z) by file name.

YES:

import {A} from './A.sol'
import {B} from './B.sol'

NO:

import {B} from './B.sol'
import {A} from './A.sol'
C. Group imports by external and local with a new line in between.

For example

import {Math} from '/solady/Math.sol'

import {MyHelper} from './MyHelper.sol'

In test files, imports from /test should be their own group, as well.

import {Math} from '/solady/Math.sol'

import {MyHelper} from '../src/MyHelper.sol'

import {Mock} from './mocks/Mock.sol'

7. Comments

A. Commenting to group sections of the code is permitted.

Sometimes authors and readers find it helpful to comment dividers between groups of functions. This permitted, however ensure the style guide ordering of functions is still followed.

For example

/// External Functions ///
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/*                   VALIDATION OPERATIONS                    */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
B. ASCII Art

ASCII art is permitted in the space between the end of the Pragmas and the beginning of the imports.

2. Development

A. Use Forge for testing and dependency management.

B. Testing

1. Test file names should follow Solidity Style Guide conventions for files names and also have .t before .sol.

For example, ERC20.t.sol

2. Test contract names should include the name of the contract or function being tested, followed by "Test".

For example,

  • ERC20Test
  • TransferFromTest

3. Test names should follow the convention test_functionName_outcome_optionalContext

For example

  • test_transferFrom_debitsFromAccountBalance
  • test_transferFrom_debitsFromAccountBalance_whenCalledViaPermit
  • test_transferFrom_reverts_whenAmountExceedsBalance

If the contract is named after a function, then function name can be omitted.

contract TransferFromTest {
  function test_debitsFromAccountBalance() ...
}

4. Prefer tests that test one thing.

This is generally good practice, but especially so because Forge does not give line numbers on assertion failures. This makes it hard to track down what, exactly, failed if a test has many assertions.

YES:

function test_transferFrom_debitsFrom() {
  ...
}

function test_transferFrom_creditsTo() {  
  ...
}

function test_transferFrom_emitsCorrectly() {
  ...
}

function test_transferFrom_reverts_whenAmountExceedsBalance() {
  ...
}

NO:

function test_transferFrom_works() {
  // debits correctly
  // credits correctly
  // emits correctly
  // reverts correctly
}

Note, this does not mean a test should only ever have one assertion. Sometimes having multiple assertions is helpful for certainty on what is being tested.

function test_transferFrom_creditsTo() {
  assertEq(balanceOf(to), 0);
  ...
  assertEq(balanceOf(to), amount);
}

5. Use variables for important values in tests

YES:

function test_transferFrom_creditsTo() {
  assertEq(balanceOf(to), 0);
  uint amount = 10;
  transferFrom(from, to, amount);
  assertEq(balanceOf(to), amount);
}

NO:

function test_transferFrom_creditsTo() {
  assertEq(balanceOf(to), 0);
  transferFrom(from, to, 10);
  assertEq(balanceOf(to), 10);
}

6. Prefer fuzz tests.

All else being equal, prefer fuzz tests.

YES:

function test_transferFrom_creditsTo(uint amount) {
  assertEq(balanceOf(to), 0);
  transferFrom(from, to, amount);
  assertEq(balanceOf(to), amount);
}

NO:

function test_transferFrom_creditsTo() {
  assertEq(balanceOf(to), 0);
  uint amount = 10;
  transferFrom(from, to, amount);
  assertEq(balanceOf(to), amount);
}

C. Project Setup

1. Avoid custom remappings.

Remappings help Forge find dependencies based on import statements. Forge will automatically deduce some remappings, for example

forge-std/=lib/forge-std/src/
solmate/=lib/solmate/src/

We should avoid adding to these or defining any remappings explicitly, as it makes our project harder for others to use as a dependency. For example, if our project depends on Solmate and so does theirs, we want to avoid our project having some irregular import naming, resolved with a custom remapping, which will conflict with their import naming.

D. Upgradability

1. Prefer ERC-7201 "Namespaced Storage Layout" convention to avoid storage collisions.

E. Structs

1. Where possible, struct values should be packed to minimize SLOADs and SSTOREs.

2. Timestamp fields in a struct should be at least uint32 and ideally be uint40.

uint32 will give the contract ~82 years of validity (2^32 / (60*60*24*365)) - (2024 - 1970). If space allows, uint40 is the preferred size.

3. NatSpec

A. Unless an exception or addition is specifically noted, follow Solidity NatSpec.

B. Additions

1. All external functions, events, and errors should have complete NatSpec.

Minimally including @notice. @param and @return should be present if there are parameters or return values.

2. Struct NatSpec

Structs can be documented with a @notice above and, if desired, @dev for each field.

/// @notice A struct describing an accounts position
struct Position {
  /// @dev The unix timestamp (seconds) of the block when the position was created.
  uint created;
  /// @dev The amount of ETH in the position 
  uint amount;
}

3. Newlines between tag types.

For easier reading, add a new line between tag types, when multiple are present and there are three or more lines.

YES:

/// @notice ...
///
/// @dev ...
/// @dev ...
/// 
/// @param ...
/// @param ...
/// 
/// @return

NO:

/// @notice ...
/// @dev ...
/// @dev ...
/// @param ...
/// @param ...
/// @return

4. Author should be Coinbase.

If you using the @author tag, it should be

/// @author Coinbase

Optionally followed by a link to the public Github repository.

solidity-style-guide's People

Contributors

wilsoncusack avatar anikaraghu avatar dev-araujo avatar chase-manning avatar xenoliss avatar adamegyed avatar

Stargazers

tchkvsky avatar Daniel Jimenez avatar zetsukhun avatar Paris avatar hone1er avatar Sangyeon Woo avatar Kevin Cheng avatar Tyler Ilunga avatar Shaun avatar Zer0 avatar Philip Glazman avatar Symulacr avatar  avatar  avatar  avatar Paul Razvan Berg avatar Jongseung (John) Lim avatar Taeguk Kwon avatar qdqd avatar xo avatar Mulosbron avatar owl avatar KIT avatar Smith avatar Omar Garcia avatar MaxMillions avatar we create project avatar Sebastian Abromeit avatar denuitt avatar Rocky avatar Ahmed Ali avatar LikKee Richie avatar  avatar kaden avatar  avatar Hyunsuk Shin avatar Marc Johnson avatar Brandon Torres avatar João Victor Lyra avatar  avatar Peili Liang avatar Mirko Pezo avatar Magne Sætran avatar Paul Birnbaum  avatar  avatar Tomasz Waszczyk avatar Vinta Chen avatar  avatar  avatar Georgii Molchanov avatar Paweł Kędzia avatar timtimtim.eth avatar Sarnavo Saha Sardar avatar Myron Rotter avatar Muhammad Waqar avatar Wilson Silva avatar  avatar Agustin Velez avatar Luis Videla avatar A Dzulfikar SR avatar Todor Chomakov avatar Jonathan Tang avatar Ethan Wessel avatar Vishnuram Rajkumar avatar Steve Ellis avatar J. Pratt-Delzenne avatar adu avatar Huawei avatar sambot avatar Vishnuram Rajkumar avatar a dwarf avatar takez0_o avatar  avatar Diego avatar C-DOG avatar  avatar Saikat Karmakar avatar YBM avatar  avatar cairo avatar Joshua Cook avatar  avatar Mike Ghen avatar Yuki Nakatake avatar  avatar 0xandee avatar Shivamkumar Rewatkar avatar Mohammad Abbasnejad avatar SunJc avatar  avatar Alisina Bahadori avatar 0xNov1ce avatar  avatar MantisClone avatar Ruud Erie avatar Winson Cheng avatar  avatar Franco avatar Toufik Airane avatar  avatar

Watchers

Julio M Cruz avatar danilo neves cruz avatar Francis Wagner avatar  avatar  avatar  avatar Eric Forgy avatar Igor Coraine avatar  avatar codingsh avatar

solidity-style-guide's Issues

There's no need to fear interfaces.

I strongly disagree with the recommendation to "Avoid writing interfaces," especially with the justification that it should be done because of NatSpec comments. This perspective overlooks the integral benefits interfaces bring to contract development.

  1. Enhanced Documentation: Utilizing interfaces with NatSpec, especially through the @inheritdoc feature, actually enhances documentation quality and maintainability. It could keep comments relevant without redundancy, as demonstrated in OpenZeppelin example:
  1. Improved Modularity: Interfaces are crucial for creating modular, maintainable code. They define contracts between system components, promoting clean architecture and making large solidity codebases more manageable.

  2. Essential for Design Patterns: Interfaces are vital for advanced design patterns like the Diamond Proxy (EIP-2535). This specific pattern, by itself enhances the organization and "style" of contracts, relies on interfaces, and demonstrates their benefits.

Docs: additional project setup / contract references / solidity template

IMO it's worth adding to the docs an opinion on using node_modules vs. .gitmodules, as well as the package manager used, even though they're not solidity-specific.

I would go so far as to create a template repo for the team and external developers to clone which provides the suggested formatting and, additionally, the protocol contracts. A way to quickly have access to all L1 and L2 contracts, and the expected style in a template contract, would speed up dev process and give a standardized format to import and use deployed contracts.

For example, check out https://github.com/FraxFinance/frax-template.

Opinion: adapt region comments into the style guide

I'm of the opinion that region comments drastically improve developer experience when it comes to writing Solidity code. This feature is already supported in juanfranblanco's VSCode extension, and I've been trying to get the Hardhat VSCode extension to support them as well in NomicFoundation/hardhat-vscode#526.

The pattern follows the typical convention of //#region and //#endregion which is familiar to developers coming from other programming languages like C#.

//#region ExampleRegion

// Your Solidity code here

//#endregion ExampleRegion

Screenshots

Included are screenshots demonstrating the collapsible regions in action within a Solidity file.

image


image

Optional NatSpec custom tag standardization: Terms of Service

About

Smart contract developers who want to bind an onchain Terms of Services to their solidity contract source code should include it within the contract's NatSpec tags, specifically using the @custom:tos tag.


Proposal

Considering that Terms of Service can be lengthy and occupy a significant amount of data impacting the 24.5KiB contract size limit, binding an immutable ToS to a contract version within a fixed size tag can be achieved using the following formats:

Arweave archived ToS

Format

// @custom:tos ar://ARWEAVE_HASH
  • Total tag fixed size: 60 bytes

IPFS pinned ToS

Format

// @custom:tos ipfs://CID
  • Total tag fixed size: 52 bytes

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.