跳到主要内容

注册绑定

当新用户注册使用产品时,可以为其链上地址发送一个绑定注册信息的SBT(SoulBound Token, 可以理解为不可被转移的NFT).

管理员权限 AdminCap

比起像第一单元的profile_clock示例代码中无许可的mint函数,更常见的实现方式是收到注册信息后,使用管理员权限去注册用户。

sign_up示例代码将原有的无许可注册方式改为了需要管理员权限的注册方式。

首先定义了AdminCap管理员权限Object.

public struct AdminCap has key, store {
id: UID,
}

生成AdminCap的功能放在init函数内,这是部署合约时,会自动执行而且只会执行一次的函数。

fun init(ctx: &mut TxContext) {
let admin = AdminCap {
id: object::new(ctx),
};
transfer::public_transfer(admin, ctx.sender());
}

这段函数生成了AdminCap并将其发送给部署合约的用户地址。

在原有的mint函数中,添加了&AdminCap作为输入参数,这个参数并没有被实际使用,所以参数名为_admin添加了下划线前缀。在调用mint函数的时候,就必须输入&AdminCap的Object ID, 如果不是拥有AdminCap的地址去调用就会报错,这就限定了只有管理员才能调用mint函数。

public fun mint(_admin: &AdminCap, handle: String, recipient: address, ctx: &mut TxContext) {
let profile = new(handle, ctx);
transfer::transfer(profile, recipient);
}

NFT vs SBT

在这个新的示例代码中,会发现一个新的问题,为什么转移资产有的时候用transfer::transfer, 但有的时候又用transfer::public_transfer, 它们有什么区别?

在Sui中,Object可以被分为可以被随意转移和交易的Object和不可以被随意转移和交易的Object, 前者包括NFT和Coin, 后者适合用于SBT.

在示例代码中,AdminCap在定义时同时包含了keystore的能力,属于可以被随意转移的Object.

public struct AdminCap has key, store {
id: UID,
}

Profile结构只有key能力,属于不能被随意转移的Object.

public struct Profile has key {
id: UID,
handle: String,
points: u64,
last_time: u64,
}

对于可以被随意转移的Object, 可以在PTB或任何其他合约中,使用transfer模块public_transfer, public_freeze_object, public_share_object方法去处理该Object, 可以将其转发给其他账户,变为共享或冻结的状态。

但对于不能被随意转移的Object, 就只能在定义了该Object的module内,使用transfer模块transfer, freeze_object, share_object方法去处理。

Table 与查重

在为用户注册账号时,会避免为同一个账户或地址重复注册。这时候可以用Table数据结构实现去重复。 示例程序在原来代码基础上,另外定义了一个专门用来记录已注册账户信息的数据结构。

public struct HandleRecord has key {
id: UID,
record: Table<String, bool>,
}

在为新账户进行注册时,会使用handle信息作为key进行注册。

public fun mint(
_admin: &AdminCap,
handle_record: &mut HandleRecord,
handle: String,
recipient: address,
ctx: &mut TxContext
) {
table::add<String, bool>(&mut handle_record.record, handle, true);
let profile = new(handle, ctx);
transfer::transfer(profile, recipient);
}

其中,table::add<String, bool>(&mut handle_record.record, handle, true);如果是有重复的handle就会添加失败并让程序执行回退;true布尔值在这里只是作为占位作用,没有实际含义。

作业

在注册账号查重复时,不仅要为handle去重复,还要为address去重复。在本小节示例代码的基础上,另外增加一个查重address的数据结构。在注册账号时,禁止已经注册过的handleaddress再次注册。

Table, TableVec, VecMap, VecSet, vector

如果只是为了查重复,按照常规的编程习惯,应该用set之类的数据结构,而且Sui上也有定义了类似的数据结构,比如VecMap, VecSet. 那最终我们实现的时候,为什么却选择了Table数据结构?

在Sui上,任意Object都是有数据存储上限的,VecMapVecSet都是基于vector数据结构去构建,属于单一的Object, 当存入的数据过多时,会导致无法继续被调用。

TableTableVec在新增数据时,把新存入的数据作为单一的Object, 然后用dynamic_field动态属性的方式去将新存入数据的所有权绑定到TableTableVec上,这就支持了这两个数据结构可以无上限的添加数据。

作业

阅读这几个数据结构的源代码:

如果我们开发的应用当中,有推荐注册功能,并且需要把用户之间的推荐注册关系记录到区块链上。试着从这些数据结构中选择最合适的去构建,并写出Sui Move合约代码。