Solidity Style Guide

jerichou
2025-05-22 / 0 评论 / 4 阅读 / 正在检测是否收录...

规范文档https://docs.soliditylang.org/en/v0.8.17/style-guide.html

1.缩进

每个缩进使用4个空格 应该避免使用空格和tab混合缩进

2.间隔

对于顶级声明来说应该使用两个空行

Yes:
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.0 <0.9.0;

contract A {
    // ...
}


contract B {
    // ...
}


contract C {
    // ...
}
No:
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.0 <0.9.0;

contract A {
    // ...
}
contract B {
    // ...
}

contract C {
    // ...
}

合约中的函数间隔使用一个空行 抽象合约中的函数之间不使用空行

Yes:
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.6.0 <0.9.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 {
        // ...
    }
}
No:
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.6.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 {
        // ...
    }
}

3.每行最大的长度

  • 推荐最大的长度为120个字符
  • 对于换行应该遵循的规则:

    • 第一个参数不应追加到左括号之后。
    • 只使用一个缩进。
    • 每个参数应独占一行。
    • 结束符); 应单独放在最后一行。
对于函数调用的示例
Yes:
thisFunctionCallIsReallyLong(
    longArgument1,
    longArgument2,
    longArgument3
);
No:
thisFunctionCallIsReallyLong(longArgument1,
                              longArgument2,
                              longArgument3
);

thisFunctionCallIsReallyLong(longArgument1,
    longArgument2,
    longArgument3
);

thisFunctionCallIsReallyLong(
    longArgument1, longArgument2,
    longArgument3
);

thisFunctionCallIsReallyLong(
longArgument1,
longArgument2,
longArgument3
);

thisFunctionCallIsReallyLong(
    longArgument1,
    longArgument2,
    longArgument3);
赋值语句示例
Yes:
thisIsALongNestedMapping[being][set][toSomeValue] = someFunction(
    argument1,
    argument2,
    argument3,
    argument4
);
No:
thisIsALongNestedMapping[being][set][toSomeValue] = someFunction(argument1,
                                                                   argument2,
                                                                   argument3,
                                                                   argument4);
事件定义和事件触发示例
Yes:
event LongAndLotsOfArgs(
    address sender,
    address recipient,
    uint256 publicKey,
    uint256 amount,
    bytes32[] options
);

LongAndLotsOfArgs(
    sender,
    recipient,
    publicKey,
    amount,
    options
);
No:
event LongAndLotsOfArgs(address sender,
                        address recipient,
                        uint256 publicKey,
                        uint256 amount,
                        bytes32[] options);

LongAndLotsOfArgs(sender,
                  recipient,
                  publicKey,
                  amount,
                  options);

4.源代码文件编码最好使用 UTF-8 或 ASCII 编码

5.Imports

  • imports语句应该始终放在文件的顶部
Yes:
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.0 <0.9.0;

import "./Owned.sol";

contract A {
    // ...
}


contract B is Owned {
    // ...
}
No:
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.0 <0.9.0;

contract A {
    // ...
}


import "./Owned.sol";


contract B is Owned {
    // ...
}

6.函数的放置顺序

函数从前往后放置顺序
  • constructor
  • receive function (if exists)
  • fallback function (if exists)
  • external
  • public
  • internal
  • private

对于同一优先级的函数, view and pure放在后面

Yes:
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;
contract A {
    constructor() {
        // ...
    }

    receive() external payable {
        // ...
    }

    fallback() external {
        // ...
    }

    // External functions
    // ...

    // External functions that are view
    // ...

    // External functions that are pure
    // ...

    // Public functions
    // ...

    // Internal functions
    // ...

    // Private functions
    // ...
}
No:
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;
contract A {

    // External functions
    // ...

    fallback() external {
        // ...
    }
    receive() external payable {
        // ...
    }

    // Private functions
    // ...

    // Public functions
    // ...

    constructor() {
        // ...
    }

    // Internal functions
    // ...
}

7.表达式中的空格

  • 应该避免使用多余的空格 括号、方括号或花括号内,单行函数声明除外。
Yes:
spam(ham[1], Coin({name: "ham"}));
No:
spam( ham[ 1 ], Coin( { name: "ham" } ) );
单行函数声明除外:
function singleLine() public { spam(); }
  • 逗号和分号之前去除多余的空格
Yes:
function spam(uint i, Coin coin) public;
No:
function spam(uint i , Coin coin) public ;
  • 赋值和运算符周围的超过一个空格的也需要去除
