site logo

Marico's space

Precedent:一个知道哪些 Solidity 安全建议已被推翻的代理

AI技术与应用 2026-10-06 20:56:04 6

最近折腾了一个帮 Solidity 开发者查安全建议的工具,踩了几个坑,这篇把问题说清楚。

Solidity 的安全建议更新太快,文档、EIP(以太坊改进提案)、审计报告和老博客互相矛盾——说白了就是时间在变,编译器和 EVM(以太坊虚拟机)都升级了,建议却没跟着变。关键词搜索或者直接问普通 LLM(大语言模型)会信心满满地给你一个过时的答案,因为它们只看文本,看不见"对于某个版本,某段文字其实已经替换了另一段"这个事实。

Precedent 把安全指导当成判例法来对待。每条声明都带着日期和编译器版本范围,新规则推翻旧规则。问"transfer() 在 0.8.28 上还安全吗?",你会得到:

  • 针对你确切版本的一条裁决
  • 案卷:旧建议说了什么(被划掉),是什么推翻了它,每个都链到原始来源
  • 当来源真正存在分歧、没有任何规则能主导时,状态是有争议,而不是瞎猜一个答案

它深入覆盖了 [[5]] 个模式:[[列表,例如带 transfer/send 的 ETH 转账、selfdestruct、SafeMath、ERC-4626 通胀攻击、tx.origin 作为不变更控制]]。控制案例很重要:它确保 agent 不会在本来没有分歧的地方制造分歧。

Precedent 是对已发布来源的摘要,不是审计,每个裁定都这么写。

目标用户:需要针对自己编译器版本找到可引用答案的 Solidity 开发者、做快速分类的审计人员、以及不想在去年就被废弃的模式上踩坑的黑客松选手。

Demo

在线地址: [[DEPLOYED URL]]

[[视频或 GIF:裁定解析过程,被推翻的声明被划掉,控制的声明高亮显示]]

试试这些问题 [[替换为在你的构建中效果好的问题]]:

  • [[问题 1:一个版本相关的过时陷阱模式]]
  • [[问题 2:一个真正有争议的模式]]
  • [[问题 3:稳定控制案例,应该显示无分歧]]

代码

[[GITHUB REPO URL]]

工作原理

Question + compiler version │ ▼ Precedent agent (Vercel AI SDK, multi-step tool loop) │ ├──► Context MCP endpoint A (Knowledge Base) │ what do the sources say? where do they conflict? │ ├──► Context MCP endpoint B (dataset, GROQ) │ verified claims in scope for this version, │ with supersession and sources ▼ Verdict: status, stance, claim IDs only │ ▼ UI fetches claim text, dates, URLs from Sanity by ID │ ▼ Ruling card + docket timeline
Enter fullscreen mode Exit fullscreen mode
  1. 识别从问题和版本选择器中提取模式和版本。如果没给版本,agent 会假设数据集里的最新版本并说明。
  2. 读取来源通过 Knowledge Base 端点,看它们说了什么、哪里有冲突。
  3. 查询声明通过 GROQ 端点:那些版本范围覆盖该版本的已验证声明,以及哪些声明取代了哪些。
  4. 裁决。控制性声明是范围内且没有被其他范围内声明取代的声明。如果它们的立场一致,状态是已定。如果不一致,就是有争议,不选赢家。如果不存在,就是超出范围。
  5. 返回 ID,不返回正文。agent 发出一个包含声明 ID 的结构化裁定。UI 再从 Sanity 按 ID 加载正文、日期和 URL。

第 5 步是刻意设计的。模型永远不会写来源、URL 或日期,所以无法伪造引用。API 路由也会重新检查每个 ID 是否存在、重新计算状态;出现不匹配时用户看到错误而不是错误答案。

如何使用 Sanity

agent 通过两个 Context MCP 端点读取 Sanity,因为它们做不同的事。Knowledge Base 支持的端点提供 Knowledge Base 模式,数据集支持的端点提供 GROQ 模式,所以我用了一个。端点 A:Knowledge Base

我把 Sanity Context 指向了 [[N]] 个源文档:[[例如 Solidity 文档和变更日志、EIP、OpenZeppelin 文档和发布说明、N 份公开审计报告]]。Sanity 将它们提炼成可导航的 Knowledge Base,每个条目都保持与其来源的链接,冲突的声明也会和来源一起并列显示。[[添加你观察到的情况:产生了多少条目,它自动暴露的矛盾的例子,以及你对某个冲突做出的决定。]]

我刻意保持在 beta 版约 150 文档的预算内:小而精的主要来源集合比大而嘈杂的好。

端点 B:数据集,GROQ 模式

Knowledge Base 模式无法表达"这个声明只适用于 0.8.x,被那个声明取代了",所以我用结构化内容来建模:

类型 内容
pattern 命名模式、简短摘要和别名
source 标题、URL、发布方、种类(文档、EIP、审计、发布说明、博客)、发布日期、许可证
claim 转述声明(最多 280 字符)、立场(安全、不安全、已废弃、混合)、fromVersion / toVersion、supersedes[]、contradicts[] 和置信度标记

