帖子
分享您的知识。
+15
能力约束如何与异构集合中的动态字段相互作用?
我正在建立一个需要处理具有不同能力要求的多种资产类型的市场,我遇到了一些关于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
答案
7你遇到了Move类型和能力系统的核心,在设计异构市场时提出这些问题是绝对正确的.
首先,关于能力限制:使用 dynamic_field:: add<K, V> () 存储的任何值在编译时都必须具有存储能力. 这是 Move 中的一条硬规则:无论是在动态字段、表格还是类似结构中,没有存储的值都无法保存在存储器中. 尝试将非存储类型包装在结构中也行不通——这里的 MOVE 非常严格. 如果即使一个字段缺少存储,整个包装器也会丢失存储. 因此,不幸的是,这里没有包装解决方法. 只有密钥(且没有存储)的 Soulbound 资产将排除在写入全局存储空间的任何集合中,例如包或动态字段. 这在 Move 技能文档中有详细的记录.
至于将不同的资产类型存储在同一个集合中:如果它们都实现了密钥+存储,那么是的,一个 Bag 可以存储它们,但是在幕后,Move 并不能实现真正的多态性. 访问时必须知道类型,并且从编译器的角度来看,Bag 必须是同质的. 这引出了你的下一个问题:我们如何保持动态字段的类型安全?由于 Move 会对这些集合进行键入擦除,因此它不会记住里面的确切类型——你需要提供映射. 一种常见的模式是将每个资源包装在一个结构中,该结构包含 u8 枚举或 TypeName 等元数据,因此,当您检索对象时,您可以检查元数据并相应地向下转换或处理. 没有神奇的 RTTI;你用数据纪律来伪造它.
现在介绍幻像类型和见证模式——它们有助于编译时安全,但不能让你进行运行时自省. 所以是的,你可以创建资产
在你的市场设计中,这意味着你可能正在考虑混合策略. 对于常规 NFT 和其他可转让资产,您可以自由使用动态对象字段或 Bag,只要它们符合密钥+存储即可. 对于 soulbound 资产(这只是关键),最好的选择是将引用和关联元数据存储在并行元数据包中,或者使用不存储资产本身但通过 ID 和补充数据记录其存在的自定义列表类型. 它更复杂,但它使您可以灵活地正确执行传输语义.
参考文献
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并根据动态处理反序列化.
您可以通过以下方式设计一个支持可转让(NFT、密钥+存储)和灵魂绑定(仅限密钥)资产的Sui Move市场,同时保持类型安全和处理能力限制.
动态场的能力要求
是的,V 必须在编译时存储 dynamic_field:: add<K, V> () 和类似集合.
-要将任何值(包括动态字段和表)存储在全局存储中,都需要存储. -包装器类型无法解决此问题:如果将不存储的类型包装在结构中,则包装器也不能有存储,因为所有字段都必须有存储空间才能存储该结构(来源).
袋子里的异构存储
-Bag(或任何基于字段的动态集合)只能存储满足所需能力的类型. -对于动态对象字段,该值必须具有密钥 + 存储. -对于动态字段,该值必须有存储. -你不能将只有密钥的类型(例如灵魂绑定代币)存储在需要存储的包中. -这是移动类型系统的基本限制(来源).
类型安全和类型擦除
-动态字段在运行时会进行类型擦除. 访问时必须知道要检索的类型. -类型安全模式: -在值旁边存储类型元数据(例如,枚举或类型名称). -使用包含资产及其类型信息的包装结构. -检索时,在投射之前检查元数据. -示例图案: -存储清单 {asset_type: u8,...} 并使用 asset_type 来确定如何处理资产.
见证模式和幻影类型
-幻影类型和见证模式在编译时有帮助,而不是运行时.
-你可以将资产
- 混合资产类型的市场设计
-可转让资产 (NFT):使用密钥 + 存储,存储在动态对象字段或 Bag 中. -Soulbound 代币(仅限密钥):不能存储在需要存储的动态字段或包中. 必须单独处理它们,例如,仅存储元数据或引用(而不是对象本身). -有限制的自定义资产:只要它们有密钥 + 存储,你就可以存储它们. 如果不是,则需要单独的处理路径.
-动态字段或 Bag 中的所有值都必须有存储(以及动态对象字段的密钥). -您不能在这些集合中存储仅使用密钥的类型. -运行时的类型安全需要明确的元数据. -幻影类型/见证模式在编译时有帮助,而不是运行时.
谢谢 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
你遇到了Move类型和能力系统的核心,在设计异构市场时提出这些问题是绝对正确的.
首先,关于能力限制:使用 dynamic_field:: add<K, V> () 存储的任何值在编译时都必须具有存储能力. 这是 Move 中的一条硬规则:无论是在动态字段、表格还是类似结构中,没有存储的值都无法保存在存储器中. 尝试将非存储类型包装在结构中也行不通——这里的 MOVE 非常严格. 如果即使一个字段缺少存储,整个包装器也会丢失存储. 因此,不幸的是,这里没有包装解决方法. 只有密钥(且没有存储)的 Soulbound 资产将排除在写入全局存储空间的任何集合中,例如包或动态字段. 这在 Move 技能文档中有详细的记录.
至于将不同的资产类型存储在同一个集合中:如果它们都实现了密钥+存储,那么是的,一个 Bag 可以存储它们,但是在幕后,Move 并不能实现真正的多态性. 访问时必须知道类型,并且从编译器的角度来看,Bag 必须是同质的. 这引出了你的下一个问题:我们如何保持动态字段的类型安全?由于 Move 会对这些集合进行键入擦除,因此它不会记住里面的确切类型——你需要提供映射. 一种常见的模式是将每个资源包装在一个结构中,该结构包含 u8 枚举或 TypeName 等元数据,因此,当您检索对象时,您可以检查元数据并相应地向下转换或处理. 没有神奇的 RTTI;你用数据纪律来伪造它.
现在介绍幻像类型和见证模式——它们有助于编译时安全,但不能让你进行运行时自省. 所以是的,你可以创建资产并让 T 发生变化,但是 Asset 的每个实例化仍然必须满足集合的能力限制. 如果你想将资产和资产都存储在同一个地方,它们必须都实现存储,而SoulBoundToken可能不会. 你需要以不同的方式处理这些问题——可能使用引用或仅存储不可转让资产的元数据,而不是尝试存储对象.
在你的市场设计中,这意味着你可能正在考虑混合策略. 对于常规 NFT 和其他可转让资产,您可以自由使用动态对象字段或 Bag,只要它们符合密钥+存储即可. 对于 soulbound 资产(这只是关键),最好的选择是将引用和关联元数据存储在并行元数据包中,或者使用不存储资产本身但通过 ID 和补充数据记录其存在的自定义列表类型. 它更复杂,但它使您可以灵活地正确执行传输语义.
你知道答案吗?
请登录并分享。
Sui is a Layer 1 protocol blockchain designed as the first internet-scale programmable blockchain platform.