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) ![](https://hackmd.io/_uploads/B1QhbYrun.png) :::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); } } ```