Yes:
x = 1;
y = 2;
longVariable = 3;
No:
x            = 1;
y            = 2;
longVariable = 3;
  • 不要在receive和fallback函数名的地方增加多余的空格
Yes:
receive() external payable {
    ...
}

fallback() external {
    ...
}
No:
receive () external payable {
    ...
}

fallback () external {
    ...
}

8.控制结构

合约、库、函数和结构体主体应该和左括号在一行
Yes:
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.0 <0.9.0;

contract Coin {
    struct Bank {
        address owner;
        uint balance;
    }
}
No:
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.0 <0.9.0;

contract Coin
{
    struct Bank {
        address owner;
        uint balance;
    }
}
if、while 和 for 关键词与左括号之间应该有一个空格,右括号和左花括号之间也应该有一个空格
Yes:
if (...) {
    ...
}

for (...) {
    ...
}
No:
if (...)
{
    ...
}

while(...){
}

for (...) {
    ...;}
控制结构中如果包含的语句只在一行时可以省略括号
Yes:
if (x < 10)
    x += 1;
No:
if (x < 10)
    someArray.push(Coin({
        name: 'spam',
        value: 42
    }));
对于包含 else 或 else if 子句的 if 块,else 应该与 if 的右括号放在同一行
Yes:
if (x < 3) {
    x += 1;
} else if (x > 7) {
    x -= 1;
} else {
    x = 5;
}


if (x < 3)
    x += 1;
else
    x -= 1;
No:
if (x < 3) {
    x += 1;
}
else {
    x -= 1;
}

9.函数定义

对于比较短的函数定义,建议将函数体的左括号与函数定义保持在同一行。
右括号的缩进级别应与函数定义相同。
左括号前应有一个空格。
Yes:
function increment(uint x) public pure returns (uint) {
    return x + 1;
}

function increment(uint x) public pure onlyOwner returns (uint) {
    return x + 1;
}
No:
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;}
函数的修饰符应该是按照如下顺序:
  1. Visibility
  2. Mutability
  3. Virtual
  4. Override
  5. 自定义 修饰符
Yes:
function balance(uint from) public view override returns (uint)  {
    return balanceOf[from];
}

function shutdown() public onlyOwner {
    selfdestruct(owner);
}
No:
function balance(uint from) public override view returns (uint)  {
    return balanceOf[from];
}

function shutdown() onlyOwner public {
    selfdestruct(owner);
}
对于较长的函数定义,建议将每个参数单独放在一行,并与函数内语句保持相同的缩进级别。右括号和右括号也应单独放在一行,并与函数定义保持相同的缩进级别。
Yes:
function thisFunctionHasLotsOfArguments(
    address a,
    address b,
    address c,
    address d,
    address e,
    address f
)
    public
{
    doSomething();
}
No:
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();
}
如果函数有修饰符 修饰符放在一列
Yes:
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();
}
No:
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();
}
多行输出参数和返回语句应遵循“最大行长度”部分中建议的换行长行的相同样式
Yes:
function thisFunctionNameIsReallyLong(
    address a,
    address b,
    address c
)
    public
    returns (
        address someAddressName,
        uint256 LongArgument,
        uint256 Argument
    )
{
    doSomething()

    return (
        veryLongReturnArg1,
        veryLongReturnArg2,
        veryLongReturnArg3
    );
}
No:
function thisFunctionNameIsReallyLong(
    address a,
    address b,
    address c
)
    public
    returns (address someAddressName,
             uint256 LongArgument,
             uint256 Argument)
{
    doSomething()

    return (veryLongReturnArg1,
            veryLongReturnArg1,
            veryLongReturnArg1);
}
对于继承的合约上的base需要参数的构造函数,如果函数很长或难以阅读,建议将base构造函数以与修饰符相同的方式放到多行。
Yes:
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <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)
    {
        // do something with param5
        x = param5;
    }
}
No:
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <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) {
        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) {
            x = param5;
        }
}
对于简单的函数定义使用一行
function shortFunction() public { doSomething(); }

10.Mappings

在变量声明中,不要用空格分隔关键字 mapping 和其类型。不要用空格分隔任何嵌套的 mapping 关键字和其类型。
Yes:
mapping(uint => uint) map;
mapping(address => bool) registeredAddresses;
mapping(uint => mapping(bool => Data[])) public data;
mapping(uint => mapping(uint => s)) data;
No:
mapping (uint => uint) map;
mapping( address => bool ) registeredAddresses;
mapping (uint => mapping (bool => Data[])) public data;
mapping(uint => mapping (uint => s)) data;

11.变量定义

定义数组的时候不要将类型和[]分开
Yes:
uint[] x;
No:
uint [] x;

