news 2026/9/21 18:24:08

Diem 框架中的 RegisteredCurrencies 模块:货币注册机制的源码级解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Diem 框架中的 RegisteredCurrencies 模块:货币注册机制的源码级解析

Diem 框架中的 RegisteredCurrencies 模块:货币注册机制的源码级解析

【免费下载链接】diemDiem’s mission is to build a trusted and innovative financial network that empowers people and businesses around the world.项目地址: https://gitcode.com/gh_mirrors/di/diem

导读

RegisteredCurrencies是 Diem 区块链(Move 语言实现的链上框架)中负责**注册货币代码(currency code)**的核心模块。它以DiemConfig链上配置的形式维护一张全局的货币代码清单,并作为Diem::register_currency等货币发行流程的底层登记入口。读完本文,你将掌握:该模块的数据结构设计与权限模型、两个公开函数(initialize/add_currency_code)的完整实现与中止条件、它与DiemConfigDiemRolesDiemTimestamp的协作关系,以及 Move 形式化规范(spec)对它的约束验证方式。

模块定位:货币代码的全局登记簿

在 Diem 的链上框架中,一个货币要想流通,首先必须被“注册”。RegisteredCurrencies模块的职责非常聚焦——正如其在 RegisteredCurrencies.move 源码注释中所写:

Module for registering currencies in Diem. Basically, this means adding a string (vector ) for the currency name to vector of names in DiemConfig.

即:把每个货币的名称(以vector<u8>字符串表示)追加到DiemConfig中维护的一个名称向量里。它不负责货币的铸造、销毁或汇率,只负责维护“哪些货币代码已被登记”这一事实。

该模块的依赖关系非常清晰(见 模块源码):

  • DiemFramework::DiemConfig:把货币代码清单作为链上配置发布与读取;
  • DiemFramework::DiemTimestamp:校验初始化是否发生在 genesis 阶段;
  • DiemFramework::Roles:校验调用者是否具有 DiemRoot 角色;
  • Std::Errors:构造标准错误码;
  • Std::Vector:对货币代码向量做包含检查与追加操作。

这一依赖结构也决定了它的两个基本事实:该模块只能由 Diem 根账户在 genesis 时初始化后续任何新增货币代码的操作都必须由 Diem 根账户签名发起

数据结构:RegisteredCurrencies结构体

模块中仅定义一个结构体(见 模块源码):

struct RegisteredCurrencies has copy, drop, store { currency_codes: vector<vector<u8>>, }
  • currency_codes: vector<vector<u8>>:一个二维字节向量。外层向量按添加顺序保存所有已注册货币;内层vector<u8>是货币代码的 UTF-8 字符串表示,例如b"XUS"b"XDX"
  • 结构体同时具备copydropstore三种能力(ability),这是DiemConfig泛型配置类型的前提:DiemConfig<Config>要求Config: copy + drop + store(见 DiemConfig.move),因此该结构体可以被整体复制、丢弃并存储为链上资源。

从语义上讲,这个结构体本身并不直接持有key能力,它总是作为DiemConfig<RegisteredCurrencies>的 payload 挂在 Diem 根账户地址下,这一点由模块级不变量(见下文)保证。

错误常量:重复注册的防护

const ECURRENCY_CODE_ALREADY_TAKEN: u64 = 0;

该常量表示“尝试添加一个已经被占用的货币代码”。配合Errors::invalid_argument(ECURRENCY_CODE_ALREADY_TAKEN)使用,最终产生的 Move 错误码为INVALID_ARGUMENT (0x1)类别下编号 0 的错误。注意invalid_argument返回的原始 abort code 是1Errors标准库中INVALID_ARGUMENT = 1的类别前缀),而ECURRENCY_CODE_ALREADY_TAKEN = 0是模块内序号;二者经Errors::invalid_argument组合后,实际 abort 码由测试用例可以印证(见下文测试部分)。

初始化:initialize

实现解析

public fun initialize(dr_account: &signer) { DiemTimestamp::assert_genesis(); Roles::assert_diem_root(dr_account); DiemConfig::publish_new_config( dr_account, RegisteredCurrencies { currency_codes: Vector::empty() } ); }

初始化流程由三道防线构成:

  1. 时间防线DiemTimestamp::assert_genesis()—— 断言当前处于 genesis 阶段(时间戳尚未进入运行状态),确保该模块只能在链创世时被初始化一次;
  2. 权限防线Roles::assert_diem_root(dr_account)—— 断言签名者具备 DiemRoot 角色;
  3. 发布动作DiemConfig::publish_new_config<RegisteredCurrencies>(dr_account, ...)—— 以 Diem 根账户为发布者,将初始状态{ currency_codes: Vector::empty() }(空清单)发布为一条DiemConfig配置。

也就是说,初始状态下系统不预置任何货币代码,XUSXDX等正式货币是在 genesis 之后通过货币注册流程逐个登记的。

调用方:Diem::initialize

