合约编码规范

良好统一的编程风格,有助于提高代码的可读性和可维护性。根据风格整理成规范,可以让团队之间更好的配合。

下面是我结合网络上别人分享的内容,以及参考 Vscode 代码格式化整理优化出的;抛砖引玉,仅供参考。

  • 如果合约对状态变量进行了修改,需要抛出事件。

  • 构造函数的参数必须是storagememory,不能使用calldata;

  • 版权注释在文件的任何位置都可以被编译器识别,但建议把它放在文件的顶部第一行。

代码布局

  • 源码编码

    • UTF-8

  • 缩进

    • 使用 4 个空格代替制表符作为缩进,避免空格与制表符混用。

  • 2 个合约定义之间空 2 行。

    pragma solidity ^0.5.0;
    
    contract LedgerBalance {
        //...
    }
    
    
    contract Updater {
        //...
    }
    
  • 2 个函数之间空 1 行,在只有声明的情况下,不需要空行。

    pragma solidity ^0.5.0;
    
    contract A {
        function balance() public pure;
        function account() public pure;
    }
    
    
    contract B is A {
        function balance() public pure {
            // ...
        }
    
        function account() public pure {
            // ...
        }
    }
    
  • 单行不要太长,VScode 一行默认不超过 80 个字符。

  • 函数声明如果太长,左括号不换行,每个参数一行并缩进,右括号换行,并对齐左括号所在行。

    function_with_a_long_name(
        longArgument1,
        longArgument2,
        longArgument3
    );
    
    variable = function_with_a_long_name(
        longArgument1,
        longArgument2,
        longArgument3
    );
    
    event multipleArguments(
        address sender,
        address recipient,
        uint256 publicKey,
        uint256 amount,
        bytes32[] options
    );
    
    MultipleArguments(
        sender,
        recipient,
        publicKey,
        amount,
        options
    );
    
  • 避免在圆括号、方括号或大括号后有空格。

  • 控制结构的大括号左括号不换行,右括号换行,与左括号所在行对齐。

    pragma solidity ^0.5.0;
    
    contract Coin {
        struct Bank {
            address owner;
            uint balance;
        }
    }
    
    
    if (x < 3) {
        x += 1;
    } else if (x > 7) {
        x -= 1;
    } else {
        x = 5;
    }
    if (x < 3)
        x += 1;
    else
        x -= 1;
    
  • 函数声明,添加可见性标签。可见性标签应该放在自定义修饰符之前。

    function kill() public onlyowner {
        selfdestruct(owner);
    }
    
  • 声明映射变量时避免多余空格。

    mapping(uint => uint) map; // 不是 mapping (uint => uint) map;
    mapping(address => bool) registeredAddresses;
    mapping(uint => mapping(bool => Data[])) public data;
    mapping(uint => mapping(uint => s)) data;
    
  • 声明数组变量时避免多余空格。

    uint[] x;  // 不是 unit [] x;
    
  • 字符串声明,使用双引号声明字符串,而不是单引号。

    str = "foo";
    str = "Hamlet says, 'To be or not to be...'";
    

代码中各部分的顺序

代码中各部分顺序如下:

  1. License

  2. Pragma

  3. import

  4. interface

  5. library

  6. contract

在 Interface、库或 Contract 中,各部分顺序应为:

  1. Type declaration : 类型声明

  2. State variable : 状态变量

  3. Event : 事件

  4. Modifier : 函数修改器

  5. Errors : 自定义错误

  6. Constructor : 构造函数

  7. Function : 函数

    1. 函数按visibility:可见性进行排序: External - Public - Internal - Private

    2. 同一可见性,按照 mutability:状态可变性 排序: payable - - view - pure

    // External functions
    // ...
    
    // External view functions
    // ...
    
    // External pure functions
    // ...
    
    // Public functions
    // ...
    
    // Internal functions
    // ...
    
    // Private functions
    // ...
    
  8. Helper : 辅助函数

