Sui.

帖子

分享您的知识。

赏金+15

Xavier.eth.
Jun 17, 2025
专家问答

能力约束如何与异构集合中的动态字段相互作用?

我正在建立一个需要处理具有不同能力要求的多种资产类型的市场,我遇到了一些关于Move类型系统的基本问题. 我想将不同的资产类型存储在同一个集合中,但它们有不同的能力:

-常规 NFT:key + store(可转让) -Soulbound 代币:key 仅限(不可转让) -具有转移限制的自定义资产

public struct Marketplace has key {
    id: UID,
    listings: Bag,  // Want to store different asset types here
}

// This works for transferable assets
public fun list_transferable<T: key + store>(
    marketplace: &mut Marketplace,
    asset: T,
    price: u64
) { /* ... */ }

// But how to handle soulbound assets?
public fun list_soulbound<T: key>(  // No store ability
    marketplace: &mut Marketplace,
    asset_ref: &T,  // Can only take reference
    price: u64
) { /* How do I store metadata about this? */ }

关键问题:

-能力要求:使用时dynamic_field::add<K, V>()V``store 编译时是否总是需要的?包装器类型能解决这个问题吗? -异构存储:单个 Bag 能否存储具有不同能力集(key + store + copyvskey + store)的对象,并在运行时以不同的方式处理它们? -类型安全:由于动态字段会执行类型擦除,因此在检索值时如何保持类型安全?存储类型元数据的模式是什么? -见证模式:能力限制如何与幻影类型一起使用?我可以将Asset<Type1>和存储Asset<Type2>在同一个集合中并稍后提取类型信息吗?

建立一个系统,在该系统中,NFT、soulbound 代币和受限资产都需要市场功能,但转移语义不同.

我尝试过包装器类型,每个能力集合有多个集合,单独的类型元数据存储. 每种方法都在类型安全性、燃气成本和复杂性之间进行权衡.

  • Sui
  • Architecture
1
7
分享
评论
.

答案

7
harry phan.
Jun 18 2025, 03:58

你遇到了Move类型和能力系统的核心,在设计异构市场时提出这些问题是绝对正确的.

首先,关于能力限制:使用 dynamic_field:: add<K, V> () 存储的任何值在编译时都必须具有存储能力. 这是 Move 中的一条硬规则:无论是在动态字段、表格还是类似结构中,没有存储的值都无法保存在存储器中. 尝试将非存储类型包装在结构中也行不通——这里的 MOVE 非常严格. 如果即使一个字段缺少存储,整个包装器也会丢失存储. 因此,不幸的是,这里没有包装解决方法. 只有密钥(且没有存储)的 Soulbound 资产将排除在写入全局存储空间的任何集合中,例如包或动态字段. 这在 Move 技能文档中有详细的记录.

至于将不同的资产类型存储在同一个集合中:如果它们都实现了密钥+存储,那么是的,一个 Bag 可以存储它们,但是在幕后,Move 并不能实现真正的多态性. 访问时必须知道类型,并且从编译器的角度来看,Bag 必须是同质的. 这引出了你的下一个问题:我们如何保持动态字段的类型安全?由于 Move 会对这些集合进行键入擦除,因此它不会记住里面的确切类型——你需要提供映射. 一种常见的模式是将每个资源包装在一个结构中,该结构包含 u8 枚举或 TypeName 等元数据,因此,当您检索对象时,您可以检查元数据并相应地向下转换或处理. 没有神奇的 RTTI;你用数据纪律来伪造它.

现在介绍幻像类型和见证模式——它们有助于编译时安全,但不能让你进行运行时自省. 所以是的,你可以创建资产并让 T 发生变化,但是 Asset 的每个实例化仍然必须满足集合的能力限制. 如果你想将资产和资产都存储在同一个地方,它们必须都实现存储,而SoulBoundToken可能不会. 你需要以不同的方式处理这些问题——可能使用引用或仅存储不可转让资产的元数据,而不是尝试存储对象.

在你的市场设计中,这意味着你可能正在考虑混合策略. 对于常规 NFT 和其他可转让资产,您可以自由使用动态对象字段或 Bag,只要它们符合密钥+存储即可. 对于 soulbound 资产(这只是关键),最好的选择是将引用和关联元数据存储在并行元数据包中,或者使用不存储资产本身但通过 ID 和补充数据记录其存在的自定义列表类型. 它更复杂,但它使您可以灵活地正确执行传输语义.

参考文献

  1. https://github.com/MystenLabs/sui/blob/c7ec9546978b3c52b0c57bbdb9693f5068dd3383/external-crates/move/documentation/book/src/abilities.md
  2. https://github.com/sui-foundation/sui-move-intro-course/blob/92bb4986ad91c5ffd01c10c5b0d3bbbfa9d12507/unit-four/lessons/2_dynamic_fields.md
