A minimal set of conventions help keep our contracts consistent, and ensure our PR discussions are focused on the code changes, and not the style. This guide is not intended to be comprehensive, but rather to provide a starting point for the style of the code.
Unless an exception or addition is specifically noted, we follow the Solidity Style Guide
-
Private state variables should be prefixed with an underscore.
uint256 private _totalSupply;
-
Private or internal functions should be prefixed with an underscore.
function _transfer(address from, address to, uint256 amount) internal { // ... }
-
Event names should be in the 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.
event Published(); -
Constructor parameters, when they shadow a state variable, should be prefixed with an underscore.
constructor(uint256 _totalSupply) { totalSupply = _totalSupply; }
-
Local variables or function parameters when they shadow a state variable should be suffixed with an underscore.
uint256 public totalSupply; address public owner; function setTotalSupply(uint256 totalSupply_) public { totalSupply = totalSupply_; address owner_ = msg.sender; owner = owner_; }
-
Events and structs when part of the contract's public API should be defined in the contract interface.
If they are an implementation detail, or emitted by a specific implementation, they should instead be defined in the contract itself, and not part of the interface.
interface IMyContract { // This event and struct are part of the interface event Published(); struct MyStruct { uint256 value; } }
-
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.
-
Return values are generally not named, unless they are not immediately clear or there are multiple return values.
function expiration() public view returns (uint256) { // Good function hasRole() public view returns (bool isMember, uint32 currentDelay) { // Good
-
Unchecked arithmetic blocks should contain comments explaining why overflow is guaranteed not to happen. If the reason is immediately apparent from the line above the unchecked block, the comment may be omitted.
-
Interface names should have a capital I prefix.
-
Prefer custom errors over require strings.
Custom errors are usually more gas efficient. The exception to this is
requirestatements in the constructor or for situations that should never happen(e.g. invariants that should never be violated but we still put a guard in the code).YES:
uint256 public balance; error InsufficientBalance(uint256 balance, uint256 required); function withdraw(uint256 amount) external { require(amount <= balance, InsufficientBalance(balance, amount)); balance -= amount; }
NO:
uint256 public balance; function withdraw(uint256 amount) external { require(amount <= balance, "Insufficient balance"); balance -= amount; }
-
Constructors should have natspec, but it can be simpler than other functions. If the constructor simply assigns values to state variables, specifying the parameters is sufficient.
/// @param _inclusionDelay The delay before next set of inclusions can be processed. /// @param _blobRefRegistry The address of the blob reference registry. constructor(uint256 _inclusionDelay, address _blobRefRegistry) { inclusionDelay = _inclusionDelay; blobRefRegistry = IBlobRefRegistry(_blobRefRegistry); }
TODO: Add testing guide
This style guide was inspired by the following sources: