mcpField
D
develop-secure-contracts

develop-secure-contracts

github.com/openzeppelin开发219550 次安装2 天前更新
代码仓库

关于 develop-secure-contracts

这是一个面向智能合约开发者的 OpenZeppelin 集成技能,涵盖多种合约生态。它要求先检查现有项目和依赖,再优先采用库组件,并以最小改动集成所需模式。

主要功能

  1. 01检查项目合约与已安装依赖
  2. 02优先使用 OpenZeppelin 库组件
  3. 03从源码和文档发现集成模式
  4. 04通过 CLI 生成并比较参考代码
  5. 05将必要改动整合到现有合约

适用场景

  1. 01为现有合约添加访问控制或暂停功能
  2. 02查找不同生态中的库组件与用法
  3. 03根据生成代码确定功能集成改动

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/openzeppelin/openzeppelin-skills --skill develop-secure-contracts

安装后在 Claude Code 中输入/develop-secure-contracts确认已加载。

详细介绍

使用 OpenZeppelin 开发安全智能合约

核心工作流

回答前先理解请求

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

重要:始终先阅读项目

在生成代码或建议更改之前:

  1. 搜索用户的项目,查找现有合约(使用 Glob 搜索 **/*.sol、**/*.cairo、**/*.rs、**/*.move 等)
  2. 阅读相关合约文件,了解已有内容
  3. 默认进行集成,而非替换——用户说“添加可暂停功能”或“使其可升级”时,指的是修改其现有代码,而不是生成新内容。只有在用户明确要求时(“从头开始”“替换这个”),才进行替换。

如果无法读取文件,请明确指出失败情况——报告尝试读取的路径和原因。询问路径是否正确。绝不要在不说明的情况下,像文件不存在一样直接改用通用回复。

基本规则:优先使用库组件,而非自定义代码

在编写任何逻辑之前,先在 OpenZeppelin 库中查找现有组件:

  1. 是否有完全匹配的组件? 直接导入并使用——继承它、实现其 trait,或与其组合。完成。
  2. 是否有接近匹配的组件? 导入并扩展它——只重写库标记为可重写的函数(virtual、钩子、可配置参数)。
  3. 是否没有匹配的组件? 只有此时才编写自定义逻辑。先浏览库的目录结构并确认。

绝不要将库的源代码复制或嵌入用户的合约。 始终从依赖项导入,以便项目获得安全更新。绝不要手动编写库已提供的内容:

  • 如果存在 Pausable 或 ERC20Pausable,绝不要编写自定义的 paused 修饰符
  • 如果存在 Ownable,绝不要编写 require(msg.sender == owner)
  • 如果库的基类合约已处理 ERC165 逻辑,绝不要自行实现
方法

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

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

有关完整的分步流程,请参阅下文的模式发现与集成。

CLI 生成器作为参考

使用 npx @openzeppelin/contracts-cli 生成参考实现,以发现模式: 先将基础版本生成到一个文件中,再将启用某项功能的版本生成到另一个文件中,比较二者的差异,然后将更改应用到用户的代码中。CLI 输出是正确集成的权威参考——以此为准,确定某项功能需要哪些导入、继承、存储和重写。

有关生成、比较和应用工作流的详细信息,请参阅 CLI 生成器。

如果所需功能没有对应的 CLI 命令,请使用模式发现与集成中的通用模式发现方法。没有 CLI 命令并不意味着库不支持该功能——这只表示没有对应的生成器。

模式发现与集成

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

**前提条件:**始终遵循上述优先使用库的决策树 (优先使用库组件,而非自定义代码;绝不复制或嵌入源代码)。

步骤 1:识别依赖项并搜索库
  1. 在项目中搜索合约文件:使用 Glob 搜索 **/*.sol、**/*.cairo、**/*.rs、 **/*.move,或下方查找表中对应的扩展名。
  2. 阅读现有合约中的导入/使用语句,识别已在使用的 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 相同
    • Sui Move:根据 Move.toml 解析——构建后,MVR 源代码会缓存在 ~/.move/ 下, 并按依赖项镜像到 build/<project_package>/sources/dependencies/<move_package_name>/
  4. 浏览依赖项的目录列表,了解有哪些可用组件。针对已安装的源代码使用 Glob 模式(例如 node_modules/@openzeppelin/contracts/**/*.sol)。 不要假设自己了解库的内容——务必通过列出目录进行确认。
  5. 如果本地未安装该依赖项,请克隆或浏览其规范仓库 (见下方查找表)。
