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)的完整实现与中止条件、它与DiemConfig、Diem、Roles、DiemTimestamp的协作关系,以及 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"。- 结构体同时具备
copy、drop、store三种能力(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 是1(Errors标准库中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() } ); }初始化流程由三道防线构成:
- 时间防线:
DiemTimestamp::assert_genesis()—— 断言当前处于 genesis 阶段(时间戳尚未进入运行状态),确保该模块只能在链创世时被初始化一次; - 权限防线:
Roles::assert_diem_root(dr_account)—— 断言签名者具备 DiemRoot 角色; - 发布动作:
DiemConfig::publish_new_config<RegisteredCurrencies>(dr_account, ...)—— 以 Diem 根账户为发布者,将初始状态{ currency_codes: Vector::empty() }(空清单)发布为一条DiemConfig配置。
也就是说,初始状态下系统不预置任何货币代码,XUS、XDX等正式货币是在 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::AbortsIfNotGenesis、Roles::AbortsIfNotDiemRoot、DiemConfig::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, ¤cy_code), Errors::invalid_argument(ECURRENCY_CODE_ALREADY_TAKEN) ); Vector::push_back(&mut config.currency_codes, currency_code); DiemConfig::set(dr_account, config); }执行路径分四步:
DiemConfig::get<RegisteredCurrencies>()读出当前清单(按值拷贝,得益于结构体的copy能力);- 用
Vector::contains检查新代码是否已存在,若已存在则抛Errors::invalid_argument(ECURRENCY_CODE_ALREADY_TAKEN)中止; Vector::push_back将新代码追加到向量末尾,保持注册顺序;DiemConfig::set(dr_account, config)以 Diem 根签名写回配置。
这里存在一个值得注意的细节:add_currency_code内部并未显式调用Roles::assert_diem_root,其权限约束来自DiemConfig::set的实现——DiemConfig把RegisteredCurrencies声明为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 }它封装了“读取当前货币代码清单”的规范访问路径,被InitializeEnsures、AddCurrencyCodeAbortsIf、AddCurrencyCodeEnsures等多个 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"); }三个用例分别验证:
- 非 Diem 根调用
initialize必须失败(abort_code = 1,来自Roles::assert_diem_root的INVALID_ARGUMENT类别); - genesis 之后再次调用
initialize必须失败(abort_code = 1,来自DiemTimestamp::assert_genesis,且此时配置已发布,publish_new_config同样会中止); - 重复添加已被占用的货币代码
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),仅供参考