欢迎订阅专栏:10分钟智能合约:进阶实战
其他漏洞审查:低危、信息性及代码质量缺陷
除了高危和中危漏洞,审计报告还需涵盖低危(Low)、信息性(Informational) 以及代码质量与可维护性问题。虽然它们通常不直接导致资金损失,但会影响合约的可读性、可升级性、Gas 效率、用户信任度及长期安全性。
一、低危漏洞(Low Severity)
低危漏洞在特定条件下可能造成有限影响,但攻击者难以直接利用,或者影响范围很小。
1. 拒绝服务(局部)
- 定义:攻击者可使特定用户无法使用某个功能,而非整个合约瘫痪。
- 示例:
withdraw()调用transfer时,若接收方是恶意合约并revert,则该用户无法提款,但其他用户不受影响。 - 审查要点:检查所有涉及外部调用的函数,是否允许用户绕过(如使用
try/catch或提供替代提款路径)。 - 修复建议:采用拉取模式(
withdraw)或使用try/catch隔离失败。
2. 条件竞争(Race Condition)
- 定义:在非原子操作中,多个交易可能相互干扰,但结果可预测或影响较小。
- 示例:
claimReward()与updateReward()之间存在微小时间窗口,导致用户获取略多或略少的奖励。 - 审查要点:检查非
nonReentrant且涉及状态变化的函数,是否有多笔交易相互影响的可能。 - 修复建议:使用原子操作或增加互斥锁(Mutex)。
3. 输入校验不足(非关键)
- 定义:某些函数的参数未被充分验证,但不会直接导致资金损失。
- 示例:
setFee(uint256 fee)允许将fee设为超过 100%,但后续计算可能导致溢出(0.8+ 自动回滚)或用户误解。 - 审查要点:检查所有
external/public函数的参数边界。 - 修复建议:增加
require限制合理范围(如fee <= 1e18)。
4. 依赖过时编译器版本
- 定义:使用
pragma solidity < 0.8.0且未使用 SafeMath,存在潜在的溢出风险(但 0.8+ 已默认检查)。 - 审查要点:检查
pragma声明,确认是否使用最新稳定版(至少 0.8.x)。 - 修复建议:升级到 0.8.25+ 并测试。
二、信息性漏洞(Informational)
信息性漏洞不直接威胁安全,但影响透明度、监控效率或用户体验。
1. 缺少事件(Event)
- 定义:关键状态变更(如所有权转移、参数调整、大额转账)未发出事件。
- 审查要点:检查所有
onlyOwner函数、set函数及资金变动函数是否包含emit。 - 修复建议:为每个敏感操作添加事件,并遵循
Indexed字段原则便于过滤。
2. 拼写错误或命名不规范
- 定义:变量名、函数名拼写错误,或未遵循 Solidity 命名规范(如
_前缀表示内部)。 - 审查要点:人工检查代码可读性,使用 linter(如
solhint)。 - 修复建议:统一命名风格,避免缩写或歧义。
3. 未使用的变量或函数
- 定义:声明了变量或函数但从未使用,可能造成误导。
- 审查要点:使用
slither的--detect unused-*检测。 - 修复建议:删除无用代码或添加
_前缀(如__gap保留槽)。
4. 缺少注释或文档
- 定义:复杂逻辑(如利率计算、授权流程)未在代码中注释。
- 审查要点:检查
NatSpec格式是否完整。 - 修复建议:为所有
public/external函数添加@notice、@param、@return。
5. 硬编码地址
- 定义:关键合约地址(如预言机、代币)被硬编码,不利于升级。
- 审查要点:搜索
0x字符串,确认是否可配置。 - 修复建议:使用
immutable或setter函数(带时间锁)管理。
三、代码质量与可维护性
这些建议旨在提升代码的健壮性,降低未来出错概率。
1. Gas 优化
- 定义:代码结构导致不必要的 Gas 消耗,增加用户成本。
- 常见问题:
- 循环中使用
storage读写而非memory。 - 重复计算相同值。
- 使用
bool数组(可用uint256位打包)。 public变量自动生成 getter,但可改为internal以节省。
- 循环中使用
- 审查要点:结合
forge snapshot对比优化前后的 Gas 消耗。 - 修复建议:优先使用
memory变量,减少SLOAD/SSTORE操作。
2. 使用 require vs assert
- 定义:
require用于输入验证,assert用于内部不变量,但误用可能导致 Gas 退还差异或攻击面。 - 审查要点:检查
assert是否用于外部条件(错误用法)。 - 修复建议:除内部检查外,一律使用
require。
3. 函数可见性不当
- 定义:内部函数被标记为
public或external,增加攻击面。 - 审查要点:检查每个函数的可见性是否符合最小权限原则。
- 修复建议:除非必要,内部函数使用
internal或private。
4. 可升级模式中的存储布局
- 定义:在可升级合约中,新版本添加变量时可能覆盖旧存储槽。
- 审查要点:检查是否使用
__gap预留槽位,且升级时仅追加变量。 - 修复建议:遵循 OpenZeppelin 升级指南,使用
UUPS或Transparent Proxy模式。
5. 依赖库版本管理
- 定义:使用的第三方库(如 OpenZeppelin)版本过旧,可能存在已知漏洞。
- 审查要点:检查
package.json或foundry.toml中的依赖版本。 - 修复建议:定期更新至最新稳定版,并关注安全公告。
四、审查方法总结
| 类别 | 审查方法 | 工具支持 |
|---|---|---|
| 低危逻辑 | 人工审查 + 攻击链模拟 | Foundry 测试 |
| 信息性 | 人工审查 + linter | solhint, slither |
| Gas 优化 | 基准测试与对比 | forge snapshot, gas-report |
| 代码质量 | 人工审查 + 静态分析 | slither, aderyn |
五、报告模板示例
### [L-01] 提款函数缺少失败隔离
**严重等级**:低危
**合约**:`Vault.sol`
**代码位置**:`Vault.sol#L45`
**描述**:`withdraw()` 在循环中调用 `transfer`,如果某个接收方合约 revert,将阻塞该用户后续提款,但不影响其他用户。
**建议**:使用 `try/catch` 捕获异常,或采用拉取模式(`withdraw` 由用户主动调用)。
**状态**:待修复
六、总结
其他漏洞审查的目标是确保合约不仅安全,而且清晰、高效、易于维护。虽然它们不如高危漏洞紧迫,但在长期运营中,良好的代码质量能显著降低未来引入新漏洞的风险。审计团队应将这部分内容作为报告的完整组成部分,并给出可操作的改进建议。