近期Sui出了一个新的教程Let‘sMoveSui,所有的lesson我都做了一遍,分享我的总结
近期 Sui 出了一个新的教程 Let‘s Move Sui, 所有的 lesson 我都做了一遍, 分享我的总结。 本文代码: <https://github.com/upupnoah/move-x/tree/main/lets_move_sui/sources>
Move 代码被组织成模块, 可以把一个模块看成是区块链上的一个智能合约
可以通过调用这些模块中的函数来与模块进行交互,可以通过事务或其他 Move 代码来实现, 事务将被发送到并由Sui区块链进行处理,一旦执行完成,结果的更改将被保存
一个模块由 module
关键字 + 地址 + 模块名组成, 其中地址可以写成 alias (定义在 Move.toml中), 例如 lets_move_sui
module lets_move_sui::sui_fren {
}
u8
, u16
, u32
, u64
, u128
, u256
bool
address
String
vector
, 例如 vector<u64>
结构体是 Sui Move 中的一个基本概念
结构体可以看成是一组相关字段的组合, 每个字段都有自己的类型
Sui Move 可以为结构体添加4 种能力: key、store、drop、copy
, 这些能力后续会慢慢涉及
下面是一个基本的结构体名为 AdminCap, 内部含有一个 num_frens字段
public struct AdminCap {
num_frens: u64,
}
添加一个私有函数 init, 这是一个特殊的函数, 会在 module 部署到区块链时, 自动调用
fun init(ctx: &mut TxContext) {
let admin_cap = AdminCap {
id: object::new(ctx),
num_frens: 1000,
};
transfer::share_object(admin_cap);
}
Object
是 Sui 中的一个基本概念一个 Object 一般都有 key
能力以及含有一个类型为 UID
的 id
字段
sui::object::new
: 创建一个新对象。返回必须存储在 Sui 对象中的 UID
public fun new(ctx: &mut TxContext): UID {
UID {
id: ID { bytes: tx_context::fresh_object_address(ctx) },
}
}
创建 Ticket: create_ticket
示例代码
module 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));
}
}
一个 Ticket 一般都有过期时间
Ticket 结构体
中添加一个 expiration_time
字段ticket_module
中添加一个 is_expired
方法来检测是否过期module 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,
expiration_time: u64,
}
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));
}
public fun is_expired(ticket: &Ticket, clock: &Clock): bool {
ticket.expiration_time <= clock::timestamp_ms(clock)
}
}
需要在函数中传入一个可变引用
module 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;
}
}
sui::object::delete
: 删除该对象及其 UID
。这是消除 UID
的唯一方法
public fun delete(id: UID) {
let UID { id: ID { bytes } } = id;
delete_impl(bytes)
}
示例代码
module 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);
}
}
x^y
表示 以 x 为底, 指数为 y相同的类型可以直接进行算术运算, 但是不同的类型之间想要进行算术运算, 则需要进行转换
在 Move 中类型转换可以使用这种形式: (x as <type>)
, 例如(x as u64)
fun 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 相当于是一个动态数组, 这是 Move 内置的一个数据结构, 后续会了解到更多
创建 vector
// 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
struct MyObject has key {
id: UID,
values: vector<u64>,
bool_values: vector<bool>,
address_values: vector<address>,
}
init
函数必须是 Private
的, 它会在合约部署的时候由 Sui 虚拟机(VM)
调用Public
意味着它可以被任何其他的 Move module 和 transactions 调用Private
意味着只能被当前 Move module 调用, 并且不能从 transaction 中调用module lets_move_sui::sui_fren {
use sui::tx_context::{Self, TxContext};
use sui::transfer;
use sui::object::{Self, ID, UID};
use std::string::String;
// use std::vector; // built-in
use sui::event;
public struct AdminCap has key {
id: UID,
num_frens: u64,
}
public struct SuiFren has key {
id: UID,
generation: u64,
birthdate: u64,
attributes: vector<String>,
}
fun init(ctx: &mut TxContext) {
let admin_cap = AdminCap {
id: object::new(ctx),
num_frens: 10^3, // 1000
};
transfer::share_object(admin_cap);
}
// change the mint function to transfer the object to the sender
public fun mint(generation: u64, birthdate: u64, attributes: vector<String>, ctx: &mut TxContext) {
let uid = object::new(ctx);
let sui_fren = SuiFren {
id: uid,
generation,
birthdate,
attributes,
};
transfer::transfer(sui_fren, tx_context::sender(ctx));
}
public fun burn(sui_fren: SuiFren) {
let SuiFren {
id,
generation: _,
birthdate: _,
attributes: _,
} = sui_fren;
object::delete(id);
}
public fun get_attributes(sui_fren: &SuiFren): vector<String> {
sui_fren.attributes
}
public fun update_attributes(sui_fren: &mut SuiFren, attributes: vector<String>) {
sui_fren.attributes = attributes;
}
}
Shared Objects
可以被任何用户读取和修改
Owned Objects
是私有对象,只有拥有它们的用户才能读取和修改(所有权)
Receiving<T>
解决(后续会提及)sui::transfer::share_object
: 将给定的对象转换为一个可变的共享对象,每个人都可以访问和修改
public fun share_object<T: key>(obj: T) {
share_object_impl(obj)
}
sui::transfer::transfer
: 将 obj
的所有权转移给接收者。obj
必须具有 key
属性
public fun transfer<T: key>(obj: T, recipient: address) {
transfer_impl(obj, recipient)
}
示例代码
module 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 是你的module 将区块链上发生的事情传达给应用程序前端的一种方式
应用程序可以监听某些 Event 来采取行动
Event 主要用来给”链下”组件 与 “链上”组件进行交互
sui::event::emit
: 发出自定义的 Move Event,将数据发送到链下。 由下面的函数签名可知, emit 的参数需要一个包含 copy
和 drop
能力的类型
public native fun emit<T: copy + drop>(event: T);
示例代码
module 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),
});
}
}
Move代码被组织成模块,每个模块类似于其他区块链上的单个智能合约。这种模块化设计在Sui中得到了强调,旨在促使开发者保持模块小巧且分布在不同文件中,同时坚持清晰的数据结构和代码规范。这样做既方便应用程序集成,也便于用户理解。
模块通过入口和公共函数提供API,用户可以通过事务或其他Move代码调用这些函数来与模块交互。这些交互由Sui区块链处理,并且会保存任何结果变更。
结构是由相关字段组成的集合,每个字段都有自己的类型,如数字、布尔值和向量。每个结构都可以拥有“能力”,包括键(key
)、存储(store
)、放置(drop
)、复制(copy
)。这些能力描述了结构在语言中的行为方式。
Sui Move 支持以下数据类型:无符号整数、布尔值、地址、字符串、向量以及自定义结构类型
理解对象的生命周期、如何阅读、更新以及删除对象是学习Move语言的重要部分。此外,还需要区分共享对象与拥有对象的概念。
向量被理解为动态数组,对于管理智能合约中的项目列表至关重要,反映了区块链应用程序中对于灵活数据结构的需求。
事件是模块用来通知应用程序前端区块链上发生了某些事情的一种方式。应用可以监听特定的事件,并在这些事件发生时采取行动。
public
):可以从任何其他Move模块和事务中被调用。private
):只能在同一个模块中调用,不能从事务中调用。如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!