Publication
Partagez vos connaissances.
+15
Comment les contraintes de capacité interagissent-elles avec les champs dynamiques dans des collections hétérogènes ?
Je suis en train de créer une place de marché qui doit gérer plusieurs types d'actifs avec des exigences de capacité différentes, et je me suis posé quelques questions fondamentales concernant le système de types de Move. Je souhaite stocker différents types de ressources dans la même collection, mais leurs fonctionnalités sont différentes :
- NFT classiques :
key + store
(transférables) - Jetons Soulbound :
key
uniquement (non transférables) - Actifs personnalisés avec restrictions de transfert
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? */ }
Questions clés :
- Compétences requises : lors de l'utilisation
dynamic_field::add<K, V>()
, en a-t-onV
toujours besoinstore
au moment de la compilation ? Les types de wrapper peuvent-ils contourner ce problème ? - Stockage hétérogène : un seul sac peut-il stocker des objets dotés de différents ensembles de capacités (
key + store + copy
vskey + store
) et les gérer différemment lors de l'exécution ? - Sécurité des types : étant donné que les champs dynamiques permettent d'effacer les caractères, comment puis-je garantir la sécurité des types lors de la récupération des valeurs ? Quel est le modèle de stockage des métadonnées de type ?
- Modèle de témoin : comment fonctionnent les contraintes de capacité avec les types de fantômes ? Puis-je stocker
Asset<Type1>
et stockerAsset<Type2>
dans la même collection et extraire les informations de type ultérieurement ?
Construire un système dans lequel les NFT, les jetons soul bound et les actifs restreints nécessitent tous des fonctionnalités de marché, mais avec une sémantique de transfert différente.
J'ai essayé des types de wrapper, plusieurs collections par ensemble de capacités, un stockage de métadonnées de type séparé. Chacune comporte des compromis entre la sécurité du type, les coûts du gaz et la complexité.
- Sui
- Architecture
Réponses
5Vous vous trouvez au cœur du système de types et de capacités de Move et vous avez tout à fait raison de vous poser ces questions lorsque vous concevez un marché hétérogène.
Tout d'abord, à propos des contraintes de capacité : toute valeur que vous stockez à l'aide de dynamic_field : :add<K, V> () doit avoir la capacité de stockage au moment de la compilation. Il s'agit d'une règle stricte dans Move : les valeurs sans stockage ne peuvent tout simplement pas être conservées dans le stockage, que ce soit dans des champs dynamiques, des tables ou des structures similaires. Essayer d'encapsuler un type hors magasin dans une structure ne fonctionnera pas non plus. MOVE est strict ici. Si ne serait-ce qu'un seul champ manque de place, l'ensemble du wrapper perd également de la place. Malheureusement, il n'y a pas de solution de contournement du wrapper ici. Les ressources Soulbound ne comportant qu'une clé (et aucune boutique) sont exclues de toute collection qui écrit dans un stockage mondial, comme un sac ou un champ dynamique. Ceci est bien documenté dans la documentation de Move abilities.
En ce qui concerne le stockage de différents types d'actifs dans la même collection : s'ils implémentent tous key + store, alors oui, un seul sac peut les stocker, mais sous le capot, Move ne fait pas de véritable polymorphisme. Le type doit être connu au moment de l'accès et le Bag doit être homogène du point de vue du compilateur. Cela m'amène à la question suivante : comment préserver la sécurité des types dans les champs dynamiques ? Étant donné que Move efface les caractères pour ces collections, il ne mémorisera pas le type exact qui se trouve à l'intérieur. Vous devez fournir le mappage. Un modèle courant consiste à encapsuler chaque actif dans une structure qui inclut des métadonnées telles qu'une énumération u8 ou un TypeName. Ainsi, lorsque vous récupérez l'objet, vous inspectez les métadonnées et vous les réduisez ou les gérez en conséquence. Il n'y a pas de RTTI magique ; vous pouvez le simuler en faisant preuve de discipline en matière de données.
Passons maintenant aux types de fantômes et aux modèles de témoins : ils sont utiles pour la sécurité au moment de la compilation mais ne vous permettent pas d'introspection lors de l'exécution. Donc oui, vous pouvez créer
Dans la conception de votre place de marché, cela signifie que vous envisagez probablement une stratégie hybride. Pour les NFT ordinaires et autres actifs transférables, vous pouvez utiliser librement des champs d'objets dynamiques ou des sacs, à condition qu'ils soient conformes à key + store. Pour les ressources liées à l'âme, qui ne sont que essentielles, le mieux est de stocker les références et les métadonnées associées dans un sac de métadonnées parallèle, ou d'utiliser un type de liste personnalisé qui ne stocke pas l'actif lui-même mais enregistre son existence via un identifiant et des données supplémentaires. C'est plus complexe, mais cela vous donne la flexibilité nécessaire pour appliquer correctement la sémantique des transferts.
Références
##1. Les valeurs des champs dynamiques sont-elles toujours nécessaires store
?
dynamic_field::add
Si vous souhaitezstockerla valeur directement dans un champ dynamique (par exemple, viastore
), celle-ci doit satisfaire à toutes les capacités requises par le type du champ, y compris. Si V
ce n'est pas store
le cas, vous ne pouvez pas le placer directement dans un champ dynamique.
Mais voici le truc :
Vouspouvez encapsulerde tels types dans une autre structure qui en possède
store
, même si le type interne ne le fait pas.
Exemple :
struct AssetWrapper<T: key> has store {
inner: T,
}
Même si c'T
est le key
cas, il AssetWrapper<T>
peut être stocké car il l'a lui-mêmestore
.
Vous pouvez maintenant faire :
dynamic_field::add(&mut object, key, AssetWrapper { inner: soulbound_token });
Cela vous permet de stocker des actifs transférables et non transférables sous la même abstraction.
##2. Un seul sac peut-il contenir des objets dotés de différents ensembles de capacités ?
La réponse courte est :oui, en utilisant des wrappers et des techniques d'effacement de caractères. Mais il y a des compromis.
Dans Move, vous ne pouvez pas avoir directement une liste de plusieurs types de béton à moins qu'ils ne soient emballés dans un conteneur générique.
Une bonne approche consiste à utiliser un wrapper tel que :
struct AnyAsset has store {
// Maybe include metadata or witness type info
data: Vector<u8>, // serialized representation?
type_info: TypeInfo, // Type metadata
}
Ou mieux encore, utilisez un encodage d'énumération de style variant (s'il est pris en charge par votre dialecte Move) :
enum AssetType has store {
Transferable(NFT),
Soulbound(SoulboundToken),
Restricted(RestrictedAsset),
}
Cependant, cela nécessite une correspondance exhaustive et ne s'adapte pas bien d'un module à l'autre.
Donc, encore une fois :encapsulation + champs dynamiques + type de métadonnéesest plus flexible.
#3. Comment garantir la sécurité du type lors de la récupération ?
Move effectue uneffacement de typedans les champs dynamiques. Ainsi, une fois que vous avez récupéré une valeur d'un champ dynamique, vous obtenez simplement une valeur d'un certain type, V
mais vous ne savez pas ce qu'il V
y a au moment de l'exécution.
Pour résoudre ce problème en toute sécurité, vous devez :
- Stockezsaisissez les métadonnéesà côté de la valeur.
- Utilisez unmodèle témoinou uneintégration de balises de typepour réaffirmer les informations de type lors de la récupération.
Modèle : stocker les métadonnées du type
struct AssetWithMetadata<T: key> has store {
value: T,
type_info: TypeInfo,
}
Stockez ensuite AssetWithMetadata<NFT>
ou AssetWithMetadata<SoulboundToken>
dans un champ dynamique.
Lors de la récupération, vérifiez le type par type_info
rapport au type attendu avant de faire quoi que ce soit de dangereux.
N° 4. Schéma des témoins et types de fantômes
Vous pouvez absolument utiliser des types fantômes comme témoins pour coder le comportement, comme la transférabilité.
Par exemple :
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>,
}
Puis :
type NFT = Asset<_, Transferable>;
type Soulbound = Asset<_, NonTransferable>;
Vous pouvez ensuite écrire une logique comme suit :
public fun transfer<T>(asset: Asset<T, Transferable>) {
// allowed
}
public fun transfer<T>(_asset: Asset<T, NonTransferable>) {
// disallowed
abort();
}
Cela vous permet d'appliquer les règles de transfert au moment de la compilation.
Mais pour stocker les deux types dans la même collection, vous aurez toujours besoin d'un emballage :
struct AnyAsset has store {
data: Vector<u8>,
type_info: TypeInfo,
}
type_info
Et gérez la désérialisation de manière dynamique en fonction de.
Voici comment vous pouvez concevoir une place de marché Sui Move qui prend en charge à la fois les actifs transférables (NFT, clé + boutique) et les actifs liés à l'âme (clé uniquement), tout en maintenant les contraintes de sécurité des types et de capacité de manipulation.
Exigences de capacité pour les champs dynamiques
Oui, V doit avoir un store au moment de la compilation pour dynamic_field : :add<K, V> () et les collections similaires.
- store est obligatoire pour toute valeur à stocker dans le stockage global, y compris les champs dynamiques et les tables.
- Les types de wrapper ne peuvent pas contourner ce problème : si vous encapsulez un type sans store dans une structure, le wrapper ne peut pas non plus avoir de store, car tous les champs doivent avoir un store pour que la structure ait store (source).
Stockage hétérogène dans un sac
- Le sac (ou toute autre collection dynamique basée sur des champs) ne peut stocker que les types qui répondent aux capacités requises.
- Pour les champs d'objets dynamiques, la valeur doit comporter key + store.
- Pour les champs dynamiques, la valeur doit être stockée.
- Vous ne pouvez pas stocker un type contenant uniquement une clé (par exemple, un jeton Soulbound) dans un sac qui doit être stocké.
- Il s'agit d'une restriction fondamentale du système de type Move (source).
Sécurité des types et effacement des types
- Les champs dynamiques sont effacés lors de l'exécution. Vous devez connaître le type que vous souhaitez récupérer au moment de l'accès.
- Schéma de sécurité du type :
- Stockez les métadonnées de type (par exemple, une énumération ou TypeName) à côté de la valeur.
- Utilisez une structure wrapper qui inclut à la fois les informations relatives à l'actif et à son type.
- Lors de la récupération, vérifiez les métadonnées avant le casting.
- Exemple de schéma :
- Store Listing {asset_type : u8,...} et utilisez asset_type pour déterminer comment gérer l'actif.
Modèles de témoins et types de fantômes
- Les types fantômes et le modèle témoin aident au moment de la compilation, pas au moment de l'exécution.
- Vous pouvez utiliser Asset
pour différents types, mais vous devez tout de même vous assurer que tous les T possèdent les capacités requises pour la collection. - Lors de l'exécution, vous ne pouvez pas extraire les informations de type d'un type fantôme ; vous devez stocker des métadonnées explicites (source).
- Conception du marché pour les types d'actifs mixtes
-
Actifs transférables (NFT) : utilisez key + store, stockez-les dans des champs d'objets dynamiques ou des sacs.
-
Jetons Soulbound (clé uniquement) : ne peuvent pas être stockés dans des champs dynamiques ou des sacs nécessitant un stockage. Vous devez les gérer séparément, par exemple en stockant uniquement les métadonnées ou les références (pas l'objet lui-même).
-
Ressources personnalisées avec restrictions : tant qu'elles ont clé + magasin, vous pouvez les stocker. Si ce n'est pas le cas, vous avez besoin d'un chemin de traitement distinct.
-
Toutes les valeurs des champs dynamiques ou des sacs doivent avoir une valeur store (et une clé pour les champs d'objets dynamiques).
-
Vous ne pouvez pas stocker des types dont la clé est la seule dans ces collections.
-
La sécurité des types lors de l'exécution nécessite des métadonnées explicites.
-
Les types de fantômes/modèles témoins aident au moment de la compilation, pas au moment de l'exécution.
Merci Xavier.ETH pour cette question technique et réfléchie. Voici ma contribution concernant les contraintes de capacité et les champs dynamiques dans Sui Move :
✅Oui, toute valeur (V
) utilisée dans dynamic_field::add<K, V>()
doit avoir une store
capacité.
Ceci est strictement appliqué dans les contrôles de compilation de Move. Même le fait d'encapsuler une valeur non stockée dans une structure n'aidera pas : si un champ manquestore
, la structure entière le perd également.
key + store
✅Il est possible de stocker des actifs hétérogènes dotés de capacités différentes, mais uniquement sous la contrainte que tous les actifs du champ dynamique/des BAGS doivent satisfaire au même ensemble de capacités(généralement).
🔒Les actifs Soulbound dotés uniquement d'une key
capacité ne peuvent pas être stockés directementdans de telles collections. Au lieu de cela, vous pouvez :
- Stockezuniquement les métadonnées(ID, type enum, etc.)
- Utilisez un mappage parallèle comme
dynamic_field::add<AssetID, Metadata>
pour les types Soulbound. - Conservez un registre basé sur des références qui suit les listes d'actifs Soulbound sans stocker l'objet complet.
🧩Les types fantômesaidentau moment de la compilation seulement. Vous aurez toujours besoin de métadonnées de type d'exécution explicites pour la logique de récupération réelle.
🚀Architecture hybride suggérée :
Bag<AssetID, TransferableAsset>
pour les actifs avecstore
Map<AssetID, SoulboundMetadata>
pour les types à clé uniquementAssetWrapper
structure avectype_id
ouu8 asset_type
pour identifier le type au moment de l'exécution
Merci encore ! J'ai hâte de voir cette place de marché évoluer !
— M. Rifat Hossen
Si vous créez une place de marché qui prend en charge plusieurs types d'actifs dotés de capacités différentes dans Sui Move, le principal défi est de savoir comment lescontraintes de capacitéde Move interagissent avec leschamps dynamiqueset les données hétérogènes. Vous ne pouvez pas stocker directement des valeurs sans cette store
capacité, donc oui. Lorsque vous utilisezdynamic_field::add<K, V>()
, la valeur V
doit avoir cette store
capacité au moment de la compilation. store
Cela signifie que vous ne pouvez pas stocker directement quelque chose comme un jeton Soulbound à moins qu'il ne soit enveloppé dans un type qui en a. Pour contourner ce problème, vous pouvez utiliser desstructures wrapperqui contiennent des métadonnées relatives à la ressource ou un identifiant de référence tout en satisfaisant aux capacités requises. Par exemple, un SoulboundListing<T: key>
wrapper peut contenir uniquement une référence ou des métadonnées, et non l'objet complet. Pour le stockage hétérogène, un Bag
or Table
ne peut stocker qu'un seul type par instanciation, mais vous pouvez le faire fonctionner en encapsulant les actifs dans un type personnalisé de type énumération ou en utilisant des types fantômes de type trait pour simuler les informations de type. Vous n'obtiendrez pas de polymorphisme natif, mais vous pouvez suivre les types d'actifs à l'aide d'un balisage manuel ou utiliser lemodèle témoin, où les types fantômes et les discriminants d'exécution vous aident à simuler la séparation au niveau des types. Si vous utilisez Asset<T>
pour chaque type de ressource, et que les deux T1``T2
sont fantômes, alors oui. Vous pouvez stocker Asset<T1>
et Asset<T2>
dans un conteneur partagé en effaçant les caractères dans un wrapper commun. Vous devrez ensuite stocker les informations de saisie séparément (comme un type_id
champ) pour qu'elles correspondent correctement lors de la lecture. Il s'agit d'un équilibre entrela sécurité du type, l'efficacité gazeuseet lacomplexité de la conception. L'utilisation de collections distinctes par type de ressource est la solution la plus sûre pour des raisons de sécurité, mais les types de wrapper et les métadonnées indexées sont plus évolutifs lorsque vous recherchez de la flexibilité. Vous devrez choisir selon que le coût du gaz ou la simplicité du développeur sont plus importants pour votre cas d'utilisation.
Connaissez-vous la réponse ?
Veuillez vous connecter et la partager.
Sui is a Layer 1 protocol blockchain designed as the first internet-scale programmable blockchain platform.
Gagne ta part de 1000 Sui
Gagne des points de réputation et obtiens des récompenses pour avoir aidé la communauté Sui à se développer.
- Pourquoi BCS exige-t-il un ordre de champs exact pour la désérialisation alors que les structures Move ont des champs nommés ?53
- « Erreurs de vérification de sources multiples » dans les publications du module Sui Move - Résolution automatique des erreurs43
- Échec de la transaction Sui : objets réservés pour une autre transaction25
- Comment les contraintes de capacité interagissent-elles avec les champs dynamiques dans des collections hétérogènes ?05