{"content":{"title":"Sui Move One Time Witness, Publisher, Object Display","body":"# 一：概要\r\n\r\n国王将从勇士当中挑选有能力的晋升为骑士，整个过程可以分成如下几点：\r\n\r\n- 国王有且只有一个，在神的指导下可以提升自身能力值。\r\n- 只要有能力，勇士可以有无数个，在自身努力之下可以提升能力值。\r\n- 晋升为骑士的条件是勇士的能力不低于国王的能力值，每一个骑士将公开自身信息但他/她的能力值也就此止步，无法再提升。\r\n\r\n# 二：分析\r\n\r\n思考如何通过一次性见证$\\mathit {(One\\ Time\\ Witness)}$，发布者$\\mathit {(Publisher)}$以及对象显示$\\mathit {(Object\\ Display)}$来实现？\r\n\r\n**注意：** 本篇内容仅针对初学者，目的是将这三者尽可能串联起来，形成一个不那么枯燥又容易理解的例子，所以设计过程及最终呈现可能存在优化空间。\r\n\r\n## 2.1 一次性见证\r\n\r\n一次性见证$\\mathit {(One\\ Time\\ Witness,\\ OTW)}$是一种特殊类型的实例，该类型的定义需要具备如下条件：\r\n\r\n- 以模块的名字命名，下划线 $\\mathit {\\_}$ 保留，但所有字母大写。\r\n- 只拥有 $\\mathit {drop}$​ 能力修饰符。\r\n\r\n$\\mathit {OTW}$ 只在模块初始化器中创建，并保证是唯一的，可以用`types::is_one_time_witness(&witness)`来判断传入的 $\\mathit {witness}$ 是不是 $\\mathit {OTW}$。\r\n\r\n借助 $\\mathit {OTW}$，对创建**国王**的函数进行限制，以此来保证其唯一性。\r\n\r\n## 2.2 发布者\r\n\r\n发布者$\\mathit {(Publisher)}$对象用于代表发布者的权限，它本身并不代表任何特定的用例，主要通过`package::from_module<T>`和`package::from_package<T>`来检查传入的类型为 $\\mathit T$ （泛型或指定一个类型）的参数与 $\\mathit {publisher}$ 是否处在同一个模块或包中。\r\n\r\n为了保证模块当中发布者的唯一性，需要用到上面提到的 $\\mathit {OTW}$，通过`package::claim_and_keep(otw, ctx)`创建一个 $\\mathit {publisher}$ 并将其所有权转移给发布者；如果不着急转移所有权，可以通过`let publisher = package::claim(otw, ctx)`来获得 $\\mathit {publisher}$，接下去可以借助这个对象，来做一些其它的事情（比如定义对象显示等），但是在最后，不要忘记将它的所有权手动通过 $\\mathit {transfer}$ 移交给发布者。\r\n\r\n借助发布者的权限，比肩神明，唯有此方能**指导国王能力提升**。\r\n\r\n## 2.3 对象显示\r\n\r\n拥有 $\\mathit {Publisher}$ 对象的构建者可以通过`sui::display`模块来自定义对象的显示属性，以供生态系统在链下处理数据，所有属性都可以通过`{property}`语法访问同时作为字符串插入其中，例如：\r\n\r\n```\r\n{\r\n    \"name\": \"{name}\",\r\n    \"link\": \"https://sui-heroes.io/hero/{id}\",\r\n    \"img_url\": \"ipfs://{img_url}\",\r\n    \"description\": \"A true Hero of the Sui ecosystem!\",\r\n    \"project_url\": \"https://sui-heroes.io\",\r\n    \"creator\": \"Unknown Sui Fan\"\r\n}\r\n```\r\n\r\n当`MyObject`和`Display<MyObject>`匹配时，可以通过`web`进行查看自定义的属性显示，具体规范请点击 [$\\mathit {sui\\_getObject}$](https://docs.sui.io/sui-api-ref#sui_getobject)，不要忘记将其中的 $\\mathit {showDisplay}$ 填为 $\\mathit {true}$。\r\n\r\n# 三：代码实现\r\n\r\n国王：\r\n\r\n- 创建 $\\mathit {king}$ 的时候需要传入一个 $\\mathit {witness}$，以此来进行限制。\r\n- 提升能力值需要 $\\mathit {publisher}$，且其要与 $\\mathit {King}$ 结构定义所处同一个包内。\r\n\r\n```move\r\nmodule king_knight::king {\r\n    use sui::object::{Self, UID};\r\n    use sui::tx_context::{Self, TxContext};\r\n    use sui::transfer;\r\n    use sui::types;\r\n    use sui::package::{Self, Publisher};\r\n\r\n    const ENOTWITNESS: u64 = 0;\r\n    const ENOTPACKAGE: u64 = 1;\r\n\r\n    struct King has key {\r\n        id: UID,\r\n        ability: u64,\r\n    }\r\n\r\n    public fun create_king<T: drop>(witness: T, ctx: &mut TxContext) {\r\n        assert!(types::is_one_time_witness(&witness), ENOTWITNESS);\r\n        transfer::transfer(King {\r\n            id: object::new(ctx),\r\n            ability: 66,\r\n        }, tx_context::sender(ctx));\r\n    }\r\n\r\n    entry fun rise(publisher: &Publisher, king: &mut King) {\r\n        assert!(package::from_package<King>(publisher), ENOTPACKAGE);\r\n        king.ability = king.ability + 1;\r\n    }\r\n\r\n    public fun get_ability(king: &King): u64 {\r\n        king.ability\r\n    }\r\n}\r\n```\r\n\r\n勇士：\r\n\r\n- 创建函数不加限制，因为所有人都可以拥有属于自己的勇士。\r\n- 晋升后勇士对象将不复存在，没有为它赋予 $\\mathit {drop}$ 能力，所以需要手动解构。<br>这里稍微有点特殊，晋升为骑士后的属性是一致的，所以需要将 $\\mathit {name},\\ \\mathit {ability}$ 用类似于 $\\mathit {python}$ 当中的元组的形式作为返回值。\r\n\r\n```move\r\nmodule king_knight::warrior {\r\n    use sui::object::{Self, UID};\r\n    use sui::tx_context::{Self, TxContext};\r\n    use sui::transfer;\r\n    use std::string::String;\r\n\r\n    struct Warrior has key {\r\n        id: UID,\r\n        name: String,\r\n        ability: u64,\r\n    }\r\n\r\n    entry fun create_warrior(name: String, ability: u64, ctx: &mut TxContext) {\r\n        transfer::transfer(Warrior {\r\n            id: object::new(ctx),\r\n            name,\r\n            ability,\r\n        }, tx_context::sender(ctx));\r\n    }\r\n\r\n    entry fun rise(warrior: &mut Warrior) {\r\n        warrior.ability = warrior.ability + 1;\r\n    }\r\n\r\n    public fun get_ability(warrior: &Warrior): u64 {\r\n        warrior.ability\r\n    }\r\n\r\n    public fun destroy(warrior: Warrior): (String, u64) {\r\n        let Warrior{id, name, ability} = warrior;\r\n        object::delete(id);\r\n        (name, ability)\r\n    }\r\n}\r\n```\r\n\r\n骑士：\r\n\r\n- $\\mathit {init}$ 当中用 $\\mathit {otw}$ 生成 $\\mathit {publisher}$，再借助其创建 $\\mathit {display}$，其中的 $\\mathit {keys}$ 和 $\\mathit {values}$ 想要更改的话可以用 $\\mathit {add\\_multiple},\\ \\mathit {edit},\\ \\mathit {remove}$ 等`sui::display`当中的函数进行操作，具体请点击[这里](https://github.com/MystenLabs/sui/blob/main/crates/sui-framework/packages/sui-framework/sources/display.move)，当然，最后别忘记调用 $\\mathit {update\\_version}$ 来触发事件，使网络中各个完整节点监听此事件并获取该类型的新显示模板。\r\n- 晋升为骑士后无法再对其进行更改，所以使用 $\\mathit {freeze\\_object}$ 将其所有权变更为不可变共享。\r\n\r\n```move\r\nmodule king_knight::knight {\r\n    use sui::object::{Self, UID};\r\n    use sui::tx_context::{Self, TxContext};\r\n    use sui::transfer;\r\n    use std::string::{Self, String};\r\n    use sui::package;\r\n    use sui::display;\r\n\r\n    struct KNIGHT has drop {}\r\n\r\n    struct Knight has key {\r\n        id: UID,\r\n        name: String,\r\n        ability: u64,\r\n    }\r\n\r\n    fun init(otw: KNIGHT, ctx: &mut TxContext) {\r\n        let keys = vector[\r\n            string::utf8(b\"name is\"),\r\n            string::utf8(b\"ability is\"),\r\n        ];\r\n\r\n        let values = vector[\r\n            string::utf8(b\"{name}\"),\r\n            string::utf8(b\"{ability}\"),\r\n        ];\r\n\r\n        let publisher = package::claim(otw, ctx);\r\n\r\n        let display = display::new_with_fields<Knight>(&publisher, keys, values, ctx);\r\n        display::update_version(&mut display);\r\n\r\n        transfer::public_transfer(publisher, tx_context::sender(ctx));\r\n        transfer::public_transfer(display, tx_context::sender(ctx));\r\n    }\r\n\r\n    public fun create_knight(name: String, ability: u64, ctx: &mut TxContext) {\r\n        let knight = Knight {\r\n            id: object::new(ctx),\r\n            name,\r\n            ability,\r\n        };\r\n        transfer::freeze_object(knight);\r\n    }\r\n}\r\n```\r\n\r\n交互：\r\n\r\n- 通过 $\\mathit {otw}$ 创建另一个模块当中的国王。\r\n- 晋升时判断能力值，如果满足条件，则将勇士解构后得到的信息用于创建骑士。\r\n\r\n```move\r\nmodule king_knight::interact {\r\n    use sui::tx_context::TxContext;\r\n\r\n    use king_knight::warrior::{Self, Warrior};\r\n    use king_knight::king::{Self, King};\r\n    use king_knight::knight::create_knight;\r\n\r\n    const ENOTENOUGHABILITY: u64 = 0;\r\n\r\n    struct INTERACT has drop {}\r\n\r\n    fun init(otw: INTERACT, ctx: &mut TxContext) {\r\n        king::create_king(otw, ctx);\r\n    }\r\n\r\n    entry fun rise(warrior: Warrior, king: &King, ctx: &mut TxContext) {\r\n        assert!(warrior::get_ability(&warrior) >= king::get_ability(king), ENOTENOUGHABILITY);\r\n        let (name, ability) = warrior::destroy(warrior);\r\n        create_knight(name, ability, ctx);\r\n    }\r\n}\r\n```\r\n\r\n# 四：发布并调用\r\n\r\n`sui client publish --gas-budget 100000000`\r\n\r\n根据发布成功的信息，$\\mathit {export}$ 几个值，方便后续调用。\r\n\r\n```\r\nexport PACKAGE_ID=0x4f81d54db52fee57cae7f6d22e0746f729c59b3ad4a8f513857cbd57417d18e8\r\nexport PUBLISHER=0x44c23e62e75a1c765dc10e48212cf5f35d658d84813e1caf19feefd619437c62\r\nexport KING=0xaf25300b110c4a41e52dbdfa998a36de88b3c2cdf25757814ac2e7aa5571d3c3\r\n```\r\n\r\n其中，可以通过`sui client object $KING`来查看国王的能力值是否为默认设定的 $\\text {66}$。\r\n\r\n我们用`sui client call --package $PACKAGE_ID --module warrior --function create_warrior --args Nigdle 65 --gas-budget 100000000`来创建一个 $\\mathit {warrior}$，姓名为 $\\mathit {Nigdle}$，初始能力值设定为 $\\text {65}$。<br>别忘记把它的对象地址也 $\\mathit {export}$ 一下`export WARRIOR=0xb6c63d92e7042cc4ede8201ac515ce54aac62808b1e5a237830f093d8386481c`。<br>此时如果尝试晋升为骑士，会得到如下报错：\r\n\r\n```\r\nError executing transaction: Failure {\r\n    error: \"MoveAbort(MoveLocation { module: ModuleId { address: 4f81d54db52fee57cae7f6d22e0746f729c59b3ad4a8f513857cbd57417d18e8, name: Identifier(\\\"interact\\\") }, function: 1, instruction: 10, function_name: Some(\\\"rise\\\") }, 0) in command 0\",\r\n}\r\n```\r\n\r\n这是因为能力值不足，通过`sui client call --package $PACKAGE_ID --module warrior --function rise --args $WARRIOR --gas-budget 100000000`提升至 $\\text {66}$，至于是否提升成功，可以通过`sui client object $WARRIOR`来查看。\r\n\r\n这个时候再通过`sui client call --package $PACKAGE_ID --module interact --function rise --args $WARRIOR $KING --gas-budget 100000000`尝试晋升为骑士，就成功了。\r\n\r\n原来的 $\\mathit {warrior}$ 已经不复存在，`sui client object $WARRIOR`也将报错说这个对象已经被删除。<br>复制下来新创建的骑士的对象地址，用类似的命令去查看里面存储的内容，发现它的名字是 $\\mathit {Nigdle}$，能力值是 $\\text {66}$。这是一个共享的不可变对象，谁都可见，同时谁都无法进行更改。<br>如果你有条件，可以通过 [$\\mathit {sui\\_getObject}$](https://docs.sui.io/sui-api-ref#sui_getobject) 进行实验并查看 $\\mathit {Knight}$ 在 $\\mathit {web}$ 上的显示是否如同预期的那样。\r\n\r\n接下去，国王在神明$\\mathit (publisher)$授意下提升自身能力：<br>`sui client call --package $PACKAGE_ID --module king --function rise --args $PUBLISHER $KING --gas-budget 100000000`<br>这个时候，如果创建一个能力值为 $\\text {66}$ 的 $\\mathit {warrior}$ 就无法再晋升，而那一位名为 $\\mathit {Nigdle}$ 的骑士由于占尽先机，就与其形成了鲜明的对比，但 $\\mathit {Nigdle}$ 的上限也到此为止了。\r\n\r\n**注意：** 本篇内容仅针对初学者，目的是将这三者尽可能串联起来，形成一个不那么枯燥又容易理解的例子，所以设计过程及最终呈现可能存在优化空间。\r\n\r\n# 五：加入组织，共同进步！\r\n\r\n- [Sui 中文开发群(TG)](https://t.me/move_cn)\r\n- $\\mathit{Move}$ 语言学习交流群: 79489587"},"author":{"user":"https://learnblockchain.cn/people/19165","address":null},"history":"bafkreibfn7m5jf2flvklokkv6t5g74lghimjnezqpu3iwkw4kfrnrayw4i","timestamp":1711756978,"version":1}