Sui.

Publicación

Comparte tu conocimiento.

Recompensa+10

Peera Admin.
May 29, 2025
P&R expertos

¿Por qué BCS requiere un orden de campo exacto para la deserialización cuando las estructuras Move tienen campos con nombre?

¿Por qué BCS requiere un orden exacto de los campos para la deserialización cuando las estructuras de Move tienen campos con nombre? He profundizado en la codificación y decodificación de BCS en Move, especialmente para la comunicación entre cadenas y el procesamiento de datos fuera de la cadena. Mientras estudiaba los ejemplos de la documentación de Sui Move, me encontré con algunos comportamientos que parecen contradictorios y estoy intentando entender las decisiones de diseño subyacentes.

Según la especificación de BCS, «no hay estructuras en BCS (ya que no hay tipos); la estructura simplemente define el orden en el que se serializan los campos». Esto significa que, al deserializar, debemos usar peel_*las funciones exactamente en el mismo orden en que se definieron los campos de estructura.

Mis preguntas específicas:

  1. Justificación del diseño: ¿Por qué BCS exige que los campos coincidan exactamente en el orden de los campos cuando las estructuras de movimiento tienen campos con nombre? ¿No sería más sólido serializar los nombres de los campos junto con los valores, de forma similar a JSON u otros formatos autodescriptivos?
  2. Interacción de tipos genéricos: Los documentos mencionan que «los tipos que contienen campos de tipo genérico se pueden analizar hasta el primer campo de tipo genérico». Considera esta estructura:

struct ComplexObject<T, U> has drop, copy {
    id: ID,
    owner: address,
    metadata: Metadata,
    generic_data: T,
    more_metadata: String,
    another_generic: U
}

¿Cómo funciona exactamente la deserialización parcial aquí? ¿Puedo deserializar hasta more_metadata e ignorar ambos campos genéricos, o el primer campo genérico (generic_data) bloquea por completo la deserialización posterior? 4. Coherencia entre idiomas: al utilizar la biblioteca JavaScript @mysten /bcs para serializar los datos que consumirán los contratos de Move, qué ocurre si:

  • ¿Reordeno accidentalmente los campos del objeto de JavaScript?
  • ¿La definición de la estructura Move cambia el orden de los campos en una actualización de contrato?
  • ¿Tengo estructuras anidadas con sus propios parámetros genéricos?
  1. Implicaciones prácticas: En los sistemas de producción, ¿cómo gestionan los equipos la evolución del esquema BCS? ¿Versionan sus esquemas de BCS o esperan que el orden de los campos de las estructuras sea inmutable una vez implementados?
  • Sui
  • Move
5
2
Cuota
Comentarios
.

Respuestas

2
0xduckmove.
May 31 2025, 15:14

La serialización canónica binaria (BCS) está en marcha

Aquí están mis respuestas a sus preguntas

Por qué el orden de los campos debe coincidir exactamente

Imagina que tienes una estructura de movimiento definida de esta manera:

module example::ordering {
    struct MyStruct has copy, drop, store {
        a: u64,
        b: bool,
    }

    public fun to_bytes_example(): vector<u8> {
        let instance = MyStruct { a: 42, b: true };
        bcs::to_bytes(&instance)
    }

    public fun from_bytes_example(bytes: vector<u8>): MyStruct {
        bcs::from_bytes(&bytes)
    }
}

Todos saben que la serialización de MyStruct {a: 42, b: true} produce ocho bytes para el u64 42 (en little-endian) seguidos de un byte para true (por ejemplo, 0x01). Ahora, si alguna vez cambias la estructura a:

public struct MyStruct has copy, drop, store {
    b: bool,
    a: u64,
}

los bytes serializados comenzarían con un byte para el booleano y, a continuación, ocho bytes para el entero. Sin embargo, el deserializador de Move seguirá intentando leer los ocho primeros bytes como un u64 y el siguiente byte como un bool.

Como BCS nunca grabó «esto fue primero un bool», Move termina interpretando los datos sobrantes como un número incorrecto o fallando rotundamente. En resumen, el intercambio de campos lo estropea todo.


Cómo afectan los genéricos a la deserialización parcial

¿Puedo omitirlos si solo me interesan algunos campos posteriores

La respuesta es: no, a menos que sepa exactamente cómo analizar esos genéricos. Imagina esta estructura de movimiento:

module example::generics {
    use std::string::String;

    public struct ComplexObject<T, U> has copy, drop, store {
        id: ID,
        owner: address,
        metadata: vector<u8>,
        generic_data: T,
        more_metadata: String,
        another_generic: U,
    }

