One Time Witness, Publisher, Object Display...
国王将从勇士当中挑选有能力的晋升为骑士,整个过程可以分成如下几点:
思考如何通过一次性见证$\mathit {(One\ Time\ Witness)}$,发布者$\mathit {(Publisher)}$以及对象显示$\mathit {(Object\ Display)}$来实现?
注意: 本篇内容仅针对初学者,目的是将这三者尽可能串联起来,形成一个不那么枯燥又容易理解的例子,所以设计过程及最终呈现可能存在优化空间。
一次性见证$\mathit {(One\ Time\ Witness,\ OTW)}$是一种特殊类型的实例,该类型的定义需要具备如下条件:
$\mathit {OTW}$ 只在模块初始化器中创建,并保证是唯一的,可以用types::is_one_time_witness(&witness)
来判断传入的 $\mathit {witness}$ 是不是 $\mathit {OTW}$。
借助 $\mathit {OTW}$,对创建国王的函数进行限制,以此来保证其唯一性。
发布者$\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}$ 移交给发布者。
借助发布者的权限,比肩神明,唯有此方能指导国王能力提升。
拥有 $\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"
}
当MyObject
和Display<MyObject>
匹配时,可以通过web
进行查看自定义的属性显示,具体规范请点击 $\mathit {sui_getObject}$,不要忘记将其中的 $\mathit {showDisplay}$ 填为 $\mathit {true}$。
国王:
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
}
}
勇士:
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)
}
}
骑士:
sui::display
当中的函数进行操作,具体请点击这里,当然,最后别忘记调用 $\mathit {update_version}$ 来触发事件,使网络中各个完整节点监听此事件并获取该类型的新显示模板。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<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);
}
}
交互:
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}$ 的上限也到此为止了。
注意: 本篇内容仅针对初学者,目的是将这三者尽可能串联起来,形成一个不那么枯燥又容易理解的例子,所以设计过程及最终呈现可能存在优化空间。
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!