Sui Move One Time Witness, Publisher, Object Display

  • Nigdle
  • 更新于 2024-03-30 08:02
  • 阅读 722

One Time Witness, Publisher, Object Display...

  • 国王有且只有一个,在神的指导下可以提升自身能力值。
  • 只要有能力,勇士可以有无数个,在自身努力...

一:概要

国王将从勇士当中挑选有能力的晋升为骑士,整个过程可以分成如下几点:

  • 国王有且只有一个,在神的指导下可以提升自身能力值。
  • 只要有能力,勇士可以有无数个,在自身努力之下可以提升能力值。
  • 晋升为骑士的条件是勇士的能力不低于国王的能力值,每一个骑士将公开自身信息但他/她的能力值也就此止步,无法再提升。

二:分析

思考如何通过一次性见证$\mathit {(One\ Time\ Witness)}$,发布者$\mathit {(Publisher)}$以及对象显示$\mathit {(Object\ Display)}$来实现?

注意: 本篇内容仅针对初学者,目的是将这三者尽可能串联起来,形成一个不那么枯燥又容易理解的例子,所以设计过程及最终呈现可能存在优化空间。

2.1 一次性见证

一次性见证$\mathit {(One\ Time\ Witness,\ OTW)}$是一种特殊类型的实例,该类型的定义需要具备如下条件:

  • 以模块的名字命名,下划线 $\mathit {_}$ 保留,但所有字母大写。
  • 只拥有 $\mathit {drop}$​ 能力修饰符。

$\mathit {OTW}$ 只在模块初始化器中创建,并保证是唯一的,可以用types::is_one_time_witness(&witness)来判断传入的 $\mathit {witness}$ 是不是 $\mathit {OTW}$。

借助 $\mathit {OTW}$,对创建国王的函数进行限制,以此来保证其唯一性。

2.2 发布者

发布者$\mathit {(Publisher)}$对象用于代表发布者的权限,它本身并不代表任何特定的用例,主要通过package::from_module<T>package::from_package<T>来检查传入的类型为 $\mathit T$ (泛型或指定一个类型)的参数与 $\mathit {publisher}$ 是否处在同一个模块或包中。

为了保证模块当中发布者的唯一性,需要用到上面提到的 $\mathit {OTW}$,通过package::claim_and_keep(otw, ctx)创建一个 $\mathit {publisher}$ 并将其所有权转移给发布者;如果不着急转移所有权,可以通过let publisher = package::claim(otw, ctx)来获得 $\mathit {publisher}$,接下去可以借助这个对象,来做一些其它的事情(比如定义对象显示等),但是在最后,不要忘记将它的所有权手动通过 $\mathit {transfer}$ 移交给发布者。

借助发布者的权限,比肩神明,唯有此方能指导国王能力提升

2.3 对象显示

拥有 $\mathit {Publisher}$ 对象的构建者可以通过sui::display模块来自定义对象的显示属性,以供生态系统在链下处理数据,所有属性都可以通过{property}语法访问同时作为字符串插入其中,例如:

{
    "name": "{name}",
    "link": "https://sui-heroes.io/hero/{id}",
    "img_url": "ipfs://{img_url}",
    "description": "A true Hero of the Sui ecosystem!",
    "project_url": "https://sui-heroes.io",
    "creator": "Unknown Sui Fan"
}

MyObjectDisplay<MyObject>匹配时,可以通过web进行查看自定义的属性显示,具体规范请点击 $\mathit {sui_getObject}$,不要忘记将其中的 $\mathit {showDisplay}$ 填为 $\mathit {true}$。

三:代码实现

国王:

  • 创建 $\mathit {king}$ 的时候需要传入一个 $\mathit {witness}$,以此来进行限制。
  • 提升能力值需要 $\mathit {publisher}$,且其要与 $\mathit {King}$ 结构定义所处同一个包内。
module king_knight::king {
    use sui::object::{Self, UID};
    use sui::tx_context::{Self, TxContext};
    use sui::transfer;
    use sui::types;
    use sui::package::{Self, Publisher};

    const ENOTWITNESS: u64 = 0;
    const ENOTPACKAGE: u64 = 1;

    struct King has key {
        id: UID,
        ability: u64,
    }

    public fun create_king<T: drop>(witness: T, ctx: &mut TxContext) {
        assert!(types::is_one_time_witness(&witness), ENOTWITNESS);
        transfer::transfer(King {
            id: object::new(ctx),
            ability: 66,
        }, tx_context::sender(ctx));
    }

    entry fun rise(publisher: &Publisher, king: &mut King) {
        assert!(package::from_package<King>(publisher), ENOTPACKAGE);
        king.ability = king.ability + 1;
    }

    public fun get_ability(king: &King): u64 {
        king.ability
    }
}

勇士:

  • 创建函数不加限制,因为所有人都可以拥有属于自己的勇士。
  • 晋升后勇士对象将不复存在,没有为它赋予 $\mathit {drop}$ 能力,所以需要手动解构。<br>这里稍微有点特殊,晋升为骑士后的属性是一致的,所以需要将 $\mathit {name},\ \mathit {ability}$ 用类似于 $\mathit {python}$ 当中的元组的形式作为返回值。
module king_knight::warrior {
    use sui::object::{Self, UID};
    use sui::tx_context::{Self, TxContext};
    use sui::transfer;
    use std::string::String;

    struct Warrior has key {
        id: UID,
        name: String,
        ability: u64,
    }

    entry fun create_warrior(name: String, ability: u64, ctx: &mut TxContext) {
        transfer::transfer(Warrior {
            id: object::new(ctx),
            name,
            ability,
        }, tx_context::sender(ctx));
    }