文件结构分享

我的文件结构,分享给大家,仅供参考,抛砖引玉。代码块分割主要是让合约的逻辑更清晰,团队合作写代码的时候,都能按照统一的约定来进行编码。

不同功能的代码块使用下面任意一种做明显标记。

第一种:

/*
  * ========================================
  * State Variables
  * ========================================
  */

第二种:(两边各 12 个 =)

/* ============ State Variables ============ */

如果需要写详细的 NetSpec 注释,我比较喜欢使用第一种。如果是不需要这样做,比较喜欢第二种。第一种写法,在第一章同志们好 那里已经演示过了,这里演示第二种写法。如下是写法的演示:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;

// import

// interface

// library

contract Demo {
    /* ============ Type Declaration ============ */
    struct Book {
        string title;
        string author;
        uint256 book_id;
    }

    /* ============ State Variables ============ */
    address public immutable owner;
    Book[] public bookcase;

    /* ============ Events ============ */
    event Hello(string);

    /* ============ Modifier ============ */
    modifier onlyOwner() {
        require(msg.sender == owner, "Only owner");
        _;
    }

    /* ============ Errors ============ */
    error MyError(string);

    /* ============ Constructor ============ */
    constructor() {
        owner = msg.sender;
    }

    /* ============ Functions ============ */

    /* ============ External Functions ============ */
    function hello() external onlyOwner {
        emit Hello("Hello Comrades");
    }

    /* ============ Helper ============ */
    function getBalance() external view returns (uint256) {
        return address(this).balance;
    }
}

命名约定

  • 合约和库名: 大驼峰式命名。

    // SPDX-License-Identifier: MIT
    pragma solidity ^0.8.18;
    
    contract Owned {
        //...
    }
    
  • 合约和库名: 匹配它们的文件名。

    // SPDX-License-Identifier: MIT
    pragma solidity ^0.8.18;
    
    // 文件名:Owned.sol
    contract Owned {
        //...
    }
    
  • 如果文件中有多个合约/库,使用核心合约/库的名称。🤔️

    // SPDX-License-Identifier: MIT
    pragma solidity ^0.8.18;
    
    // 文件名:Owned.sol
    contract Owned {
        address public owner;
    
        constructor() public {
            owner = msg.sender;
        }
    
        modifier onlyOwner() {
            //....
            _;
        }
    
        function transferOwnership(address newOwner) public onlyOwner returns(true){
            //...
            return true;
        }
    }
    
    // SPDX-License-Identifier: MIT
    pragma solidity ^0.8.18;
    
    import "./Owned.sol";
    
    // Congress.sol
    contract Congress is Owned {
    //...
    }
    
  • 结构体名称: 大驼峰式命名。

    struct BookInfo {
        string title;
        string author;
        uint256 book_id;
    }
    
  • 事件名称: 大驼峰式命名

    event AfterTransfer(address ads);
    
  • 函数名: 小驼峰命名

    function balanceOf(address account) external view returns (uint256){};
    
  • internal函数名:_+小驼峰命名

    function _grantRole(address _ads, bytes32 _role) internal {}
    
  • internal变量:_+小驼峰

  • 函数参数:小驼峰+_

    constructor(string memory name_, string memory symbol_) {
        _name = name_;
        _symbol = symbol_;
    }
    
  • 局部变量和状态变量:小驼峰命名

    mapping(address => uint256) public balanceOf;
    
  • 常量:大写字母单词用下划线分隔。

    address public constant MIN_BLOCKS;
    
  • 函数修改器: 小驼峰命名

    modifier onlyOwner(){
        require(msg.sender==owner,"must owner address");
        _;
    }
    
  • 枚举的名字:大驼峰式命名

    enum Status {
        None,
        Pending,
        Shiped,
        Completed,
        Rejected,
        Canceled
    }
    

更多内容

重要需要总结的内容:

https://docs.soliditylang.org/zh/latest/style-guide.html