【免费下载链接】publications
Publications from Trail of Bits
本文基于 Trail of Bits 在 DeFi Security Summit 101 上的报告《Smart Contracts: The Beta》(演讲人 Nat Chin,仓库文档见 README,完整幻灯片见 DSS 101.pdf)整理而成。报告以攀岩为贯穿性隐喻,把智能合约开发比作攀上一座尚未有人登顶("the beta")的岩壁:规格文档是起始保护点(starting hold),单元测试是锚点(anchor),静态分析是岩架/猴架(jug / monkey bars),模糊测试则是登顶的枢纽(crux)。读完本文,你可以得到一套可直接落地的开发流程:如何撰写并维护规格文档、如何组织单元测试与边界条件、如何用 Slither 处理静态分析告警、如何用 Echidna 进行属性测试(invariant 测试),以及一套贯穿始终的"安全检查清单"。
报告定位:如何写出安全的智能合约代码
报告的开场问题非常直接:HOW TO WRITE SAFE CODE(如何写出安全的代码)。作者 Nat Chin 来自 Trail of Bits,报告所在的仓库是 Trail of Bits 的公开出版物合集,其区块链工具线(Slither、Echidna、Medusa、Caracal、solc-select)全部开源,报告中讲解的工具即来自这条研发线。
报告的叙事框架是"安全攀登":
| 攀岩概念 | 对应开发环节 | 报告章节 |
|---|---|---|
| The Start / Starting Hold | 规格文档(Specification) | Step 1: The Start Hold |
| Anchor Point(锚点) | 单元测试(Unit Testing) | Step 2 的第一层 |
| Jug / Monkey Bars(岩架、猴架) | 静态分析(Static Analysis,以 Slither 为代表) | Step 2 的第二层 |
| The Crux(最难路段) | 模糊测试 / 属性测试(Fuzzing,以 Echidna 为代表) | Step 2 的第三层 |
以下各节严格按幻灯片的脉络展开,并在每节末尾结合本仓库中的佐证材料(源码、数据集、同系列报告)做纵深补充。
Step 1:起始保护点——先写规格文档(Specification)
幻灯片给出的第一步是"THE START HOLD",核心论点是:规格文档是整个安全流程的起点,后面所有的测试与分析都以它为基准。
规格文档要定义三件事
幻灯片(Specifictions 页)列出规格文档必须回答的三个基本问题:
- 定义你在构建什么(Define what you're building);
- 定义谁将使用你的代码(Define who will use your code);
- 定义代码的预期使用方式(Define how it is meant to be used)。
一份合格规格文档的完整要素
报告把规格文档的要素分成两组。第一组面向"行为与假设":
- 写下所有假设(Writing down all of your assumptions),并且要确保这些假设成立(Ensure these assumptions hold);
- 识别并列出所有边界情况(Identifying and listing all edge cases);
- 对全部数学假设给出证明(Proving all mathematical assumptions)——对 DeFi 类合约尤其关键,如费率、利息、汇率计算;
- 识别用户流(Identifying user flows);
- 确定数据校验假设(Determining data validation assumptions)——即哪些输入被信任、哪些必须校验。
第二组面向"可观测的行为契约":
- 详细说明代码何时 revert(Detailing when your code reverts);
- 明确说明代码何时不 revert(Specifying when your code does not revert);
- 为所有函数编写 Natspec 文档(Documenting Natspec on all functions);
- 编写说明预期行为的用户指南(Creating user guides outlining expected behavior)。
这里值得注意的是"何时不 revert"与"revert"被同时列出:安全上危险的往往是隐式放行(silently accepted),而规格文档的作用就是把"什么输入应当被拒绝、什么输入应当被接受"从口头约定变成可核对的文本。
规格文档的收益与维护纪律
报告给出的收益是两条:
- 你知道你的代码应该做什么(You'll know what your code is supposed to do);
- 你知道你的代码应该如何工作(You'll know how your code is supposed to work)。
而"How to write a spec"一页给出了维护纪律,这三条应被视为硬性流程要求:
- 确保规格文档是最新的(Ensure your spec is up to date);
- 把规格文档检入版本控制(Check it into source control);
- 每次修改代码时同步更新规格文档(Update it every time you change code)。
从仓库惯例看,本仓库 README 的 front-matter(title/date/authors/conference/resources)就是同一思路在文档层面的体现:每份出版物都显式登记标题、日期、作者与资源位置,便于后续引用与核对。
Step 2:借"原力"——用工具保持安全
报告的第二步是"USE THE FORCE TOOLS",对应攀岩中"用绳索、主锁、锚点、机械塞保持安全"(We use tools to stay safe: Rope, carabiners, anchors, cams)。工具链在智能合约安全中的层次是:单元测试 → 静态分析 → 模糊测试,由浅入深、由确定性到探索性。
你的锚点:单元测试(Unit Testing)
幻灯片先用一张"不合格锚点"的图提醒:测试本身如果质量低,等于没有保护。随后给出具体做法(So what should you do?):
- 使用你详细的规格文档来写测试(Use your detailed specifications)——测试用例直接映射规格条目;
- 测试"Happy path"(预期成功的功能);
- 测试"Unhappy path"(预期失败的功能)——即各种应当 revert 的路径;
- 尽早识别你的边界情况(Identify your edge cases early);
- 最低达到 100% 的语句覆盖率和分支覆盖率(Minimum 100% statement and branch coverage)。
单元测试的收益(Benefits of Unit Testing):
- 能轻松发现规格与代码之间的偏差(Find deviations between specifications and code easily);
- 倒逼你写出可测试的代码(Forces you to write testable code);
- 在开发过程中尽早发现 bug(Identify bugs as early as possible while developing);
- 为代码提供"防回归"保障,避免同一 bug 被重新引入(Future-proofs your code from re-introducing the same bug)。
同时也明确指出单元测试的两条局限(Limitations of Unit Testing):
- 测试只和你的输入一样好(Tests are only as good as your inputs);
- 不测试组件之间的集成行为(Does not test integration between components)。
这正好引出了下一节:单测覆盖不了的"组件间"和"未知输入组合",需要静态分析与模糊测试补位。
边界情况(Edge Cases)的系统化思考
"Considering Edge Cases"页给出三个追问,幻灯片中特别标注"后面还会回来讲这个"(We'll come back to this),呼应后文的模糊测试:
- 识别输入的安全边界(Identify safe bounds of input);
- 你的代码在越界输入下会怎样?(What does your code do under unsafe bounds?)
- 考虑你的代码没有处理的情形(Consider what your code does not handle)——"没有被处理"本身就是一种危险行为。
岩架与猴架:静态分析(以 Slither 为代表)
幻灯片把静态分析比作岩壁上的岩架(jug)与猴架(monkey bars):"We love them. They make our climb easier."(我们喜欢它们,它们让攀登更容易。)其定位一句话概括:自动化的智能合约最佳实践检查器(Automated smart contract best practices checker),即"你的自动找虫器"(Your automatic bug finder)。
报告点名工具为Slither(该页附其项目地址,本仓库另有多份 Slither 相关出版物,见 Building a Practical Static Analyzer for Smart Contracts 与 Write Better Smart Contracts By Checking Them With Slither's Python API)。报告给出的 Slither 事实:
- 它会告诉你哪里可能有问题(Will tell you what might be problematic);
- 对你的代码做静态分析(Analyzes your code statically);
- 内置 87 个检测器,匹配常见的已知漏洞(Matches 87 detectors with commonly known vulnerabilities)——此为幻灯片(2023 年 DSS 101 版本)中给出的数字,对应当时版本,实际数量随版本演进而变化。
仓库佐证:Slither 的架构与 Python API 能力
本仓库的 Dagstuhl 2021 报告 2021-10-27-dagstuhl-slither.pdf 及其 README 补充了 Slither 的实现细节:Slither 把 Solidity 合约转换为一个名为SlithIR的面向安全分析的中间表示(intermediate representation),在此之上应用污点追踪(taint tracking)等经典程序分析技术;其四大用途是漏洞自动检测、辅助理解合约、辅助代码评审、自动化代码优化检测,并强调研究者可以利用其 Python API 构建自定义静态分析(Researchers can leverage its Python API to build custom static analyses)。
这一点在仓库中有可直接运行的示例:PyChain 2022 报告 Write Better Smart Contracts By Checking Them With Slither's Python API 附带了示例合约 AlarmClock.sol 与分析脚本 detect.py。示例合约中,cancelAndRefund函数把奖励计算建立在tx.gasprice之上:
function cancelAndRefund(address who) external { uint startGas = gasleft(); // SOME EXPENSIVE CALL who.call(msg.data); uint gasUsed = startGas - gasleft() + gasBonus; uint rewardOwed = gasUsed * tx.gasprice; payable(msg.sender).transfer(rewardOwed); }而 detect.py 用 Slither Python API 做污点检查,找出"转账金额是否被tx.gasprice这个污点源影响":
from slither import Slither slither = Slither("AlarmClock.sol") from slither.slithir.operations import Transfer from slither.analyses.data_dependency.data_dependency import is_dependent from slither.core.declarations.solidity_variables import SolidityVariableComposed alarm = slither.get_contract_from_name("AlarmClock")[0] for func in alarm.functions: for node in func.nodes: for operation in node.irs: if isinstance(operation, Transfer) and is_dependent( operation.call_value, SolidityVariableComposed("tx.gasprice"), func ): print(f"{node.expression} uses tainted input, tx.gasprice, in {func}")这个例子恰好演示了规格文档中"数据校验假设"落到代码检查的方式:先声明"支付金额不得依赖tx.gasprice(攻击者可控)"这一假设,再用污点分析程序化验证它。
Slither 告警的处理决策树
幻灯片给出了非常实用的三档处理策略(Slither Findings: What do?):
- 如果代码当前可被利用 → 修复它(If code is currently exploitable, fix it);
- 如果代码可能被利用 → 记录它(If code can be exploitable, document it);
- 如果代码当前不可被利用 → 让 Slither 静默该告警,并记录理由(If code is not currently exploitable, silent Slither and document it)。
注意第三档强调"静默必须伴随文档":suppress 不是遗忘,而是把"为什么可以接受这个告警"变成可审计的决策。
静态分析的优缺点
幻灯片的 Pros and Cons 对照:
- 优点:运行快(Runs quickly)、告诉你哪里看起来有问题(Tells you what looks wrong)、强制推行最佳实践(Enforces best practices);
- 缺点:啰嗦、误报率高(Verbose, high false positive rate)。
仓库佐证:告警的可检测性分类
本仓库的 datasets/smart_contract_audit_findings 数据集为"哪些 bug 适合静态分析发现"提供了实证视角。其 initial_findings.csv 中每条审计发现都带static与dynamic两个 0/1 字段,README 定义:static表示"该发现是否可被当前或近期自动静态分析合理地检测",dynamic表示"是否可被自动动态分析(包括自定义属性)合理地检测"。例如首条数据reentrancy,low,medium,1,1,0表示重入类问题同时适合静态与动态手段。这与幻灯片"静态分析快但误报多、模糊测试深但设置难"的分工判断相互印证:选型的关键不是二选一,而是按告警类型组合使用两类手段。
The Crux:模糊测试(Fuzzing,以 Echidna 为代表)
幻灯片把模糊测试称为攀登中最难的路段("The Crux: Fuzzing"),它承担单测与静态分析都覆盖不到的部分:用随机输入轰炸你的代码。四个要点:
- 用随机输入给你的代码施加压力(Stress your code with random inputs);
- 倒逼你识别系统属性(不变量 invariants)(Forces you to identify system properties (invariants));
- 提供机器人化的单元测试(Provides roboticized unit testing)——即把"人写单测"升级为"工具自动生成并执行海量测试序列";
- 端到端(E2E)测试的搭建可能更有挑战(Can be more challenging to set up E2E)。
Echidna:报告点名的智能合约模糊测试器
幻灯片中 Echidna 的定位(Smart contract fuzzer):
- 自动为函数生成输入(Automatically generates inputs for functions);
- 检测并高亮属性违例(Detects and highlights property violations);
- 报告还提示关注Medusa的后续进展(Watch for developments on Medusa)——Medusa 同为 Trail of Bits 工具线成员,在开场介绍页与 Echidna、Slither、Caracal、solc-select 并列。
Echidna 的优缺点对照:
- 优点:发现复杂 bug(Finds complex bugs)、检测边界情况(Detect edge cases)、比单元测试更完整(More complete than unit testing);
- 缺点:搭建起来更难(Can be harder to setup)。
仓库佐证:属性测试的方法论纵深
本仓库有两份与"如何写好 invariant"直接相关的同系列材料,可作为 DSS 101 报告的延伸阅读:
- Property-based testing of smart contracts(Empire Hacking 2018,JP Smith):明确指出"单元测试一次只测一个已知用例,而属性测试通过声明通用不变量来覆盖未知用例",并给出 Takeaway——"对智能合约做属性测试时,你必然会撞上用户通常会在单测中遗漏的条件"。这恰好回应了 DSS 101 报告中模糊测试"倒逼识别系统属性"的论点。
- How to Fuzz Like a Pro(Nat Chin 与 Josselin Feist,DeFi Security Summit / EthCC / ETH Taipei):专门讲"什么是代码不变量、如何编写、以及如何高效使用 Echidna 检查它们",与本报告同一位作者(Nat Chin),可视为本报告的深化版。
- 论文层面,仓库收录了 Echidna 的正式论文 echidna_issta2020.pdf 与海报 echidna_fc_poster.pdf。
把三份材料串起来,模糊测试环节的可执行理解是:先写 invariant("什么必须永远为真")→ 让 Echidna 自动生成调用序列去尝试推翻它 → 对违例序列做最小化复盘。不变量正是 Step 1 中"写下所有假设"的机器可执行版本。
收尾:区块链安全安全检查清单(Safety Checks)
幻灯片最后一页把整场报告收敛为一份可贴在墙上的清单(Blockchain Security Safety Checks):
- 安全是持续过程(Security is a continuous process);
- 把一切写下来(Write everything down);
- 写新代码的同时补充单元测试(Add unit tests as you write new code);
- 每个新提交都跑一遍 Slither(Run Slither on every new commit);
- 确定你的不变量(Determine invariants);
- 跑模糊测试(Run fuzz tests)。
这份清单与报告结构一一对应:第 2、3 条对应 Step 1(文档与单测),第 4 条对应静态分析岩架,第 5、6 条对应模糊测试 crux。它给出的隐含结论是:静态分析应进入 CI 级别的"每次提交"节奏,而模糊测试作为更重的深度检查按项目节奏持续运行——这也解释了为何幻灯片把 Slither 的优点第一条列为"Runs quickly"。
报告最后指向了更多内容(Incident Response Plan、Development Workflow、token 集成风险、EVM 与 EVM Opcodes、Transaction Tracing 等),对应 Trail of Bits 的智能合约安全资料站 secure-contracts.com;本仓库内的 citation_guidelines.pdf 与 README 说明了该出版物仓库的整体结构与引用方式,可供检索其他相关出版物(本仓库presentations/下还有大量 Slither、Manticore、Echidna、DARPA AIxCC/Buttercup 等智能合约与自动化安全工具的报告与幻灯片)。
小结
《Smart Contracts: The Beta》给出的是一条"由确定走向探索"的安全开发阶梯:
- 规格文档回答"代码应该做什么",且必须随代码同步更新、纳入版本控制;
- 单元测试(happy path + unhappy path + 边界情况,100% 语句/分支覆盖)回答"代码实际做了什么",其天花板是"输入的质量"与"只看单组件";
- **静态分析(Slither)**用 87 个检测器(DSS 101 版)快速、持续地强制最佳实践,告警按"可被利用则修 / 可能被利用则记录 / 不可被利用则静默并记录"三档处置;
- **模糊测试(Echidna)**把规格中的假设转写成不变量,用自动生成的调用序列去推翻它们,覆盖单测与静态分析都够不到的集成与组合行为。
四条手段共用同一份源头资产——规格文档中的假设与边界清单。工具会随版本演进(如 Slither 检测器数量变化、Medusa 等后续工具的进展),但"先写下假设、再逐层用工具验证假设"的方法骨架保持稳定。
【免费下载链接】publications
Publications from Trail of Bits
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考