OpenZeppelin Stellar 合约架构

这篇文章详细阐述了 OpenZeppelin Stellar 合约库的架构设计,该库基于 Soroban SDK 为 Stellar 网络构建智能合约。它采用模块化、基于 Trait 的设计,强调代码复用和可扩展性,并通过关联类型实现了互斥扩展。文章还深入介绍了双层抽象、模块化扩展系统、存储架构以及对 SEP-41 等标准的兼容性。

OpenZeppelin Stellar 合约架构

本文档概述了 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/                      # 安全审计报告

核心架构原则

1. 基于 Trait 的设计与关联类型

该库广泛使用 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; // 用于批量铸造优化
扩展 Trait 约束

扩展通过关联类型约束限制到特定的合约类型:

// 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 实现覆盖机制

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)
    }
}
这种方法的优点
  1. 编译时安全:不兼容的扩展无法组合
  2. 零运行时开销:所有调度都在编译时解析
  3. 直观的 API:开发人员无需指定泛型或复杂类型
  4. 自动行为覆盖:方法根据合约类型自动使用正确的实现
  5. 模块化设计:扩展可以独立开发和维护

这种模式提供了一种新颖的解决方案,解决了在基于 trait 的扩展系统中提供类型安全和开发人员人体工程学方面的挑战,避免了运行时检查或复杂泛型约束的需求。

2. 双层架构

该库提供两个抽象级别:

高级函数
  • 包含所有必要的检查、验证和授权
  • 自动处理状态更改逻辑和事件发送
  • 提供安全的默认设置和全面的错误处理
  • 适用于标准用例和快速开发
低级函数
  • 为自定义工作流提供粒度控制
  • 需要手动处理验证和授权
  • 能够组合复杂的业务逻辑
  • 适用于需要定制的高级用例

3. 模块化扩展系统

该架构支持可以混合搭配的可选扩展。以下是 Fungible Token 的扩展列表:

  • Burnable:代币销毁功能
  • Capped:最大供应量限制
  • Allowlist:基于白名单的访问控制
  • Blocklist:基于黑名单的访问控制
  • Metadata:增强的代币信息(名称、符号、小数位数)
  • Vault:资产存取与份额代币化

存储架构

存储键设计

该库采用结构化的存储键方法:

##[contracttype]
pub enum StorageKey {
    TotalSupply,
    Balance(Address),
    Allowance(AllowanceKey),
}

TTL 管理:

该库处理存储条目的扩展以防止过期,除了 instance 存储条目。 扩展 instance 存储条目是合约开发者的责任。

合约实现模式

1. 基础实现模式

##[contract]
pub struct MyToken;

##[contractimpl(contracttrait)]
impl FungibleToken for MyToken {
    ContractType = Base;
    // 此处自定义覆盖(可选)
}

2. 扩展组合模式

##[contractimpl(contracttrait)]
impl FungibleBurnable for MyToken {
    // 销毁功能
}

##[contractimpl(contracttrait)]
impl Pausable for MyToken {
    // 可暂停功能
}

3. 作为辅助工具的宏

该库提供宏来提高代码的清晰度,通过注释函数而不是将业务逻辑作为常规代码放在函数内部来改善 DevX(例如 #[only_owner]#[when_not_paused]

引入新宏的原则
  • 样板代码的减少必须能证明引入新领域特定语言(DSL)所增加的复杂性是合理的。
  • 宏应该直观:开发人员应该能够以最少的文档参考来采用它们;几个清晰的例子就足够了。
  • 只要可能,宏抽象的逻辑应该保持透明和可调试,例如通过使用 cargo expand 来检查生成的代码。更详细地说明这一点:新引入的宏最好不要直接与其他繁重的过程宏一起工作,因为扩展后的代码将与其他宏的扩展代码交织在一起,这将使检查和调试变得更加困难。

集成架构

SEP-41 合规性

该库确保与用于可替代代币的 SEP-41 (Stellar Enhancement Proposal 41) 完全兼容:

  • 标准接口实现
  • 必需的函数签名
  • 事件发送标准
  • 错误处理约定

跨生态系统兼容性

旨在模仿熟悉的标准:

  • ERC-20 相似性:为 Ethereum 开发者提供熟悉的接口
  • Stellar 资产合约兼容性:与现有 Stellar 基础设施无缝集成

NFT 标准兼容性

不可替代代币实现旨在与现有 NFT 标准兼容,同时利用 Stellar 的独特功能:

  • ERC-721 相似性:为处理 NFT 的 Ethereum 开发者提供熟悉的接口和模式

    • 核心所有权和转移功能
    • 审批机制(单一代币和操作员审批)
    • 元数据处理
    • 标准事件(Transfer、Approval、ApprovalForAll)
  • SEP 扩展:结合 Stellar 独有的 NFT 功能增强

    • 针对 Stellar 的执行环境进行了优化
    • 与更广泛的 Stellar 生态系统兼容
    • 专为跨链互操作性设计

性能考量

  • Stellar 中的读取操作是免费的:这意味着在设计合约时,主要目标是最小化写入操作。我们可以慷慨地进行读取操作。
  • Stellar 中的计算通常是廉价的:拥有清晰、可维护、可读且为开发者体验优化的代码,比榨取合约的每一丝性能更优先。在可能的情况下进行优化仍然是我们的目标,但不能以牺牲开发者体验为代价。在 enumerableconsecutive 扩展设计中可以看到一个很好的平衡。这些扩展已经考虑了成本,拥有最小化 gas 使用的最佳代码,同时提供了易于理解和调试的清晰且可维护的代码。

测试架构

全面的测试覆盖

  • 单元测试:单个函数测试
  • 集成测试:跨模块交互测试
  • 基于属性的测试:不变性验证
  • 模糊测试:边缘情况发现

部署架构

WASM 编译

  • 目标wasm32v1-none
  • 优化:发布版本进行大小优化
  • 无标准库环境:最小的运行时占用

代码约定

  • 我们严格遵循 cargo fmtcargo clippy 规则
  • 我们倾向于使用声明性代码而不是命令性代码
  • 我们的目标是实现最地道的 Rust 代码

AI 使用指南

  • 遵循代码约定和文件夹结构
  • 在可能的情况下使用现有类型/函数
  • 未经证明不要引入新的依赖项
  • 在适用情况下,始终优先使用声明式代码而不是命令式代码
  • 原文链接: github.com/OpenZeppelin/...
  • 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

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