Develop Secure Smart Contracts with OpenZeppelin
Core Workflow
Understand the Request Before Responding
For conceptual questions ("How does Ownable work?"), explain without generating code. For implementation requests, proceed with the workflow below.
CRITICAL: Always Read the Project First
Before generating code or suggesting changes:
- 1. Search the user's project for existing contracts (
Glob for **/*.sol, **/*.cairo, **/*.rs, etc.) - Read the relevant contract files to understand what already exists
- Default to integration, not replacement — when users say "add pausability" or "make it upgradeable", they mean modify their existing code, not generate something new. Only replace if explicitly requested ("start fresh", "replace this").
If a file cannot be read, surface the failure explicitly — report the path attempted and the reason. Ask whether the path is correct. Never silently fall back to a generic response as if the file does not exist.
Fundamental Rule: Prefer Library Components Over Custom Code
Before writing ANY logic, search the OpenZeppelin library for an existing component:
- 1. Exact match exists? Import and use it directly — inherit, implement its trait, compose with it. Done.
- Close match exists? Import and extend it — override only functions the library marks as overridable (virtual, hooks, configurable parameters).
- No match exists? Only then write custom logic. Confirm by browsing the library's directory structure first.
NEVER copy or embed library source code into the user's contract. Always import from the dependency so the project receives security updates. Never hand-write what the library already provides:
- - Never write a custom
paused modifier when Pausable or ERC20Pausable exists - Never write
require(msg.sender == owner) when Ownable exists - Never implement ERC165 logic when the library's base contracts already handle it
Methodology
The primary workflow is pattern discovery from library source code:
- 1. Inspect what the user's project already imports
- Read the dependency source and docs in the project's installed packages
- Identify what functions, modifiers, hooks, and storage the dependency requires
- Apply those requirements to the user's contract
See Pattern Discovery and Integration below for the full step-by-step procedure.
MCP Generators as an Optional Shortcut
If MCP generator tools are available at runtime, use them to accelerate pattern discovery:
generate a baseline, generate with a feature enabled, compare the diff, and apply the changes to the user's code. This replaces the manual source-reading step but follows the same principle — discover patterns, then integrate them.
See MCP Generators (Optional) for details on checking availability and using the generate-compare-apply shortcut.
If no MCP tool exists for what's needed, use the generic pattern discovery methodology from Pattern Discovery and Integration. The absence of an MCP tool does not mean the library lacks support — it only means there is no generator.
Pattern Discovery and Integration
Procedural guide for discovering and applying OpenZeppelin contract integration patterns
by reading dependency source code. Works for any ecosystem and any library version.
Prerequisite: Always follow the library-first decision tree above
(prefer library components over custom code, never copy/embed source).
Step 1: Identify Dependencies and Search the Library
- 1. Search the project for contract files:
Glob for **/*.sol, **/*.cairo, **/*.rs,
or the relevant extension from the lookup table below.
- 2. Read import/use statements in existing contracts to identify which OpenZeppelin components
are already in use.
- 3. Locate the installed dependency in the project's dependency tree:
- Solidity:
node_modules/@openzeppelin/contracts/ (Hardhat/npm) or
lib/openzeppelin-contracts/ (Foundry/forge)
- Cairo: resolve from
Scarb.toml dependencies — source cached by Scarb
- Stylus: resolve from
Cargo.toml — source in
target/ or the cargo registry cache
(
~/.cargo/registry/src/)
- Stellar: resolve from
Cargo.toml — same cargo cache locations as Stylus
- 4. Browse the dependency's directory listing to discover available components. Use INLINECODE20
patterns against the installed source (e.g.,
node_modules/@openzeppelin/contracts/**/*.sol).
Do not assume knowledge of the library's contents — always verify by listing directories.
- 5. If the dependency is not installed locally, clone or browse the canonical repository
(see lookup table below).
Step 2: Read the Dependency Source and Documentation
- 1. Read the source file of the component relevant to the user's request.
- Look for documentation within the source: NatSpec comments (
///, /** */) in Solidity,
doc comments (
///) in Rust and Cairo, and README files in the component's directory.
- 3. Determine the integration strategy using the decision tree from the Critical Principle:
- If the component satisfies the need directly → import and use as-is.
- If customization is needed → identify extension points the library provides (virtual
functions, hook functions, configurable constructor parameters). Import and extend.
- Only if no component covers the need → write custom logic.
- 4. Identify the public API: functions/methods exposed, events emitted, errors defined.
- Identify integration requirements — this is the critical step:
- Functions the integrator MUST implement (abstract functions, trait methods, hooks)
- Modifiers, decorators, or guards that must be applied to the integrator's functions
- Constructor or initializer parameters that must be passed
- Storage variables or state that must be declared
- Inheritance or trait implementations required (always via import, never via copy)
- 6. Search for example contracts or tests in the same repository that demonstrate correct
usage. Look in
test/,
tests/,
examples/, or
mocks/ directories.
Step 3: Extract the Minimal Integration Pattern
From Step 2, construct the minimal set of changes needed:
- - Imports / use statements to add
- Inheritance / trait implementations to add (always via import from the dependency)
- Storage to declare
- Constructor / initializer changes (new parameters, initialization calls)
- New functions to add (required overrides, hooks, public API)
- Existing functions to modify (add modifiers, call hooks, emit events)
If the contract is upgradeable, any of the above may affect storage compatibility. Consult the relevant upgrade skill before applying.
Do not include anything beyond what the dependency requires. This is the minimal diff
between "contract without the feature" and "contract with the feature."
Step 4: Apply Patterns to the User's Contract
- 1. Read the user's existing contract file.
- Apply the changes from Step 3 using the
Edit tool. Do not replace the entire file —
integrate into existing code.
- 3. Check for conflicts: duplicate access control systems, conflicting function overrides,
incompatible inheritance. Resolve before finishing.
- 4. Do not ask the user to make changes themselves — apply directly.
Repository and Documentation Lookup Table
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/) |
Directory Structure Conventions
Where to find components within each repository:
| Category | Solidity | Cairo | Stylus | Stellar |
|---|
| Tokens | INLINECODE39 | INLINECODE40 | INLINECODE41 | INLINECODE42 |
| 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/ |
Browse these paths first when searching for a component.
Known Version-Specific Considerations
Do not assume override points from prior knowledge — always verify by reading the installed source. Functions that were virtual in an older version may no longer be in the current one, making them non-overridable. The source NatSpec will indicate the correct override point (e.g., NOTE: This function is not virtual, {X} should be overridden instead).
A known example: the Solidity ERC-20 transfer hook changed between v4 and v5. Read the installed ERC20.sol to confirm which function is virtual before recommending an override.
MCP Generators (Optional)
MCP generators are template/scaffolding tools that produce OpenZeppelin contract boilerplate. They are not required — they accelerate pattern discovery when available.
Checking Availability
Discover MCP tools dynamically at runtime. Look for tools with names matching patterns like solidity-erc20, cairo-erc721, stellar-fungible, etc. Server names follow patterns like OpenZeppelinSolidityContracts, OpenZeppelinCairoContracts, or OpenZeppelinContracts.
MCP tool schemas are self-describing. To learn what a generator supports, inspect its parameter list — each boolean parameter (e.g., pausable, mintable, upgradeable) corresponds to a feature toggle. Do not rely on prior knowledge of what parameters exist; read the schema each time, since tools are updated independently of this skill.
Generate-Compare-Apply Shortcut
When an MCP generator exists for the contract type:
- 1. Generate baseline — call with only required parameters, all features disabled
- Generate with feature — call again with one feature enabled
- Compare — diff baseline vs. variant to identify exactly what changed (imports, inheritance, state, constructor, functions, modifiers)
- Apply — edit the user's existing contract to add the discovered changes
For interacting features (e.g., access control + upgradeability), generate a combined variant as well.
When No MCP Tool Exists or a Feature Is Not Covered
The absence of an MCP tool does NOT mean the library lacks support. It only means there is no generator for that contract type. Always fall back to the generic pattern discovery methodology in Pattern Discovery and Integration.
Similarly, when an MCP tool exists but does not expose a parameter for a specific feature, do not stop there. Fall back to pattern discovery for that feature: read the installed library source to find the relevant component, extract the integration requirements, and apply them to the user's contract.
使用OpenZeppelin开发安全智能合约
核心工作流程
在响应前理解请求
对于概念性问题(Ownable是如何工作的?),进行解释而不生成代码。对于实现请求,按以下工作流程进行。
关键:始终先阅读项目
在生成代码或建议更改之前:
- 1. 搜索用户的项目中已有的合约(使用Glob搜索/.sol、/.cairo、/.rs等)
- 阅读相关合约文件以了解已存在的内容
- 默认进行集成,而非替换*——当用户说添加暂停功能或使其可升级时,他们是指修改现有代码,而非生成新代码。仅在明确要求时才进行替换(重新开始、替换这个)。
如果无法读取文件,需明确报告失败——说明尝试的路径和原因。询问路径是否正确。切勿在文件不存在时静默回退到通用响应。
基本原则:优先使用库组件而非自定义代码
在编写任何逻辑之前,先搜索OpenZeppelin库中已有的组件:
- 1. 存在完全匹配? 直接导入并使用——继承、实现其trait、组合使用。完成。
- 存在近似匹配? 导入并扩展——仅覆盖库标记为可覆盖的函数(虚函数、钩子、可配置参数)。
- 不存在匹配? 只有在此情况下才编写自定义逻辑。先浏览库的目录结构进行确认。
切勿复制或嵌入库源代码到用户的合约中。 始终从依赖中导入,以便项目获得安全更新。切勿手写库已提供的功能:
- - 当Pausable或ERC20Pausable存在时,切勿编写自定义的paused修饰器
- 当Ownable存在时,切勿编写require(msg.sender == owner)
- 当库的基础合约已处理ERC165逻辑时,切勿自行实现
方法论
主要工作流程是从库源代码中发现模式:
- 1. 检查用户项目已导入的内容
- 阅读项目中已安装包的依赖源代码和文档
- 识别依赖所需的函数、修饰器、钩子和存储
- 将这些要求应用到用户的合约中
详见下方的模式发现与集成完整步骤流程。
MCP生成器作为可选快捷方式
如果运行时MCP生成器工具可用,使用它们加速模式发现:生成基线、启用功能生成、比较差异、将更改应用到用户代码。这替代了手动阅读源代码的步骤,但遵循相同原则——发现模式,然后集成。
详见MCP生成器(可选)了解如何检查可用性并使用生成-比较-应用快捷方式。
如果没有所需的MCP工具,使用模式发现与集成中的通用模式发现方法论。缺少MCP工具并不意味着库不支持——仅表示没有生成器。
模式发现与集成
通过阅读依赖源代码发现和应用OpenZeppelin合约集成模式的过程指南。适用于任何生态系统和任何库版本。
前提条件: 始终遵循上述库优先决策树
(优先使用库组件而非自定义代码,切勿复制/嵌入源代码)。
步骤1:识别依赖并搜索库
- 1. 搜索项目中的合约文件:使用Glob搜索/.sol、/.cairo、/*.rs,
或下方查找表中对应的扩展名。
- 2. 阅读现有合约中的import/use语句,识别已使用的OpenZeppelin组件。
- 在项目的依赖树中定位已安装的依赖:
- 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. 阅读与用户请求相关的组件的源文件。
- 查找源代码中的文档:Solidity中的NatSpec注释(///、/ */)、
Rust和Cairo中的文档注释(///),以及组件目录中的README文件。
- 3. 使用关键原则中的决策树确定集成策略:
- 如果组件直接满足需求→按原样导入和使用。
- 如果需要自定义→识别库提供的扩展点(虚函数、钩子函数、可配置构造函数参数)。导入并扩展。
- 仅在没有组件覆盖需求时→编写自定义逻辑。
- 4. 识别公共API:暴露的函数/方法、发出的事件、定义的错误。
- 识别集成要求——这是关键步骤:
- 集成者必须实现的函数(抽象函数、trait方法、钩子)
- 必须应用于集成者函数的修饰器、装饰器或守卫
- 必须传递的构造函数或初始化器参数
- 必须声明的存储变量或状态
- 所需的继承或trait实现(始终通过导入,绝不通过复制)
- 6. 搜索同一仓库中的示例合约或测试,这些展示了正确的用法。查找test/、tests/、examples/或mocks/目录。
步骤3:提取最小集成模式
从步骤2中,构建所需的最小更改集:
- - 要添加的导入/use语句
- 要添加的继承/trait实现(始终通过从依赖导入)
- 要声明的存储
- 构造函数/初始化器更改(新参数、初始化调用)
- 要添加的新函数(必需的覆盖、钩子、公共API)
- 要修改的现有函数(添加修饰器、调用钩子、发出事件)
如果合约是可升级的,上述任何更改都可能影响存储兼容性。在应用前咨询相关升级技能。
不要包含超出依赖要求的任何内容。这是没有该功能的合约和具有该功能的合约之间的最小差异。
步骤4:将模式应用到用户合约
- 1. 阅读用户现有的合约文件。
- 使用Edit工具应用步骤3中的更改。不要替换整个文件——
集成到现有代码中。
- 3. 检查冲突:重复的访问控制系统、冲突的函数覆盖、不兼容的继承。在完成前解决。
- 不要要求用户自行更改——直接应用。
仓库和文档查找表
cairo-contracts |
docs.openzeppelin.com/contracts-cairo | .cairo | Scarb缓存(从Scarb.toml解析) |
| Stylus |
rust-contracts-stylus |
docs.openzeppelin.com/contracts-stylus | .rs | Cargo缓存(~/.cargo/registry/src/) |
| Stellar |
stellar-contracts(
架构) |
docs.openzeppelin.com/stellar-contracts | .rs | Cargo缓存(~/.cargo/registry/src/) |
目录结构约定
每个仓库中组件的查找位置:
| 类别 | Solidity | Cairo | Stylus | Stellar |
|---|
| 代币 | contracts/token/{ERC20,ERC721,ERC1155}/ | packages/token/ | contracts/src/token/ | packages/tokens/ |
| 访问控制 |
contracts/access/ | packages/access/ |