智能合约审计检查清单
33 项检查,分 10 组。和绝大部分「审计清单」不同的是, 每一项旁边都写着「这一项工具能不能帮你查」——其中 21 项是任何静态工具都查不出来的。 把这件事标出来,比假装覆盖更有用。
勾选记录只存在你这台机器的浏览器里(localStorage),不会上传。 关闭页面后再打开仍在;点「重置」才会清空。
Owner 地址是谁?是单一私钥,还是多签?
只能人工检查特权操作是否有时间锁?改动前用户能否提前看到?
工具能提示,判断需人工有没有 renounceOwnership?Owner 是否已经调用过?
工具能提示,判断需人工mint 函数有没有权限控制?是 external 还是 internal?
工具可自动检查总供应有硬上限吗?上限是常量还是可改的变量?
只能人工检查有没有 burn 函数?谁能对「别人的」余额调用 burn?
只能人工检查_burn(msg.sender, ...) 安全,_burn(address, ...) 带 onlyOwner 就危险。有没有黑名单 / 白名单机制?谁能改?
工具可自动检查税率是常量还是可改?可改的话上限是多少?
工具能提示,判断需人工交易开关归谁管?关掉之后用户还能赎回或卖出吗?
工具可自动检查【必做】实际买入一点,然后立刻尝试全部卖出。
只能人工检查每个外部调用是否都在状态更新之后?
工具能提示,判断需人工重入锁是否覆盖了全部有外部调用的入口?
工具能提示,判断需人工低层 call 的返回值检查了吗?
工具可自动检查address.call(...) 失败时不抛异常,只返回 false。不检查返回值,就等于「转账失败但状态已更新」,直接丢钱。.call{ 与 .call(,确认每个都有 require(success) 或显式的 success 判断。合约是否接收 ERC-777 / ERC-721 / ERC-1155 回调?回调函数里做了什么?
只能人工检查价格从哪里来?用的是现货储备比,还是 TWAP / 外部预言机?
只能人工检查getReserves() 语法上毫无问题,危险来自「这个值可被瞬间改变」。这一步只能人工判断:价格是否来自单一池子的即时状态。如果用 TWAP,窗口多长?短窗口照样能被操纵。
只能人工检查有没有多源交叉验证?偏离阈值如何设定?
只能人工检查签名的摘要里包含 chainId 和 verifyingContract 吗?
只能人工检查\x19\x01,确认 domain 里有 chainId 与 verifyingContract。keccak256(abi.encodePacked(...)) 无论拼了什么参数都语法正确。本站的 EIP-712 工具可以帮你手工验证 domain separator 是否如预期。有没有 nonce?已用过的签名会被标记吗?
只能人工检查ecrecover 是否拒绝了高 s 值?签名是否可被篡改?
只能人工检查bytes 变了。如果合约用签名字节做去重标识,这个签名就能被用两次。s <= secp256k1n/2 的检查(或直接用 OpenZeppelin ECDSA)。除法舍入的方向对谁有利?
只能人工检查a / b 在任何上下文里都合法。这需要结合业务语义逐处推演。金库的首笔存款是否存在「捐赠放大份额」的攻击面?
只能人工检查用的是 Solidity 0.8+ 还是 0.7 及更早?
工具能提示,判断需人工是否使用代理?实现合约地址是多少?升级权归谁?
工具可自动检查升级前后的存储变量顺序一致吗?有没有新变量插在中间?
只能人工检查initialize 函数能否被重复调用?有没有 initializer 修饰器?
只能人工检查initializer 修饰器(OpenZeppelin Initializable),且实现合约的构造函数里调用了 _disableInitializers()。流动性锁定了吗?锁多久?锁定凭证的合约地址是什么?
只能人工检查持仓是否集中在少数地址?前 10 名占比多少?
只能人工检查有没有「一笔交易内可完成的获利路径」?
只能人工检查审计报告原件能查到吗?审计的是哪个 commit?
只能人工检查审计范围覆盖了哪些合约?有没有「托管在范围外」的关键逻辑?
只能人工检查有没有漏洞赏金计划?金额与项目 TVL 匹配吗?
只能人工检查这份清单来自真实审计流程里反复出问题的点,但它不是一份合规文件: 勾完只说明「这些该问的问题都问过了」,不构成对合约安全的判断。 想理解某一类漏洞为什么危险、怎么写才对,去漏洞库看可运行的两版代码对照。
为什么每项都要标「工具覆盖度」
清单最容易变成摆设:几十个勾选框全勾完、产出一份漂亮报告、然后什么都没查出来。 问题的根源在于——用户不知道自己刚才勾的那一下,到底验证了什么。
所以每一类都显式区分:有规则能给出证据行号、有规则但只能提示存在性、纯逻辑问题工具完全查不出。 第三类不是凑数的——历史上造成最大损失的几类漏洞(闪电贷操纵价格、签名重放、舍入套利) 全在第三类里,它们的代码在语法上完全正常。
扫描引擎有对应规则,命中时直接给出文件与行号。这类项扫一遍就能拿到证据。
工具能看出「代码里出现了一个可改税率的 setter」, 但判不了它被设成 2 天时间锁还是被 Owner 随时一键改。
危险来自「这段代码在什么环境下运行」,不是写法。必须看懂业务的人逐条推演。
四步走完一遍
- 1先把Rug Pull 扫描器跑一遍,拿到有证据行号的命中列表——这些项可以直接标成「发现问题」,不用人工确认。
- 2剩下「只能提示存在性」的项,回到源码逐处确认参数与权限——是时间锁还是即时生效,差别就在这里。
- 3最后过「工具查不出」的 21 项。这一批花的时间最长, 但历史损失最大的问题也都在这里。每条都可以点「备注」把当时的判断记下来。
- 4导出 Markdown 报告。报告会显式列出「工具覆盖不到、且仍是未检查状态」的条目—— 这一节的存在是为了防止拿一份完整度虚高的报告去说服别人。
勾选状态保存在你这台机器的 localStorage 里,不会上传到任何地方。 换浏览器或清空站点数据会丢,所以要留档请导出报告。
10 个分组各查什么
合约里最危险的东西不是 bug,而是「某个人能做什么」。先把特权函数和 Owner 是谁搞清楚,再谈其他。
增发权是归零最直接的方式:不需要攻击,持有者被稀释就够了。
这一类决定了「你能不能卖出去」。静态扫描能提示存在性,但永远替代不了实际卖出测试。
只要合约会调用外部地址,就要问:调用之后我改状态了吗?对方的回调能重新进来吗?
这一组全部是 none——历史损失最大的漏洞就在这里,而它们代码语法完全正常,静态扫描一条都抓不到。
签名本身不含「在哪条链、给哪个合约、第几次用」,这三件事必须显式写进去。
整数除法会截断。在有份额、有价格的地方,截断的方向决定了谁能套利。
可升级合约的风险不在逻辑里,而在「版本之间」:存储槽冲突、初始化函数被重复调用。
合约代码没问题,不代表项目没问题。这一组看的是代码之外的东西:流动性、持仓、代币分配。
"已审计"三个字必须能查到原始报告。这一组帮你判断一份审计报告到底覆盖了什么。