Move —— Sui 对象所有权模型与事件

Sui 对象在创建后必须显式指定其所有权归属。本文介绍三种所有权模式的区别:独有对象(Owned)只能被所有者操作、共享对象(Shared)允许任何人操作、不可变对象(Immutable)永久只读。同时讲解如何使用事件(Event)让链下应用监听链上状态变化。

1. Sui 资产的三种形态

Sui 对象被创建 (object::new) 后,必须通过 transfer 模块显式指定去向。

1.1 独有对象 (Owned Object)

  • APItransfer::transfer(obj, recipient)
  • 权限私有。只有 Owner 能发起交易修改它。
  • 场景:NFT、个人钱包资产。

1.2 共享对象 (Shared Object)

  • APItransfer::share_object(obj)
  • 权限公开。任何人都可发起交易读取或修改。
  • 场景:公共计数器、DEX 流动性池、投票箱。

1.3 不可变对象 (Immutable Object)

  • APItransfer::freeze_object(obj)
  • 权限只读。包括创建者在内,无人能修改或删除。
  • 场景:系统配置、固定规则。

2. 事件 (Event)

2.1 为什么要用事件?

在共享对象场景下,对象状态只保存”当前结果”(比如 value=100)。如果前端或数据库需要展示动态的历史记录(比如用户在什么时间调用了increment),只是从区块浏览器查对象状态是做不到的。通过 Indexer通过监听链上事件,可以高效地将动作同步到 SQL 数据库中,供前端查询历史轨迹。

2.2 定义与使用

  • 定义:事件是一个标准的结构体,必须拥有 copydrop 能力,因为它只是一个临时消息,不需要存储在链上。
  • 发射:使用 sui::event::emit 发送消息。

3. Counter 合约

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
use sui::event;

const EValueTooLarge: u64 = 0;

// 定义事件结构体,必须有 copy, drop 能力,因为它是临时消息
public struct CounterIncremented has copy, drop {
user: address,
new_value: u64,
}

public struct Counter has key {
id: UID,
value: u64,
}

public entry fun create(ctx: &mut TxContext) {
// Shared Object (共享对象),我们希望做一个公共计数器,大家都能点
let counter = Counter {
id: object::new(ctx),
value: 0,
};
// 从 transfer 改为 share_object
transfer::share_object(counter);
}

public entry fun increment(counter: &mut Counter, ctx: &TxContext) {
counter.value = counter.value + 1;

assert!(counter.value <= 10, EValueTooLarge);

// 发射事件,为了方便链下数据库索引历史记录
event::emit(CounterIncremented {
user: tx_context::sender(ctx),
new_value: counter.value,
});
}

// delete 函数保持不变 (但在共享对象模式下,通常只有管理员能删,L16 会讲)
public entry fun delete(counter: Counter) {
let Counter { id, value: _ } = counter;
object::delete(id);
}

4. 总结

概念 API / 关键字 核心作用
共享对象 share_object 让全网用户共同操作同一个状态。
不可变对象 freeze_object 锁定数据,作为永久只读常量。
事件 event::emit 解决共享状态下的”可观测性”问题,通知链下应用。

5. CLI 参考

1
2
3
4
5
sui client publish --gas-budget 100000000

sui client call --package 0xPackageID --module counter --function create --gas-budget 10000000

sui client call --package 0xPackageID --module counter --function increment --args 0xCounterID --gas-budget 10000000