10
最佳答案
评论
.
Owen.
Owen4662
Jun 23 2025, 14:33

1. 动态字段中的值总是需要的store吗?

dynamic_field::add如果你想直接在动态字段(例如 viastore)中存储值,它必须满足该字段类型要求的所有能力——包括. 如果V没有store,则无法将其直接放入动态字段.

但诀窍在此:

你可以这样的类型包装在另一个具有的结构store中,即使内部类型没有.

示例:

struct AssetWrapper<T: key> has store {
    inner: T,
}

尽管T只有有key,但AssetWrapper<T>可以存储,因为它本身有store.

现在你可以做到:

dynamic_field::add(&mut object, key, AssetWrapper { inner: soulbound_token });

这使您可以在同一个抽象中存储可转让和不可转让的资产.


2. 一个包可以存放具有不同能力套装的物品吗?

简短的答案是:是的,使用包装器和文字擦除技术. 但是有一些权衡取舍.

在 Move 中,除非将多个具体类型打包在通用容器中,否则您无法直接获得多个具体类型的列表.

一个好的方法是使用像这样的包装器:

struct AnyAsset has store {
    // Maybe include metadata or witness type info
    data: Vector<u8>,     // serialized representation?
    type_info: TypeInfo, // Type metadata
}

或者更好的是,使用变体风格的枚举编码(如果您的 Move 方言支持):

enum AssetType has store {
    Transferable(NFT),
    Soulbound(SoulboundToken),
    Restricted(RestrictedAsset),
}

但是,这需要详尽的匹配,并且不能很好地跨模块扩展.

因此,再说一遍:包装 + 动态字段 + 类型元数据更加灵活.


3. 检索时如何保持类型安全?

Move 在动态字段中执行类型擦除. 因此,一旦你从动态字段中检索了一个值,你只会得到某种类型的值 V——但你不知道V运行时是什么.

为了安全地解决这个问题,你需要:

-将键入元数据存储在值旁边. -使用见证模式键入标签嵌入在检索时重新插入文字信息.

模式:存储类型元数据

struct AssetWithMetadata<T: key> has store {
    value: T,
    type_info: TypeInfo,
}

然后将AssetWithMetadata<NFT>或存储AssetWithMetadata<SoulboundToken>在动态字段中.

检索时,在type_info执行任何不安全操作之前,请先对照预期的类型进行检查.


4. 证人模式和幻影类型

你绝对可以使用幻像类型作为见证来编码行为,例如可转移性.

例如:

struct Transferable; // marker type
struct NonTransferable; // marker type

struct Asset<T: drop + copy + store + key, Policy: phantom> has key, store {
    id: UID,
    policy: PhantomData<Policy>,
}

然后:

type NFT = Asset<_, Transferable>;
type Soulbound = Asset<_, NonTransferable>;

然后你可以写这样的逻辑:

public fun transfer<T>(asset: Asset<T, Transferable>) {
    // allowed
}

public fun transfer<T>(_asset: Asset<T, NonTransferable>) {
    // disallowed
    abort();
}

这使您可以在编译时强制执行传输规则.

但是要将两种类型存储在同一个集合中,您仍然需要一个包装器:

struct AnyAsset has store {
    data: Vector<u8>,
    type_info: TypeInfo,
}

type_info并根据动态处理反序列化.

5
评论
.
0xduckmove.
Jun 18 2025, 04:11

您可以通过以下方式设计一个支持可转让(NFT、密钥+存储)和灵魂绑定(仅限密钥)资产的Sui Move市场,同时保持类型安全和处理能力限制.

动态场的能力要求

是的,V 必须在编译时存储 dynamic_field:: add<K, V> () 和类似集合.

-要将任何值(包括动态字段和表)存储在全局存储中,都需要存储. -包装器类型无法解决此问题:如果将不存储的类型包装在结构中,则包装器也不能有存储,因为所有字段都必须有存储空间才能存储该结构(来源).

袋子里的异构存储

-Bag(或任何基于字段的动态集合)只能存储满足所需能力的类型. -对于动态对象字段,该值必须具有密钥 + 存储. -对于动态字段,该值必须有存储. -你不能将只有密钥的类型(例如灵魂绑定代币)存储在需要存储的包中. -这是移动类型系统的基本限制(来源).

类型安全和类型擦除

-动态字段在运行时会进行类型擦除. 访问时必须知道要检索的类型. -类型安全模式: -在值旁边存储类型元数据(例如,枚举或类型名称). -使用包含资产及其类型信息的包装结构. -检索时,在投射之前检查元数据. -示例图案: -存储清单 {asset_type: u8,...} 并使用 asset_type 来确定如何处理资产.

见证模式和幻影类型

