Publicación
Comparte tu conocimiento.
+15
¿Cómo interactúan las restricciones de capacidad con los campos dinámicos en colecciones heterogéneas?
Estoy creando un mercado que necesita gestionar varios tipos de activos con diferentes requisitos de capacidad, y me he planteado algunas preguntas fundamentales sobre el sistema de tipos de Move. Quiero almacenar diferentes tipos de activos en la misma colección, pero tienen diferentes capacidades:
- NFT normales:
key + store
(transferibles) - Tokens Soulbound:
key
únicamente (no transferibles) - Activos personalizados con restricciones de transferencia
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? */ }
Preguntas clave:
- Requisitos de habilidad: Cuando se usa
dynamic_field::add<K, V>()
, ¿V
siempre se necesitastore
en tiempo de compilación? ¿Pueden los tipos de contenedores solucionar esto? - Almacenamiento heterogéneo: ¿puede una sola bolsa almacenar objetos con diferentes conjuntos de habilidades (
key + store + copy
contrakey + store
) y manipularlos de forma diferente durante el tiempo de ejecución? - Seguridad de tipos: dado que los campos dinámicos eliminan los tipos, ¿cómo puedo mantener la seguridad de los tipos al recuperar los valores? ¿Cuál es el patrón para almacenar los metadatos de tipo?
- Patrón de testigos: ¿Cómo funcionan las restricciones de habilidad con los tipos de fantasmas? ¿Puedo
Asset<Type1>``Asset<Type2>
guardarlos en la misma colección y extraer la información de tipos más adelante?
Construir un sistema en el que los NFT, los tokens de soul bound y los activos restringidos requieran funciones de mercado, pero con una semántica de transferencia diferente.
He probado los tipos de contenedores, varias colecciones por conjunto de habilidades y el almacenamiento de metadatos de tipos separados. Cada uno tiene sus ventajas y desventajas entre la seguridad del tipo, los costos de la gasolina y la complejidad.
- Sui
- Architecture
Respuestas
5Te estás topando con el núcleo del sistema de tipos y habilidades de Move y tienes toda la razón al plantear estas preguntas al diseñar un mercado heterogéneo.
En primer lugar, sobre las restricciones de capacidad: cualquier valor que almacenes con dynamic_field: :add<K, V> () debe tener la capacidad de almacenamiento en el momento de la compilación. Esta es una regla estricta en Move: los valores sin almacenamiento simplemente no pueden conservarse en el almacenamiento, ya sea en campos dinámicos, tablas o construcciones similares. Intentar empaquetar en una estructura un tipo que no esté almacenado tampoco funcionará; en este caso, mover es algo estricto. Si incluso un campo carece de almacenamiento, todo el contenedor también pierde almacenamiento. Así que, lamentablemente, aquí no hay una solución alternativa para el envoltorio. Los activos de Soulbound con solo una clave (y sin almacén) se excluyen de cualquier colección que se escriba en un almacenamiento global, como una bolsa o un campo dinámico. Esto está bien documentado en los documentos de habilidades de Move.
En cuanto al almacenamiento de diferentes tipos de activos en la misma colección: si todos implementan la clave y la función almacenar, entonces sí, una sola bolsa puede almacenarlos, pero en el fondo, Move no tiene ningún polimorfismo real. El tipo debe conocerse en el momento del acceso y el Bag debe ser homogéneo desde la perspectiva del compilador. Esto nos lleva a la siguiente pregunta: ¿cómo mantenemos la seguridad de los tipos en campos dinámicos? Como Move borra los tipos de estas colecciones, no recordará qué tipo exacto hay en ellas; tú tienes que proporcionar el mapeo. Un patrón habitual es empaquetar cada activo en una estructura que incluya metadatos, como una enumeración u8 o un TypeName, de forma que, cuando recuperes el objeto, inspecciones los metadatos y los elimines o gestiones en consecuencia. No existe un RTTI mágico; se finge con disciplina en materia de datos.
Ahora hablemos de los tipos fantasma y los patrones testigo: son útiles para garantizar la seguridad en tiempo de compilación, pero no permiten realizar introspecciones sobre el tiempo de ejecución.
En el diseño de tu mercado, esto significa que es probable que estés buscando una estrategia híbrida. En el caso de los NFT normales y otros activos transferibles, puedes usar campos de objetos dinámicos o bolsas con total libertad, siempre y cuando se ajusten a lo establecido en Key + Store. En el caso de los activos relacionados con el soul, que solo son clave, lo mejor es almacenar las referencias y los metadatos asociados en una bolsa de metadatos paralela, o utilizar un tipo de anuncio personalizado que no almacene el activo en sí, sino que registre su existencia mediante un identificador y datos adicionales. Es más complejo, pero ofrece la flexibilidad necesaria para aplicar correctamente la semántica de las transferencias.
Referencias
#1. store
¿Los valores de los campos dinámicos siempre son necesarios?
dynamic_field::add
Si quieresalmacenarel valor directamente en un campo dinámico (por ejemplo, víastore
), este debe cumplir con todos los requisitos exigidos por el tipo de campo, incluidos los siguientes: Si V
no lo tienestore
, no puede ponerlo directamente en un campo dinámico.
Pero este es el truco:
Puedes envolveresos tipos dentro de otra estructura que lo tenga
store
, incluso si el tipo interno no lo tiene.
Ejemplo:
struct AssetWrapper<T: key> has store {
inner: T,
}
Aunque T
solo lo tienekey
, AssetWrapper<T>
se puede almacenar porque él mismo lo tienestore
.
Ahora puedes hacer:
dynamic_field::add(&mut object, key, AssetWrapper { inner: soulbound_token });
Esto le permite almacenar activos transferibles e intransferibles en la misma abstracción.
#2. ¿Puede una sola bolsa almacenar objetos con diferentes conjuntos de habilidades?
La respuesta corta es:sí, usando envoltorios y técnicas de borrado tipográfico. Pero hay ventajas y desventajas.
En Move, no puedes tener directamente una lista de varios tipos de concreto a menos que estén envueltos en un contenedor genérico.
Un buen enfoque es usar un contenedor como:
struct AnyAsset has store {
// Maybe include metadata or witness type info
data: Vector<u8>, // serialized representation?
type_info: TypeInfo, // Type metadata
}
O mejor aún, usa una codificación de enumeración de estilo variante (si tu dialecto Move la admite):
enum AssetType has store {
Transferable(NFT),
Soulbound(SoulboundToken),
Restricted(RestrictedAsset),
}
Sin embargo, esto requiere una coincidencia exhaustiva y no se adapta bien a todos los módulos.
De nuevo:empaquetar más campos dinámicos + escribir metadatoses más flexible.
#3. ¿Cómo mantener la seguridad tipográfica al recuperarlos?
Move realiza elborrado de tipoen campos dinámicos. V
Por lo tanto, una vez que recuperas un valor de un campo dinámico, solo obtienes un valor de algún tipo, V
pero no sabes qué es lo que está en tiempo de ejecución.
Para resolver esto de forma segura, necesitas:
- Almacenemetadatos de tipojunto con el valor.
- Utilice unpatrón de testigoo untipo de etiqueta incrustadapara reafirmar la información de tipos una vez recuperada.
Patrón: tipo de almacén de metadatos
struct AssetWithMetadata<T: key> has store {
value: T,
type_info: TypeInfo,
}
A continuación, almacene AssetWithMetadata<NFT>
o AssetWithMetadata<SoulboundToken>
en un campo dinámico.
Cuando lo recuperes, compruébalo con type_info
el tipo esperado antes de hacer algo que no sea seguro.
#4. Patrones de testigos y tipos de fantasmas
No cabe duda de que puedes utilizar tipos de fantasmas como testigos para codificar el comportamiento, por ejemplo, la transferibilidad.
Por ejemplo:
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>,
}
Luego:
type NFT = Asset<_, Transferable>;
type Soulbound = Asset<_, NonTransferable>;
Luego puedes escribir lógica como:
public fun transfer<T>(asset: Asset<T, Transferable>) {
// allowed
}
public fun transfer<T>(_asset: Asset<T, NonTransferable>) {
// disallowed
abort();
}
Esto le permite hacer cumplir las reglas de transferencia en tiempo de compilación.
Sin embargo, para almacenar ambos tipos en la misma colección, igual necesitarás un contenedor:
struct AnyAsset has store {
data: Vector<u8>,
type_info: TypeInfo,
}
type_info
Y gestione la deserialización de forma dinámica en función de.
A continuación, te explicamos cómo puedes diseñar un mercado de Sui Move que admita tanto activos transferibles (NFT, llave + tienda) como limitados (solo clave), al tiempo que mantenga las restricciones de seguridad tipográfica y capacidad de manejo.
Requisitos de habilidad para los campos dinámicos
Sí, V debe estar almacenado en el momento de la compilación para dynamic_field: :add<K, V> () y colecciones similares.
- store es obligatorio para que cualquier valor se almacene en un almacenamiento global, incluidos los campos dinámicos y las tablas.
- Los tipos de contenedor no pueden solucionar este problema: si empaquetas un tipo sin almacenarlo en una estructura, el contenedor tampoco puede tener almacenamiento, porque todos los campos deben tener almacenamiento para que la estructura tenga almacenamiento (source).
Almacenamiento heterogéneo en una bolsa
- La bolsa (o cualquier colección dinámica basada en campos) solo puede almacenar tipos que cumplan con las habilidades requeridas.
- En el caso de los campos de objetos dinámicos, el valor debe tener la clave + store.
- Para los campos dinámicos, el valor debe tener almacenamiento.
- No puedes almacenar un tipo con solo una clave (por ejemplo, una ficha vinculada a un alma) en una bolsa que requiera almacenamiento.
- Esta es una restricción fundamental del sistema de tipos Move (source).
Seguridad de tipos y borrado de tipos
- Los campos dinámicos se borran de forma tipográfica durante el tiempo de ejecución. Debe saber el tipo que desea recuperar en el momento del acceso.
- Patrón para garantizar la seguridad de los tipos:
- Almacene los metadatos del tipo (por ejemplo, una enumeración o un nombre de tipo) junto con el valor.
- Usa una estructura contenedora que incluya tanto el activo como su información de tipo.
- Al recuperarlos, comprueba los metadatos antes de convertirlos.
- Ejemplo de patrón:
- Almacene el anuncio {asset_type: u8,...} y utilice asset_type para determinar cómo gestionar el activo.
Tipos de patrones de testigos y fantasmas
- Los tipos fantasma y el patrón testigo ayudan en tiempo de compilación, no en tiempo de ejecución.
- Puedes usar Asset
para diferentes tipos, pero aun así debes asegurarte de que todos los T tengan las habilidades necesarias para la colección. - Durante el tiempo de ejecución, no puedes extraer información tipográfica de un tipo fantasma; debes almacenar metadatos explícitos (source).
- Diseño de mercado para tipos de activos mixtos
-
Activos transferibles (NFT): utilice la clave + almacene, almacénelos en campos de objetos dinámicos o bolsas.
-
Fichas Soulbound (solo claves): no se pueden almacenar en campos dinámicos ni en bolsas que requieran almacenamiento. Debes gestionarlos por separado, por ejemplo, almacenando solo los metadatos o las referencias (no el objeto en sí).
-
Activos personalizados con restricciones: siempre que tengan una clave o un almacén, puedes almacenarlos. Si no es así, necesitas una ruta de gestión independiente.
-
Todos los valores de los campos dinámicos o las bolsas deben tener un valor de almacenamiento (y una clave para los campos de objetos dinámicos).
-
No puedes almacenar tipos con solo una clave en estas colecciones.
-
La seguridad de los tipos en tiempo de ejecución requiere metadatos explícitos.
-
Los tipos de fantasmas y los patrones de testigos ayudan en tiempo de compilación, no en tiempo de ejecución.
Gracias Xavier.eth por esta pregunta técnica y reflexiva. Esta es mi contribución sobre las restricciones de habilidad y los campos dinámicos en Sui Move:
✅Sí, cualquier valor (V
) usado en dynamic_field::add<K, V>()
debe tener store
habilidad.
Esto se aplica estrictamente en las comprobaciones en tiempo de compilación de Move. Incluso incluir un valor que no esté almacenado dentro de una estructura no servirá de nada; si falta algún campostore
, toda la estructura también lo pierde.
key + store
✅Es posible almacenar activos heterogéneos con diferentes habilidades, pero solo si todos los activos del campo dinámico/BAG deben cumplir con el mismo conjunto de habilidades(normalmente).
🔒Los activos de Soulbound que solo tengan key
habilidades no se pueden almacenar directamenteen este tipo de colecciones. En su lugar, puedes hacer lo siguiente:
- Almacenarsolo metadatos(ID, enumeración de tipos, etc.)
dynamic_field::add<AssetID, Metadata>
- Usa un mapeo paralelo, como en el caso de los tipos relacionados con el alma. - Mantén un registro basado en referencias que rastree las listas de activos de Soulbound sin almacenar el objeto completo.
🧩Los tipos fantasmagóricosayudansolo en tiempo de compilación. Seguirás necesitando metadatos explícitos del tipo de tiempo de ejecución para la lógica de recuperación real.
🚀Arquitectura híbrida sugerida:
Bag<AssetID, TransferableAsset>
- para activos con store
Map<AssetID, SoulboundMetadata>
para tipos de solo claveAssetWrapper
estructura contype_id
o parau8 asset_type
identificar el tipo en tiempo de ejecución
¡Gracias de nuevo! ¡Espero ver evolucionar este mercado!
— Sr. Rifat Hossen
Si está creando un mercado que admite varios tipos de activos con diferentes capacidades en Sui Move, el desafío clave es cómo lasrestricciones de capacidadde Move interactúan con loscampos dinámicosy los datos heterogéneos. store``dynamic_field::add<K, V>()``V
No puedes almacenar valores directamente sin la store
capacidad, así que sí. Cuando lo utilices, el valor debe tener la capacidad necesaria en el momento de la compilación. store
Esto significa que no puedes almacenar directamente algo como un token de Soulbound, a menos que esté incluido en un tipo que sí lo tenga. Como solución alternativa, puedes usarestructuras contenedorasque contengan metadatos sobre el activo o un identificador de referencia y, al mismo tiempo, cumplan con las capacidades requeridas. Por ejemplo, un SoulboundListing<T: key>
contenedor puede contener solo una referencia o metadatos, no el objeto completo. En el caso del almacenamiento heterogéneo, un Bag``Table
quirófano solo puede almacenar un tipo por instancia, pero puede hacer que funcione empaquetando los activos en un tipo personalizado similar a una enumeración o utilizando tipos fantasma similares a rasgos para simular la información de tipos. No obtendrás polimorfismo nativo, pero puedes rastrear los tipos de activos etiquetándolos manualmente o usar elpatrón testigo, en el que los tipos fantasma y los discriminantes en tiempo de ejecución ayudan a simular la separación a nivel de tipos. Asset<T>``T1
Si los usas T2
para cada tipo de activo, y ambos, Asset<T1>
y Asset<T2>
son fantasmas, entonces sí. Puedes almacenarlos en un contenedor compartido borrándolos en un contenedor común. type_id
A continuación, tendrás que almacenar la información de tipo por separado (como un campo) para que coincidan correctamente al leerla. Es un acto de equilibrio entre laseguridad de tipo, laeficiencia del gasy lacomplejidad del diseño. El uso de recopilaciones independientes por tipo de activo es lo más seguro por motivos de seguridad, pero los tipos de contenedores y los metadatos indexados son más escalables cuando se busca flexibilidad. Tendrá que elegir en función de si el costo del gas o la simplicidad del desarrollador son más importantes para su caso de uso.
Sabes la respuesta?
Inicie sesión y compártalo.
Sui is a Layer 1 protocol blockchain designed as the first internet-scale programmable blockchain platform.
Gana tu parte de 1000 Sui
Gana puntos de reputación y obtén recompensas por ayudar a crecer a la comunidad de Sui.
- ¿Por qué BCS requiere un orden de campo exacto para la deserialización cuando las estructuras Move tienen campos con nombre?53
- «Errores de verificación de múltiples fuentes» en las publicaciones del módulo Sui Move: resolución automática de errores43
- Fallo en la transacción Sui: objetos reservados para otra transacción25
- ¿Cómo interactúan las restricciones de capacidad con los campos dinámicos en colecciones heterogéneas?05