帖子
分享您的知识。
使用 public_receive 进行跨模块儿童管理
这是 “Sui Move 中的亲子对象” 系列的第 3 部分.
有时,您的父类型和子类型是在不同的模块甚至不同的包中定义的. 例如,您可能有一个可以存储任何类型的 Parcel 对象的通用仓库对象. 仓库模块想要提取一个包裹子节点,但是包裹类型是在其他地方定义的. 在这种情况下,我们使用 transfer:: public_receive,它是 receive 的跨模块表兄弟.
###接收 vs public_receive
正如我们所见,transfer:: receive
transfer:: public_receive
为什么需要商店?因为存储标志着该对象可以安全地保存并在其定义模块之外传递. 仅限密钥的对象可能具有原始模块希望在传输/接收时强制执行的自定义不变量;通过排除public_receive中的那些不变量,Sui会强制开发人员在模块内处理它们(正如我们将在灵魂绑定对象中看到的那样). 如果一个对象有存储,则更宽松,而且 Sui 允许使用通用的传输/接收逻辑在外部对其进行管理.
###示例:分离父模块和子模块
让我们用一个简单的场景来说明一下:一个存储 Parcel 对象的仓库. 包裹类型在自己的模块中定义,仓库在另一个模块中定义. 我们将展示仓库如何使用 public_receive 接收子包裹.
module demo::parcel { // Child module
use sui::object::{Self, UID};
use sui::tx_context::{Self, TxContext};
/// A parcel object that can be stored in a Warehouse.
/// It has both key and store, so it can be transferred across modules.
struct Parcel has key, store { id: UID, contents: vector<u8> }
public entry fun create_parcel(contents: vector<u8>, ctx: &mut TxContext): Parcel {
Parcel { id: object::new(ctx), contents }
}
}
module demo::warehouse { // Parent module
use sui::transfer::{Self, Receiving, public_receive};
use demo::parcel::{Self, Parcel};
use sui::object::{UID};
use sui::tx_context::{Self, TxContext};
struct Warehouse has key { id: UID, location: address }
public entry fun create_warehouse(location: address, ctx: &mut TxContext): Warehouse {
Warehouse { id: object::new(ctx), location }
}
/// Receive a Parcel that was sent to this Warehouse.
/// Returns the Parcel to the caller (transferred to caller's address).
public entry fun withdraw_parcel(
warehouse: &mut Warehouse,
parcel_ticket: Receiving<Parcel>,
ctx: &mut TxContext
): Parcel {
// Using public_receive because Parcel is defined in another module and has store
let parcel = public_receive<Parcel>(&mut warehouse.id, parcel_ticket) [oai_citation_attribution:27‡docs.sui.io](https://docs.sui.io/concepts/transfers/transfer-to-object#:~:text=%2F%2F%2F%20Given%20mutable%20,argument) [oai_citation_attribution:28‡github.com](https://github.com/MystenLabs/sui/blob/main/crates/sui-framework/packages/sui-framework/sources/transfer.move#:~:text=public%20fun%20public_receive,T%3E%29%3A%20T);
// Transfer the parcel to the transaction sender (so the caller gets ownership)
transfer::transfer(parcel, tx_context::sender(ctx));
// We return nothing because we've transferred the Parcel out to the caller.
}
}
让我们分解一下 redath_parcel 中发生了什么:
-我们打电话给 public_receive
**为什么要在包裹中加入店铺?**如果 Parcel 缺乏存储能力(即只有密钥),则
**函数调用模式:**要在交易中使用它们,流程为:
- **存款(转移到对象):**致电转移:: public_transfer(parcel_obj,@warehouse_id)将包裹发送到仓库. 这会将包裹的所有者标记为仓库. (我们在这里使用 public_transfer 是因为它在 Parcel 模块之外,而且 Parcel 有存储. 在包裹模块内,普通的传输也可以. )
**提款(收回):**稍后,致电撤回包裹(warehouse_obj,接收(包裹编号,...)). SDK 可以通过引用包裹的 ID 和最新版本来获取接收信息. 该函数将调用 public_receive 然后将包裹转移给你.
在拨打 redth_parcel 电话后,包裹的所有者回到了一个地址(你的),所以它又是一个普通的地址拥有的物品. 仓库不再拥有它.
**跨模块注意事项:**请注意,仓库模块需要了解包裹类型(我们使用 demo:: parcel:: Parcel). 这是因为我们将接收明确键入为接收
**为什么 public_receive 而不只是调用 receive?**如果我们尝试在
**别忘了家长的许可:**即使有 public_receive,你仍然需要那个 &mut warehouse.id. 我们之所以得到它,是因为撤回包裹在 Warehouse 的模块中并接受 &mut Warehouse. 因此,只有可以这样称呼的人(仓库的所有者)才能提取包裹. 如果仓库模块没有公开提供这样的函数,那么也没有人可以在外部对其子模块调用 public_receive. 因此,跨模块不会绕过父级的控制;它只允许父级的代码处理其未定义类型的子类型.
关于存储能力的说明:提供对象存储可以使其更加灵活,但限制稍微减少一点——任何具有父引用的模块都可以使用 public_receive 将其拉出. 如果你想限制对象的检索方式(例如,强制执行自定义逻辑或防止轻松提取),你可以故意将其设为仅限密钥. 我们将用灵魂束缚的物体来看这个例子. 在这种情况下,你可以实现自定义接收函数,而不是依赖 public_receive.
总结一下这部分:public_receive 是你管理其他模块中定义的子对象的好友,只要这些对象具有存储能力即可. 它允许您构建跨模块系统(例如我们的仓库/包裹),同时仍然尊重所有权和访问控制. 只要记得在子类型上添加存储,并在从模块外部将它们发送给父类型时使用 public_transfer 即可.
- Sui
- Architecture
Sui is a Layer 1 protocol blockchain designed as the first internet-scale programmable blockchain platform.