Solidity 编码规范推荐标准, 作者列出了 15 条非常使用的规则, 例如:指定固定的编译器版本、导入代码时指定版本, 并使用命名导入,以及变量命名时注意事项。
- 原文链接: https://www.rareskills.io/post/solidity-style-guide
- 译文出自:登链翻译计划
- 译者:翻译小组 校对:Tiny 熊
- 本文永久链接:learnblockchain.cn/article…
本文的目的不是重复官方的Solidity 编程风格指南,你应该阅读它。相反,本文旨在记录代码审查或审计中常见的偏离风格指南的情况。这里的一些项目不在风格指南中,但却是 Solidity 开发人员常见的风格错误。
当然,如果没有它,你的代码也能编译,但会收到警告,所以只需让警告消失即可。
你可能见过像下面这样的程序:
pragma solidity ^0.8.0;
和
pragma solidity 0.8.21;
你应该在什么时候使用哪个版本?如果你是编译和部署合约的人,你知道你编译的 Solidity 版本,所以为了清楚起见,你应该根据你使用的编译器固定 Solidity 版本。
另一方面,如果你正在创建一个可供其他人扩展的库,比如 OpenZeppelin 和 Solady,你就不应该固定 pragma,因为你不知道最终用户将使用哪个版本的编译器。
不是这样做:
import "@openzepplin/contracts/token/ERC20/ERC20.sol";
这样做
import "@openzeppelin/contracts@4.9.3/token/ERC20/ERC20.sol";
点击 github 左侧的分支下拉菜单,然后点击标签(tags),选择最新发布的版本,即可获得最新版本。使用最新的正式版本(非 rc,即非候选发布版本)。
如果你没有对导入进行版本更新,而底层库又进行了更新,那么你的代码有可能无法编译或出现意外行为。
不要这样做
import "@openzeppelin/contracts@4.9.3/token/ERC20/ERC20.sol";
这样做
import {ERC20} from "@openzeppelin/contracts@4.9.3/token/ERC20/ERC20.sol";
如果在导入文件中定义了多个合约或库,就会污染命名空间。如果编译器优化程序不删除这些代码,就会导致代码死机(这一点不应依赖编译器优化程序)。
如果你使用的是像 Slither 这样的智能合约安全工具,这种情况会被自动捕获。但一定要删除这些内容。不要害怕删除代码。
natspec(自然语言注释规范)的目的是提供易于人类阅读的内联文档。
合约的 natspec 示例如下:
/// @title Liquidity token for Foo protocol
/// @author Foo Incorporated
/// @notice Notes for non-technical readers/
/// @dev Notes for people who understand Solidity
contract LiquidityToken {
}
根据风格指南, 函数在合约内顺序,应先按 "外部性 "排序,再按 "状态变化性 "排序。
函数应按以下顺序排列:接收和回退函数(如有)、外部函数、公共函数、内部函数和私有函数。
在这几组中,先是 payable 函数,然后是非 payable 函数,再是视图函数,最后是pure(纯)函数。
contract ProperLayout {
// type declarations, e.g. using Address for address
// state vars
address internal owner;
uint256 internal _stateVar;
uint256 internal _starteVar2;
// events
event Foo();
event Bar(address indexed sender);
// errors
error NotOwner();
error FooError();
error BarError();
// modifiers
modifier onlyOwner() {
if (msg.sender != owner) {
revert NotOwner();
}
_;
}
// functions
constructor() {
}
receive() external payable {
}
falback() external payable {
}
// functions are first grouped by
// - external
// - public
// - internal
// - private
// note how the external functions "descend" in order of how much they can modify or interact with the state
function foo() external payable {
}
function bar() external {
}
function baz() external view {
}
function qux() external pure {
}
// public functions
function fred() public {
}
function bob() public view {
}
// internal functions
// internal view functions
// internal pure functions
// private functions
// private view functions
// private pure functions
}
如果你在代码中看到数字 100,它是什么?100%?100 个基点?
一般来说,数字应该作为常数写在合约的顶部。
不要写成:
uint256 secondsPerDay = 60 * 60 * 24;
而是这样写:
1 days;
不要写
require(msg.value == 10**18 / 10, "must send 0.1 ether");
而是:
require(msg.value == 0.1 ether, "must send 0.1 ether");
不这样写:
uint256 private constant BASIS_POINTS_DENOMINATOR = 10000
而是:
uint256 private constant BASIS_POINTS_DENOMINATOR = 10_000
virtual 修饰符的意思是 "可由子合约重写"。但如果你知道自己不会重写函数(因为你是部署者),那么这个修饰符就是多余的。删除它即可。
以下方式正确:
// visibility (payability), [virtual], [override], [custom]
function foo() public payable onlyAdmin {
}
function bar() internal view virtual override onlyAdmin {
}
natspec 有时被称为 "solidity 注释样式",其正式名称是 natspec:
其规则与合约级 natspec 类似,不过我们还根据函数参数和返回值参数注释。
这是在不使用长参数变量的情况下描述参数名称的好方法:
/// @notice Deposit ERC20 tokens
/// @dev emits a Deposit event
/// @dev reverts if the token is not allowlisted
/// @dev reverts if the contract is not approved by the ERC20
/// @param token The address of the ERC20 token to be deposited
/// @param amount The amount of ERC20 tokens to deposit
/// @returns the amount of liquidity tokens the user receives
function deposit(address token, uint256 amount) public returns (uint256) {
}
// If the contract inherits functions, you can also inherit their natspec
/// @inheritdoc Lendable
function calculateAccumulatedInterest(address token, uint256 since) public override view returns (uint256 interest) {
}
对于 @dev 参数注释,最好能说明它能做哪些状态变化,例如发出事件、发送以太币、自毁等。
@notice 和参数 natspec 可由 Etherscan 读取。
从下面的 代码 截图中,你可以看到 Etherscan 从何处获得这些信息。
这一点不言自明。如果代码被注释掉,还留在那里, 就会显得杂乱无章。
为变量命名是编写优秀代码的难点之一,但它对代码的可读性大有裨益。
一些小贴士:
internal
,应确保它在其他上下文中没有其他含义,例如,与状态变量同名的函数参数就不要使用下划线了。组织大型代码库是一门艺术。学习这门艺术的最好方法就是研究大型成熟项目的代码库。
如果你要系统学习Solidity ,你可以学习:
本翻译由 DeCert.me 协助支持, 来 DeCert 码一个未来, 支持每一位开发者构建自己的可信履历。
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!