这篇文章详细阐述了 OpenZeppelin Stellar 合约库的架构设计,该库基于 Soroban SDK 为 Stellar 网络构建智能合约。它采用模块化、基于 Trait 的设计,强调代码复用和可扩展性,并通过关联类型实现了互斥扩展。文章还深入介绍了双层抽象、模块化扩展系统、存储架构以及对 SEP-41 等标准的兼容性。
本文档概述了 OpenZeppelin Stellar Contracts 库的架构设计和结构,该库是使用 Soroban SDK 构建的 Stellar 网络智能合约的综合集合。
OpenZeppelin Stellar Contracts 库遵循模块化、基于 trait 的架构,以提高代码重用性、可扩展性和可维护性。该架构旨在提供高级便捷功能和低级粒度控制,允许开发人员为其用例选择适当的抽象级别。
stellar-contracts/
├── packages/ # 核心库包
│ ├── access/ # 基于角色的访问控制和所有权模式
│ ├── contract-utils/ # 实用工具(可暂停、可升级、密码学)
│ ├── macros/ # 过程宏和派生宏
│ ├── test-utils/ # 测试实用工具和辅助函数
│ └── tokens/ # 代币实现(可替代、不可替代)
│ ├── src/
│ │ ├── fungible/ # 可替代代币实现
│ │ │ ├── extensions/ # 可选代币扩展
│ │ │ ├── utils/ # 实用函数和辅助函数
│ │ │ ├── mod.rs # 核心trait定义、常量、错误和事件
│ │ │ ├── storage.rs # 存储管理和状态操作
│ │ │ └── test.rs # 全面的测试套件
│ │ └── non_fungible/ # 不可替代代币实现
│ └── lib.rs
├── examples/ # 合约实现示例
└── audits/ # 安全审计报告
该库广泛使用 Rust trait 来定义标准接口和行为,并采用复杂的方法来启用方法覆盖,并通过关联类型强制执行互斥扩展:
该架构最复杂的一个方面是它如何防止不兼容的扩展一起使用。这是通过 关联类型 和 trait 约束 实现的:
// Core trait with associated type
trait NonFungibleToken {
type ContractType: ContractOverrides;
fn transfer(e: &Env, from: Address, to: Address, token_id: u32) {
Self::ContractType::transfer(e, from, to, token_id);
}
// ... other methods
}
// Contract type markers
pub struct Base; // 默认实现
pub struct Enumerable; // 用于枚举功能
pub struct Consecutive; // 用于批量铸造优化
扩展通过关联类型约束限制到特定的合约类型:
// Enumerable 只能与 Enumerable 合约类型一起使用
trait NonFungibleEnumerable: NonFungibleToken<ContractType = Enumerable> {
fn total_supply(e: &Env) -> u32;
fn get_owner_token_id(e: &Env, owner: Address, index: u32) -> u32;
// ...
}
// Consecutive 只能与 Consecutive 合约类型一起使用
trait NonFungibleConsecutive: NonFungibleToken<ContractType = Consecutive> {
// 批量铸造功能
}
这种设计使得实现冲突的扩展 不可能:
// ✅ 这有效 - 使用 Enumerable
impl NonFungibleToken for MyContract {
type ContractType = Enumerable;
// ... implementations
}
impl NonFungibleEnumerable for MyContract {
// ... enumerable methods
}
// ❌ 这无法编译 - Consecutive 需要不同的 ContractType
// impl NonFungibleConsecutive for MyContract { ... }
// ^^^ 错误:预期 `Consecutive`,找到 `Enumerable`
ContractOverrides trait 提供了因合约类型而异的实际实现:
trait ContractOverrides {
fn transfer(e: &Env, from: &Address, to: &Address, token_id: u32) {
// 默认实现(由 Base 使用)
Base::transfer(e, from, to, token_id);
}
// ... other overridable methods
}
// Base 使用默认实现
impl ContractOverrides for Base {}
// Enumerable 覆盖特定方法
impl ContractOverrides for Enumerable {
fn transfer(e: &Env, from: &Address, to: &Address, token_id: u32) {
// 自定义 Enumerable 转移逻辑
Enumerable::transfer(e, from, to, token_id);
}
}
// Consecutive 覆盖不同方法
impl ContractOverrides for Consecutive {
fn owner_of(e: &Env, token_id: u32) -> Address {
// 自定义 Consecutive 所有权查询
Consecutive::owner_of(e, token_id)
}
}
这种模式提供了一种新颖的解决方案,解决了在基于 trait 的扩展系统中提供类型安全和开发人员人体工程学方面的挑战,避免了运行时检查或复杂泛型约束的需求。
该库提供两个抽象级别:
该架构支持可以混合搭配的可选扩展。以下是 Fungible Token 的扩展列表:
该库采用结构化的存储键方法:
##[contracttype]
pub enum StorageKey {
TotalSupply,
Balance(Address),
Allowance(AllowanceKey),
}
该库处理存储条目的扩展以防止过期,除了 instance 存储条目。
扩展 instance 存储条目是合约开发者的责任。
##[contract]
pub struct MyToken;
##[contractimpl(contracttrait)]
impl FungibleToken for MyToken {
ContractType = Base;
// 此处自定义覆盖(可选)
}
##[contractimpl(contracttrait)]
impl FungibleBurnable for MyToken {
// 销毁功能
}
##[contractimpl(contracttrait)]
impl Pausable for MyToken {
// 可暂停功能
}
该库提供宏来提高代码的清晰度,通过注释函数而不是将业务逻辑作为常规代码放在函数内部来改善 DevX(例如 #[only_owner]、#[when_not_paused])
该库确保与用于可替代代币的 SEP-41 (Stellar Enhancement Proposal 41) 完全兼容:
旨在模仿熟悉的标准:
不可替代代币实现旨在与现有 NFT 标准兼容,同时利用 Stellar 的独特功能:
ERC-721 相似性:为处理 NFT 的 Ethereum 开发者提供熟悉的接口和模式
SEP 扩展:结合 Stellar 独有的 NFT 功能增强
enumerable 和 consecutive 扩展设计中可以看到一个很好的平衡。这些扩展已经考虑了成本,拥有最小化 gas 使用的最佳代码,同时提供了易于理解和调试的清晰且可维护的代码。wasm32v1-nonecargo fmt 和 cargo clippy 规则
- 原文链接: github.com/OpenZeppelin/...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!