审计Solidity智能合约时需要注意的事项清单。
作者:Sec-Labs | 发布时间:
项目地址
https://github.com/tamjid0x01/SmartContracts-audit-checklist
SmartContracts-audit-checklist
相关技术点
- Solidity智能合约安全审计
- Consensys的Solidity-Code-Metrics
- SLoc(cloc)
- Solidity文件数量
- Solidity文件的代码行数
- Solidity文件的SHA256哈希值
- Solidity文件中的外部调用
项目用途
该项目提供了一个Solidity智能合约审计的检查清单,包括以下内容:
- 审计范围:在开始审计之前,需要了解项目的代码库。可以使用Consensys的Solidity-Code-Metrics工具来完成这个任务,该工具可以帮助我们快速地理解项目的范围。
- 一般审计方法:提供了审计智能合约的一般方法,例如:
- 是否有空字符串
- 是否有不安全的随机数生成器
- 是否有不安全的算术操作
- 是否有重入漏洞等
- 授权审计:提供了审计授权的方法,例如:
- 是否有未经授权的合约调用
- 是否有授权问题
- 是否有不当的授权等
- 资金流审计:提供了审计资金流的方法,例如:
- 是否有资金流问题
- 是否有资金流转移问题
- 是否有未经授权的资金流等
- 安全性审计:提供了审计安全性的方法,例如:
- 是否有安全问题
- 是否有可能的攻击向量等
同时,该项目还提供了一些审计前的辅助工具,例如SLoc(cloc)、Solidity文件数量、Solidity文件的代码行数、Solidity文件的SHA256哈希值、Solidity文件中的外部调用等,可以帮助我们更好地了解项目的代码库,从而更好地进行智能合约审计。
智能合约审计清单

Solidity智能合约审核时需要检查的事项清单。
这不是一个全面的列表,而且Solidity正在迅速发展,所以请您尽职调查。
并非所有列出的项目都适用于您特定的智能合约。
审计范围
在开始审核之前,您需要了解项目的代码库。您可以使用Consensys的Solidity-Code-Metrics轻松完成此任务。
这里是详细信息,您可以轻松了解项目范围。
🌐 npm install solidity-code-metrics
这是📊tintinweb.solidity-metrics背后的数字处理引擎。