    public fun partial_deserialize(bytes: vector<u8>): String {
        let obj: ComplexObject<Signer, Signer> = bcs::from_bytes(&bytes);
        obj.more_metadata
    }
}


Cuando llamasbcs::from_bytes::<ComplexObject<Signer, Signer>>(&bytes), el analizador sabe que Tes Signer, que en Sui es una dirección de 32 bytes de tamaño fijo. ULee 32 bytes, descubre que es su generic_data, luego pasa a leer «more_metadata: String» (que tiene un prefijo de longitud) y, finalmente, another_generic:.

Pero si no especificó T o si T fuera algo así como un vector cuya longitud no se conoce de antemano, no hay forma de averiguar dónde comienza more_metadata.

BCS no tiene marcadores de longitud integrados para campos arbitrarios; debes decodificar generic_data por completo antes de que puedas siquiera pensar en more_metadata. En otras palabras, la deserialización parcial se detiene en el primer campo genérico, a menos que ya sepa su tipo exacto.


Mantener JavaScript y Move sincronizados con @mysten /bcs

Al serializar en JavaScript con@mysten/bcs, debes registrar tu estructura usando el mismo orden de campos exactamente como en Move. De lo contrario, el bcs: :from_bytes de Move malinterpretará los bytes. Esto es lo que parece:

import { Bcs, u64, bool } from "@mysten/bcs";

const bcs = new Bcs();

// Match Move’s struct: struct MyStruct { a: u64, b: bool }
bcs.registerStructType("MyStruct", {
  a: u64(),
  b: bool(),
});

// If you accidentally do this instead:
// bcs.registerStructType("MyStruct", {
//   b: bool(),
//   a: u64(),
// });

const instance = { a: 42n, b: true };
const bytes = bcs.ser("MyStruct", instance);

Como Move espera [8 bytes de u64] [1 byte de bool], si JavaScript emite [1 byte de bool] [8 bytes de u64], el analizador de Move intenta leer ocho bytes para el u64 pero ve basura. Esta falta de coincidencia provoca un error de decodificación o daña los datos.

Ahora, supongamos que más adelante intentas actualizar esa estructura de Move:

module upgrade::v1 {
    struct MyStruct has copy, drop, store {
        a: u64,
        b: bool,
    }
}

module upgrade::v2 {
    struct MyStruct has copy, drop, store {
        b: bool,
        a: u64,
    }
}

Una vez que implementas upgrade: :v1, las interfaces JS comienzan a serializarse como [u64] [bool]. Si implementas una actualización que intercambie esos campos, las nuevas llamadas en Move esperarán [bool] [u64]. De repente, los bytes antiguos no tienen sentido en la versión 2, y cualquier nueva serialización interrumpe a los clientes antiguos.

Esta es exactamente la razón por la que las políticas de actualización de Move prohíben cambiar el orden de los campos de la estructura en una actualización «compatible». Si es absolutamente necesario modificar el diseño, publica una nueva estructura (por ejemplo, MyStructV2) y migra los datos de forma gradual.

Las estructuras anidadas o genéricas son la misma historia:

module nest::demo {
    struct Inner has copy, drop, store {
        x: u64,
        y: vector<u8>,
    }

    struct Outer<T> has copy, drop, store {
        id: u64,
        payload: T,
        note: vector<u8>,
    }
}

En el lado de JavaScript, harías algo como:

import { Bcs, u64, vector, Struct } from "@mysten/bcs";

const bcs = new Bcs();

// Register Inner exactly as Move defined it
bcs.registerStructType("Inner", {
  x: u64(),
  y: vector(u8()),
});

// Register Outer<Inner>, matching Move’s field order
bcs.registerStructType("Outer<Inner>", {
  id: u64(),
  payload: new Struct("Inner"),
  note: vector(u8()),
});

Cuando JS serializa Outer, escribe los 8 bytes de id; luego recurre a Inner, escribiendo 8 bytes para x y una y con el prefijo de longitud; luego escribe note. El bcs: :from_bytes de Move sigue los mismos pasos.

Si alguna parte de ese esquema anidado no coincide, usaste vector () en lugar de vector () y todo se rompe, porque los prefijos de longitud y los tamaños de los elementos serán diferentes.

Para la pregunta final

En un sistema de producción basado en Move, una vez que publicas una estructura en cadena (y especialmente cuando los clientes han empezado a leer y escribir datos de BCS), el diseño de esa estructura es prácticamente inmutable. Tanto Aptos como Sui requieren que las actualizaciones compatibles dejen intactos los diseños de los campos estructurales existentes. Si intentas reordenar, insertar o eliminar campos en una estructura publicada, la actualización se considera incompatible y, por lo general, las herramientas de gobierno o de lanzamiento la bloquean.

