使用 OpenZeppelin 开发安全的智能合约

本文详细介绍了如何使用OpenZeppelin合约库开发安全的智能合约,强调优先使用库组件而非自定义代码。它提供了从库源码中发现集成模式的详细步骤,涵盖了依赖识别、源码阅读、模式提取和应用。文章还提到了使用MCP生成器作为可选的快捷方式来加速集成。

核心工作流程

在回复前理解请求

对于概念性问题(例如“Ownable 是如何工作的?”),请解释而不生成代码。对于实现请求,请按照以下工作流程进行。

关键点: 始终先阅读项目

在生成代码或提出修改建议之前:

  1. 搜索用户的项目以查找现有合约(使用 Glob 模式匹配 **/*.sol, **/*.cairo, **/*.rs 等)
  2. 阅读相关的合约文件以了解已存在的内容
  3. 默认集成而非替换——当用户说“添加暂停功能”或“使其可升级”时,他们是指修改其现有代码,而不是生成新代码。仅在明确要求时才替换(例如“重新开始”、“替换此项”)。

如果文件无法读取,请明确指出失败——报告尝试的路径和原因。询问路径是否正确。切勿在静默中回退到通用响应,仿佛文件不存在一样。

基本规则:优先选择库组件而非自定义代码

在编写任何逻辑之前,请在 OpenZeppelin 库中搜索现有组件:

  1. 存在精确匹配项? 直接导入并使用——继承、实现其 trait、或与其组合。完成。
  2. 存在近似匹配项? 导入并扩展它——仅覆盖库标记为可覆盖的函数(virtual, Hook, 可配置参数)。
  3. 不存在匹配项? 仅在此情况下才编写自定义逻辑。首先通过浏览库的目录结构进行确认。

切勿将库源代码复制或嵌入到用户的合约中。 始终从依赖项导入,以便项目能够接收安全更新。切勿手动编写库已提供的内容:

  • 当存在 PausableERC20Pausable 时,切勿编写自定义的 paused 修改器
  • 当存在 Ownable 时,切勿编写 require(msg.sender == owner)
  • 当库的基础合约已处理 ERC165 逻辑时,切勿实现 ERC165 逻辑

方法论

主要的工作流程是从库源代码中发现模式

  1. 检查用户项目已导入的内容
  2. 阅读项目已安装包中的依赖项源代码和文档
  3. 确定该依赖项需要哪些函数、修改器、Hook和存储
  4. 将这些要求应用于用户的合约

完整的逐步程序请参见下文的 模式发现与集成

MCP 生成器作为可选捷径

如果在运行时有可用的 MCP 生成器工具,请使用它们来加速模式发现:生成一个基线,启用一个功能后再次生成,比较差异,然后将更改应用到用户的代码中。这取代了手动阅读源代码的步骤,但遵循相同的原则——发现模式,然后集成它们。

有关检查可用性和使用生成-比较-应用捷径的详细信息,请参见 MCP 生成器(可选)

如果没有适用于所需功能的 MCP 工具,请使用 模式发现与集成 中的通用模式发现方法。没有 MCP 工具并不意味着库缺乏支持——它仅表示没有生成器。

模式发现与集成

通过阅读依赖项源代码来发现和应用 OpenZeppelin 合约集成模式的程序指南。适用于任何生态系统和任何库版本。

先决条件:始终遵循上述库优先决策树(优先选择库组件而非自定义代码,切勿复制/嵌入源代码)。

步骤 1:识别依赖项并搜索库

  1. 搜索项目中的合约文件:使用 Glob 模式匹配 **/*.sol, **/*.cairo, **/*.rs,或从下面的查找表中获取相关扩展名。
  2. 阅读现有合约中的 import/use 语句,以识别哪些 OpenZeppelin 组件已被使用。
  3. 在项目的依赖树中定位已安装的依赖项:
    • Solidity: node_modules/@openzeppelin/contracts/ (Hardhat/npm) 或 lib/openzeppelin-contracts/ (Foundry/forge)
    • Cairo: 从 Scarb.toml 依赖项中解析——源代码由 Scarb 缓存
    • Stylus: 从 Cargo.toml 中解析——源代码位于 target/ 或 cargo 注册表缓存中 (~/.cargo/registry/src/)
    • Stellar: 从 Cargo.toml 中解析——与 Stylus 相同的 cargo 缓存位置
  4. 浏览依赖项的目录列表以发现可用组件。针对已安装的源代码使用 Glob 模式(例如,node_modules/@openzeppelin/contracts/**/*.sol)。不要假设了解库的内容——始终通过列出目录进行验证。
  5. 如果依赖项未在本地安装,请克隆或浏览规范存储库(参见下表)。

步骤 2:阅读依赖项源代码和文档

  1. 阅读与用户请求相关的组件的源代码文件。
  2. 在源代码中查找文档:Solidity 中的 NatSpec 注释(///, /** */),Rust 和 Cairo 中的文档注释(///),以及组件目录中的 README 文件。
  3. 使用关键原则中的决策树确定集成策略:
    • 如果组件直接满足需求 → 按原样导入和使用。
    • 如果需要自定义 → 识别库提供的扩展点(virtual 函数、Hook函数、可配置的构造函数参数)。导入并扩展。
    • 仅在没有组件涵盖需求时 → 编写自定义逻辑。
  4. 识别公共 API:暴露的函数/方法、发出的事件、定义的错误。
  5. 识别集成要求——这是关键步骤:
    • 集成方必须实现的函数(抽象函数、trait 方法、Hook)
    • 必须应用于集成方函数的修改器、装饰器或守卫
    • 必须传递的构造函数或初始化器参数
    • 必须声明的存储变量或状态
    • 所需的继承或 trait 实现(始终通过导入,切勿通过复制)
  6. 在同一存储库中搜索演示正确用法的示例合约或测试。查看 test/, tests/, examples/mocks/ 目录。

步骤 3:提取最小集成模式

根据步骤 2,构建所需的最小更改集:

  • 需要添加的导入 / use 语句
  • 需要添加的继承 / trait 实现(始终通过从依赖项导入)
  • 需要声明的存储
  • 构造函数 / 初始化器更改(新参数、初始化调用)
  • 需要添加的新函数(必需的覆盖、Hook、公共 API)
  • 需要修改的现有函数(添加修改器、调用Hook、发出事件)

如果合约是可升级的,上述任何更改都可能影响存储兼容性。在应用之前,请查阅相关的升级技能。

不要包含超出依赖项要求的内容。这是“不带此功能的合约”和“带此功能的合约”之间的最小差异。

步骤 4:将模式应用于用户的合约

  1. 阅读用户现有的合约文件。
  2. 使用 Edit 工具应用步骤 3 中的更改。不要替换整个文件——集成到现有代码中。
  3. 检查冲突:重复的访问控制系统、冲突的函数覆盖、不兼容的继承。在完成前解决这些问题。
  4. 不要要求用户自行更改——直接应用。

存储库和文档查找表

Ecosystem Repository Documentation File Extension Dependency Location
Solidity openzeppelin-contracts docs.openzeppelin.com/contracts .sol node_modules/@openzeppelin/contracts/ or lib/openzeppelin-contracts/
Cairo cairo-contracts docs.openzeppelin.com/contracts-cairo .cairo Scarb cache (resolve from Scarb.toml)
Stylus rust-contracts-stylus docs.openzeppelin.com/contracts-stylus .rs Cargo cache (~/.cargo/registry/src/)
Stellar stellar-contracts (Architecture) docs.openzeppelin.com/stellar-contracts .rs Cargo cache (~/.cargo/registry/src/)

目录结构约定

在每个存储库中查找组件的位置:

Category Solidity Cairo Stylus Stellar
Tokens contracts/token/{ERC20,ERC721,ERC1155}/ packages/token/ contracts/src/token/ packages/tokens/
Access control contracts/access/ packages/access/ contracts/src/access/ packages/access/
Governance contracts/governance/ packages/governance/ packages/governance/
Proxies / Upgrades contracts/proxy/ packages/upgrades/ contracts/src/proxy/ packages/contract-utils/
Utilities / Security contracts/utils/ packages/utils/, packages/security/ contracts/src/utils/ packages/contract-utils/
Accounts contracts/account/ packages/account/ packages/accounts/

已知版本特定考量

不要根据先前的知识假设覆盖点——始终通过阅读已安装的源代码进行验证。在旧版本中是 virtual 的函数在当前版本中可能不再是 virtual,从而使其不可覆盖。源代码的 NatSpec 将指示正确的覆盖点(例如,注意:此函数不是 virtual,应覆盖 {X} 代替之)。

一个已知示例:Solidity ERC-20 transfer Hook在 v4 和 v5 之间发生了变化。在建议覆盖之前,请阅读已安装的 ERC20.sol 以确认哪个函数是 virtual

MCP 生成器(可选)

MCP 生成器是模板/脚手架工具,用于生成 OpenZeppelin 合约样板代码。它们不是必需的——它们在可用时加速模式发现。

检查可用性

在运行时动态发现 MCP 工具。查找名称匹配 solidity-erc20, cairo-erc721, stellar-fungible 等模式的工具。服务器名称遵循 OpenZeppelinSolidityContracts, OpenZeppelinCairoContractsOpenZeppelinContracts 等模式。

MCP 工具的 schema 是自描述的。要了解生成器支持什么,请检查其参数列表——每个布尔参数(例如,pausable, mintable, upgradeable)都对应一个功能切换。不要依赖关于存在哪些参数的先验知识;每次都阅读 schema,因为工具的更新独立于此技能。

生成-比较-应用捷径

当存在适用于合约类型的 MCP 生成器时:

  1. 生成基线——仅使用必需参数调用,所有功能禁用
  2. 生成带功能版本——再次调用并启用一个功能
  3. 比较——对比基线和变体以准确识别更改内容(导入、继承、状态、构造函数、函数、修改器)
  4. 应用——编辑用户现有合约以添加发现的更改

对于交互功能(例如,访问控制 + 可升级性),也生成一个组合变体。

当没有 MCP 工具存在或功能未涵盖时

没有 MCP 工具并不意味着库缺乏支持。它仅表示该合约类型没有生成器。始终回退到 模式发现与集成 中的通用模式发现方法。

同样,当 MCP 工具存在但未暴露特定功能的参数时,不要就此止步。回退到该功能的模式发现:阅读已安装的库源代码以找到相关组件,提取集成要求,并将其应用于用户的合约。

  • 原文链接: github.com/OpenZeppelin/...
  • 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
OpenZeppelin
OpenZeppelin
江湖只有他的大名,没有他的介绍。