此外,这里提供了一些有用的工具,这些工具可以在审核之前帮助您。
SLoc (cloc)
$ cloc */
86 text files.
86 unique files.
1 file ignored.
github.com/AlDanial/cloc v 1.82 T=0.24 s (354.5 files/s, 86952.0 lines/s)
-------------------------------------------------------------------------------
Language files blank comment code
-------------------------------------------------------------------------------
Solidity 72 3027 6000 10274
TypeScript 13 223 86 1239
-------------------------------------------------------------------------------
SUM: 85 3250 6086 11513
-------------------------------------------------------------------------------
Solidity文件数
$ find . -name '*.sol' | wc -l
72
每个Solidity文件的代码行数
$ find . -name '*.sol' | xargs wc -l
137 ./contracts/BaseJumpRateModelV2.sol
84 ./contracts/CarefulMath.sol
39 ./contracts/CErc20Immutable.sol
190 ./contracts/CEther.sol
...
148 ./contracts/Unitroller.sol
85 ./contracts/WhitePaperInterestRateModel.sol
19293 total
文件的SHA256哈希值
$ shasum -a 256 contracts/*.sol
32111c1b2bcdb051fa5c2564cd2a5e0662e699472ca5373499f67dca9c71cf47 contracts/BaseJumpRateModelV2.sol
c98ee33d13672016db21d4d6353b45eccb5c9f77499df77c254574a0481c0c03 contracts/CDaiDelegate.sol
650bdf61c685b50b3f016553f822c35b4c605a0c477791b35f3de0a7cb61d390 contracts/CNft.sol
...
a56f8cf884f0bceb918bbb078aaa5cd3ef90002323787729d70fdee6b4a1c602 contracts/Unitroller.sol
b5d06e0d725b01ecb8d0b88aa89300ddc0399904d84915a311f42f96970ba997 contracts/WhitePaperInterestRateModel.sol
Solidity文件中的某些外部调用
$ egrep '\.\w*\(.*\)' contracts/* -nr
contracts/BaseJumpRateModelV2.sol:85: return borrows.mul(1e18).div(cash.add(borrows).sub(reserves));
contracts/BaseJumpRateModelV2.sol:116: uint oneMinusReserveFactor = uint(1e18).sub(reserveFactorMantissa);
contracts/BaseJumpRateModelV2.sol:118: uint rateToPool = borrowRate.mul(oneMinusReserveFactor).div(1e18);
...
contracts/Timelock.sol:64: bytes32 txHash = keccak256(abi.encode(target, value, signature, data, eta));
contracts/Timelock.sol:74: bytes32 txHash = keccak256(abi.encode(target, value, signature, data, eta));
contracts/Timelock.sol:99: (bool success, bytes memory returnData) = target.call.value(value)(callData);
contracts/WhitePaperInterestRateModel.sol:37: baseRatePerBlock = baseRatePerYear.div(blocksPerYear);
一般审查方法:- [ ] 所有的函数都是internal类型,除非明确需要是public/external类型。[?]
-
没有算术运算溢出/下溢。
-
使用OpenZeppelin安全数学库[?]。
-
不能意外地发送以太币或代币到地址
0x0。 -
在进行操作和状态变更之前使用
require检查条件。 -
在设置状态之前并执行操作。
-
受到重入攻击的保护(A调用B调用A)。[?]
-
正确实现了ERC20界面[?]。
-
只在必要的地方使用修饰符。
-
所有类型都被明确设置(例如使用
uint256而不是uint)。 -
所有方法和循环都在允许的最大气体限制内。
-
构造函数中没有不必要的初始化(记住,已经设置了默认值)。
-
有完整的测试覆盖;测试了每个智能合约方法和每种可能的输入类型。
-
通过使用随机输入执行模糊测试。
-
测试了合约可能处于的所有不同状态。
-
以wei为单位处理以太币和代币数量。
-
众筹结束块/时间戳在开始块/时间戳之后。
-
众筹代币兑换/转换率被正确设置。
-
众筹软/硬上限已设置。
-
已设置并测试了允许的最小/最大投资额。
-
已测试了众筹白名单功能。
-
已测试了众筹退款逻辑。
-
众筹参与者获得他们的比例代币数量或被允许要求他们的捐款。
-
已正确配置每个众筹阶段的长度(例如预售、公开销售)。
-
指定哪些函数只受所有者控制(例如暂停众筹、推进众筹阶段、启用代币分发等)。
-
已测试了众筹锁定逻辑。
-
当所有者启用时,众筹具有故障安全模式,限制对函数的调用并启用退款功能。
-
如果有合理意义,则放置了回退函数。
-
回退函数不接受调用数据或仅接受前缀数据以避免函数签名冲突。
-
导入的库已经过审计,并且不包含可以在未来版本中被恶意使用的动态部分。[?]
-
代币转移语句被包装在
require中。 -
正确使用
require和assert。仅在永远不应该发生的情况下使用assert,通常用于在进行更改后验证状态。 -
使用
keccak256而不是别名sha3。 -
受到ERC20短地址攻击的保护。[?]。
-
受到递归调用攻击的保护。
-
任意字符串输入都有长度限制。
-
没有暴露任何机密数据(区块链上的所有数据都是公开的)。
-
尽可能避免使用数组,使用映射代替。
-
不依赖块哈希进行随机性。矿工对此有影响。
-
任何地方都不使用
tx.origin。[?] -
删除项目时,数组项会向下移动以避免留下间隙。
-
使用
revert而不是throw。 -
当条件不满足时,函数立即退出。
-
使用最新稳定版本的Solidity。
-
更喜欢接收者提取资金的模式,而不是合约发送资金,但不总是适用。
-
解决了编译器警告。## 变量
-
V1- 是否可以设为internal? -
V2- 是否可以设为constant? -
V3- 是否可以设为immutable? -
V4- 是否已设置其可见性?(SWC-108) -
V5- 变量的目的和其他重要信息是否使用 natspec 进行了文档化? -
V6- 是否可以与相邻的存储变量一起打包? -
[ ]
V7- 是否可以在一个结构体中与多个其他变量一起打包? -
V8- 除了与其他变量一起打包时可以使用较小的类型外,请始终使用完整的 256 位类型。 -
V9- 如果它是公共数组,是否提供了一个单独的函数来返回整个数组? -
V10- 只有在需要有意防止子合约访问变量时,才使用private,优先考虑使用internal来实现灵活性。
结构体
-
S1- 是否需要使用结构体?变量是否可以在存储中直接进行打包? -
S2- 其字段是否一起打包(如果可能的话)? -
S3- 是否使用 natspec 文档化了结构体及其所有字段的目的?
函数
-
F1- 是否可以设为external? -
F2- 是否应设为internal? -
F3- 是否应设为payable? -
F4- 是否可以与另一个类似的函数合并? -
F5- 即使函数只能由信任的用户调用,也要验证所有参数是否在安全范围内。 -
F6- 是否遵循了“检查-效果”模式?(SWC-107) - [ ]-
F7- 检查前置交易可能性,例如 approve 函数。(SWC-114) -
F8- 是否可能存在燃气不足的欺诈行为?(SWC-126) -
F9- 是否应用了正确的修饰符,例如onlyOwner/requiresAuth? -
F10- 是否总是对返回值进行了赋值? -
F11- 编写并测试关于函数执行前状态的不变量。 -
F12- 编写并测试关于函数返回或任何状态更改后状态的不变量。 -
F13- 注意函数命名,因为人们会根据名称推断行为。 -
F14- 如果函数有意不安全(为了节省燃气等),则使用一个不便的名称来引起注意其风险。 -
F15- 是否使用 natspec 对所有参数、返回值、副作用和其他信息进行了文档化? -
F16- 如果该函数允许操作系统中的另一个用户,则不要假定msg.sender是被操作的用户。 -
F17- 如果该函数要求合约处于未初始化状态,请检查显式的initialized变量。不要使用owner == address(0)或其他类似检查的替代品。 -
F18- 只有在需要有意防止子合约调用函数时,才使用private,优先考虑使用internal来实现灵活性。 -
F19- 如果存在合法(且安全)的情况下,子合约可能希望覆盖函数的行为,则使用virtual。
修饰符
-
M1- 是否没有进行存储更新(除了在重入锁中)? -
M2- 是否避免了外部调用? -
M3- 是否使用 natspec 文档化了修饰符的目的和其他重要信息?
代码- [ ] C1 - 使用SafeMath或0.8的检查数学?(SWC-101)
-
C2- 是否多次读取了任何存储槽? -
C3- 是否使用了任何可能导致DoS的未限定循环/数组?(SWC-128) -
C4- 仅在长时间间隔内使用block.timestamp。(SWC-116) -
C5- 不要使用block.number来计算经过的时间。(SWC-116) -
C7- 尽可能避免使用delegatecall,特别是对外部(即使是受信任的)合同。(SWC-112) -
C8- 在迭代数组时不要更新数组的长度。 -
C9- 不要使用blockhash()等来生成随机数。(SWC-120) -
C10- 签名是否受到nonce和block.chainid的重播保护(SWC-121) -
C11- 确保所有签名使用EIP-712。(SWC-117 SWC-122) -
C12- 如果使用> 2个动态类型,则不应将abi.encodePacked()的输出哈希化。通常最好使用abi.encode()。(SWC-133) -
C13- 注意汇编,不要使用任意数据。(SWC-127) -
C14- 不要假设特定的ETH余额。(SWC-132) -
C15- 避免燃气不足的恶意行为。(SWC-126) -
C16- 私有数据并不是私有的。(SWC-136) -
C17- 在内存中更新结构/数组不会修改存储中的结构/数组。 -
C18- 永远不要隐藏状态变量。(SWC-119) -
C19- 不要改变函数参数。 -
C20- 在计算值时,是否动态计算比存储更便宜? -
C21- 是否从正确的合同(主合同vs克隆合同)中读取了所有状态变量? -
C22- 是否正确使用比较运算符(>,<,>=,<=),特别是防止差一的错误? -
C23- 是否正确使用逻辑运算符(==,!=,&&,||,!),特别是防止差一的错误? -
C24- 除非乘法可能溢出,否则始终在除法之前进行乘法。 -
C25- 是否用直观名称的常量替换了魔法数字? -
C26- 如果ETH的接收方有一个回退函数并且已回退,是否可能导致DoS?(SWC-113) -
C27- 使用SafeERC20或安全地检查返回值。 -
C28- 不要在循环中使用msg.value。 -
C29- 如果递归委托调用可能是可能的(例如合同继承了Multicall/Batchable),请勿使用msg.value。 -
C30- 不要假设msg.sender始终是相关的用户。 -
C31- 除了用于模糊测试或形式验证,不要使用assert()。(SWC-110) -
C32- 不要使用tx.origin进行授权。(SWC-115) -
C33- 不要使用address.transfer()或address.send()。使用.call.value(...)("")代替。(SWC-134) -
C34- 在使用低级调用时,在调用之前确保合同存在。 -
C35- 在调用具有许多参数的函数时,请使用命名参数语法。 -
C36- 不要使用汇编创建2。更喜欢现代盐创建语法。 -
C37- 不要使用汇编访问chainid或合同代码/大小/哈希。更喜欢现代Solidity语法。 -
C38- 将变量设置为零值(0,false,""等)时,请使用delete关键字。 -
C39- 在可能的情况下尽可能注释“为什么”。 -
C40- 如果使用晦涩的语法或编写非传统的代码,请注释“什么”。 -
C41- 在复杂和固定点数学旁边注释解释+示例输入/输出。 -
C42- 在进行优化的地方注释解释,以及它们节省了多少燃气的估计值。 -
C43- 在某些优化故意避免的地方注释解释,以及如果实现会/不会节省多少燃气的估计值。 -
C44- 在不可能溢出/下溢的情况下或人类时间尺度上不可能发生溢出/下溢(计数器等)的情况下,请使用unchecked块。无论何时使用unchecked,请注释解释以及它节省了多少燃气(如果相关)。 -
C45- 不要依赖Solidity的算术运算符优先级规则。除了使用括号来覆盖默认运算符优先级外,还应使用括号来强调它。 -
C46- 传递给逻辑/比较运算符(&&/||/> =/==/等)的表达式不应具有副作用。 -
C47- 在执行可能导致精度损失的算术运算时,请确保它使系统中的正确参与者受益,并使用注释记录它。 -
C48- 每当在内联或@devnatspec注释中使用重入锁时,请记录使用它的原因。 -
C49- 在模糊处理仅对特定数字范围进行操作的函数时,使用模数来缩紧模糊处理器的输入(例如x = x%10000 + 1以限制从1到10,000)。 -
C50- 尽可能使用三元表达式简化分支逻辑。 -
C51- 在操作多个地址时,请问自己如果它们相同会发生什么。## 外部调用 -
X1- 是否真的需要进行外部合约调用? -
X2- 如果出现错误,是否可能导致拒绝服务攻击?例如balanceOf()抛出异常。(SWC-113) -
X3- 如果调用重新进入当前函数是否会有害? -
X4- 如果调用重新进入另一个函数是否会有害? -
X5- 是否检查了结果并处理了错误?(SWC-104) -
X6- 如果使用了所有提供的 gas,会发生什么? -
X7- 如果返回大量数据,是否会导致调用合约的 gas 耗尽? -
X8- 如果调用特定函数,不要假设success意味着该函数存在(幻影函数)。
静态调用
-
S1- 是否真的需要进行外部合约调用? -
S2- 是否在接口中实际标记为 view? -
S3- 如果出现错误,是否可能导致拒绝服务攻击?例如balanceOf()抛出异常。(SWC-113) -
S4- 如果调用进入无限循环,是否可能导致拒绝服务攻击?
事件
-
E1- 是否应该索引任何字段? -
E2- 是否将相关操作的创建者包含为索引字段? -
E3- 不要索引字符串或字节等动态类型。 -
E4- 是否在事件发出时,并使用 natspec 记录了所有字段的文档? -
E5- 所有在发出事件的函数中操作的用户/ID是否都存储为索引字段?
合约
-
T1- 使用 SPDX 许可证标识符。 -
T2- 是否为每个存储变异函数发出了事件? -
T3- 检查正确的继承关系,保持简单线性。(SWC-125) -
T4- 如果合约应该接收转移的 ETH,请使用receive() external payable函数。 -
T5- 编写并测试有关存储状态之间关系的不变量。 -
T6- 是否使用 natspec 记录了合约的目的及其与其他合约的交互方式? -
T7- 如果另一个合约必须继承它才能解锁其全部功能,则应将合约标记为abstract。 -
T8- 对于在构造函数中设置的任何非不可变变量,在其在其他地方发生变异时发出适当的事件。 -
T9- 避免过度继承,因为它掩盖了复杂性并鼓励过度抽象。 -
T10- 始终使用命名导入语法明确声明从另一个文件导入的合约。 -
T11- 将导入按其文件夹/包分组。使用空行分隔组。外部依赖组应优先,然后是模拟/测试合约(如果相关),最后是本地导入。 -
T12- 用@noticenatspec 注释概括合约的目的和功能。在@devnatspec 注释中记录合约如何与项目内/外其他合约交互。
项目
-
P1- 使用正确的许可证(如果您依赖于 GPL 代码,则必须使用 GPL 等)。 -
P2- 对所有内容进行单元测试。 -
P3- 尽可能进行模糊测试。 -
P4- 在可能的情况下使用符号执行。 -
P5- 运行 Slither/Solhint 并查看所有结果。
DeFi- [ ] D1 - 检查你对其他合约的操作和返回值的假设。
-
D2- 不要混淆内部记账和实际余额。 -
D3- 不要将AMM的现货价格作为Oracle使用。 -
D4- 不要在没有通过链外或通过Oracle获得价格目标的情况下交易AMM。 -
D5- 使用合理检查来防止Oracle/价格操纵。 -
D6- 注意重新定价的代币。如果不支持,请确保文档中记录了该属性。 -
D7- 注意ERC-777代币。即使是您信任的代币,如果它是ERC-777,则也可能执行重入攻击。 -
D8- 注意转移费用的代币。如果不支持,请确保文档中记录了该属性。 -
D9- 注意使用太多或太少小数的代币。确保记录了最大和最小支持值。 -
D10- 小心依赖合约的原始代币余额来确定收益。提供了一种从直接发送到合约的资产中恢复的方式的合约可能会破坏基于地址的原始Ether或代币余额的份额价格函数。 -
D11- 如果您的合约是代币批准的目标,请勿从用户输入中进行任意调用。
平台
以下是一些可以进行智能合约审计并获得大量赏金的平台。
公共智能合约审计报告列表
这里列出了一些公共审计报告,供学习审计使用。让我们享受阅读一些很酷的审计报告。
- Consensys : 报告
- Peckshield : 报告
- Openzeppelin : 报告
- TrailofBits : 报告
- Quillhash : 报告
- Hacken : 报告
- Beosin : 报告
- Iosiro : 报告
- Oak Security : 报告
- G0 group : 报告
- Hexens : 报告
- Sherlock : 报告
- Code4rena : 报告