-幻影类型和见证模式在编译时有帮助,而不是运行时. -你可以将资产用于不同的类型,但你仍然需要确保所有 T 都具有收集所需的技能. -在运行时,您无法从虚拟类型提取类型信息;必须存储显式元数据(来源).

  1. 混合资产类型的市场设计

-可转让资产 (NFT):使用密钥 + 存储,存储在动态对象字段或 Bag 中. -Soulbound 代币(仅限密钥):不能存储在需要存储的动态字段或包中. 必须单独处理它们,例如,仅存储元数据或引用(而不是对象本身). -有限制的自定义资产:只要它们有密钥 + 存储,你就可以存储它们. 如果不是,则需要单独的处理路径.

-动态字段或 Bag 中的所有值都必须有存储(以及动态对象字段的密钥). -您不能在这些集合中存储仅使用密钥的类型. -运行时的类型安全需要明确的元数据. -幻影类型/见证模式在编译时有帮助,而不是运行时.

4
评论
.
md rifat hossen.
Jun 19 2025, 17:11

谢谢 Xavier.eth 提出这个深思熟虑的技术问题. 以下是我对 Sui Move 中能力限制和动态场地的贡献:

✅**是的,V中使用的任何值 () dynamic_field::add<K, V>()都必须具有store能力. ** 这在 Move 的编译时检查中是严格执行的. 即使将非存储值包装在结构中也无济于store事——如果缺少任何字段,整个结构也会丢失.

key + store存储具有不同能力的异构资产是可能的,但前提是动态场/包中的所有资产都必须满足相同的能力集(通常).

🔒只有key技能的 Soulbound 资产不能直接存储在此类集合中. 相反,你可以:

-仅存储元数据(ID、类型枚举等) -像dynamic_field::add<AssetID, Metadata>灵魂绑定类型一样使用并行映射. -保留一个基于参考的注册表,在不存储完整对象的情况下跟踪灵魂绑定资产清单.

🧩幻影类型帮助仅在编译时. 实际检索逻辑仍需要明确的运行时类型元数据.

🚀建议的混合架构: -Bag<AssetID, TransferableAsset>适用于以下资产 store -Map<AssetID, SoulboundMetadata>适用于仅限密钥的类型 -AssetWrapper带有type_id或的结构,u8 asset_type用于在运行时识别类型

再次感谢!我期待看到这个市场的发展!

— md rifat hossen

3
评论
.
BigDev.
Aug 15 2025, 16:25

你遇到了Move类型和能力系统的核心,在设计异构市场时提出这些问题是绝对正确的.

首先,关于能力限制:使用 dynamic_field:: add<K, V> () 存储的任何值在编译时都必须具有存储能力. 这是 Move 中的一条硬规则:无论是在动态字段、表格还是类似结构中,没有存储的值都无法保存在存储器中. 尝试将非存储类型包装在结构中也行不通——这里的 MOVE 非常严格. 如果即使一个字段缺少存储,整个包装器也会丢失存储. 因此,不幸的是,这里没有包装解决方法. 只有密钥(且没有存储)的 Soulbound 资产将排除在写入全局存储空间的任何集合中,例如包或动态字段. 这在 Move 技能文档中有详细的记录.

至于将不同的资产类型存储在同一个集合中:如果它们都实现了密钥+存储,那么是的,一个 Bag 可以存储它们,但是在幕后,Move 并不能实现真正的多态性. 访问时必须知道类型,并且从编译器的角度来看,Bag 必须是同质的. 这引出了你的下一个问题:我们如何保持动态字段的类型安全?由于 Move 会对这些集合进行键入擦除,因此它不会记住里面的确切类型——你需要提供映射. 一种常见的模式是将每个资源包装在一个结构中,该结构包含 u8 枚举或 TypeName 等元数据,因此,当您检索对象时,您可以检查元数据并相应地向下转换或处理. 没有神奇的 RTTI;你用数据纪律来伪造它.

现在介绍幻像类型和见证模式——它们有助于编译时安全,但不能让你进行运行时自省. 所以是的,你可以创建资产并让 T 发生变化,但是 Asset 的每个实例化仍然必须满足集合的能力限制. 如果你想将资产和资产都存储在同一个地方,它们必须都实现存储,而SoulBoundToken可能不会. 你需要以不同的方式处理这些问题——可能使用引用或仅存储不可转让资产的元数据,而不是尝试存储对象.

在你的市场设计中,这意味着你可能正在考虑混合策略. 对于常规 NFT 和其他可转让资产,您可以自由使用动态对象字段或 Bag,只要它们符合密钥+存储即可. 对于 soulbound 资产(这只是关键),最好的选择是将引用和关联元数据存储在并行元数据包中,或者使用不存储资产本身但通过 ID 和补充数据记录其存在的自定义列表类型. 它更复杂,但它使您可以灵活地正确执行传输语义.

2
评论
.

你知道答案吗?

请登录并分享。