Cuando necesitas cambiar el esquema, la mayoría de los equipos toman una de estas rutas:

En primer lugar, puedes incrustar un campo versión pequeña: u8 al principio de tu estructura para que los lectores puedan ver el número de versión y bifurcarse en consecuencia:

public struct DataV1 has copy, drop, store {
    version: u8,       // 1
    field_a: u64,
    field_b: bool,
}

public struct DataV2 has copy, drop, store {
    version: u8,       // 2
    field_a: u64,
    field_b: bool,
    field_c: vector<u8>, // new field in version 2
}

Here, any consumer can read the first byte; if it’s 1, they parse with DataV1. If it’s 2, they parse with DataV2. The downside is old clients that only know about DataV1 may choke on version 2 unless you write special fallback logic.

Another approach is to publish a new struct entirely, such as MyStructV2, leaving MyStructV1 unchanged. You migrate on-chain data via a transaction that reads V1 and writes the new V2. All old code that still understands only MyStructV1 continues to work until you decide to deprecate it.

On Sui, you also have dynamic fields for more flexibility. Instead of tacking on a new field to an existing struct, you store additional data as a separate child object keyed by the original object’s ID. Because the base struct’s BCS layout never changes, clients reading it still see the original fields. Then, whenever they need to read or write the extra data, they know to query the dynamic child. Dynamic fields are a powerful way to extend object schemas without ever touching the original struct layout.

Some teams reserve padding fields:

``rust

struct MyStruct has copy, drop, store { field_a: u64, reserved_1: u64, // unused for now field_b: bool, }


















2
Comentarios
.
Owen.
May 31 2025, 04:13

1.** ¿Por qué BCS requiere un orden de campo exacto?**

Porque BCS solo serializa valores sin procesar, no nombres de campos ni metadatos de tipos.

Las estructuras de movimiento tienen campos con nombre, peroBCS los trata como tuplas ordenadas. Los nombres de los campos son información en tiempo de compilación yno se incluyen en la salida serializada.

Ejemplo:

struct MyStruct {
    a: u64,
    b: bool,
}
  • Serializado como: [u64 bytes] + [bool bytes] u64``bool- El deserializador debe leer entonces:el pedido importa

Esto hace que BCS:

  • Rápido y compacto
  • No se describe a sí mismo ni es flexible en cuanto a esquemas

###2.** ¿Cómo funciona la deserialización parcial con los tipos genéricos?**

Dado:

struct ComplexObject<T, U> {
    id: ID,
    owner: address,
    metadata: Metadata,
    generic_data: T,      // <-- generic
    more_metadata: String,
    another_generic: U,   // <-- generic
}

Regla:

generic_dataPuedes deserializar hasta el primer campo genérico (). TDespués de eso, el análisis se detiene a menos que sepas cómo decodificar.

Así que: T``more_metadata- Si no sabe cómo analizaranother_generic,no puedealcanzar o de forma segura.

  • Debe utilizar las peel_*funciones de bajo nivel con cuidado si desea un acceso parcial.

Solo los prefijos no genéricos se pueden analizar de forma segura sin tener conocimientos completos del tipo.


3. @mysten/bcsCoherencia entre idiomas con JavaScript ()

-El orden de los campos debe coincidir exactamente, igual que en Move.

  • Reordenar los campos en el objeto JS →error de deserialización o datos malinterpretados
  • Cambiar el orden de los campos de la estructura de movimiento después de la implementación →interrumpe la compatibilidad
  • Estructuras anidadas con genéricos → solo funciona si todos los esquemas anidados están registrados correctamente

4.** ¿Cómo gestionan los equipos la evolución del esquema de BCS en la producción?**

Estrategias comunes: -Control de versiones de esquemas: adjunte el número de versión a los datos serializados -Estructuras inmutables: una vez implementados, nunca reordenes ni elimines los campos

  • Evite reordenar, cambiar el nombre o eliminar los campos después de la implementación
  • Utilice campos reservados o rellenados para permitir una futura expansión
  • Introduce nuevas estructuras en lugar de modificar las antiguas
  • Prefiere tipos concretos en estructuras multilingües
0
Comentarios
.

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.

306Publicaciones450Respuestas
Sui.X.Peera.

Gana tu parte de 1000 Sui

Gana puntos de reputación y obtén recompensas por ayudar a crecer a la comunidad de Sui.

Campaña de RecompensasJunio
Usamos cookies para asegurarnos de que obtenga la mejor experiencia en nuestro sitio web.
Más información