跳到正文
fevou.独立加密知识站

代币审计报告怎么看:审的是哪个合约、哪个版本、哪些问题没修

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

这份审计,审的是哪份代码:阅读线索示意
一份审计报告要核对的几处:范围里的仓库与 commit、每条问题的状态、报告写明的前提。

看三处:审计范围里写的仓库、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,时间窗结束到发布,隔了两个多月。

OpenZeppelin 官网 Uniswap v4 Core Audit 报告页顶部:面包屑 Research / Security Audits,标题、日期 August 27, 2024、署名 OpenZeppelin Security,Summary 中的 Type、Timeline、Languages,左侧目录含 Scope、Security Model and Trust Assumptions 等章节
OpenZeppelin 官网 Uniswap v4 Core Audit 报告页顶部,要看的是 Summary 里的 Timeline 和页面顶部的日期(2026年10月截图)。

时间窗是审计方看代码的那段时间,发布日期是报告公开的时间。中间这段,项目方在改代码。样本里唯一一条 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 resolved3 条。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 地址,是讲漏洞时拿来举例的别的代币。要接上,得再走两站:

  1. 项目方的官方文档。Uniswap 开发者文档的 v4 Deployments 页按网络列了部署地址,以太坊主网的 PoolManager 是 0x000000000004444c5dc75cB358380D2e3dE08A90。
  2. 区块浏览器。在 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 审计报告里,特权角色写在信任假设一节,没有算进问题统计。

审计报告是一两年前的,还能参考吗?

看这段时间合约动过没有:是不是代理合约、有没有换过实现、有没有重新部署。动过的,要看最近一次改动之后有没有新报告;没动过的,旧报告仍按范围、状态那几处照常核对。报告日期的早晚,不单独决定它还作不作数。