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