一:概要
国王将从勇士当中挑选有能力的晋升为骑士,整个过程可以分成如下几点:
- 国王有且只有一个,在神的指导下可以提升自身能力值。
- 只要有能力,勇士可以有无数个,在自身努力之下可以提升能力值。
- 晋升为骑士的条件是勇士的能力不低于国王的能力值,每一个骑士将公开自身信息但他/她的能力值也就此止步,无法再提升。
二:分析
思考如何通过一次性见证 ( O n e T i m e W i t n e s s ) \mathit {(One\ Time\ Witness)} (One Time Witness),发布者 ( P u b l i s h e r ) \mathit {(Publisher)} (Publisher)以及对象显示 ( O b j e c t D i s p l a y ) \mathit {(Object\ Display)} (Object Display)来实现?
注意: 本篇内容仅针对初学者,目的是将这三者尽可能串联起来,形成一个不那么枯燥又容易理解的例子,所以设计过程及最终呈现可能存在优化空间。
2.1 一次性见证
一次性见证 ( O n e T i m e W i t n e s s , O T W ) \mathit {(One\ Time\ Witness,\ OTW)} (One Time Witness, OTW)是一种特殊类型的实例,该类型的定义需要具备如下条件:
- 以模块的名字命名,下划线 _ \mathit {\_} _ 保留,但所有字母大写。
- 只拥有 d r o p \mathit {drop} drop 能力修饰符。
O
T
W
\mathit {OTW}
OTW 只在模块初始化器中创建,并保证是唯一的,可以用types::is_one_time_witness(&witness)
来判断传入的
w
i
t
n
e
s
s
\mathit {witness}
witness 是不是
O
T
W
\mathit {OTW}
OTW。
借助 O T W \mathit {OTW} OTW,对创建国王的函数进行限制,以此来保证其唯一性。
2.2 发布者
发布者
(
P
u
b
l
i
s
h
e
r
)
\mathit {(Publisher)}
(Publisher)对象用于代表发布者的权限,它本身并不代表任何特定的用例,主要通过package::from_module<T>
和package::from_package<T>
来检查传入的类型为
T
\mathit T
T (泛型或指定一个类型)的参数与
p
u
b
l
i
s
h
e
r
\mathit {publisher}
publisher 是否处在同一个模块或包中。
为了保证模块当中发布者的唯一性,需要用到上面提到的
O
T
W
\mathit {OTW}
OTW,通过package::claim_and_keep(otw, ctx)
创建一个
p
u
b
l
i
s
h
e
r
\mathit {publisher}
publisher 并将其所有权转移给发布者;如果不着急转移所有权,可以通过let publisher = package::claim(otw, ctx)
来获得
p
u
b
l
i
s
h
e
r
\mathit {publisher}
publisher,接下去可以借助这个对象,来做一些其它的事情(比如定义对象显示等),但是在最后,不要忘记将它的所有权手动通过
t
r
a
n
s
f
e
r
\mathit {transfer}
transfer 移交给发布者。
借助发布者的权限,比肩神明,唯有此方能指导国王能力提升。
2.3 对象显示
拥有
P
u
b
l
i
s
h
e
r
\mathit {Publisher}
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
进行查看自定义的属性显示,具体规范请点击
s
u
i
_
g
e
t
O
b
j
e
c
t
\mathit {sui\_getObject}
sui_getObject,不要忘记将其中的
s
h
o
w
D
i
s
p
l
a
y
\mathit {showDisplay}
showDisplay 填为
t
r
u
e
\mathit {true}
true。
三:代码实现
国王:
- 创建 k i n g \mathit {king} king 的时候需要传入一个 w i t n e s s \mathit {witness} witness,以此来进行限制。
- 提升能力值需要 p u b l i s h e r \mathit {publisher} publisher,且其要与 K i n g \mathit {King} 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
}
}
勇士:
- 创建函数不加限制,因为所有人都可以拥有属于自己的勇士。
- 晋升后勇士对象将不复存在,没有为它赋予
d
r
o
p
\mathit {drop}
drop 能力,所以需要手动解构。
这里稍微有点特殊,晋升为骑士后的属性是一致的,所以需要将 n a m e , a b i l i t y \mathit {name},\ \mathit {ability} name, ability 用类似于 p y t h o n \mathit {python} 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)
}
}
骑士:
-
i
n
i
t
\mathit {init}
init 当中用
o
t
w
\mathit {otw}
otw 生成
p
u
b
l
i
s
h
e
r
\mathit {publisher}
publisher,再借助其创建
d
i
s
p
l
a
y
\mathit {display}
display,其中的
k
e
y
s
\mathit {keys}
keys 和
v
a
l
u
e
s
\mathit {values}
values 想要更改的话可以用
a
d
d
_
m
u
l
t
i
p
l
e
,
e
d
i
t
,
r
e
m
o
v
e
\mathit {add\_multiple},\ \mathit {edit},\ \mathit {remove}
add_multiple, edit, remove 等
sui::display
当中的函数进行操作,具体请点击这里,当然,最后别忘记调用 u p d a t e _ v e r s i o n \mathit {update\_version} update_version 来触发事件,使网络中各个完整节点监听此事件并获取该类型的新显示模板。 - 晋升为骑士后无法再对其进行更改,所以使用 f r e e z e _ o b j e c t \mathit {freeze\_object} 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<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);
}
}
交互:
- 通过 o t w \mathit {otw} 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
根据发布成功的信息, e x p o r t \mathit {export} export 几个值,方便后续调用。
export PACKAGE_ID=0x4f81d54db52fee57cae7f6d22e0746f729c59b3ad4a8f513857cbd57417d18e8
export PUBLISHER=0x44c23e62e75a1c765dc10e48212cf5f35d658d84813e1caf19feefd619437c62
export KING=0xaf25300b110c4a41e52dbdfa998a36de88b3c2cdf25757814ac2e7aa5571d3c3
其中,可以通过sui client object $KING
来查看国王的能力值是否为默认设定的
66
\text {66}
66。
我们用sui client call --package $PACKAGE_ID --module warrior --function create_warrior --args Nigdle 65 --gas-budget 100000000
来创建一个
w
a
r
r
i
o
r
\mathit {warrior}
warrior,姓名为
N
i
g
d
l
e
\mathit {Nigdle}
Nigdle,初始能力值设定为
65
\text {65}
65。
别忘记把它的对象地址也
e
x
p
o
r
t
\mathit {export}
export 一下export WARRIOR=0xb6c63d92e7042cc4ede8201ac515ce54aac62808b1e5a237830f093d8386481c
。
此时如果尝试晋升为骑士,会得到如下报错:
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
提升至
66
\text {66}
66,至于是否提升成功,可以通过sui client object $WARRIOR
来查看。
这个时候再通过sui client call --package $PACKAGE_ID --module interact --function rise --args $WARRIOR $KING --gas-budget 100000000
尝试晋升为骑士,就成功了。
原来的
w
a
r
r
i
o
r
\mathit {warrior}
warrior 已经不复存在,sui client object $WARRIOR
也将报错说这个对象已经被删除。
复制下来新创建的骑士的对象地址,用类似的命令去查看里面存储的内容,发现它的名字是
N
i
g
d
l
e
\mathit {Nigdle}
Nigdle,能力值是
66
\text {66}
66。这是一个共享的不可变对象,谁都可见,同时谁都无法进行更改。
如果你有条件,可以通过
s
u
i
_
g
e
t
O
b
j
e
c
t
\mathit {sui\_getObject}
sui_getObject 进行实验并查看
K
n
i
g
h
t
\mathit {Knight}
Knight 在
w
e
b
\mathit {web}
web 上的显示是否如同预期的那样。
接下去,国王在神明
(
p
u
b
l
i
s
h
e
r
)
\mathit (publisher)
(publisher)授意下提升自身能力:sui client call --package $PACKAGE_ID --module king --function rise --args $PUBLISHER $KING --gas-budget 100000000
这个时候,如果创建一个能力值为
66
\text {66}
66 的
w
a
r
r
i
o
r
\mathit {warrior}
warrior 就无法再晋升,而那一位名为
N
i
g
d
l
e
\mathit {Nigdle}
Nigdle 的骑士由于占尽先机,就与其形成了鲜明的对比,但
N
i
g
d
l
e
\mathit {Nigdle}
Nigdle 的上限也到此为止了。
注意: 本篇内容仅针对初学者,目的是将这三者尽可能串联起来,形成一个不那么枯燥又容易理解的例子,所以设计过程及最终呈现可能存在优化空间。
五:加入组织,共同进步!
- Sui 中文开发群(TG)
- M o v e \mathit{Move} Move 语言学习交流群: 79489587