本篇笔记学习 Move 合约的父子对象和灵魂绑定
由于 Move 合约中没有区分一个对象转移给的是一个账户还是一个对象 ID,所以当一个对象转交给另一个对象时,这两个对象就形成了父子关系。
// 定义父对象数据结构
public struct Parent has key {
id:UID,
name: String,
}
// 定义子对象数据结构
public struct Child has key {
id:UID,
description: String,
}
// 创建父对象,并转移给发送者
public entry fun new_parent(ctx: &mut TxContext) {
transfer::transfer(Parent{
id: object::new(ctx),
name: string::utf8(b"Ch1hiro"),
}, tx_context::sender(ctx));
}
// 创建子对象,并转移给父对象
public entry fun new_child(parent_id: address, ctx: &mut TxContext) {
transfer::transfer(Child{
id: object::new(ctx),
description: string::utf8(b"this is a description"),
}, parent_id);
}
当对象属于某个账户时,可以通过上下文来访问该对象,如果对象属于某一个对象时,需要通过对象来访问。在嵌套对象中可以通过访问对象字段的方式去访问被嵌套对象。独有对象的值并不在对象中保存,自然不能像嵌套对象那样访问,那独有对象该怎么访问呢?
嵌套对象:被嵌套对象必须拥有 store 能力,直接或间接的的作为嵌套对象的一个字段
在 Move 的 transfer
模块中提供了 receive
和 public_receive
方法,
// transfer::receive()
public fun receive<T: key>(parent: &mut UID, to_receive: Receiving<T>): T {
let Receiving { id, version } = to_receive;
receive_impl(parent.to_address(), id, version)
}
该函数接收一个 可变引用的父对象 UID 和 一个具有 key 能力的对象,最终返回一个子对象
下面实际操作一下,用 receive
函数返回 Child 对象并修改字段值,最后再重新转移给父对象
// to_receive 参数直接传入 Child 对象的 ID 即可
public entry fun modify_child(parent: &mut Parent, to_receive: Receiving<Child>, _: &mut TxContext) {
let mut child = transfer::receive<Child>(&mut parent.id, to_receive);
child.description = string::utf8(b"have changed");
transfer::transfer(child, object::uid_to_address(&parent.id));
}
上面的 receive
函数只能用于父对象和子对象在同一个模块内,如果在不同模块则要使用 public_receive
函数
// transfer::public_receive()
public fun public_receive<T: key + store>(parent: &mut UID, to_receive: Receiving<T>): T {
let Receiving { id, version } = to_receive;
receive_impl(parent.to_address(), id, version)
}
public_receive
函数接受一个具有 key 和 store 能力的对象,因为该对象需要能在全局存储,也就是能被在模块外访问
接下来在两个模块中分别创建父对象和子对象:
module object_study::object_child_study {
use std::string::{ Self, String };
// 定义子对象数据结构
public struct Child has key, store {
id:UID,
description: String,
}
// 创建子对象
public fun new(ctx: &mut TxContext): Child {
Child{
id: object::new(ctx),
description: string::utf8(b"this is a description"),
}
}
// 修改子对象的字段值
public fun modify(child: &mut Child) {
child.description = string::utf8(b"have changed");
}
}
module object_study::object_parent_study {
use std::string::{ Self, String };
use sui::transfer::{ Receiving };
use object_study::object_child_study::{ Child, new, modify };
// 定义父对象数据结构
public struct Parent has key {
id:UID,
name: String,
}
// 创建父对象,并转移给发送者
public entry fun new_parent(ctx: &mut TxContext) {
transfer::transfer(Parent{
id: object::new(ctx),
name: string::utf8(b"Ch1hiro"),
}, tx_context::sender(ctx));
}
public entry fun new_child(parent: &Parent, ctx: &mut TxContext) {
let child = new(ctx);
transfer::public_transfer(child, object::uid_to_address(&parent.id));
}
// to_receive 参数直接传入 Child 对象的 ID 即可
public entry fun modify_child(parent: &mut Parent, to_receive: Receiving<Child>, _: &mut TxContext) {
let mut child = transfer::public_receive<Child>(&mut parent.id, to_receive);
modify(&mut child);
transfer::public_transfer(child, object::uid_to_address(&parent.id));
}
}
对象的创建和修改必须在定义的模块内进行,模块外只能调用相关的函数进行操作
下面介绍一种特殊的父子对象——灵魂绑定,可以从父对象中取出子对象访问,但是最后必须要归还给父对象
struct Child has key {
id: UID,
}
//归还子对象的清单,没有 drop 能力,不允许自动析构
struct ReturnReceipt {
object_id: ID,
return_to: address,
}
public fun get_object(parent: &mut UID, soul_bound_ticket: Receiving<Child>): (Child, ReturnReceipt) {
let soul_bound = transfer::receive(parent, soul_bound_ticket);
let return_receipt = ReturnReceipt {
return_to: object::uid_to_address(parent),
object_id: object::id(&soul_bound),
};
(soul_bound, return_receipt)
}
public fun return_object(soul_bound: Child, receipt: ReturnReceipt) {
let ReturnReceipt { return_to, object_id } = receipt;
assert!(object::id(&soul_bound) == object_id, EWrongObject);
sui::transfer::transfer(soul_bound, return_to);
}
这里 ReturnReceipt 是一个 Hot Potato,用于记录灵魂绑定的对象和要归还的地址。get_object
函数获取到一个子 Child 和 ReturnReceipt,并在return_object
函数中归还子对象并销毁 ReturnReceipt,这样父与子将永远绑定在一起。
Hot Potato(热土豆):一个没有任何能力的结构体,如果 A 函数返回一个 Hot Potato,B 函数销毁它,那么必须在函数 A 调用后调用 B 函数
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!