概述
本指南旨在約定 solidity 代碼的編碼規(guī)范。本指南是不斷變化演進的,舊的、過時的編碼規(guī)范會被淘汰,
而新的、有用的規(guī)范會被添加進來。
許多項目會實施他們自己的編碼風格指南。如遇沖突,應優(yōu)先使用具體項目的風格指南。
本風格指南中的結構和許多建議是取自 python 的 pep8 style guide 。
本指南并 不是 以指導正確或最佳的 solidity 編碼方式為目的。
本指南的目的是保持代碼的 一致性 。
來自 python 的參考文檔 pep8 。很好地闡述了這個概念。
風格指南是關于一致性的。
重要的是與此風格指南保持一致。
但項目中的一致性更重要。
一個模塊或功能內的一致性是最重要的。
但最重要的是:知道什么時候不一致 —— 有時風格指南不適用。如有疑問,請自行判斷??纯雌渌樱Q定什么看起來最好。并應毫不猶豫地詢問他人!
代碼結構
縮進
每個縮進級別使用4個空格。
制表符或空格
空格是首選的縮進方法。
應該避免混合使用制表符和空格。
空行
在 solidity 源碼中合約聲明之間留出兩個空行。
正確寫法:
contract A {
//...
}
contract B {
//...
}
contract C {
//...
}
錯誤寫法:
contract A {
//...
}
contract B {
//...
}
contract C {
// ...
}
在一個合約中的函數(shù)聲明之間留有一個空行。
在相關聯(lián)的各組單行語句之間可以省略空行。(例如抽象合約的 stub 函數(shù))。
正確寫法:
pragma solidity ^0.6.0;
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.4.0 <0.9.0;
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 {
// ...
}
}
代碼行的最大長度
基于
PEP 8 recommendation,將代碼行的字符長度控制在 79(或 99)字符來幫助讀者閱讀代碼。
折行時應該遵從以下指引:
- 第一個參數(shù)不應該緊跟在左括號后邊
- 用一個、且只用一個縮進
- 每個函數(shù)應該單起一行
- 結束符號
);應該單獨放在最后一行
函數(shù)調用
正確寫法:
thisFunctionCallIsReallyLong(
longArgument1,
longArgument2,
longArgument3
);
錯誤寫法:
thisFunctionCallIsReallyLong(longArgument1,
longArgument2,
longArgument3
);
thisFunctionCallIsReallyLong(longArgument1,
longArgument2,
longArgument3
);
thisFunctionCallIsReallyLong(
longArgument1, longArgument2,
longArgument3
);
thisFunctionCallIsReallyLong(
longArgument1,
longArgument2,
longArgument3
);
thisFunctionCallIsReallyLong(
longArgument1,
longArgument2,
longArgument3);
賦值語句
正確寫法:
thisIsALongNestedMapping[being][set][to_some_value] = someFunction(
argument1,
argument2,
argument3,
argument4
);
錯誤寫法:
thisIsALongNestedMapping[being][set][to_some_value] = someFunction(argument1,
argument2,
argument3,
argument4);
事件定義和事件發(fā)生
正確寫法:
event LongAndLotsOfArgs(
adress sender,
adress recipient,
uint256 publicKey,
uint256 amount,
bytes32[] options
);
LongAndLotsOfArgs(
sender,
recipient,
publicKey,
amount,
options
);
錯誤寫法:
event LongAndLotsOfArgs(adress sender,
adress recipient,
uint256 publicKey,
uint256 amount,
bytes32[] options);
LongAndLotsOfArgs(sender,
recipient,
publicKey,
amount,
options);
源文件編碼格式
首選 UTF-8 或 ASCII 編碼。
Imports 規(guī)范
Import 語句應始終放在文件的頂部。
正確寫法:
import "owned";
contract A {
//...
}
contract B is owned {
//...
}
錯誤寫法:
contract A {
//...
}
import "owned";
contract B is owned {
//...
}
函數(shù)順序
排序有助于讀者識別他們可以調用哪些函數(shù),并更容易地找到構造函數(shù)和
fallback函數(shù)的定義。
函數(shù)應根據(jù)其可見性和順序進行分組:
- 構造函數(shù)
- receive 函數(shù)(如果存在)
- fallback 函數(shù)(如果存在)
- 外部函數(shù)(external)
- 公共函數(shù)(public)
- 內部(internal)
- 私有(private)
在一個分組中,把 view 和 pure 函數(shù)放在最后。
正確寫法:
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.6.0;
contract A {
function A() public {
...
}
receive() external payable {
// ...
}
fallback() external {
// ...
}
// External functions
// ...
// External functions that are view
// ...
// External functions that are pure
// ...
// Public functions
// ...
// Internal functions
// ...
// Private functions
// ...
}
錯誤寫法:
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.6.0;
contract A {
// External functions
// ...
fallback() external {
// ...
}
receive() external payable {
// ...
}
// Private functions
// ...
// Public functions
// ...
function A() public {
...
}
function() public {
...
}
// Internal functions
// ...
}
表達式中的空格
在以下情況下避免無關的空格:
除單行函數(shù)聲明外,緊接著小括號,中括號或者大括號的內容應該避免使用空格。
正確寫法:
spam(ham[1], Coin({name: "ham"}));
錯誤寫法:
spam( ham[ 1 ], Coin( { name: "ham" } ) );
除外:
function singleLine() public { spam(); }
緊接在逗號,分號之前:
正確寫法:
function spam(uint i, Coin coin) public;
錯誤寫法:
function spam(uint i , Coin coin) public ;
賦值或其他操作符兩邊多于一個的空格:
正確寫法:
x = 1;
y = 2;
long_variable = 3;
錯誤寫法:
x = 1;
y = 2;
long_variable = 3;
fallback 和 receive 函數(shù)中不要包含空格:
正確寫法:
receive() external payable {
//...
}
function() public {
//...
}
錯誤寫法:
receive () external payable {
//...
}
function () public {
//...
}
控制結構
用大括號表示一個合約,庫、函數(shù)和結構。
應該:
- 開括號與聲明應在同一行。
- 閉括號在與之前函數(shù)聲明對應的開括號保持同一縮進級別上另起一行。
- 開括號前應該有一個空格。
正確寫法:
contract Coin {
struct Bank {
address owner;
uint balance;
}
}
錯誤寫法:
contract Coin
{
struct Bank {
address owner;
uint balance;
}
}
對于控制結構 if, else, while, for 的實施建議與以上相同。
另外,諸如 if, else, while, for 這類的控制結構和條件表達式的塊之間應該有一個單獨的空格,
同樣的,條件表達式的塊和開括號之間也應該有一個空格。
正確寫法:
if (...) {
//...
}
for (...) {
//...
}
錯誤寫法:
if (...)
{
//...
}
while(...){
}
for (...) {
...;}
對于控制結構, 如果 其主體內容只包含一行,則可以省略括號。
正確寫法::
if (x < 10)
x += 1;
錯誤寫法::
if (x < 10)
someArray.push(Coin({
name: 'spam',
value: 42
}));
對于具有 else 或 else if 子句的 if 塊, else 應該是與 if 的閉大括號放在同一行上。 這一規(guī)則區(qū)別于
其他塊狀結構。
正確寫法:
if (x < 3) {
x += 1;
} else if (x > 7) {
x -= 1;
} else {
x = 5;
}
if (x < 3)
x += 1;
else
x -= 1;
錯誤寫法:
if (x < 3) {
x += 1;
}
else {
x -= 1;
}
函數(shù)聲明
對于簡短的函數(shù)聲明,建議函數(shù)體的開括號與函數(shù)聲明保持在同一行。
閉大括號應該與函數(shù)聲明的縮進級別相同。
開大括號之前應該有一個空格。
正確寫法:
function increment(uint x) public pure returns (uint) {
return x + 1;
}
function increment(uint x) public pure onlyowner returns (uint) {
return x + 1;
}
錯誤寫法:
function increment(uint x) public pure returns (uint)
{
return x + 1;
}
function increment(uint x) public pure returns (uint){
return x + 1;
}
function increment(uint x) public pure returns (uint) {
return x + 1;
}
function increment(uint x) public pure returns (uint) {
return x + 1;}
你應該嚴格地標示所有函數(shù)的可見性,包括構造函數(shù)。
正確寫法:
function explicitlyPublic(uint val) public {
doSomething();
}
錯誤寫法:
function implicitlyPublic(uint val) {
doSomething();
}
函數(shù)修改器的順序應該是:
- Visibility(可見性)
- Mutability(易變性)
- Virtual(模擬)
- Override(重載)
- Custom modifiers(自定義修飾符)
正確寫法:
function balance(uint from) public view override returns (uint) {
return balanceOf[from];
}
function shutdown() public onlyowner {
selfdestruct(owner);
}
錯誤寫法:
function balance(uint from) public override view returns (uint) {
return balanceOf[from];
}
function shutdown() onlyowner public {
selfdestruct(owner);
}
對于長函數(shù)聲明,建議將每個參數(shù)獨立一行并與函數(shù)體保持相同的縮進級別。閉括號和開括號也應該
獨立一行并保持與函數(shù)聲明相同的縮進級別。
正確寫法:
function thisFunctionHasLotsOfArguments(
address a,
address b,
address c,
address d,
address e,
address f
)
public
{
doSomething();
}
錯誤寫法:
function thisFunctionHasLotsOfArguments(address a, address b, address c,
address d, address e, address f) public {
doSomething();
}
function thisFunctionHasLotsOfArguments(address a,
address b,
address c,
address d,
address e,
address f) public {
doSomething();
}
function thisFunctionHasLotsOfArguments(
address a,
address b,
address c,
address d,
address e,
address f) public {
doSomething();
}
如果一個長函數(shù)聲明有修飾符,那么每個修飾符應該下沉到獨立的一行。
正確寫法:
function thisFunctionNameIsReallyLong(address x, address y, address z)
public
onlyowner
priced
returns (address)
{
doSomething();
}
function thisFunctionNameIsReallyLong(
address x,
address y,
address z,
)
public
onlyowner
priced
returns (address)
{
doSomething();
}
錯誤寫法:
function thisFunctionNameIsReallyLong(address x, address y, address z)
public
onlyowner
priced
returns (address) {
doSomething();
}
function thisFunctionNameIsReallyLong(address x, address y, address z)
public onlyowner priced returns (address)
{
doSomething();
}
function thisFunctionNameIsReallyLong(address x, address y, address z)
public
onlyowner
priced
returns (address) {
doSomething();
}
多行輸出參數(shù)和返回值語句應該遵從 代碼行的最大長度 一節(jié)的說明。
正確寫法:
function thisFunctionNameIsReallyLong(
address a,
address b,
address c
)
public
returns (
address someAddressName,
uint256 LongArgument,
uint256 Argument
)
{
doSomething()
return (
veryLongReturnArg1,
veryLongReturnArg2,
veryLongReturnArg3
);
}
錯誤寫法:
function thisFunctionNameIsReallyLong(
address a,
address b,
address c
)
public
returns (address someAddressName,
uint256 LongArgument,
uint256 Argument)
{
doSomething()
return (veryLongReturnArg1,
veryLongReturnArg1,
veryLongReturnArg1);
}
對于繼承合約中需要參數(shù)的構造函數(shù),如果函數(shù)聲明很長或難以閱讀,建議將基礎構造函數(shù)像多個修飾符的風格那樣
每個下沉到一個新行上書寫。
正確寫法:
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.7.0;
// Base contracts just to make this compile
contract B {
constructor(uint) {
}
}
contract C {
constructor(uint, uint) {
}
}
contract D {
constructor(uint) {
}
}
contract A is B, C, D {
uint x;
constructor(uint param1, uint param2, uint param3, uint param4, uint param5)
B(param1)
C(param2, param3)
D(param4)
{
// do something with param5
x = param5;
}
}
錯誤寫法:
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.22 <0.9.0;
// Base contracts just to make this compile
contract B {
constructor(uint) {
}
}
contract C {
constructor(uint, uint) {
}
}
contract D {
constructor(uint) {
}
}
contract A is B, C, D {
uint x;
constructor(uint param1, uint param2, uint param3, uint param4, uint param5)
B(param1)
C(param2, param3)
D(param4)
public {
x = param5;
}
}
contract X is B, C, D {
uint x;
constructor(uint param1, uint param2, uint param3, uint param4, uint param5)
B(param1)
C(param2, param3)
D(param4)
public {
x = param5;
}
}
當用單個語句聲明簡短函數(shù)時,允許在一行中完成。
允許:
function shortFunction() public { doSomething(); }
這些函數(shù)聲明的準則旨在提高可讀性。
因為本指南不會涵蓋所有內容,作者應該自行作出最佳判斷。
映射
在變量聲明中,關鍵字“mapping”后面不要跟空格鍵。任何嵌套的
mapping關鍵字后也不要跟空格鍵。
正確寫法:
mapping(uint => uint) map;
mapping(address => bool) registeredAddresses;
mapping(uint => mapping(bool => Data[])) public data;
mapping(uint => mapping(uint => s)) data;
錯誤寫法:
mapping (uint => uint) map;
mapping( address => bool ) registeredAddresses;
mapping (uint => mapping (bool => Data[])) public data;
mapping(uint => mapping (uint => s)) data;
變量聲明
數(shù)組變量的聲明在變量類型和括號之間不應該有空格。
正確寫法:
uint[] x;
錯誤寫法:
uint [] x;
其他建議
- 字符串應該用雙引號而不是單引號。
正確寫法:
str = "foo";
tr = "Hamlet says, 'To be or not to be...'";
錯誤寫法:
str = 'bar';
str = '"Be yourself; everyone else is already taken." -Oscar Wilde';
- 操作符兩邊應該各有一個空格。
正確寫法:
x = 3;
x = 100 / 10;
x += 3 + 4;
x |= y && z;
錯誤寫法:
x=3;
x = 100/10;
x += 3+4;
x |= y&&z;
- 為了表示優(yōu)先級,高優(yōu)先級操作符兩邊可以省略空格。這樣可以提高復雜語句的可讀性。你應該在操作符兩邊總是使用相同的空格數(shù):
正確寫法:
x = 2**3 + 5;
x = 2*y + 3*z;
x = (a+b) * (a-b);
錯誤寫法:
x = 2** 3 + 5;
x = y+z;
x +=1;
Order of Layout(布局順序)
按以下順序布置合同結構:
- Pragma statements(版本聲明)
- Import statements(導入聲明)
- Interfaces(接口)
- Libraries(庫)
- Contracts(合同代碼)
在每個合同、庫或接口中,使用以下順序:
- Type declarations(類型聲明)
- State variables(狀態(tài)變量)
- Events(活動事件)
- Functions(函數(shù))
命名規(guī)范
當完全采納和使用命名規(guī)范時會產生強大的作用。
當使用不同的規(guī)范時,則不會立即獲取代碼中傳達的重要 元 信息。
這里給出的命名建議旨在提高可讀性,因此它們不是規(guī)則,而是透過名稱來嘗試和幫助傳達最多的信息。
最后,基于代碼庫中的一致性,本文檔中的任何規(guī)范總是可以被(代碼庫中的規(guī)范)取代。
命名風格
為了避免混淆,下面的名字用來指明不同的命名方式。
-
b(單個小寫字母) -
B(單個大寫字母) -
lowercase(小寫) -
lower_case_with_underscores(小寫和下劃線) -
UPPERCASE(大寫) -
UPPER_CASE_WITH_UNDERSCORES(大寫和下劃線) -
CapitalizedWords(駝峰式,首字母大寫) -
mixedCase(混合式,與駝峰式的區(qū)別在于首字母小寫!) -
Capitalized_Words_With_Underscores(首字母大寫和下劃線)
當在駝峰式命名中使用縮寫時,應該將縮寫中的所有字母都大寫。 因此 HTTPServerError 比 HttpServerError 好。
當在混合式命名中使用縮寫時,除了第一個縮寫中的字母小寫(如果它是整個名稱的開頭的話)以外,其他縮寫中的字母均大寫。
因此 xmlHTTPRequest 比 XMLHTTPRequest 更好。
應避免的名稱
-
l- el的小寫方式 -
O- oh的大寫方式 -
I- eye的大寫方式
切勿將任何這些用于單個字母的變量名稱。 他們經常難以與數(shù)字 1 和 0 區(qū)分開。
合約和庫名稱
合約和庫名稱應該使用駝峰式風格。比如:SimpleToken,SmartBank,CertificateHashRepository,Player,Congress, Owned。
- 合同和庫名稱也應與其文件名匹配.
- 如果合同文件包含多個合同和/或庫,則文件名應與核心合同匹配。但如果可以避免,則不建議這樣做.
如下面的示例所示,如果合同名為Congress,庫名為Owned,那么它們關聯(lián)的文件名應該是Congress.sol 和 Owned.sol
正確寫法:
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.7.0;
// Owned.sol
contract Owned {
address public owner;
constructor() {
owner = msg.sender;
}
modifier onlyOwner {
require(msg.sender == owner);
_;
}
function transferOwnership(address newOwner) public onlyOwner {
owner = newOwner;
}
}
在 Congress.sol 中:
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.0 <0.9.0;
import "./Owned.sol";
contract Congress is Owned, TokenRecipient {
//...
}
錯誤寫法:
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.7.0;
// owned.sol
contract owned {
address public owner;
constructor() {
owner = msg.sender;
}
modifier onlyOwner {
require(msg.sender == owner);
_;
}
function transferOwnership(address newOwner) public onlyOwner {
owner = newOwner;
}
}
在 Congress.sol中:
import "./owned.sol";
contract Congress is owned, tokenRecipient {
//...
}
結構體名稱
結構體名稱應該使用駝峰式風格。比如:MyCoin,Position,PositionXY。
事件名稱
事件名稱應該使用駝峰式風格。比如:Deposit,Transfer,Approval,BeforeTransfer,AfterTransfer。
函數(shù)名稱
函數(shù)應該使用混合式命名風格。比如:getBalance,transfer,verifyOwner,addMember,changeOwner。
函數(shù)參數(shù)命名
函數(shù)參數(shù)命名應該使用混合式命名風格。比如:initialSupply,account,recipientAddress,senderAddress,newOwner。
在編寫操作自定義結構的庫函數(shù)時,這個結構體應該作為函數(shù)的第一個參數(shù),并且應該始終命名為 self。
局部變量和狀態(tài)變量名稱
使用混合式命名風格。比如:totalSupply,remainingSupply,balancesOf,creatorAddress,isPreSale,tokenExchangeRate。
常量命名
常量應該全都使用大寫字母書寫,并用下劃線分割單詞。比如:MAX_BLOCKS,TOKEN_NAME,TOKEN_TICKER,CONTRACT_VERSION。
修飾符命名
使用混合式命名風格。比如:onlyBy,onlyAfter,onlyDuringThePreSale。
枚舉命名
在聲明簡單類型時,枚舉應該使用駝峰式風格。比如:TokenGroup,Frame,HashStyle,CharacterLocation。
避免命名沖突
single_trailing_underscore_
當所起名稱與內建或保留關鍵字相沖突時,建議照此慣例在名稱后邊添加下劃線。
描述注釋 NatSpec
Solidity 智能合約有一種基于以太坊自然語言說明格式(Ethereum Natural Language Specification Format)的注釋形式。
在函數(shù)和合約上添加注釋配置符號,
單行或者連續(xù)多行可以使用`///`,
或者使用多行注釋以`/**`開始,以`*/`結尾。
它們應該直接在函數(shù)聲明或語句上使用。 可在注釋中使用 Doxygen 樣式的標簽來文檔化函數(shù)、 標注形式校驗通過的條件,和提供一個當用戶試圖調用一個函數(shù)時顯示給用戶的 確認性文字。
下面是一個簡單的智能合約,添加配置注釋的樣子:
pragma solidity >=0.4.16 <0.9.0;
/// @author The Solidity Team
/// @title A simple storage example
contract TinyStorage {
uint storedData;
/// Store `x`.
/// @param x the new value to store
/// @dev stores the number in the state variable `storedData`
function set(uint x) public {
storedData = x;
}
/// Return the stored value.
/// @dev retrieves the value of the state variable `storedData`
/// @return the stored value
function get() public view returns (uint) {
return storedData;
}
}
建議對所有公共接口(ABI 中的所有內容)使用上面說推薦的方式對 Solidity 合約進行完全注釋。