Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

内部约束(Internal Constraint)

除「转移限制」等与 key / store 相关的规则外,Sui 字节码验证器还对一类 API 施加内部约束(internal constraint):调用带有泛型参数 T 的某些函数时,T 必须由当前调用模块定义——即该类型对调用方而言是「内部的」。这与 Move 类型系统在字面上的规则并不完全等同,容易在第一次遇到时报错却不解其意,因此单独说明。

规则在说什么

对受内部约束的函数,验证器要求:

泛型实参 T 的「定义模块」必须与发出调用的模块相同。

换句话说,你不能在模块 A 里调用 foo<T>(),却传入仅在模块 B 中定义的类型(除非 foo 的签名与验证器规则另有规定)。该检查在发布包时完成,无法靠运行时技巧绕过。

这一机制与「转移限制」等规则目的一致:让关键操作(事件形态、部分存储相关行为等)所涉及的类型由发布该逻辑的模块掌控,降低任意模块冒充或滥用类型的风险。

典型例子:sui::event::emit

sui::event 中的 emit 在链上事件流里会标注完全限定类型名;若允许任意模块对任意类型调用 emit,语义与安全模型都会变得模糊。因此验证器要求 T 对调用模块为内部类型:

module sui::event;

/// 若调用方模块未定义 `T`,验证器会拒绝发布。
public native fun emit<T: copy + drop>(event: T);

合法:类型定义与 emit 调用在同一模块内。

module examples::exercise_internal;

use sui::event;

public struct A has copy, drop {}

public fun call_internal() {
    event::emit(A {});
}

不合法:试图用其他模块定义的值类型作为事件负载(此处借标准库类型说明「非本模块定义」):

public fun call_foreign_fail() {
    use std::type_name;

    event::emit(type_name::get<A>());
    // 验证器报错:`sui::event::emit` 必须使用当前模块定义的类型。
}

实际开发中更常见的是误用「别的包里的结构体」当事件体——同样会触发内部约束。

与「转移限制」的关系

  • 转移限制主要围绕 transfer / public_transfer / share_object / freeze_object 等,并与 keystore 能力组合相关。
  • 内部约束是验证器对一类泛型 API 的横向规则;emit<T> 是其中最常踩坑的例子。

二者都体现「关键操作所操作的类型应由谁定义」这一思路,但检查的 API 集合与错误信息不同,需要对照文档与报错分别理解。

还有哪些 API 会涉及

内部约束并非对所有泛型函数生效,而是 Sui Framework(及链上相关规则)为特定函数单独声明的验证条件。除 emit 外,文档与源码中会逐步列出;编写代码时以编译器 / 验证器报错为准。

若在实现「可插拔类型」或跨模块组合时遇到「类型必须在本模块定义」类错误,应先检查是否触发了某条内部约束,再考虑调整类型定义位置或使用 public 包装、能力模式等设计。

小结

要点说明
定义调用方模块必须是泛型 T定义模块
目的保证事件等关键路径上的类型由可信模块声明
与 Move 语言规则由 Sui 验证器 施加,而非一般 Move 语义中的可见性
排查将事件类型、相关结构体挪到发出调用的同一模块内定义,或改用允许的设计模式

更多关于事件与类型的实践说明,见事件系统

关于仅定义类型所在模块可调用的 transfer / public_transfer 等与存储相关的规则,见转移限制(与 emit 的约束相关但检查点不同)。