编译器版本存为数字(例如 0.8.28 变成 8028),这样 GROQ 就能比较。只有我对照原始来源手动核查过的声明才会标记为 verified,只有这些才会被使用。[[N]] 条声明,[[5]] 个模式。

裁定是计算出来的,不是存储的。这个查询返回每个范围内声明以及哪个声明取代了它:

*[_type == "claim" && confidence == "verified" && pattern._ref == $patternId && (!defined(fromVersion) || fromVersion <= $v) && (!defined(toVersion) || toVersion >= $v)]{ _id, statement, stance, "source": source->{title, url, publishedAt}, "supersededBy": *[_type == "claim" && ^._id in supersedes[]._ref]._id }
Enter fullscreen mode Exit fullscreen mode

控制性声明是那些在范围内且 supersededBy 中没有范围内声明的。因为裁定是派生出来的,修复一条声明或添加一条更新的会立即改变所有受影响的答案,无需重新整理。

为什么结构化在这里很重要

关键词搜索找到提到某个模式的段落。它没有办法知道对于特定编译器版本,一个段落替换了另一个段落。supersedes 链和版本范围是内容模型,agent 的正确性依赖于此。Knowledge Base 显示来源说了什么;数据集说明哪个声明是控制的。

agent 使用的工具

[[列出每个端点实际暴露的工具名称,在运行时发现的,以及 agent 各自用于什么。示例格式:tool_name(端点 A):用于...]]

Context 端点是只读的,所以 agent 无法更改内容。声明由脚本播种并在 Studio 中编辑。

它真的比替代方案强吗?

我写了 [[N]] 个"过时陷阱"问题,其中显而易见的答案是过时的:[[N]] 个过时陷阱、[[N]] 个真正有争议的案例、[[N]] 个稳定控制,以及 [[N]] 个超出范围。我对照已验证声明手工标注了真实答案,然后用相同的模型、相同的输出 schema、每个问题 [[3]] 次运行跑了三个系统:

  • A:关键词搜索在原始来源上(把排名最高的段落给模型,无工具)
  • B:只用 Knowledge Base(端点 A,不加声明层)
  • C:Precedent(两个端点都用)

因为每个系统都返回相同的结构化裁定,评分是确定性的而不是模型评判模型:如果答案匹配了被取代的声明就是过时,如果引用包含了控制性声明就是正确。

系统 过时答案率(越低越好) 引用控制来源 应该在有争议时说"有争议"
A:关键词搜索 [[X%]] [[X%]] [[X%]]
B:只用 Knowledge Base [[X%]] [[X%]] [[X%]]
C:Precedent [[X%]] [[X%]] [[X%]]

[[用你自己的话写两三句:哪个系统最好,B 在哪里失败了、为什么,以及一个具体问题展示了差异。]]

设计决策

  • 只从模型返回 ID。防止伪造引用。
  • 有争议是一等公民。在真正冲突的来源之间选赢家的安全工具比直接说"有争议"的更糟糕。
  • 只用已验证声明。未经审查的声明永远不会进入裁定路径。
  • 不回退到模型记忆。如果端点失败,用户得到错误,而不是模型从训练数据里的猜测。
  • 小而深。[[5]] 个模式做好比 50 个模式做糊强。
  • 一个控制案例。tx.origin 没有分歧,所以正确的 agent 应该报告稳定答案。

护栏

Precedent 绝不写漏洞利用代码,也绝不审计用户代码。每个裁定都显示"汇总已发布来源,不是审计"。声明是我的转述,附有原文链接,我尽可能使用了宽松许可证的来源。

挑战与教训

[[从你实际构建中写 3 到 4 条真实的条目。提示:最先坏的是什么?Knowledge Base 比预期好还是差?模型在哪里误读了取代关系,你怎么修复的?手工验证声明花了多长时间,你发现了什么?]]

局限性,说实话

  • [[N]] 个问题是个小样本,所以百分比是指示性的,不是结论性的。
  • 只覆盖了 [[5]] 个模式。
  • 声明是转述的且手工验证的,所以可能有错误。在依赖某个裁定前检查链接的来源。
  • [[Knowledge Base 在 beta 中;提及任何不如预期工作的东西。]]

下一步

  • 更多模式和更多时代的 EVM
  • 粘贴代码片段并检测它涉及哪些模式
  • 比较模式:同一模式在两个编译器版本之间的对比
  • [[其他你真正计划的东西]]

技术栈:Next.js、TypeScript、Vercel AI SDK、Sanity Studio 和 Sanity Context、[[模型名称和提供商]],部署在 [[Vercel]] 上,使用 Google Antigravity 作为编码环境。(仅在实际使用时保留)

Sanity 项目详情

项目 ID:[[YOUR SANITY PROJECT ID]](数据集:[[DATASET NAME]])
[[或者:公开数据集 URL,并确认数据集是公开的]]