12.Other Recommendations

string类型应该使用双引号而不是单引号
Yes:
str = "foo";
str = "Hamlet says, 'To be or not to be...'";
No:
str = 'bar';
str = '"Be yourself; everyone else is already taken." -Oscar Wilde';
运算符周围应该有一个空格
Yes:
x = 3;
x = 100 / 10;
x += 3 + 4;
x |= y && z;
No:
x=3;
x = 100/10;
x += 3+4;
x |= y&&z;
高优先级的运算符周围可以省略空格
Yes:
x = 2**3 + 5;
x = 2*y + 3*z;
x = (a+b) * (a-b);
No:
x = 2** 3 + 5;
x = y+z;
x +=1;

13.布局顺序

合约中的元素布局顺序
  1. Pragma statements
  2. Import statements
  3. Interfaces
  4. Libraries
  5. Contracts
在合约中库、接口顺序
  1. Type declarations
  2. State variables
  3. Events
  4. Modifiers
  5. Functions

14.命名规范

命名风格

为了避免困惑,以下的将指代不同命名风格

  • b (single lowercase letter)
  • B (single uppercase letter)
  • lowercase
  • UPPERCASE
  • UPPER_CASE_WITH_UNDERSCORES
  • CapitalizedWords (or CapWords)
  • mixedCase (differs from CapitalizedWords by initial lowercase character!)

Note

在 CapWords 中使用首字母缩写时,请将首字母缩写的所有字母大写。因此,HTTPServerError 优于 HttpServerError。在 mixedCase 中使用首字母缩写时,请将首字母缩写的所有字母大写,但如果它是名称的开头,则保留第一个字母小写。因此,xmlHTTPRequest 优于 XMLHTTPRequest。

避免使用
  • l - Lowercase letter el
  • O - Uppercase letter oh
  • I - Uppercase letter eye

切勿将这些字符用作单字母变量名。它们通常与数字 1 和 0 难以区分。

合约和库名
  • 合约和库的名字应该使用类似 CapWords的风格. Examples: SimpleToken, SmartBank, CertificateHashRepository, Player, Congress, Owned.
  • 合约和库的名字应该匹配他们的文件名.
  • 如果一个合约文件包含了多个合约或者库,那么这个文件名应该以核心的合约命名。如果可以避免尽量不要这样。
Yes:
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.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 {
    //...
}
No:
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.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.7.0;


import "./owned.sol";


contract Congress is owned, tokenRecipient {
    //...
}
结构体名字

结构体名字应该使用 CapWords 风格. Examples: MyCoin, Position, PositionXY.

事件名字

事件名字应该使用CapWords 风格. Examples: Deposit, Transfer, Approval, BeforeTransfer, AfterTransfer.

函数名字

函数名字应该使用混合大小写. Examples: getBalance, transfer, verifyOwner, addMember, changeOwner.

函数参数名

函数参数应该使用缓和大小写.Examples: initialSupply, account, recipientAddress, senderAddress, newOwner.

当编写对自定义结构进行操作的库函数时,该结构应该是第一个参数,并且应始终命名为“self”

本地状态变量名

使用大小写混合Examples: totalSupply, remainingSupply, balancesOf, creatorAddress, isPreSale, tokenExchangeRate.

常量

常量应该使用大写字母并且下划线分割 Examples: MAX_BLOCKS, TOKEN_NAME, TOKEN_TICKER, CONTRACT_VERSION.

修饰符名

使用大小写混合 Examples: onlyBy, onlyAfter, onlyDuringThePreSale.

枚举类型名

名字命名应该是使用CapWords风格. Examples: TokenGroup, Frame, HashStyle, CharacterLocation.

15.NatSpec

建议使用 NatSpec 对所有公共接口(ABI 中的所有内容)进行完整注释。

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.16 <0.9.0;

/// @author The Solidity Team
/// @title A simple storage example
contract SimpleStorage {
    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;
    }
}
Tag Context
@titleA title that should describe the contract/interfacecontract, library, interface
@authorThe name of the authorcontract, library, interface
@noticeExplain to an end user what this doescontract, library, interface, function, public state variable, event
@devExplain to a developer any extra detailscontract, library, interface, function, state variable, event
@paramDocuments a parameter just like in Doxygen (must be followed by parameter name)function, event
@returnDocuments the return variables of a contract’s functionfunction, public state variable
@inheritdocCopies all missing tags from the base function (must be followed by the contract name)function, public state variable
@custom:...Custom tag, semantics is application-definedeverywhere
1

评论 (0)

取消