DynamicField(动态字段)将结构体和对象组合在一起的方式可以将动态字段看作是在对象结构上没有明确定义的不可见字段需要用到的模块以及函数:usesui::dynamic_field;例子:在Laptop中添加动态字段Sticker,key为Sticker
需要用到的模块以及函数:
use sui::dynamic_field;
例子: 在 Laptop
中添加动态字段 Sticker
, key 为 StickerName
public struct Laptop has key {
id: UID,
screen_size: u64,
model: u64,
}
public struct StickerName has copy, drop, store {
name: String,
}
public struct Sticker has key, store {
id: UID,
image_url: String,
}
dynamic_field::add
(增)
public fun add_sticker(laptop: &mut Laptop, name: String, image_url: String, ctx: &mut TxContext) {
let sticker_name = StickerName {name};
let sticker = Sticker {
id: object::new(ctx),
image_url};
dynamic_field::add(&mut laptop.id,sticker_name, sticker);
}
dynamic_field::remove
(删)
public fun remove_sticker(laptop: &mut Laptop, name: String) {
let sticker_name = StickerName { name };
dynamic_field::remove(&mut laptop.id, sticker_name);
}
dynamic_field::borrow_mut
(改)
public fun set_image_url(laptop: &mut Laptop, name: String, new_url: String) {
let sticker_name = StickerName {name};
let sticker_mut_reference: &mut Sticker = dynamic_field::borrow_mut(&mut laptop.id, sticker_name);
sticker_mut_reference.image_url = new_url;
}
dynamic_field::borrow
(查)
public fun read_image_url(laptop: &Laptop, name: String): String {
let sticker_name = StickerName {name};
let sticker_reference: &Sticker = dynamic_field::borrow(&laptop.id, sticker_name);
sticker_reference.image_url
}
dynamic_field::exists_
: 根据 key 来检查 value 是否存在
public fun extend_hat(sui_fren: &mut SuiFren, description: String, duration: u64) {
if (dynamic_object_field::exists_(&sui_fren.id, string::utf8(HAT_KEY))) {
let hat: &mut Hat = dynamic_field::borrow_mut(&mut sui_fren.id, string::utf8(HAT_KEY));
dynamic_field::add(&mut hat.id, EXTENSION_1, HatExtension1 {
description,
duration,
});
};
}
先来看一个例子:
为每个状态结构使用多个共享对象
struct PriceConfigs has key {
id: UID,
price_range: vector<u64>,
}
struct StoreHours has key {
id: UID,
open_hours: vector<vector<u8>>,
}
struct SpecConfigs has key {
id: UID,
specs_range: vector<u64>,
}
fun init(ctx: &mut TxContext) {
let price_configs = PriceConfigs {
id: object::new(ctx),
price_range: vector[1000, 5000],
};
let store_hours = StoreHours {
id: object::new(ctx),
open_hours: vector[vector[9, 12], vector[1, 5]],
};
let spec_configs = SpecConfigs {
id: object::new(ctx),
specs_range: vector[1000, 10000],
};
transfer::share(price_configs);
transfer::share(store_hours);
transfer::share(spec_configs);
}
一个购买 laptop 的函数(很长)
public fun purchase_laptop(price_configs: &PriceConfigs, store_hours: &SotreHours, &spec_configs: &SpecConfigs, laptop: String, price: u64, ctx: &mut TxContext) {
}
使用动态对象字段优化
struct StateConfigs has key {
id: UID,
}
const PRICE_CONFIGS: vector<u8> = b"PRICE_CONFIGS";
struct PriceConfigs has store {
price_range: vector<u64>,
}
const STORE_HOURS: vector<u8> = b"STORE_HOURS";
struct StoreHours has store {
open_hours: vector<vector<u8>>,
}
const SPEC_CONFIGS: vector<u8> = b"SPEC_CONFIGS";
struct SpecConfigs has store {
specs_range: vector<u64>,
}
fun init(ctx: &mut TxContext) {
let state_configs = StateConfigs {
id: object::new(ctx),
};
dynamic_fields::add(&mut state_configs.id, PRICE_CONFIGS, PriceConfigs {
id: object::new(ctx),
price_range: vector[1000, 5000],
});
dynamic_fields::add(&mut state_configs.id, STORE_HOURS, StoreHours {
id: object::new(ctx),
open_hours: vector[vector[9, 12], vector[1, 5]],
});
dynamic_fields::add(&mut state_configs.id, SPEC_CONFIGS, SpecConfigs {
id: object::new(ctx),
specs_range: vector[1000, 10000],
});
transfer::share(state_configs);
}
优化后的购买 laptop 函数, 只需要传入 state_configs 即可, 对调用者友好
public fun purchase_laptop(state_configs: &StateConfigs, laptop: String, price: u64, ctx: &mut TxContext) {
}
一般不建议添加超过10个动态字段,因为它们可能会散布在代码中,难以找到。有一种好方法可以解决这个问题,并且仍然能够轻松扩展现有的对象 → 将新添加的字段分组到一个单独的结构体中
use sui::dynamic_field;
struct Laptop has key {
id: Id,
}
const EXTENSION_1: u64 = 1;
struct PurchaseDetails has store {
customer_name: String,
street_address: String,
price: u64,
}
public fun add_purchase_details(laptop: &mut Laptop, customer_name: String, street_address: String, price: u64) {
dynamic_field::add(&mut laptop.id, EXTENSION_1, PurchaseDetails {
customer_name,
street_address,
price,
});
}
还可以使用相同的模式来增强已添加为另一个对象的动态对象字段的对象(Sticker)
use sui::dynamic_object_field;
use sui::dynamic_field;
struct Laptop has key {
id: Id,
}
struct Sticker has key, store {
id: Id,
}
const EXTENSION_1: u64 = 1;
struct StickerPurchaseDetails has store {
customer_name: String,
street_address: String,
price: u64,
}
public fun add_sticker_purchase_details(laptop: &mut Laptop, sticker_name: String, customer_name: String, street_address: String, price: u64) {
let sticker: &mut Sticker = dynamic_object_field::borrow_mut(laptop, sticker_name);
dynamic_field::add(&mut sticker.id, EXTENSION_1, StickerPurchaseDetails {
customer_name,
street_address,
price,
});
}
Dynamic Object Field
与 Dynamic Field
唯一的区别就是不会删除要添加的对象, Dynamic Field 会删除要添加的对象在区块链上的存储(链下会查不到 object id), 这与对象封装(object wrapping)时看到的副作用相同动态对象字段
添加到另一个 Object 后, 他的所有权归动态对象字段
本身所有dynamic_object_field
模块中例子
让 Laptop 对象拥有 Sticker 对象
struct Laptop has key {
id: UID,
}
struct Sticker has key, store {
id: UID,
image_url: String,
}
public fun add_sticker(laptop: &Laptop, sticker: Sticker) {
transfer::public_transfer(sticker, object::uid_to_address(&laptop.id));
}
将 Laptop 对象的 Sticker 移除, 需要使用 Receiving<T>
与 transfer::public_receive
public fun remove_sticker(laptop: &mut Laptop, sticker: Receiving<Sticker>) {
let sticker = transfer::public_receive(&mut laptop.id, sticker);
// Do something with the sticker
}
在发送 Transaction 时,所有者可以指定贴纸对象的地址
,Sui虚拟机将自动将其转换为Receiving<Sticker>
类型
transfer::public_receive
将其从定义其结构的 module 之外提取出来。 使用在定义其结构的 module 中, 使用 transfer::receive
提取思考一个 GiftBox 中存放礼物的例子
只需要一个礼物某种类型的礼物
struct GiftBox has key {
id: UID,
inner: SuiFren,
}
entry fun wrap_fren(fren: SuiFren, ctx: &mut TxContext) {
let gift_box = GiftBox {
id: object::new(ctx),
inner: fren,
};
transfer::transfer(gift_box, tx_context::sender(ctx));
}
我们想要礼物不仅仅为 1 个, 可以增加多个字段
struct GiftBox has key {
id: UID,
inner_1: SuiFren,
inner_2: SuiFren,
inner_3: SuiFren,
inner_4: SuiFren,
inner_5: SuiFren,
}
但是这样礼物数量始终是固定的, 这个时候可以使用 vector
struct GiftBox has key {
id: UID,
frens: vector<SuiFren>,
}
但是这样礼物类型始终是一样的, 这个时候可以使用 ObjectBag
use sui::object_bag::{Self, ObjectBag};
struct MyBag has key {
id: UID,
object_bag: ObjectBag,
}
public fun create_bag(ctx: &mut TxContext) {
transfer::transfer(MyBag {
id: object::new(ctx),
object_bag: object_bag::new(ctx),
}, tx_context::sender(ctx));
}
public fun add_to_bag<SomeObject>(my_bag: &mut MyBag, key: String, object: SomeObject) {
object_bag::add(&mut my_bag.object_bag, key, object);
}
ObjectBag
的语法和动态字段
很像
ObjectBag
不同,ObjectTable
只允许存储单一类型的对象vector
呢?
ObjectTable
的功能有限,但当用户想要为表中的不同对象分配特定的键名时,它很有用use sui::object_table::{Self, ObjectTable};
struct MyObject has key {
id: UID,
}
struct MyTable has key {
id: UID,
table: ObjectTable<String, MyObject>,
}
public fun create_table(ctx: &mut TxContext) {
transfer::transfer(MyTable {
id: object::new(ctx),
table: object_table::new(ctx),
}, tx_context::sender(ctx));
}
public fun add_to_table(my_bag: &mut MyBag, key: String, object: MyObject) {
object_table::add(&mut my_bag.object_bag, key, object);
}
从技术上来讲, Object id 和 address 是一样的, 都是 唯一标识符, 可以让我们识别并获得对象的数据(通过 id/address)
public fun get_object_id_from_address(object_addr: address): ID {
object::id_from_address(object_addr)
}
public fun get_object_address(object: &MyObject): address {
object::id_to_address(&object.id)
}
object::new(ctx)
: 函数原型
public fun new(ctx: &mut TxContext): UID {
UID {
id: ID { bytes: tx_context::fresh_object_address(ctx) },
}
}
fresh_object_address
是Move中的一种特殊类型函数 - 原生函数
fresh_object_address
可以看到用户的 Transaction Payload
并使用它来为对象生成特殊地址。它还使用一个计数器来跟踪在同一交易中已经创建了多少个对象利用生成的 object_id 的哈希值可以用作随机数函数的种子 (存在风险) (了解即可)
use sui::bcs;
public fun get_random_value(ctx: &mut TxContext): u64 {
let object_id = object::new(ctx);
let bytes = object::uid_to_bytes(&object_id);
let random_number = bcs::peel_u64(&mut bcs::new(bytes));
object::delete(object_id);
random_number
}
存在的问题
逻辑上需要合理
一个 Laptop电脑店
的例子
struct Laptop has key {
id: UID,
screen: Screen,
keyboard: Keyboard,
hard_drive: HardDrive,
}
struct Screen has key, store {
id: UID,
}
struct Keyboard has key, store {
id: UID,
}
struct HardDrive has key, store {
id: UID,
}
动态字段和动态对象字段是在Sui Move合约开发中处理复杂状态和对象关系的两种强大工具。动态字段允许开发者在对象中动态添加、移除或修改没有在对象结构中明确定义的字段,而动态对象字段则用于在对象之间建立所有权和引用关系,但不会从全局存储中移除被引用的对象。
动态字段和动态对象字段主要用于以下场景:
另外,Object Wrapping是另一种组合对象的方式,通过将一个对象存放在另一个对象中,可以在逻辑上建立它们之间的关系,但这会使被封装的对象从全局存储中移除,并且在Web界面上不可见。
最后,设计中要避免过度使用对象,尤其是在不需要独立存在的组件或当存在大量交互和依赖关系时。这不仅会增加理解和管理的复杂性,也可能影响用户交互和交易构建的简便性。
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!