    entry fun rise(warrior: &mut Warrior) {
        warrior.ability = warrior.ability + 1;
    }

    public fun get_ability(warrior: &Warrior): u64 {
        warrior.ability
    }

    public fun destroy(warrior: Warrior): (String, u64) {
        let Warrior{id, name, ability} = warrior;
        object::delete(id);
        (name, ability)
    }
}

骑士:

  • $\mathit {init}$ 当中用 $\mathit {otw}$ 生成 $\mathit {publisher}$,再借助其创建 $\mathit {display}$,其中的 $\mathit {keys}$ 和 $\mathit {values}$ 想要更改的话可以用 $\mathit {add_multiple},\ \mathit {edit},\ \mathit {remove}$ 等sui::display当中的函数进行操作,具体请点击这里,当然,最后别忘记调用 $\mathit {update_version}$ 来触发事件,使网络中各个完整节点监听此事件并获取该类型的新显示模板。
  • 晋升为骑士后无法再对其进行更改,所以使用 $\mathit {freeze_object}$ 将其所有权变更为不可变共享。
module king_knight::knight {
    use sui::object::{Self, UID};
    use sui::tx_context::{Self, TxContext};
    use sui::transfer;
    use std::string::{Self, String};
    use sui::package;
    use sui::display;

    struct KNIGHT has drop {}

    struct Knight has key {
        id: UID,
        name: String,
        ability: u64,
    }

    fun init(otw: KNIGHT, ctx: &mut TxContext) {
        let keys = vector[
            string::utf8(b"name is"),
            string::utf8(b"ability is"),
        ];

        let values = vector[
            string::utf8(b"{name}"),
            string::utf8(b"{ability}"),
        ];

        let publisher = package::claim(otw, ctx);

        let display = display::new_with_fields&lt;Knight>(&publisher, keys, values, ctx);
        display::update_version(&mut display);

        transfer::public_transfer(publisher, tx_context::sender(ctx));
        transfer::public_transfer(display, tx_context::sender(ctx));
    }

    public fun create_knight(name: String, ability: u64, ctx: &mut TxContext) {
        let knight = Knight {
            id: object::new(ctx),
            name,
            ability,
        };
        transfer::freeze_object(knight);
    }
}

交互:

  • 通过 $\mathit {otw}$ 创建另一个模块当中的国王。
  • 晋升时判断能力值,如果满足条件,则将勇士解构后得到的信息用于创建骑士。
module king_knight::interact {
    use sui::tx_context::TxContext;

    use king_knight::warrior::{Self, Warrior};
    use king_knight::king::{Self, King};
    use king_knight::knight::create_knight;

    const ENOTENOUGHABILITY: u64 = 0;

    struct INTERACT has drop {}

    fun init(otw: INTERACT, ctx: &mut TxContext) {
        king::create_king(otw, ctx);
    }

    entry fun rise(warrior: Warrior, king: &King, ctx: &mut TxContext) {
        assert!(warrior::get_ability(&warrior) >= king::get_ability(king), ENOTENOUGHABILITY);
        let (name, ability) = warrior::destroy(warrior);
        create_knight(name, ability, ctx);
    }
}

四:发布并调用

sui client publish --gas-budget 100000000

根据发布成功的信息,$\mathit {export}$ 几个值,方便后续调用。

export PACKAGE_ID=0x4f81d54db52fee57cae7f6d22e0746f729c59b3ad4a8f513857cbd57417d18e8
export PUBLISHER=0x44c23e62e75a1c765dc10e48212cf5f35d658d84813e1caf19feefd619437c62
export KING=0xaf25300b110c4a41e52dbdfa998a36de88b3c2cdf25757814ac2e7aa5571d3c3

其中,可以通过sui client object $KING来查看国王的能力值是否为默认设定的 $\text {66}$。

我们用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>此时如果尝试晋升为骑士,会得到如下报错:

Error executing transaction: Failure {
    error: "MoveAbort(MoveLocation { module: ModuleId { address: 4f81d54db52fee57cae7f6d22e0746f729c59b3ad4a8f513857cbd57417d18e8, name: Identifier(\"interact\") }, function: 1, instruction: 10, function_name: Some(\"rise\") }, 0) in command 0",
}

这是因为能力值不足,通过sui client call --package $PACKAGE_ID --module warrior --function rise --args $WARRIOR --gas-budget 100000000提升至 $\text {66}$,至于是否提升成功,可以通过sui client object $WARRIOR来查看。

这个时候再通过sui client call --package $PACKAGE_ID --module interact --function rise --args $WARRIOR $KING --gas-budget 100000000尝试晋升为骑士,就成功了。

原来的 $\mathit {warrior}$ 已经不复存在,sui client object $WARRIOR也将报错说这个对象已经被删除。<br>复制下来新创建的骑士的对象地址,用类似的命令去查看里面存储的内容,发现它的名字是 $\mathit {Nigdle}$,能力值是 $\text {66}$。这是一个共享的不可变对象,谁都可见,同时谁都无法进行更改。<br>如果你有条件,可以通过 $\mathit {sui_getObject}$ 进行实验并查看 $\mathit {Knight}$ 在 $\mathit {web}$ 上的显示是否如同预期的那样。

接下去,国王在神明$\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}$ 的上限也到此为止了。

注意: 本篇内容仅针对初学者,目的是将这三者尽可能串联起来,形成一个不那么枯燥又容易理解的例子,所以设计过程及最终呈现可能存在优化空间。

五:加入组织,共同进步!

点赞 1
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
Nigdle
Nigdle
0xa745...Fe19
江湖只有他的大名,没有他的介绍。