步骤 2:阅读依赖项的源代码和文档
  1. 阅读与用户请求相关的组件源文件。
  2. 查找源代码中的文档:Solidity 中的 NatSpec 注释(///、/** */)、Rust 和 Cairo 中的文档注释(///),以及组件目录中的 README 文件。
  3. 根据关键原则中的决策树确定集成策略:
    • 如果组件可直接满足需求 → 导入并直接使用。
    • 如果需要自定义 → 找出库提供的扩展点(虚函数、钩子函数、可配置的构造函数参数)。导入并扩展。
    • 仅当没有组件能满足需求时 → 编写自定义逻辑。
  4. 确认公共 API:公开的函数/方法、发出的事件、定义的错误。
  5. 确认集成要求——这是关键步骤:
    • 集成者必须实现的函数(抽象函数、trait 方法、钩子)
    • 集成者的函数必须应用的修饰符、装饰器或保护机制
    • 必须传入的构造函数或初始化器参数
    • 必须声明的存储变量或状态
    • 必需的继承或 trait 实现(始终通过导入实现,绝不复制)
  6. 在同一仓库中搜索演示正确用法的示例合约或测试。查看 test/、tests/、examples/ 或 mocks/ 目录。
步骤 3:提取最简集成模式

根据步骤 2,整理出所需的最少改动:

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

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

不要加入依赖项未要求的内容。这是“未实现该功能的合约”和“已实现该功能的合约”之间的最小差异。

第 4 步:将模式应用到用户的合约中
  1. 阅读用户现有的合约文件。
  2. 使用 Edit 工具应用第 3 步中的更改。不要替换整个文件 — 而应将更改整合到现有代码中。
  3. 检查冲突:重复的访问控制系统、冲突的函数重写、不兼容的继承。完成前解决这些问题。
  4. 不要要求用户自行修改 — 直接应用更改。
仓库与文档查找表
生态系统仓库文档文件扩展名依赖位置
Solidityopenzeppelin-contractsdocs.openzeppelin.com/contracts.solnode_modules/@openzeppelin/contracts/ 或 lib/openzeppelin-contracts/
Cairocairo-contractsdocs.openzeppelin.com/contracts-cairo.cairoScarb 缓存(从 Scarb.toml 解析)
Stylusrust-contracts-stylusdocs.openzeppelin.com/contracts-stylus.rsCargo 缓存(~/.cargo/registry/src/)
Stellarstellar-contracts (架构)docs.openzeppelin.com/stellar-contracts.rsCargo 缓存(~/.cargo/registry/src/)
Sui Movecontracts-sui (llms.txt · 架构)docs.openzeppelin.com/contracts-sui.moveMove Registry 缓存(~/.move/,从 Move.toml 解析)
目录结构约定

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

类别SolidityCairoStylusStellar
代币contracts/token/{ERC20,ERC721,ERC1155}/packages/token/contracts/src/token/packages/tokens/
访问控制contracts/access/packages/access/contracts/src/access/packages/access/
治理contracts/governance/packages/governance/—packages/governance/
代理 / 升级contracts/proxy/packages/upgrades/contracts/src/proxy/packages/contract-utils/
工具 / 安全contracts/utils/packages/utils/, packages/security/contracts/src/utils/packages/contract-utils/
账户contracts/account/packages/account/—packages/accounts/

搜索组件时,请先浏览这些路径。

Sui Move 不在这个固定表格中,也没有 @openzeppelin/contracts-cli 生成器,因此始终使用上述模式发现方法 — 将某个包的 examples/ 作为规范的集成示例,并通过 MVR 导入,而不是复制源代码。其他所有内容(包集合、组合与风格约定、确切的 API 和工具链)都应从库自身的元数据中发现,从 llms.txt 开始;setup-sui-contracts 技能涵盖完整的设置、依赖、--build-env 构建和质量门禁流程。

特定版本的已知注意事项

不要凭以往经验假定可重写的扩展点——务必阅读已安装的源代码进行确认。旧版本中为 virtual 的函数在当前版本中可能不再如此,因此无法重写。源代码中的 NatSpec 会指出正确的重写点(例如,NOTE: This function is not virtual, {X} should be overridden instead)。

一个已知示例:Solidity ERC-20 转账钩子在 v4 和 v5 之间发生了变化。建议重写前,请阅读已安装的 ERC20.sol,确认哪个函数是 virtual。

CLI 生成器

@openzeppelin/contracts-cli 包可通过命令行生成 OpenZeppelin 合约实现参考。只要存在对应的合约类型命令,就在生成、比较、应用工作流中将其用作参考来源。

查找命令和选项

运行 npx @openzeppelin/contracts-cli --help 列出可用命令。每个命令对应一种合约类型(例如,solidity-erc20、cairo-erc721、stellar-fungible)。运行 npx @openzeppelin/contracts-cli <command> --help 查看可用选项。不要依赖以往经验推断有哪些选项;每次对话开始时都要检查 --help,因为 CLI 可能已更新。

生成、比较、应用快捷流程

当存在对应合约类型的 CLI 命令时,将生成的输出传入临时文件并进行差异比较,避免生成的合约代码进入对话上下文:

  1. 生成基线版本 — 仅使用必需选项运行,并禁用所有功能,将输出传入文件:
    npx @openzeppelin/contracts-cli solidity-erc20 --name MyToken --symbol MTK > /tmp/oz-baseline.sol
    
  2. 生成包含功能的版本 — 再次运行并启用该功能,将输出传入第二个文件:
    npx @openzeppelin/contracts-cli solidity-erc20 --name MyToken --symbol MTK --pausable > /tmp/oz-variant.sol
    
  3. 比较 — 对这两个文件进行差异比较,准确识别发生变化的内容(导入、继承、状态、构造函数、函数、修饰符):
    diff /tmp/oz-baseline.sol /tmp/oz-variant.sol
    
  4. 应用 — 编辑用户现有的合约,加入发现的更改

对于相互作用的功能(例如,访问控制 + 可升级性),还要生成一个组合版本。

不存在 CLI 命令或未覆盖某项功能时

不存在 CLI 命令并不意味着该库不支持该功能。这只表示没有对应合约类型的生成器。此时务必回退到 模式发现与集成 中的通用模式发现方法。

同样,当存在 CLI 命令但未提供某项特定功能的选项时,不要就此止步。针对该功能回退到模式发现:阅读已安装的库源代码,查找相关组件,提取集成要求,并将其应用到用户的合约中。

评论

支持 Markdown