Sui Move GitHub : https://github.com/MystenLabs/sui
sui :
https://github.com/MystenLabs/sui/tree/main/crates/sui-framework/packages/sui-framework/sources
std :
https://github.com/MystenLabs/sui/tree/main/crates/sui-framework/packages/move-stdlib/sources
### 常用的Sui原生Module有以下這些
1.std::ascii
ASCII編碼的字串(只能顯示英文字母、數字、標點符號)
:::spoiler std::ascii code
```move
// ASCII編碼的字串,存放ASCII編碼的位元組陣列
struct String has copy, drop, store {
bytes: vector<u8>,
}
// ASCII編碼的字元,存放ASCII編碼的位元組
struct Char has copy, drop, store {
byte: u8,
}
// * 將位元組byte轉換為字元Char,並進行檢查確保符合ASCII標準
public fun char(byte: u8): Char {
assert!(is_valid_char(byte), EINVALID_ASCII_CHARACTER);
Char { byte }
}
// * 將位元組陣列bytes轉換為字串String
public fun string(bytes: vector<u8>): String {
let x = try_string(bytes);
assert!(
option::is_some(&x),
EINVALID_ASCII_CHARACTER
);
option::destroy_some(x)
}
// 如果string中的所有字符都是符合ASCII標準,則返回true,否則返回false
public fun all_characters_printable(string: &String): bool {
let len = vector::length(&string.bytes);
let i = 0;
while ({
spec {
invariant i <= len;
invariant forall j in 0..i: is_printable_char(string.bytes[j]);
};
i < len
}) {
let byte = *vector::borrow(&string.bytes, i);
if (!is_printable_char(byte)) return false;
i = i + 1;
};
spec {
assert i == len;
assert forall j in 0..len: is_printable_char(string.bytes[j]);
};
true
}
// * 在字串string後方加入一個字元char
public fun push_char(string: &mut String, char: Char) {
vector::push_back(&mut string.bytes, char.byte);
}
// * 移除字串string最後一個字元char,並返回Char
public fun pop_char(string: &mut String): Char {
Char { byte: vector::pop_back(&mut string.bytes) }
}
// * 回傳字串string的長度
public fun length(string: &String): u64 {
vector::length(as_bytes(string))
}
// * 取得string內部的bytes
public fun as_bytes(string: &String): &vector<u8> {
&string.bytes
}
// 取得string內部的bytes,但原本的string會消失
public fun into_bytes(string: String): vector<u8> {
let String { bytes } = string;
bytes
}
// 取得char內部的byte,但原本的char會消失
public fun byte(char: Char): u8 {
let Char { byte } = char;
byte
}
// 如果符合ASCII標準則返回true,否則為false
public fun is_valid_char(b: u8): bool {
b <= 0x7F
}
```
:::
<br>
2.std::string
UTF8編碼的字串(能顯示中文)
:::spoiler std::string code
```move
// UTF8編碼的字串,存放UTF8編碼的位元組陣列
struct String has copy, drop, store {
bytes: vector<u8>,
}
// * 傳入bytes,回傳一個符合UTF8的String
public fun utf8(bytes: vector<u8>): String {
assert!(internal_check_utf8(&bytes), EINVALID_UTF8);
String{bytes}
}
// 將 ASCII 字串轉換為 UTF8 字串
public fun from_ascii(s: ascii::String): String {
String { bytes: ascii::into_bytes(s) }
}
// 將 UTF8 字串轉換為 ASCII 字串
public fun to_ascii(s: String): ascii::String {
let String { bytes } = s;
ascii::string(bytes)
}
// 返回字串內部的bytes
public fun bytes(s: &String): &vector<u8> {
&s.bytes
}
// * 檢查字串是否為空值,空值則返回true,不為空值返回false
public fun is_empty(s: &String): bool {
vector::is_empty(&s.bytes)
}
// * 返回該字串的長度
public fun length(s: &String): u64 {
vector::length(&s.bytes)
}
// * 在原本的字串(s)後,添加新字串(r)
public fun append(s: &mut String, r: String) {
vector::append(&mut s.bytes, r.bytes)
}
// * 該位元組陣列會轉換成utf8字串,並添加在原本的字串後方
public fun append_utf8(s: &mut String, bytes: vector<u8>) {
append(s, utf8(bytes))
}
// * 在原本的字串(s),插入新字串(o)在指定的索引值(at)中
public fun insert(s: &mut String, at: u64, o: String) {
let bytes = &s.bytes;
assert!(at <= vector::length(bytes) && internal_is_char_boundary(bytes, at), EINVALID_INDEX);
let l = length(s);
let front = sub_string(s, 0, at);
let end = sub_string(s, at, l);
append(&mut front, o);
append(&mut front, end);
*s = front;
}
// * 從字串(s)擷取從索引值(i)到索引值(j)-1的字串
public fun sub_string(s: &String, i: u64, j: u64): String {
let bytes = &s.bytes;
let l = vector::length(bytes);
assert!(
j <= l && i <= j && internal_is_char_boundary(bytes, i) && internal_is_char_boundary(bytes, j),
EINVALID_INDEX
);
String{bytes: internal_sub_string(bytes, i, j)}
}
// * 返回字串(r)在字串(s)第一個找到的索引值,如果都沒有則返回字串(s)的長度
public fun index_of(s: &String, r: &String): u64 {
internal_index_of(&s.bytes, &r.bytes)
}
```
:::
<br>
3.std::vector
自定義類型的陣列
:::spoiler std::vector code
```move
// * 回傳一個包含e的新vector
public fun singleton<Element>(e: Element): vector<Element> {
let v = empty();
push_back(&mut v, e);
v
}
// 反轉vector的排列順序
public fun reverse<Element>(v: &mut vector<Element>) {
let len = length(v);
if (len == 0) return ();
let front_index = 0;
let back_index = len -1;
while (front_index < back_index) {
swap(v, front_index, back_index);
front_index = front_index + 1;
back_index = back_index - 1;
}
}
// * 把other vector push進 lhs vector
public fun append<Element>(lhs: &mut vector<Element>, other: vector<Element>) {
reverse(&mut other);
while (!is_empty(&other)) push_back(lhs, pop_back(&mut other));
destroy_empty(other);
}
// * 判斷vector是否為空
public fun is_empty<Element>(v: &vector<Element>): bool {
length(v) == 0
}
// * 如果Element(e)存在於vector(v)中,則返回true,否則返回false
public fun contains<Element>(v: &vector<Element>, e: &Element): bool {
let i = 0;
let len = length(v);
while (i < len) {
if (borrow(v, i) == e) return true;
i = i + 1;
};
false
}
// * 如果Element(e)存在於vector(v),則返回(true, 索引值index),否則返回(false, 0)
public fun index_of<Element>(v: &vector<Element>, e: &Element): (bool, u64) {
let i = 0;
let len = length(v);
while (i < len) {
if (borrow(v, i) == e) return (true, i);
i = i + 1;
};
(false, 0)
}
// * 移除vector(v)中索引值(i)位置的資料
public fun remove<Element>(v: &mut vector<Element>, i: u64): Element {
let len = length(v);
// i out of bounds; abort
if (i >= len) abort EINDEX_OUT_OF_BOUNDS;
len = len - 1;
while (i < len) swap(v, i, { i = i + 1; i });
pop_back(v)
}
```
:::
<br>
4.sui::address
錢包地址、Object ID的address
:::spoiler sui::address code
```move
// 將address轉成Binary陣列
public fun to_bytes(a: address): vector<u8> {
bcs::to_bytes(&a)
}
// 將address轉換成ascii編碼的字串
public fun to_ascii_string(a: address): ascii::String {
ascii::string(hex::encode(to_bytes(a)))
}
// 將address轉換成utf8編碼的字串
public fun to_string(a: address): string::String {
string::from_ascii(to_ascii_string(a))
}
```
:::
<br>
5.sui::balance
提供資產餘額、供應量的操作
:::spoiler sui::balance code
```move
// 供應量
struct Supply<phantom T> has store {
value: u64
}
// 資產餘額
struct Balance<phantom T> has store {
value: u64
}
// 取得Balance內的value(資產餘額)
public fun value<T>(self: &Balance<T>): u64 {
self.value
}
// * 取得Supply內的value(供應量)
public fun supply_value<T>(supply: &Supply<T>): u64 {
supply.value
}
// * 建立一個供應量為0的Supply
public fun create_supply<T: drop>(_: T): Supply<T> {
Supply { value: 0 }
}
// * 按value增加供應量,並使用該值創建一個新的Balance
public fun increase_supply<T>(self: &mut Supply<T>, value: u64): Balance<T> {
assert!(value < (18446744073709551615u64 - self.value), EOverflow);
self.value = self.value + value;
Balance { value }
}
// * 銷毀Balance並減少Supply內的供應量,並返回銷毀的資產餘額
public fun decrease_supply<T>(self: &mut Supply<T>, balance: Balance<T>): u64 {
let Balance { value } = balance;
assert!(self.value >= value, EOverflow);
self.value = self.value - value;
value
}
// * 建造一個資產餘額為0的Balance
public fun zero<T>(): Balance<T> {
Balance { value: 0 }
}
// * 把balance跟self合併,留下self物件,balance會消失
public fun join<T>(self: &mut Balance<T>, balance: Balance<T>): u64 {
let Balance { value } = balance;
self.value = self.value + value;
self.value
}
// * 拆分2包Balance,原先的Balance餘額扣除value,新拆分出來的Balance餘額為value
public fun split<T>(self: &mut Balance<T>, value: u64): Balance<T> {
assert!(self.value >= value, ENotEnough);
self.value = self.value - value;
Balance { value }
}
```
:::
<br>
6.sui::clock
時間操作(通常會使用Sui的Clock,object id = 0x6)