RegisteredCurrencies::initialize并不是一个游离的入口,它被Diem模块的初始化流程显式调用。在 Diem.move 中:

public fun initialize(dr_account: &signer) { DiemTimestamp::assert_genesis(); // Operational constraint CoreAddresses::assert_diem_root(dr_account); RegisteredCurrencies::initialize(dr_account); }

可见Diem::initialize先校验调用地址确为@DiemRoot,随即委托给RegisteredCurrencies::initialize完成货币清单的初始化。因此可以推断:注册货币的全局清单是 Diem 框架启动(genesis)过程中的一个固定环节,与CurrencyInfo、铸币/销毁能力(MintCapability/BurnCapability)体系的初始化同步完成。

规范约束

initialize的 Move 规范(spec)通过两个 schema 定义了中止与后置条件(见 RegisteredCurrencies.move):

  • InitializeAbortsIf:依次引入DiemTimestamp::AbortsIfNotGenesisRoles::AbortsIfNotDiemRootDiemConfig::PublishNewConfigAbortsIf<RegisteredCurrencies>,即非 genesis 阶段、非 Diem 根、或配置已发布时都必须中止;
  • InitializeEnsures:引入DiemConfig::PublishNewConfigEnsures,并断言len(get_currency_codes()) == 0,即初始化后清单长度必须为 0。

新增货币代码:add_currency_code

实现解析

public fun add_currency_code( dr_account: &signer, currency_code: vector<u8>, ) { let config = DiemConfig::get<RegisteredCurrencies>(); assert( !Vector::contains(&config.currency_codes, &currency_code), Errors::invalid_argument(ECURRENCY_CODE_ALREADY_TAKEN) ); Vector::push_back(&mut config.currency_codes, currency_code); DiemConfig::set(dr_account, config); }

执行路径分四步:

  1. DiemConfig::get<RegisteredCurrencies>()读出当前清单(按值拷贝,得益于结构体的copy能力);
  2. Vector::contains检查新代码是否已存在,若已存在则抛Errors::invalid_argument(ECURRENCY_CODE_ALREADY_TAKEN)中止;
  3. Vector::push_back将新代码追加到向量末尾,保持注册顺序;
  4. DiemConfig::set(dr_account, config)以 Diem 根签名写回配置。

这里存在一个值得注意的细节:add_currency_code内部并未显式调用Roles::assert_diem_root,其权限约束来自DiemConfig::set的实现——DiemConfigRegisteredCurrencies声明为friend(见 DiemConfig.move),并且set只允许具有ModifyConfigCapability<RegisteredCurrencies>的账户修改配置,而该能力仅授予 Diem 根账户(见 DiemConfig.move)。因此,非 Diem 根调用会在DiemConfig::set处被拒绝,这一点也由AddCurrencyCodeAbortsIf规范中的DiemConfig::SetAbortsIf覆盖(见 RegisteredCurrencies.move)。

实际调用方:Diem::register_currency

add_currency_code的真正业务入口是货币注册函数Diem::register_currency<CoinType>(见 Diem.move)。该函数完成:

  • 校验 Diem 根角色与@CurrencyInfo地址约束;
  • 校验0 < scaling_factor <= MAX_SCALING_FACTOR
  • @CurrencyInfo下发布CurrencyInfo<CoinType>(含汇率、缩放因子、小数位、事件句柄等);
  • 最后调用RegisteredCurrencies::add_currency_code(dr_account, currency_code)登记代码;
  • 返回MintCapability<CoinType>BurnCapability<CoinType>

由此可以推断出完整的“注册一个货币”的语义链条:类型注册(CurrencyInfo资源) + 代码登记(RegisteredCurrencies清单)是原子绑定在一起的,代码被占用会导致整个注册中止(对应规范RegisterCurrencyAbortsIf中的RegisteredCurrencies::AddCurrencyCodeAbortsIf,见 Diem.move)。

另外,add_currency_code的规范还保证了追加语义:Vector::eq_push_back(get_currency_codes(), old(get_currency_codes()), currency_code),即新清单等于旧清单末尾追加新代码,不改变已有元素的顺序(见 RegisteredCurrencies.move)。

模块规范:全局不变量与辅助函数

初始化不变量

模块级规范定义了最重要的全局不变量(见 RegisteredCurrencies.move):

invariant [suspendable] DiemTimestamp::is_operating() ==> DiemConfig::spec_is_published<RegisteredCurrencies>();

含义:只要链进入运行状态(is_operating),RegisteredCurrencies配置必须已被发布。它把“genesis 必须初始化该配置”从约定上升为可被 Move Prover 验证的形式化约束,防止任何 genesis 流程遗漏这一步。

辅助函数

规范中定义了仅供规范使用的辅助函数get_currency_codes()(见 RegisteredCurrencies.move):

fun get_currency_codes(): vector<vector<u8>> { DiemConfig::get<RegisteredCurrencies>().currency_codes }

它封装了“读取当前货币代码清单”的规范访问路径,被InitializeEnsuresAddCurrencyCodeAbortsIfAddCurrencyCodeEnsures等多个 schema 复用。

单元测试验证

仓库在 RegisteredCurrencyTests.move 中提供了针对该模块的三组单元测试,直接验证了本文所述的权限与去重语义:

#[test(dr = @DiemRoot, tc = @TreasuryCompliance, alice = @0x2)] #[expected_failure(abort_code = 1)] fun cannot_call_initialize_as_non_diem_root(dr: signer, tc: signer, alice: signer) { Genesis::setup(&dr, &tc); RegisteredCurrencies::initialize(&alice); } #[test(dr = @DiemRoot, tc = @TreasuryCompliance)] #[expected_failure(abort_code = 1)] fun cannot_call_initialize_outside_genesis(dr: signer, tc: signer) { Genesis::setup(&dr, &tc); RegisteredCurrencies::initialize(&dr); } #[test(dr = @DiemRoot, tc = @TreasuryCompliance)] #[expected_failure(abort_code = 7)] fun cannot_add_currency_whose_currency_code_has_already_been_taken(dr: signer, tc: signer) { Genesis::setup(&dr, &tc); RegisteredCurrencies::add_currency_code(&dr, b"XDX"); }

三个用例分别验证:

  1. 非 Diem 根调用initialize必须失败(abort_code = 1,来自Roles::assert_diem_rootINVALID_ARGUMENT类别);
  2. genesis 之后再次调用initialize必须失败(abort_code = 1,来自DiemTimestamp::assert_genesis,且此时配置已发布,publish_new_config同样会中止);
  3. 重复添加已被占用的货币代码b"XDX"必须失败(abort_code = 7,即Errors::invalid_argument(ECURRENCY_CODE_ALREADY_TAKEN)组合出的错误码),反向证明了XDX在 genesis 流程中已被登记、且去重断言真实生效。

这些测试同时也印证:Genesis::setup完成的链创世流程会预先登记XDX等货币,与 Diem.move 中“注册货币并写入 RegisteredCurrencies 清单”的设计闭环一致。

配套参考与进一步阅读

  • 模块源码:language/diem-framework/modules/RegisteredCurrencies.move
  • 本文对应的自动生成文档:language/diem-framework/modules/doc/RegisteredCurrencies.md
  • 配置宿主模块(publish_new_config/get/set的实现):language/diem-framework/modules/DiemConfig.move
  • 货币注册主流程(register_currency/register_SCS_currency):language/diem-framework/modules/Diem.move
  • 角色校验(DiemRoot):language/diem-framework/modules/Roles.move
  • 单元测试:language/diem-framework/tests/RegisteredCurrencyTests.move
  • 链上模块总览:language/diem-framework/modules/doc/overview.md

如需在本地复现上述测试,可进入language/diem-framework目录,通过项目自带的move-unit-test工具链运行RegisteredCurrencyTests(测试文件已按#[test]标注,直接纳入框架的单元测试构建即可)。

【免费下载链接】diemDiem’s mission is to build a trusted and innovative financial network that empowers people and businesses around the world.项目地址: https://gitcode.com/gh_mirrors/di/diem

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/21 18:23:18

Python+Vue全栈开发在线导游预约系统实战

1. 项目概述&#xff1a;基于PythonVue的在线导游预约系统去年接手了一个旅游平台的导游预约模块改造项目&#xff0c;客户要求从原有的电话预约模式升级为全流程在线化系统。经过技术选型&#xff0c;最终采用PythonDjango/FlaskVue.js的技术栈实现了这套系统。这个方案在保证…

作者头像 李华
网站建设 2026/9/21 18:14:17

Java 21+Spring Boot 3构建企业级RAG与智能体工作流

1. 项目概述&#xff1a;为什么在企业级AI工程中&#xff0c;Java 21 Spring Boot 3 是 RAG 与智能体落地的“稳态选择”别卷 Python 了——这句话不是唱衰 Python&#xff0c;而是直击当前 AI 工程化落地中最常被忽视的现实矛盾&#xff1a;原型快 ≠ 上线稳&#xff0c;单点…

作者头像 李华
网站建设 2026/9/21 18:13:57

Codex vs Claude Code:TaoToken 下跑一次 Go 仓库重构的 Token

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/21 18:13:48

Codex 自查 Skill 读不出 Credits?TaoToken 这样填 Base URL

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/21 18:13:36

校园跑腿外卖平台全栈开发与优化实践

1. 校园跑腿外卖平台全栈解决方案解析作为一名参与过多个校园O2O项目开发的技术负责人&#xff0c;今天想和大家分享一套经过实战检验的校园跑腿外卖系统全栈解决方案。这套系统采用PHPThinkPHP框架开发&#xff0c;包含用户端、骑手端和商家端三个核心模块&#xff0c;支持多校…

作者头像 李华