审查方法概述:
- 阅读项目的文档、规范和合同,了解智能合约的用途。
- 在查看代码之前,在设想中构建一个期望中的承载架构模型。
- 快速浏览递归,感受项目结构,可以利用 Surya 这类工具。
- 将项目架构与您考虑的合约架构模型进行比较,检查不符合预期的部分。
- 创建威胁模型并介绍高级攻击防御的理论。
- 查看与交换机相关的地方,尤其重要的是、、、、、、和。
transfer
优先检查它们,确保安全。transferFrom
send
call
delegatecall
selfdestruct
- 查看与外部交互的区域,并确保所有关于它们的假设都是有效的,例如价格只会上涨等等。
- 对契约进行一般性的逐行审查。
- 从威胁模型中每个参与者的角度进行另一次审查。
- 快速浏览项目的测试+代码覆盖率,并深入了解缺乏覆盖率的区域。
- 运行 Slither/Solhint 等工具并审查其输出。
- 检查相关项目及其审计,以检查是否存在类似问题或疏忽。
指标
V1
- 是否可以internal
?V2
- 是否可以constant
?V3
- 是否可以immutable
?V4
- 是否设置了可见性?(SWC-108)V5
- 变量的用途和其他重要信息是否使用 natspec 标准记录?V6
- 它可以与相邻的存储变量一起备份吗?V7
- 是否可以和其他变量放在一个struct
中?V8
- 使用完整的256位类型,无需与其他变量一起共享。V9
- 如果它是一个公共的array
,是否提供了一个单独的函数来返回完整的数据库?V10
- 如果不是有意阻止子合约访问变量,则使用更灵活的internal
,而不是private
。
结构体
S1
- 这里是否有必要用struct
?只能用原始信号吗?S2
- 它的字段是否预留在一起?S3
-struct
的用途和所有字段是否使用natspec标准记录?
函数
F1
- 是否可以external
?F2
- 是否应该是internal
?F3
- 是否应该是payable
?F4
- 它是否可以与另一个类似的函数合并?F5
- 验证所有参数都在安全范围内,该函数只能由受信任的用户调用。F6
- 是否遵循check-before-effect模式的检查? (SWC-107)F7
- 检查抢跑的可能性,例如批准功能。(SWC-114)F8
- 是否会受到insufficient gas griefing
伤害?(SWC-126)F9
- 是否应用了正确的修饰符,例如onlyOwner
/requiresAuth
?F10
- 是否总是分配返回值?F11
- 写下并测试使得函数不能正常运行的状态不变量。F12
- 写下并测试返回值和改变状态的不变量F13
- 命名函数时名称要小心,因为人们会根据来想象的行为。F14
- 如果一个函数是故意不安全的(为了省油等原因),使用一个特别的名字来引起人们对它的风险的注意。F15
- 所有参数、返回值、副作用和其他信息是否都使用 natspec 标准记录?F16
- 如果该功能允许对系统中的另一个用户进行操作,则不要假设msg.sender
是被操作的用户。F17
- 如果函数要求约定未初始化状态,请使用显式的initialized
变量检查。不要使用owner == address(0)
或其他类似的检查作为替代产品。F18
- 如果不是有意阻止子合约访问变量,则使用更灵活的internal
,而不是private
。F19
- 仅在子约定中希望合法(且安全)重写函数的行为时使用virtual
。
修饰符
M1
- 是否没有进行存储更新(重入锁)?M2
- 是否避免了外部调用?M3
- 修饰符的用途和其他重要信息是否使用 natspec 标准记录?
代码
C1
- 是否使用SafeMath
或solidity 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.encode()
,而不是abi.encodePacked()
。 (SWC-133)C13
- 对待内联服饰。 (SWC-127)C14
- 不要假设特定的ETH
余额。 (SWC-132)C15
- 避免insufficient gas griefing
攻击。 (SWC-126)C16
-private
数据不是隐私的。(SWC-136)C17
- 更新memory
中的struct
/array
不会在storage
中修改它。C18
- 永远不要隐藏状态指标。 (SWC-119)C19
- 不要在函数中改变参量的值。C20
- 即时计算数值是否比存储更便宜?C21
- 所有状态标志是否都从正确的合约中读取(主合约与克隆合约)?C22
- 是否正确使用了比较错误(>
,<
,>=
,<=
),特别是防止差一错误(off-by-one error)?C23
- 是否正确使用了逻辑时序(==
,!=
,&&
,||
,!
),特别是防止差一错误(off-by-one error)?C24
- 在除法前使用乘法,除非乘法会导致故障。C25
- 魔术数字是否被替换为名称所在的位置?C26
- 如果ETH
接收者有一个fallback
函数,它会导致 DoS 吗?(SWC-113)C27
- 使用 SafeERC20 或安全检查返回值。C28
- 不要在循环中使用msg.value
。C29
- 如果可能出现循环delegatecall
,则不要使用msg.value
(比如合约继承了Multicall
/Batchable
)。C30
- 不要假设msg.sender
是相关交易的用户。C31
- 除非用于模糊测试或形式验证,否则请勿使用assert()
。 (SWC-110)C32
- 不要tx.origin
用于授权检查。 (SWC-115)C33
- 不要使用address.transfer()
或address.send()
,使用.call.value(...)("")
。 (SWC-134)C34
- 使用低级调用(low level call)时确保合约存在。C35
- 调用具有多个参数的函数时,使用命名参数语法。C36
- 不要使用内部关联调用create2
,使用新式的salt
契约创建语法。C37
-不要使用内联来获取chainid
或者约定的代码/长度/,使用新式的Solidity语法。C38
- 将变量设置基准值(0
,false
,""
)时使用delete
关键字。C39
- 配件注释代码来解释“为什么”要须。C40
- 在使用晦涩难懂的或编写非常规范的代码时,语法注释代码在做“什么”。C41
- 在复杂的数学过程旁注上解释和输入输出示例。C42
- 在优化气体的位置注释说明,并估计节省的气体。C43
- 在避免避免优化的地方注释说明,并估计消耗多少gas。C44
- 在不可能的上溢/下溢的代码块,或者上溢/下溢在人类时间尺上不现实的代码块(投票等)使用。注释中写清楚哪里用了,并估计节省的unchecked
气体unchecked
。C45
-不要依赖Solidity
的算术运算符优先级规则。逗号不仅用来覆盖默认运算符优先级,而且可以用于强调它。C46
- 传递给逻辑/比较符号 (&&
/||
/>=
/==
) 的表达不应有副作用。C47
- 如果执行了可能导致准确性损失的技术损坏,请确保它有利于系统中的正确参与者,并在注释中记录它。C48
- 在注释中写下函数需要重入锁的原因。C49
- 如果模糊函数仅支持特定范围的参数,则使用取模操作限制参数输入范围(例如x = x % 10000 + 1
将范围限制在从 1 到 10,000 )。C50
- 问卷使用三元表达式来简化逻辑。C51
- 当对多个地址进行操作时,询问自己是否相同的话会发生什么。
外部调用
X1
- 是否真的需要外部合约调用?X2
- 如果运行时报错,是否会导致DoS?比如balanceOf()
回滚。 (SWC-113)X3
- 如果调用重新进入当前函数是否存在?X4
- 如果调用重新进入另一个函数是否有细菌?X5
- 是否检查了结果并处理错误?(SWC-104)X6
- 如果用光gas
后会发生什么?X7
- 如果返回大量数据,会导致调用合约中的gas出错吗?X8
- 如果你调用特定函数时返回了success
,也不意味着该函数存在。
静态调用
S1
- 是否真的需要外部合约调用?S2
- 是否应该标记为view
?S3
- 如果运行时报错,是否会导致DoS?比如balanceOf()
回滚。 (SWC-113)S4
- 如果调用进入无限循环,是否会导致DoS?
事件
E1
- 应该被哪些指标indexed
?E2
- 相关操作的创建者地址是否包含在索引字段中?E3
- 不要将包括string
和bytes
动态标记设为事件的inedex
。E4
- 事件释放的时间和指标是否使用 natspec 标准记录?E5
- 将释放事件的函数中所有被操作的用户/ID设为indexed
字段。E6
- 避免函数调用和事件参数中使用表达式求值,它们的求值顺序是不可预测的。
契约
T1
- 使用SPDX
许可证标识符。T2
- 是否所有会修改storage
变量的函数都释放了时间?T3
- 检查所有的继承是否正确,确保它们简洁且线性。 (SWC-125)T4
- 如果合约应接收ETH
,是否加了receive() external payable
?T5
- 写下并测试有关变量之间关系的不变量。T6
- 一致性的目的和与其他一致性的交互是否使用 natspec 标准记录?T7
- 如果另一个合约必须继承它以解锁其全部功能,则该合约应为标记abstract
。T8
- 如果构造函数中设置了非常量变量的值,且该变量的值在其他函数中被改变并释放事件(见)T2
,则构造函数中也应该释放事件。T9
- 避免过度继承,因为它掩盖了复杂性并鼓励过度抽象。T10
- 始终使用命名的导入语法来明确声明哪些约定是从另一个文件中导入的。T11
- 按文件夹/包将引入进行分组,每组之间空一行,外部依赖组放在起始处,然后是模拟/测试合约(如果),最后是本地导入。T12
- 使用 natspec 标准中的@notice
记录合约的目的和功能,@dev
记录合约如何与内部项目/外部的其他合约交互。
项目
P1
- 使用正确的许可(例如,如果您依赖的包使用了GLP协议,您也可以使用)。P2
- 单元测试所有内容。P3
- 关闭多的模糊测试。P4
- 详细了解符号的使用方法。P5
- 运行 Slither/Solhint 并审查所有发现。
去中心化金融
D1
- 检查您对其他约定作用和返回值的假设。D2
- 不要将内部估计值与账户实际余额混为一谈。D3
- 不要将AMM
现货价格设为明天的价格。D4
- 如果没有链下或预示机的价格接收目标,不要在AMM
上进行交易。D5
- 使用援性检查来防止天气预报/价格上涨。D6
- 注意变基(rebasing)代币。如果它们不受支持,要在文档中明确说明。D7
- 注意ERC-777
代币,即使是你信任的代币也可以被重新进入。D8
- 注意转账收税的代币,如果不受支持,要在文档中明确说明。D9
- 注意使用过多或过少的少量标记,要在文档中明确且简单地支持顶部。D10
- 注意依赖代币余额来确定收益的合约,这个数字可能会被挖掘。D11
- 如果您的合约是代币授权的目标,请不要根据用户输入进行任何调用
翻译修改自 : @transmission11GitHub - transmissions11/solcurity: Opinionated security and code quality standard for Solidity smart contracts.