![]() |
VOOZH | about |
Solidity is a computer programming language used to create Ethereum smart contracts. These contracts self-execute. The code and the agreements contained therein are enforced by the blockchain network. Solidity is a high-level language, meaning that it is designed to be human-readable and easy to write. It is based on the JavaScript programming language and has a syntax similar to C++. Solidity is a statically-typed language, meaning that variables have a fixed type that must be declared when they are created. It also supports complex data types such as arrays and structs (collections of related variables).
One of the main benefits of Solidity is that it allows developers to build decentralized applications (DApps) on the Ethereum platform. These DApps can be used for a wide range of applications, including supply chain management, voting systems, and financial applications.
In this style guide, we will cover several elements starting with code layout till NatSpec declaration in code, followed by an example for better understanding.
Code layout refers to the way that your solidity code is structured and formatted. Proper code layout helps to make your code more readable, maintainable, and easy to understand. With respect to solidity code, here are some guidelines to maintain an ideal code layout throughout your code.
Using 4 spaces for indentation instead of using a tab is considered a good practice. Refer to the right and wrong snippets that are written ahead to understand better.
pragma solidity >=0.4.0 <0.9.0;
// Right Practice for indentation
contract A {
// ...
}
contract B {
// ...
}
contract C {
// ...
}
pragma solidity >=0.4.0 <0.9.0;
// wrong practice for indentation
contract A {
// ...
}
contract B {
// ...
}
contract C {
// ...
}
If a line of code exceeds this limit, consider breaking it up into multiple lines. This makes it easier to read the code even on smaller screens. Refer to the right and wrong snippets written ahead to understand better.
// right approach
thisFunctionCallIsReallyLong(
tempArgument1,
tempArgument2,
tempArgument3
);
// Wrong approaches
thisFunctionCallIsReallyLong(tempArgument1,
tempArgument2,
tempArgument3
);
thisFunctionCallIsReallyLong(tempArgument1,
tempArgument2,
tempArgument3
);
thisFunctionCallIsReallyLong(
tempArgument1, tempArgument2,
tempArgument3
);
Use curly braces to enclose the bodies of functions, control structures, and contracts. Place the opening curly brace on the same line as the declaration. Place the closing curly brace on a new line. Refer to the right and wrong snippets written ahead to understand better.
pragma solidity >=0.4.0 <0.9.0;
// right practice
import "./Owned.sol";
contract A {
// ...
}
pragma solidity >=0.4.0 <0.9.0;
// wrong practice
import "./Owned.sol";
contract A
{
// ...
}
Add a blank line between the end of a function and the beginning of the next function. Refer to the right and wrong snippets written ahead to understand better.
pragma solidity >=0.6.0 <0.9.0;
// right practice
abstract contract A {
function spam() public virtual pure;
function ham() public virtual pure;
}
contract B is A {
function spam() public pure override {
// ...
}
function ham() public pure override {
// ...
}
}
pragma solidity >=0.6.0 <0.9.0;
// wrong practice
abstract contract A {
function spam() virtual pure public;
function ham() public virtual pure;
}
contract B is A {
function spam() public pure override {
// ...
}
function ham() public pure override {
// ...
}
}
Add a blank line between different blocks of code within a function (e.g. between variable declarations and function calls). Refer to the right and wrong snippets written ahead to understand better.
// right practice
uint256 public jobfirst;
doSomething();
// wrong practice
uint256 public jobfirst;
doSomething();
Never add spaces while declaration involving special characters like ("[]") etc. Add single spaces between operators and operands. Refer to the right and wrong snippets written ahead to understand better.
// right practice
unit[] a;
a = 7;
a = 100 / 10;
a += 3 + 4;
a |= c && d;
// wrong practice
unit [] a;
a=7;
a = 100/10;
a += 3+4;
a |= c&&d;
While using reserved keywords never add spaces in between. Refer to the right and wrong snippets written ahead to understand better.
// right practice
mapping(uint => uint) map;
mapping(address => bool) registeredAddresses;
mapping(uint => mapping(uint => s)) data;
// wrong practice
mapping (uint => uint) map;
mapping( address => bool ) registeredAddresses;
mapping(uint => mapping (uint => s)) data;
Imports should be sorted in alphabetical order. The same import should not be repeated. Use the import "./filename"; syntax for external contract dependencies.
// Good import "./filename1"; import "./filename2"; import "./filename3";
// Bad import "./filename1"; import "./filename1"; import "./filename3";
Functions should be ordered in the following manner:
// Good
contract Example {
constructor() public { ... }
function constantFunc1() public constant returns (...) { ... }
function constantFunc2() public constant returns (...) { ... }
function publicFunc1() public { ... }
function publicFunc2() public { ... }
function internalFunc1() internal { ... }
function internalFunc2() internal { ... }
function privateFunc1() private { ... }
function privateFunc2() private { ... }
}
// Bad
contract Example {
function internalFunc2() internal { ... }
function privateFunc1() private { ... }
constructor() public { ... }
function constantFunc1() public constant returns (...) { ... }
function publicFunc1() public { ... }
function internalFunc1() internal { ... }
function constantFunc2() public constant returns (...) { ... }
function publicFunc2() public { ... }
function privateFunc2() private { ... }
}
Use whitespace to increase readability and separate different elements in an expression. Put spaces around binary and ternary operators (e.g. +, -, ? :).
// Good
uint x = (a + b) * c;
uint y = (a > b) ? a : b;
// Bad
uint x=(a+b)*c;
uint y=(a>b)?a:b;
Declare functions using the keyword function, followed by the function name, a list of parameters in parentheses (), and the function body in curly braces {}. Avoid declaring functions with the same name, even if they have different parameters, as this can cause confusion.
// Good
function add(uint a, uint b) public returns (uint) {
return a + b;
}
// Bad
function add(uint a, uint b) {
return a + b;
}
function add(uint a, uint b) public returns (uint) {
return a + b;
}
Declare variables , followed by the variable name and it's type. Declare variables as close to their usage as possible, to increase readability. Initialize variables with a default value at declaration if possible.
// Good
function example() public {
uint a = 1;
uint b = 2;
uint c = a + b;
}
// Bad
function example() public {
uint a;
a = 1;
uint b = 2;
uint c = a + b;
}
Avoid using keywords (e.g. contract, function, etc.) as names for variables, functions, or contracts. Avoid using single-letter names, except for loop indices.
// Good
function addNumbers(uint a, uint b) public returns (uint) { ... }
for (uint i = 0; i < 10; i++) { ... }
// Bad
function contract(uint a, uint b) public returns (uint) { ... }
for (uint x = 0; x < 10; x++) { ... }
Name contracts and libraries in a descriptive manner that accurately reflects their purpose. Avoid naming contracts and libraries after their dependencies.
// Good
contract Token { ... }
library Math { ... }
// Bad
contract ERC20 { ... }
library TokenLibrary { ... }
Order of layout refers to the recommended order in which different elements of a solidity contract should be organized. It is important to follow a consistent order of layout in order to make the code more readable and easier to understand. This order might vary slightly for different programming languages. This order consists of several optional and mandatory pointers. We've tried to cover almost all (both mandatory/optional) elements as per their order of occurrences in the solidity code.
A pragma statement is a directive that specifies the version of Solidity that the contract is compatible with. It is important to include a pragma statement at the beginning of your contract to ensure that it will be compiled and run correctly on the Ethereum Virtual Machine (EVM).
Import statements are used to include code from other files or libraries in your contract. While using any predefined functionality, import statements are required. This can be useful for including commonly used functions or for sharing code between contracts.
A contract is a self-contained piece of code that can be deployed on the Ethereum blockchain. It defines the functions and variables that make up the contract, as well as the logic for how it will behave. Whereas a library is a special type of contract that cannot be deployed on its own but can be used by other contracts. It is used to encapsulate common code that can be shared between contracts.
Declare any global variables at the beginning of the contract. It is good practice to group related variables together and to declare constants first, followed by variables that will be modified during the execution of the contract.
The constructor is a special function that is executed when the contract is deployed. It is used to initialize the contract and set any initial values for variables. The constructor should be placed immediately after the global variables.
The fallback function is a special function that is executed whenever the contract receives an external call that does not match any of the other functions. It is used to handle unexpected or invalid calls to the contract. The fallback function should be placed immediately after the constructor.
Variables are used to store data in a contract. In Solidity, variables must be declared with a type, such as uint256 for unsigned integers or address for Ethereum addresses. Here these variables are different from global variables, as they will only have scope in the contract they are getting initialized or declared.
Declare all public and external functions next. Public functions can be called by any contract or external actor, while external functions can only be called by other contracts. It is good practice to group related functions together and to place functions that are more frequently used towards the top.
Declare any internal functions next. Internal functions can only be called by other functions within the same contract and are useful for encapsulating complex logic or for sharing code between functions.
Declare any private functions last. Private functions can only be called by other functions within the same contract and are useful for encapsulating sensitive logic or for sharing code between functions in a way that is not visible to external actors.
Declare any events after the functions. Events are used to trigger log entries on the blockchain that can be used for logging or for triggering external actions. An event is declared like a function, but with the "event" keyword and no function body. It can have parameters, which can be of any type except for mappings and dynamically sized arrays.
A naming convention is a set of rules for choosing names for variables, functions, and other important entities in your code. With respect to solidity code, here are some guidelines for the naming convention while writing your code.
Refer to the code snippet attached ahead for reference.
contract MyContract {
// Constants
uint256 public constant MIN_VALUE = 10;
uint256 public constant MAX_VALUE = 100;
// Variables
uint256 public totalSum;
address public owner;
mapping(address => uint256) public balances;
...
NatSpec (Natural Language Specification) is a system for documenting solidity code in a way that is easy for humans to understand. NatSpec comments are written in Markdown format and can be accessed through the Ethereum NatSpec Standard JSON Interface. To add single-line NatSpec documentation to your Solidity code, use double-slash comments ('//') followed by the NatSpec tag and the documentation text. And for adding multiple comments ("/**.....*/") Here's an example to refer to while writing comments.
/**
@notice Description of the function
@param Parameter1 Description of parameter1
@param Parameter2 Description of parameter2
@return Description of the return value
*/
Below is the solidity code to demonstrate how to comment using the NatSpec format:
Explanation: