代币审计报告怎么看:审的是哪个合约、哪个版本、哪些问题没修
「已通过 XX 审计」这句话要落到具体的东西上:审的是哪份代码,之后改没改,哪几条问题没修。这篇拿一份公开报告,从封面翻到结论,逐节对照着读。

看三处:审计范围里写的仓库、commit 和文件清单,是不是你要买的那个已部署合约地址背后的代码;审计结束之后,代码有没有再改;问题清单里哪几条的状态不是「已解决」。这三处答得上来,这份报告才和你手里的币有关系。答不上来,「已通过 XX 审计」就只是一句宣传语。
报告有不等于安全。下面按拿到一份报告、从封面往后翻的顺序讲,全程用 OpenZeppelin 在 2024 年 8 月发布的 Uniswap v4 Core Audit 当样本。选它是因为全文公开,范围、信任假设、逐条处理结果和结论都能在页面上读到。Uniswap v4 是协议,不是哪个小币。你手里那份报告如果缺了其中哪一块,缺的那块就是要记下来的地方。
审计报告去哪里找原件,怎么确认不是项目方改过的 PDF
项目方官网上的 PDF、社交媒体上的截图、群里转的「审计通过」图片,都只能当线索。原件要到审计方自己的地盘上找。
各家放报告的位置不一样。OpenZeppelin 的报告挂在官网 Research 栏目下,分类名就叫 Security Audits,样本这篇页面顶部的面包屑是 Research / Security Audits / Uniswap v4 Core Audit。Trail of Bits 把公开的报告放在 GitHub 的 trailofbits/publications 仓库,PDF 在 reviews 目录下,列表里 Uniswap v4 Core 那一行登记的时间是 Jul 2024。
Trail of Bits 在那份列表前面写了一句话,大意是:允许公开谈论的客户列在这里,更多的仍然保密。所以在审计方那边搜不到,不能直接推出报告是假的,也可能只是没公开。但对你来说结果一样:核对不了。核对不了就记「未确认」,别替项目方把这一环补上。
项目方的仓库里也可能存着报告副本。Uniswap 的 v4-core 仓库有一个 docs/security/audits 目录,里面是五个 PDF,文件名里分别带着 ABDK、Certora、Spearbit、OpenZeppelin、TrailOfBits,其中三个以 DRAFT_ 开头。五份各审哪个 commit、哪些文件,要分别翻开看,不能拿一份的范围去套另一份;文件名带 DRAFT 的,引用之前确认审计方那边有没有定稿。副本和原件对不上时该信哪份,可以参考官网与白皮书冲突时信哪份;放到审计报告上,以审计方自己发布的那份为准。
找原件时顺手核一下域名。样本报告在 openzeppelin.com 下面。域名拼写差一个字母的「审计方官网」,按没找到原件处理。
封面上有两个日期:审计时间窗和发布日期
报告开头的 Summary 里有一行 Timeline。样本写的是 From 2024-05-27 To 2024-06-21,页面顶部的发布日期是 August 27, 2024,时间窗结束到发布,隔了两个多月。

时间窗是审计方看代码的那段时间,发布日期是报告公开的时间。中间这段,项目方在改代码。样本里唯一一条 Critical 级别的问题,报告写的处理结果是 Resolved in pull request #779,而这个 PR 的 GitHub 合并记录显示,修复于 2024 年 7 月合入审计分支,落在时间窗结束之后、报告发布之前。
这里有个容易看反的地方。样本的 Scope 只写了审计开始时的那个 commit,它是带着问题的版本,修复都发生在它之后。链上代码如果和这个 commit 一模一样,不是好消息,那说明报告里要改代码的问题一条都没改。如果报告另外写了修复复核之后的 commit,就拿后一个去比。
把这两个日期记下来,后面要拿链上的日期来比:你要买的那个合约哪天部署,最近一次升级是哪天。晚于时间窗的那部分改动有没有人审过,要追问。
审计范围(Scope)怎么看:仓库、commit、文件清单
Scope 是整份报告里最该慢慢读的一节,它回答的是到底审了什么。commit 和合约地址都没写的报告,等于没告诉你审了什么。
样本的 Scope 第一句写明:审计的是 Uniswap/v4-core 仓库,commit 为 d5d4957。这几个字带着链接,点过去是 GitHub 上那一版代码,地址里是完整的提交哈希 d5d4957b35750e8cf1f3db5584e77eef4861c21e。仓库名、commit、能点开的链接都在,任何人都能打开审计方当时看的那一版。
往下是文件清单,分成两组。第一组前面写的是 new and therefore were fully audited,新写的文件,完整审计,PoolManager.sol、ProtocolFees.sol、Hooks.sol 都在这一组。第二组写的是只检查了与 Uniswap/v3-core 仓库中对应版本的差异,共七个文件:BitMath.sol、FullMath.sol、Position.sol、SqrtPriceMath.sol、SwapMath.sol、TickBitmap.sol、TickMath.sol。
同在范围之内,审的深度也分档。读你手里那份报告时,照着问这几句:
- 写没写仓库地址和 commit?只写「审计了 XX 代币合约」、没有版本的,范围这一项就是空的。
- 文件清单里有没有你关心的那个合约?假设你要买的代币合约叫 Token.sol,清单里却只有质押合约和金库合约,这份报告就没审过你要买的东西。
- 有没有写明哪些文件只做了部分检查,哪些明确不在范围内?
如果报告写的不是 commit,而是一个已部署的合约地址,也算交代了范围,对持币者还更省事:地址可以直接拿去和你要买的那个比。合约地址本身怎么核对,见同名代币怎么区分:网络与合约地址。
审计报告里的信任假设:哪些情况不在结论之内
样本里这一节叫 Security Model and Trust Assumptions,排在问题清单前面。它写的是这份审计的结论依赖哪些前提。哪条前提不成立,结论就管不到那种情况。
这一节有几句话,放到「在钱包里碰到一个陌生币」的场景下很要紧:
- 核心合约没有滑点保护,默认由外围(periphery)合约来实现。
- 非标准实现的 ERC-20 代币(原文点了 rebasing、double entry point 两类)和恶意代币,协议不支持,会让用到它们的池子出现漏洞。
- 恶意的 hook 可能用多种方式偷走用户和流动性提供者的代币。
- Uniswap v4 是无许可的,任何人都能用任何代币建池;用哪个池子,由外围合约、用户和流动性提供者自己检查、自己决定。
假设你在一个 v4 池子里看到一个不认识的币,有人拿「Uniswap 是审计过的」给它背书。照报告原文,这个背书站不住。被审的是核心合约那批文件,那个代币的合约、那个池子挂的 hook 合约都不在文件清单里(清单里的 Hooks.sol 是 v4-core 仓库自己的文件,不是某个池子挂的 hook 合约),报告还专门写了它们可能是恶意的。审计覆盖的是协议代码,不会顺带覆盖跑在协议上的每一个币。
Privileged Roles 那一小段
Privileged Roles 列出两个协议层角色,写的是这两个角色「被信任」不会作恶。报告中,这两个角色分别能做什么?
- protocol fee controller:能给任何池子设协议费,报告所审版本的上限是 0.1%;还能把累积的协议费收到任意地址。
- owner:能更换 protocol fee controller。
盯住「被信任」这三个字。它不是审计发现的问题,后面没有「已修复」的状态,就是一条摆在那里的前提。换成一个小币的报告,这一段列的可能是谁能增发、谁能冻结、谁能升级合约。报告把角色列出来,不等于替你确认了拿着角色的人可靠。这些权限现在落在哪个地址上,要自己去链上读,读法在在区块浏览器上查代币管理权限里。
这份报告没有单独的免责声明章节,限制主要写在这一节和结尾的 Conclusion。结论段说,大量使用汇编和其他优化手法明显提高了代码复杂度,thereby leaving room for undiscovered issues,给没被发现的问题留了空间。ethereum.org 的智能合约安全文档写的是:审计抓不到每一个 bug,主要作用是多提供一轮审查。
问题清单怎么读:严重级别之外,盯住每条后面的状态
先把 Summary 的总数记下来:24 条问题,18 条已解决,2 条部分解决。
- Critical:1 条,已解决 1 条;High:0 条。
- Medium:3 条,已解决 2 条;Low:3 条,已解决 2 条。
- Notes & Additional Information:16 条,已解决 12 条、部分解决 2 条。
- Client Reported:1 条,已解决 1 条。
各类相加是 1 + 0 + 3 + 3 + 16 + 1 = 24;已解决的相加是 1 + 0 + 2 + 2 + 12 + 1 = 18。
24 减 18 再减 2,还剩 4 条。统计表不会告诉你是哪 4 条,得翻到正文,看每条末尾的 Update。翻完的结果:3 条写着 Acknowledged, not resolved,Medium、Low、Notes 里各一条;还有 1 条 Notes 写的是团队联系了 EIP-6909 的作者,修改推给了规范文本,没有落在某个代码 PR 上。
| Update 里的原文 | 样本里的条数与例子 | 持币者该怎么读 |
|---|---|---|
| Resolved in pull request #… | 18 条。Critical 那条写的是 Resolved in pull request #779 | 修复对应到了具体的 PR,可以点开看改了什么。它只说明这一处改了,不涉及此后别的改动 |
| Partially resolved in pull request #… | 2 条,都在 Notes。其中一条后面跟着 Only the third item has been addressed | 一条问题里有几个小项,只改了一部分。要读后半句,弄清剩下的是哪几项 |
| Acknowledged, not resolved | 3 条。Medium 那条的标题是 Front-Running Pool's Initialization or Initial Deposit Can Lead to Draining Initial Liquidity | 项目方知道,没有改代码。看报告有没有附项目方的理由,理由依赖的是什么 |
那条没修的 Medium 值得细看。报告附了团队的回应:这类攻击由外围合约在用户添加流动性时加滑点保护来防。风险没有消失,防线设在核心合约外面,也就是这份报告的范围外面;外围合约有没有人审,要看别的报告。它和上一节「核心合约没有滑点保护、默认外围来做」说的是同一件事。读自己手里的报告时也这样对一遍:每条没修的问题,给出的理由是不是靠一个范围之外的东西撑着。
级别名各家不一样
样本用的是 Critical、High、Medium、Low 加 Notes,报告页面里没有给各级别下定义。CertiK 在官方的审计说明文章里用的是另一套:Critical、Major、Medium、Minor、Informational,并写明 Major 这一档可以包含中心化问题和逻辑错误。同一个词在两家报告里未必是同一个分量,跨报告比「几个高危」比不出东西。读每条的标题和描述,比读级别名可靠。
CertiK 那篇文章还交代了状态的来历:客户解决了问题就标为已解决,没解决的,在最终报告里标出仍然存在的风险。一份报告只有问题清单、没有每条的处理结果,你要问的是它是不是最终版。
顺带说「通过」这个词。样本的结论段里没有「通过」,写的是发现了一个严重级别和若干中等级别的漏洞,代码库稳健、文档总体完善,同时复杂度给未发现的问题留了空间。项目方说「已通过审计」,你回到原文去找对应的句子,找不到,那就是项目方自己的转述。
报告审的代码,和你在查的合约地址是同一份吗?
合上报告,回到最初的问题。报告说的是某个仓库的某个 commit,你在查的是链上的某个地址。这两头得有东西接上。
样本报告没有写被审的合约部署在哪个地址;页面里出现的几个 0x 地址,是讲漏洞时拿来举例的别的代币。要接上,得再走两站:
- 项目方的官方文档。Uniswap 开发者文档的 v4 Deployments 页按网络列了部署地址,以太坊主网的 PoolManager 是
0x000000000004444c5dc75cB358380D2e3dE08A90。 - 区块浏览器。在 Etherscan 打开这个地址的合约页,页面标题标的是 Uniswap V4: Pool Manager,源码验证状态是 Exact Match。
上面的地址和验证状态核对于 2026 年 10 月。部署地址表和浏览器上的状态都会更新,照着做的时候重新打开官方文档查一遍,不要直接抄这里的地址。
走完这两站能确认的是:官方文档指向的地址上,跑着一份公开了源码的合约,名字和报告范围里的 PoolManager.sol 对得上。Etherscan 的验证类型说明把状态分了几种:Exact Match 指源码与部署的合约代码连同构造参数都对应;Similar Match 是因为创建字节码与另一个已验证合约相同而自动匹配上的,不看构造参数;Unverified 只有字节码。只有字节码的合约,持币者没法拿它和报告对照。
确认不了的是:链上这份源码,等于被审计的那个 commit 加上报告里列的修复 PR,此外没有别的改动。样本的 Scope 只给了审计开始时的那个 commit,后面这一步要拿 Etherscan 上的源码和仓库逐个文件比。比不动,就老实记一句:范围和地址能对上合约名,代码是否一致未确认。
如果是代理合约:升级之后,旧报告不覆盖新实现
你要买的合约如果是代理合约,还得多核一层。Etherscan 的代理合约说明写道,代理合约把调用转交给另一个「实现合约」;实现地址升级过的话,重新跑一次代理验证,新的实现地址会被记录下来。ethereum.org 的合约升级文档把后果说得很白:让代理指向一份新的逻辑合约,用户调用代理时执行的代码就变了;它列的缺点里有一条,用户必须信任开发者不会随意改动合约。
落到审计报告上,报告审的是某一版实现。代理换了实现,你拿着的还是同一个地址,旧报告却管不到新代码。遇到代理合约,要找的是当前这份实现合约对应的报告,再比一下实现合约上线的日期和审计时间窗。升级权在谁手里,用前面那篇查权限的方法去读。
哪几种情况只能记「未确认」
核对到某一步读不出答案,不要自己脑补一个。下面几种情况,在笔记里写「未确认」,并写清卡在哪:
- 找不到原件。审计方官网和审计方自己的公开仓库里都没有这份报告。可能是没公开,你分辨不了。
- 没有 commit,也没有地址。报告只写了项目名或代币名,它审的可以是任何一版。
- 链上合约没有验证源码。浏览器里只有字节码,没法和报告对照。
- 你要买的合约不在文件清单里,或者报告没写它的地址,链上源码和被审的 commit 你又比不动。
- 代理合约在审计之后换过实现,新实现没有对应的报告。
- 问题清单没有状态。只列了发现,没写每条是怎么处理的。
「未确认」不是给这个币下了判决,它的意思是审计这一项暂时给不了你依据。把它当成笔记里的一个空格留着,别拿「毕竟有审计」去填。发行方、权限、持仓这些别的项照常查,整套顺序见如何研究一个代币。
留一份复查笔记:
- 报告原件链接、仓库与 commit(或合约地址)。
- 审计时间窗,以及你核对的年月。
- 尚未解决和部分解决的问题标题。
项目方以后宣布升级合约时,先用这份笔记核对改动涉及哪些文件,再找对应的新报告或补充说明。
核对审计报告时的几个追问
报告里没有严重和高危问题,是不是就更安全?
推不出来。问题少可能是代码简单,也可能是范围窄、审的文件少。回头看文件清单覆盖了哪些合约,再看信任假设里列了哪些特权角色。OpenZeppelin 的 Uniswap v4 Core 审计报告里,特权角色写在信任假设一节,没有算进问题统计。
审计报告是一两年前的,还能参考吗?
看这段时间合约动过没有:是不是代理合约、有没有换过实现、有没有重新部署。动过的,要看最近一次改动之后有没有新报告;没动过的,旧报告仍按范围、状态那几处照常核对。报告日期的早晚,不单独决定它还作不作数。