:::spoiler sui::clock code
```move
// 時間
struct Clock has key {
id: UID,
timestamp_ms: u64,
}
// * 取得clock的毫秒數
public fun timestamp_ms(clock: &Clock): u64 {
clock.timestamp_ms
}
```
:::
<br>
7.sui::coin
Coin資產
:::spoiler sui::coin code
```move
// token物件,這個物件會存放於用戶錢包底下
struct Coin<phantom T> has key, store {
id: UID,
balance: Balance<T>
}
// token的詳細資料,這個物件是Shared object
struct CoinMetadata<phantom T> has key, store {
id: UID,
// 單位數
decimals: u8,
// 名稱
name: string::String,
// 代稱(通常為名稱的縮寫)
symbol: ascii::String,
// 描述
description: string::String,
// logo圖片網址
icon_url: Option<Url>
}
// 這個token鑄造者的證明,能使用mint及burn的權力,這個物件會存放於鑄造者錢包底下
struct TreasuryCap<phantom T> has key, store {
id: UID,
total_supply: Supply<T>
}
// * 返回目前流通的總數
public fun total_supply<T>(cap: &TreasuryCap<T>): u64 {
balance::supply_value(&cap.total_supply)
}
// * 取得Coin內的餘額
public fun value<T>(self: &Coin<T>): u64 {
balance::value(&self.balance)
}
// * 取得Coin內的Balance物件
public fun balance<T>(coin: &Coin<T>): &Balance<T> {
&coin.balance
}
// * 回傳一個新的Coin,並包含balance餘額
public fun from_balance<T>(balance: Balance<T>, ctx: &mut TxContext): Coin<T> {
Coin { id: object::new(ctx), balance }
}
// 移除Coin物件,返回Balance
public fun into_balance<T>(coin: Coin<T>): Balance<T> {
let Coin { id, balance } = coin;
object::delete(id);
balance
}
// 對balance進行拆分,拆分數量為value,並返回包含value數量的Coin物件
public fun take<T>(
balance: &mut Balance<T>, value: u64, ctx: &mut TxContext,
): Coin<T> {
Coin {
id: object::new(ctx),
balance: balance::split(balance, value)
}
}
// * 把coin內的餘額添加至balance的餘額內
public fun put<T>(balance: &mut Balance<T>, coin: Coin<T>) {
balance::join(balance, into_balance(coin));
}
// * 對Coin(self)進行拆分,回傳拆分完新的Coin
public fun split<T>(
self: &mut Coin<T>, split_amount: u64, ctx: &mut TxContext
): Coin<T> {
take(&mut self.balance, split_amount, ctx)
}
// * 把Coin內的餘額拆分成n等份,拆分出來的Coin會放在陣列內回傳出去
public fun divide_into_n<T>(
self: &mut Coin<T>, n: u64, ctx: &mut TxContext
): vector<Coin<T>> {
assert!(n > 0, EInvalidArg);
assert!(n <= value(self), ENotEnough);
let vec = vector::empty<Coin<T>>();
let i = 0;
let split_amount = value(self) / n;
while ({
spec {
invariant i <= n-1;
invariant self.balance.value == old(self).balance.value - (i * split_amount);
invariant ctx.ids_created == old(ctx).ids_created + i;
};
i < n - 1
}) {
vector::push_back(&mut vec, split(self, split_amount, ctx));
i = i + 1;
};
vec
}
// 返回一個餘額為0的新Coin
public fun zero<T>(ctx: &mut TxContext): Coin<T> {
Coin { id: object::new(ctx), balance: balance::zero() }
}
// * 創建新的Currency
public fun create_currency<T: drop>(
witness: T,
decimals: u8,
symbol: vector<u8>,
name: vector<u8>,
description: vector<u8>,
icon_url: Option<Url>,
ctx: &mut TxContext
): (TreasuryCap<T>, CoinMetadata<T>) {
// Make sure there's only one instance of the type T
assert!(sui::types::is_one_time_witness(&witness), EBadWitness);
(
TreasuryCap {
id: object::new(ctx),
total_supply: balance::create_supply(witness)
},
CoinMetadata {
id: object::new(ctx),
decimals,
name: string::utf8(name),
symbol: ascii::string(symbol),
description: string::utf8(description),
icon_url
}
)
}
// * 鑄造Coin
public fun mint<T>(
cap: &mut TreasuryCap<T>, value: u64, ctx: &mut TxContext,
): Coin<T> {
Coin {
id: object::new(ctx),
balance: balance::increase_supply(&mut cap.total_supply, value)
}
}
// * 銷毀Coin
public entry fun burn<T>(cap: &mut TreasuryCap<T>, c: Coin<T>): u64 {
let Coin { id, balance } = c;
object::delete(id);
balance::decrease_supply(&mut cap.total_supply, balance)
}
```
:::
<br>
8.sui::object
ID、UID操作
:::spoiler sui::object code
```move
// ID物件,存放address
struct ID has copy, drop, store {
bytes: address
}
// UID物件,存放ID
struct UID has store {
id: ID,
}
// === id ===
// 取得 ID物件內address的bytes
public fun id_to_bytes(id: &ID): vector<u8> {
bcs::to_bytes(&id.bytes)
}
// * 取得ID物件內的address
public fun id_to_address(id: &ID): address {
id.bytes
}
// 傳入一個bytes,回傳新的ID物件
public fun id_from_bytes(bytes: vector<u8>): ID {
id_from_address(address::from_bytes(bytes))
}
// 傳入一個address,回傳新的ID物件
public fun id_from_address(bytes: address): ID {
ID { bytes }
}
// * 取得UID內的ID物件
public fun uid_as_inner(uid: &UID): &ID {
&uid.id
}
// 取得UID內ID物件內address物件內的bytes
public fun uid_to_bytes(uid: &UID): vector<u8> {
bcs::to_bytes(&uid.id.bytes)
}
// * 取得UID內ID物件內的address
public fun uid_to_address(uid: &UID): address {
uid.id.bytes
}
// * 新建一個UID
public fun new(ctx: &mut TxContext): UID {
UID {
id: ID { bytes: tx_context::fresh_object_address(ctx) },
}
}
// 刪除一個UID
public fun delete(id: UID) {
let UID { id: ID { bytes } } = id;
delete_impl(bytes)
}
// * 取得某物件內的ID
public fun id<T: key>(obj: &T): ID {
borrow_uid(obj).id
}
// 取得某物件內的bytes
public fun id_bytes<T: key>(obj: &T): vector<u8> {
bcs::to_bytes(&borrow_uid(obj).id)
}
// * 取得某物件內的address
public fun id_address<T: key>(obj: &T): address {
borrow_uid(obj).id.bytes
}
```
:::
<br>
9.sui::table
Key,Value的Map概念
:::spoiler sui::table code
```move
struct Table<phantom K: copy + drop + store, phantom V: store> has key, store {
id: UID,
size: u64,
}
// * 新建一個空的Table
public fun new<K: copy + drop + store, V: store>(ctx: &mut TxContext): Table<K, V> {
Table {
id: object::new(ctx),
size: 0,
}
}
// * 為table新建一組Key、Value,如果Key已存在則報錯
public fun add<K: copy + drop + store, V: store>(table: &mut Table<K, V>, k: K, v: V) {
field::add(&mut table.id, k, v);
table.size = table.size + 1;
}
// * 返回table內對應key的value值
public fun borrow<K: copy + drop + store, V: store>(table: &Table<K, V>, k: K): &V {
field::borrow(&table.id, k)
}
// * 移除table內對應Key的value值
public fun remove<K: copy + drop + store, V: store>(table: &mut Table<K, V>, k: K): V {
let v = field::remove(&mut table.id, k);
table.size = table.size - 1;
v
}
// * 返回是否有在table找到對應的Key
public fun contains<K: copy + drop + store, V: store>(table: &Table<K, V>, k: K): bool {
field::exists_with_type<K, V>(&table.id, k)
}
// * Table的長度
public fun length<K: copy + drop + store, V: store>(table: &Table<K, V>): u64 {
table.size
}
// * 檢查該table是否為空
public fun is_empty<K: copy + drop + store, V: store>(table: &Table<K, V>): bool {
table.size == 0
}
```
:::
<br>
10.sui::transfer
交易操作
:::spoiler sui::transfer code
```move
// * 轉移Obj給某個地址
public fun transfer<T: key>(obj: T, recipient: address) {
transfer_impl(obj, recipient)
}
// 跟上面功能一樣,只是這個Obj多個store能力
public fun public_transfer<T: key + store>(obj: T, recipient: address) {
transfer_impl(obj, recipient)
}
// * 凍結Obj,凍結後該Obj不可變及不可轉移
public fun freeze_object<T: key>(obj: T) {
freeze_object_impl(obj)
}
// 跟上面功能一樣,只是這個Obj多個store能力
public fun public_freeze_object<T: key + store>(obj: T) {
freeze_object_impl(obj)
}
// * 每個人都可以訪問和改變的可變共享對象
public fun share_object<T: key>(obj: T) {
share_object_impl(obj)
}
// 跟上面功能一樣,只是這個Obj多個store能力
public fun public_share_object<T: key + store>(obj: T) {
share_object_impl(obj)
}
```
:::
<br>
11.sui::tx_context
紀錄執行事務的資訊
:::spoiler sui::tx_context code
```move
// 當前正在執行的事務的資訊物件
struct TxContext has drop {
// 當前發起請求的用戶的地址
sender: address,
// 當前交易的hash
tx_hash: vector<u8>,
// 當前紀元號
epoch: u64,
// 當前的時間戳記
epoch_timestamp_ms: u64,
// 計數器記錄執行時創建的新 id 的數量
ids_created: u64
}
// * 取得TxContext內的發請人地址
public fun sender(self: &TxContext): address {
self.sender
}
// 取得TxContext內的hash
public fun digest(self: &TxContext): &vector<u8> {
&self.tx_hash
}
// 取得TxContext內的紀元號
public fun epoch(self: &TxContext): u64 {
self.epoch
}
// 取得TxContext內的時間戳記
public fun epoch_timestamp_ms(self: &TxContext): u64 {
self.epoch_timestamp_ms
}
```
:::
### 程式講解
我們創建一個 module_demo 專案並進到專案目錄內
```javascript
sui move new module_demo
```
```javascript
cd .\module_demo\
```
在sources建立一個cram_school.move檔案
這是我的Side Project程式,裡面是一個簡易補習班的模組
包含有學生、老師及課程。
有興趣的同學可以自己部署玩玩看,也可以嘗試者優化它
```javascript
module module_demo::cram_school {
use sui::tx_context::{Self, TxContext};
use sui::coin::{Self, Coin};
use sui::transfer;
use std::string::{Self, String};
use std::vector;
use sui::object::{Self, UID, ID};
use sui::table::{Self, Table};
// all class information
struct ClassShared has key {
id: UID,
class_table: Table<ID, bool>
}
// all teacher
struct TeacherShared has key {
id: UID,
teachers: Table<ID, bool>,
}
// all student
struct StudentShared has key {
id: UID,
students: Table<ID, bool>,
}
struct Class has key {
id: UID,
name: String,
description: String,
teacher_wallet_address: address,
teacherCap_uid: ID,
studentCap_uid_vector: vector<address>,
amount: u64,
start_time: u64,
end_time: u64,
}
struct TeacherCap has key, store {
id: UID,
name: String,
description: String,
}
struct StudentCap has key, store {
id: UID,
name: String,
}
const EAmountNotEnough: u64 = 1;
fun init(ctx: &mut sui::tx_context::TxContext) {
let classShared = ClassShared{
id: object::new(ctx),
class_table: table::new<ID, bool>(ctx),
};
let teacherShared = TeacherShared{
id: object::new(ctx),
teachers: table::new<ID, bool>(ctx),
};
let studentShared = StudentShared{
id: object::new(ctx),
students: table::new<ID, bool>(ctx),
};
transfer::share_object(classShared);
transfer::share_object(teacherShared);
transfer::share_object(studentShared);
}
public entry fun mint_teacher_cap(
teacher_shared: &mut TeacherShared,
name: vector<u8>,
description: vector<u8>,
ctx: &mut TxContext,
){
let teacherCap = TeacherCap{
id: object::new(ctx),
name: string::utf8(name),
description: string::utf8(description),
};
table::add(&mut teacher_shared.teachers, object::id(&teacherCap), true);
transfer::public_transfer(teacherCap, tx_context::sender(ctx));
}
public entry fun mint_student_cap(
student_shared: &mut StudentShared,
name: vector<u8>,
ctx: &mut TxContext,
){
let studentCap = StudentCap{
id: object::new(ctx),
name: string::utf8(name),
};
table::add(&mut student_shared.students, object::id(&studentCap), true);
transfer::public_transfer(studentCap, tx_context::sender(ctx));
}
public entry fun create_class (
class_shared: &mut ClassShared,
name: vector<u8>,
description: vector<u8>,
teacher_cap: &mut TeacherCap,
amount: u64,
start_time: u64,
end_time: u64,
ctx: &mut TxContext,
){
let class = Class{
id: object::new(ctx),
name: string::utf8(name),
description: string::utf8(description),
teacher_wallet_address: tx_context::sender(ctx),
teacherCap_uid: object::uid_to_inner(&teacher_cap.id),
studentCap_uid_vector: vector::empty<address>(),
amount: amount,
start_time: start_time,
end_time: end_time,
};
table::add(&mut class_shared.class_table, object::id(&class), true);
transfer::share_object(class);
}
public entry fun choose_class<T: drop>(
class: &mut Class,
student_cap: &StudentCap,
coin: Coin<T>,
) {
assert!(coin::value(&coin) >= class.amount, EAmountNotEnough);
vector::push_back(&mut class.studentCap_uid_vector, object::uid_to_address(&student_cap.id));
transfer::public_transfer(coin, class.teacher_wallet_address);
}
public entry fun close_class (
class_shared: &mut ClassShared,
class: &mut Class,
) {
table::remove<ID, bool>(&mut class_shared.class_table, object::uid_to_inner(&class.id));
table::add<ID, bool>(&mut class_shared.class_table, object::uid_to_inner(&class.id),false);
}
}
```