Move on Sui 基本概念
近期 Sui 出了一个新的教程 Let‘s Move Sui, 所有的 lesson 我都做了一遍, 分享我的总结。
- Module (模块)
- 基本数据类型
- Struct (结构体)
- Object
- Math
- 类型转换
- Vector
- Public function and Private function
- Shared Object and Owned Object
- Event (事件)
- 总结
近期 Sui 出了一个新的教程 Let‘s Move Sui, 所有的 lesson 我都做了一遍, 分享我的总结。
本文代码: https://github.com/upupnoah/move-x/tree/main/lets_move_sui/sources
Module (模块)
Move 代码被组织成模块, 可以把一个模块看成是区块链上的一个智能合约
可以通过调用这些模块中的函数来与模块进行交互,可以通过事务或其他 Move 代码来实现, 事务将被发送到并由Sui区块链进行处理,一旦执行完成,结果的更改将被保存
一个模块由
module
关键字 + 地址 + 模块名组成, 其中地址可以写成 alias (定义在 Move.toml中), 例如 lets_move_sui1
2
3
4module lets_move_sui::sui_fren {
}
基本数据类型
- 无符号整数:
u8
,u16
,u32
,u64
,u128
,u256
- Boolean:
bool
- Addresses:
address
- 字符串:
String
- Vector:
vector
, 例如vector<u64>
- 自定义的结构体类型, 例如下面定义的 Struct
Struct (结构体)
结构体是 Sui Move 中的一个基本概念
结构体可以看成是一组相关字段的组合, 每个字段都有自己的类型
Sui Move 可以为结构体添加4 种能力:
key、store、drop、copy
, 这些能力后续会慢慢涉及下面是一个基本的结构体名为 AdminCap, 内部含有一个 num_frens字段
1
2
3public struct AdminCap {
num_frens: u64,
}添加一个私有函数 init, 这是一个特殊的函数, 会在 module 部署到区块链时, 自动调用
1
2
3
4
5
6
7fun init(ctx: &mut TxContext) {
let admin_cap = AdminCap {
id: object::new(ctx),
num_frens: 1000,
};
transfer::share_object(admin_cap);
}
Object
Object
是 Sui 中的一个基本概念- 在 Sui 中, 所有数据可以视为不同 Object 内部的字段
- 使用结构体(Struct)来表示Object
- 可以根据 module 中定义的函数进行创建, 读取, 修改,交互 Object
创建一个 Object
一个 Object 一般都有
key
能力以及含有一个类型为UID
的id
字段sui::object::new
: 创建一个新对象。返回必须存储在 Sui 对象中的UID
1
2
3
4
5public fun new(ctx: &mut TxContext): UID {
UID {
id: ID { bytes: tx_context::fresh_object_address(ctx) },
}
}
创建 Ticket:
create_ticket
示例代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19module lets_move_sui::ticket_module {
use sui::clock::{Self, Clock};
use sui::object::{Self, ID, UID};
use sui::transfer;
use sui::tx_context::{Self, TxContext};
public struct Ticket has key {
id: UID,
}
public fun create_ticket(ctx: &mut TxContext, clock: &Clock) {
let uid = object::new(ctx);
let ticket = Ticket {
id: uid,
expiration_time: clock::timestamp_ms(clock),
};
transfer::transfer(ticket, tx_context::sender(ctx));
}
}
读取 Ojbect 中的字段
- 一个 Ticket 一般都有过期时间
- 在
Ticket 结构体
中添加一个expiration_time
字段 - 在
ticket_module
中添加一个is_expired
方法来检测是否过期
- 在
1 |
|
修改 Object 中的字段
需要在函数中传入一个可变引用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23module 0x123::my_module {
use std::vector;
use sui::object::{Self, UID};
use sui::transfer;
use sui::tx_context::TxContext;
struct MyObject has key {
id: UID,
value: u64,
}
fun init(ctx: &mut TxContext) {
let my_object = MyObject {
id: object::new(ctx),
value: 10,
};
transfer::share_object(my_object);
}
public fun set_value(global_data: &mut MyObject, value: u64) {
global_data.value = value;
}
}
删除一个 Object
sui::object::delete
: 删除该对象及其UID
。这是消除UID
的唯一方法1
2
3
4public fun delete(id: UID) {
let UID { id: ID { bytes } } = id;
delete_impl(bytes)
}示例代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19module 0x123::ticket_module {
use sui::clock::{Self, Clock};
use sui::object::{Self, UID};
use sui::transfer;
use sui::tx_context::TxContext;
struct Ticket has key {
id: UID,
expiration_time: u64,
}
public fun clip_ticket(ticket: Ticket) {
let Ticket {
id,
expiration_time: _,
} = ticket;
object::delete(id);
}
}
Math
- 在 Move 中数学运算和其他编程语言非常相似
- 需要注意的是
x^y
表示 以 x 为底, 指数为 y
类型转换
相同的类型可以直接进行算术运算, 但是不同的类型之间想要进行算术运算, 则需要进行转换
在 Move 中类型转换可以使用这种形式:
(x as <type>)
, 例如(x as u64)1
2
3
4
5
6
7
8
9
10
11
12
13
14fun mixed_types_math_error(): u64 {
let x: u8 = 1;
let y: u64 = 2;
// This will fail to compile as x and y are different types. One is u8, the other is u64.
x + y
}
fun mixed_types_math_ok(): u64 {
let x: u8 = 1;
let y: u64 = 2;
// Ok
(x as u64) + y
}
Vector
vector 相当于是一个动态数组, 这是 Move 内置的一个数据结构, 后续会了解到更多
创建 vector
1
2
3
4// The empty vector does not yet have a type declared. The first value added will determine its type.
let empty_vector = vector[];
let int_vector = vector[1, 2, 3];
let bool_vector = vector[true, true, false];vector in struct field
1
2
3
4
5
6struct MyObject has key {
id: UID,
values: vector<u64>,
bool_values: vector<bool>,
address_values: vector<address>,
}
Public function and Private function
init
函数必须是Private
的, 它会在合约部署的时候由Sui 虚拟机(VM)
调用Public
意味着它可以被任何其他的 Move module 和 transactions 调用Private
意味着只能被当前 Move module 调用, 并且不能从 transaction 中调用
1 |
|
Shared Object and Owned Object
Shared Objects
可以被任何用户读取和修改- 不能并行处理(例如修改), 并且需要严格的检查, 性能慢, 可扩展性差
Owned Objects
是私有对象,只有拥有它们的用户才能读取和修改(所有权)- 只允许直接所有权,因此如果用户A拥有对象B,而对象B拥有对象C,则用户A无法发送包含对象C的事务, 这个问题可以使用
Receiving<T>
解决(后续会提及) - 可以并行处理, 因为涉及它们的 transaction (事务) 不会相互重叠
- 只允许直接所有权,因此如果用户A拥有对象B,而对象B拥有对象C,则用户A无法发送包含对象C的事务, 这个问题可以使用
sui::transfer::share_object
: 将给定的对象转换为一个可变的共享对象,每个人都可以访问和修改1
2
3
4public fun share_object<T: key>(obj: T) {
share_object_impl(obj)
}sui::transfer::transfer
: 将obj
的所有权转移给接收者。obj
必须具有key
属性1
2
3public fun transfer<T: key>(obj: T, recipient: address) {
transfer_impl(obj, recipient)
}示例代码
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
26module lets_move_sui::shared_and_owned {
use sui::object::{Self, UID};
use sui::tx_context::{Self, TxContext};
use sui::transfer;
public struct SharedObject has key {
id: UID,
}
public struct OwnedObject has key {
id: UID,
}
public fun create_shared_object(ctx: &mut TxContext) {
let shared_object = SharedObject {
id: object::new(ctx),
};
transfer::share_object(shared_object);
}
public fun create_owned_object(ctx: &mut TxContext) {
let owned_object = OwnedObject {
id: object::new(ctx),
};
transfer::transfer(owned_object, tx_context::sender(ctx));
}
}
Event (事件)
什么是 Event? Event 是你的module 将区块链上发生的事情传达给应用程序前端的一种方式
应用程序可以监听某些 Event 来采取行动
Event 主要用来给”链下”组件 与 “链上”组件进行交互
sui::event::emit
: 发出自定义的 Move Event,将数据发送到链下。 由下面的函数签名可知, emit 的参数需要一个包含copy
和drop
能力的类型1
public native fun emit<T: copy + drop>(event: T);
示例代码
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
41module 0x123::ticket_module {
use sui::clock::{Self, Clock};
use sui::event;
use sui::object::{Self, ID, UID};
use sui::transfer;
use sui::tx_context::{Self, TxContext};
struct Ticket has key {
id: UID,
expiration_time: u64,
}
struct CreateTicketEvent has copy, drop {
id: ID,
}
struct ClipTicketEvent has copy, drop {
id: ID,
}
public fun create_ticket(ctx: &mut TxContext, clock: &Clock) {
let uid = object::new(ctx);
let id = object::uid_to_inner(&uid);
let ticket = Ticket {
id: uid,
expiration_time: clock::timestamp_ms(clock),
};
transfer::transfer(ticket, tx_context::sender(ctx));
event::emit(CreateTicketEvent {
id,
});
}
public fun clip_ticket(ticket: Ticket) {
let Ticket { id, expiration_time: _ } = ticket;
object::delete(id);
event::emit(ClipTicketEvent {
id: object::uid_to_inner(&id),
});
}
}
总结
模块(module) 组织
Move代码被组织成模块,每个模块类似于其他区块链上的单个智能合约。这种模块化设计在Sui中得到了强调,旨在促使开发者保持模块小巧且分布在不同文件中,同时坚持清晰的数据结构和代码规范。这样做既方便应用程序集成,也便于用户理解。
API和交互
模块通过入口和公共函数提供API,用户可以通过事务或其他Move代码调用这些函数来与模块交互。这些交互由Sui区块链处理,并且会保存任何结果变更。
结构体(Struct)
结构是由相关字段组成的集合,每个字段都有自己的类型,如数字、布尔值和向量。每个结构都可以拥有“能力”,包括键(key
)、存储(store
)、放置(drop
)、复制(copy
)。这些能力描述了结构在语言中的行为方式。
数据类型 (data type)
Sui Move 支持以下数据类型:无符号整数、布尔值、地址、字符串、向量以及自定义结构类型
对象 (object)
理解对象的生命周期、如何阅读、更新以及删除对象是学习Move语言的重要部分。此外,还需要区分共享对象与拥有对象的概念。
向量 (vector)
向量被理解为动态数组,对于管理智能合约中的项目列表至关重要,反映了区块链应用程序中对于灵活数据结构的需求。
事件 (event)
事件是模块用来通知应用程序前端区块链上发生了某些事情的一种方式。应用可以监听特定的事件,并在这些事件发生时采取行动。
函数 (fun)
- 公共函数(使用关键字
public
):可以从任何其他Move模块和事务中被调用。 - 私有函数(使用关键字
private
):只能在同一个模块中调用,不能从事务中调用。