Publication
Partagez vos connaissances.
+10
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 ?
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 ? J'ai approfondi le codage/décodage BCS dans Move, en particulier pour la communication inter-chaînes et le traitement des données hors chaîne. En parcourant les exemples de la documentation de Sui Move, j'ai rencontré un comportement qui semble contre-intuitif et j'essaie de comprendre les décisions de conception sous-jacentes.
Selon la spécification BCS, « il n'y a pas de structures dans BCS (puisqu'il n'y a pas de types) ; la structure définit simplement l'ordre dans lequel les champs sont sérialisés ». Cela signifie que lors de la désérialisation, nous devons utiliser peel_*
les fonctions exactement dans le même ordre que la définition du champ de structure.
Mes questions spécifiques :
- Justification de la conception : pourquoi BCS exige-t-il une correspondance exacte de l'ordre des champs alors que les structures Move ont des champs nommés ? Ne serait-il pas plus robuste de sérialiser les noms de champs à côté des valeurs, de la même manière que le JSON ou d'autres formats auto-descriptifs ?
- Interaction entre types génériques : La documentation mentionne que « les types contenant des champs de type générique peuvent être analysés jusqu'au premier champ de type générique ». Considérez cette structure :
struct ComplexObject<T, U> has drop, copy {
id: ID,
owner: address,
metadata: Metadata,
generic_data: T,
more_metadata: String,
another_generic: U
}
Comment fonctionne exactement la désérialisation partielle ici ? Puis-je désérialiser jusqu'à more_metadata et ignorer les deux champs génériques, ou est-ce que le premier champ générique (generic_data) bloque complètement la poursuite de la désérialisation ? 4. Cohérence entre les langues : lorsque vous utilisez la bibliothèque JavaScript @mysten /bcs pour sérialiser les données qui seront consommées par les contrats Move, que se passe-t-il si :
- Je réorganise accidentellement les champs de l'objet JavaScript ?
- La définition de la structure Move change l'ordre des champs lors d'une mise à niveau du contrat ?
- J'ai des structures imbriquées avec leurs propres paramètres génériques ?
- Implications pratiques : dans les systèmes de production, comment les équipes gèrent-elles l'évolution du schéma BCS ? Versiez-vous vos schémas BCS ou vous attendez-vous à ce que l'ordre des champs de structure soit immuable une fois déployé ?
- Sui
- Move
Réponses
2Sérialisation canonique binaire (BCS) en mouvement
Voici mes réponses à vos questions
Pourquoi l'ordre des champs doit correspondre exactement
Imaginez que vous ayez une structure Move définie comme ceci :
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)
}
}
Vous savez tous que la sérialisation de MyStruct {a : 42, b : true} produit huit octets pour le u64 42 (en little-endian) suivis d'un octet pour true (par exemple, 0x01). Maintenant, si jamais vous modifiez la structure comme suit :
public struct MyStruct has copy, drop, store {
b: bool,
a: u64,
}
les octets sérialisés commenceraient par un octet pour le booléen, puis huit octets pour l'entier. Pourtant, le désérialiseur de Move va toujours essayer de lire les huit premiers octets en tant que u64 et l'octet suivant en tant que booléen.
Comme BCS n'a jamais enregistré « c'était d'abord un booléen », Move finit par interpréter les données restantes comme un mauvais chiffre ou par échouer purement et simplement. Bref, l'échange de champs détruit tout.
Comment les génériques affectent la désérialisation partielle
Puis-je les ignorer si je ne me soucie que de certains champs ultérieurs ?
La réponse est : non, à moins que vous ne sachiez exactement comment analyser ces génériques. Imaginez cette structure Move :
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
}
}
Lorsque vous appelezbcs::from_bytes::<ComplexObject<Signer, Signer>>(&bytes)
, l'analyseur sait qu'il T
s'agit de Signer, qui dans Sui est une adresse de 32 octets de taille fixe. U
Il lit 32 octets, découvre qu'il s'agit de vos données génériques, puis il lit « more_metadata : String » (qui est préfixé par la longueur) et enfin another_generic :.
Mais si vous n'avez pas spécifié T ou si T était quelque chose comme un vecteur
BCS n'a pas de marqueurs de longueur intégrés pour les champs arbitraires ; vous devez décoder complètement generic_data avant même de pouvoir penser à more_metadata. En d'autres termes, la désérialisation partielle s'arrête au premier champ générique, sauf si vous connaissez déjà son type exact.
Synchroniser JavaScript et Move avec @mysten /bcs
Lorsque vous sérialisez en JavaScript avec@mysten/bcs
, vous devez enregistrer votre structure en utilisant le même ordre de champs exactement que Move. Sinon, bcs : :from_bytes de Move interprétera mal les octets. Voici à quoi cela ressemble :
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);
Puisque Move attend [8 octets de u64] [1 octet de booléen], si JavaScript émet [1 octet de booléen] [8 octets de u64], l'analyseur de Move essaie de lire huit octets pour le u64 mais voit des déchets. Cette incompatibilité entraîne une erreur de décodage ou des données corrompues.
Supposons maintenant que vous essayiez ultérieurement de mettre à niveau cette structure 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,
}
}
Une fois que vous avez déployé upgrade : :v1, les interfaces JS commencent à être sérialisées sous la forme [u64] [bool]. Si vous envoyez une mise à niveau qui permute ces champs, les nouveaux appels dans Move attendront [bool] [u64]. Tout à coup, les anciens octets deviennent inutiles sous la v2, et toute nouvelle sérialisation interrompt les anciens clients.
C'est exactement pourquoi les politiques de mise à niveau de Move interdisent de modifier l'ordre des champs de structure lors d'une mise à niveau « compatible ». Si vous devez absolument modifier la mise en page, vous publiez une nouvelle structure (par exemple MyStructV2) et vous migrez progressivement les données.
Les structures imbriquées ou génériques, c'est la même histoire :
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>,
}
}
Du côté de JavaScript, vous feriez quelque chose comme :
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()),
});
Lorsque JS sérialise Outer
Si une partie de ce schéma imbriqué ne correspond pas, vous avez utilisé vector
Pour la dernière question
Dans un système de production basé sur Move, une fois que vous avez publié une structure en chaîne (et surtout une fois que les clients ont commencé à lire et à écrire des données BCS), la disposition de cette structure est effectivement immuable. Aptos et Sui exigent que les mises à niveau compatibles laissent intactes les dispositions de champs de structure existantes. Si vous essayez de réorganiser, d'insérer ou de supprimer des champs dans une structure publiée, la mise à niveau est considérée comme incompatible et est généralement bloquée par les outils de gouvernance ou de publication.
Lorsque vous avez besoin de modifier le schéma, la plupart des équipes empruntent l'une des voies suivantes :
Tout d'abord, vous pouvez intégrer un petit champ version : u8 au début de votre structure afin que les lecteurs puissent voir un numéro de version et une branche en conséquence :
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:
struct MyStruct has copy, drop, store {
field_a: u64,
reserved_1: u64, // unused for now
field_b: bool,
}
1.Pourquoi le BCS exige-t-il un ordre exact des champs ?
Parce que BCS ne sérialise que les valeurs brutes, pas les noms de champs ni les métadonnées de type.
Les structures Move ont des champs nommés, maisBCS les traite comme des tuples ordonnés. Les noms de champs sont des informations de compilation et ne sontpas inclus dans la sortie sérialisée.
Exemple :
struct MyStruct {
a: u64,
b: bool,
}
- Sérialisé comme suit :
[u64 bytes] + [bool bytes]
u64
- Le désérialiseur doitbool
alors lire :l'ordre compte
Cela fait de BCS :
- Rapide et compact
- Ne s'auto-décrit pas et n'est pas flexible en termes de schéma
###2.Comment fonctionne la désérialisation partielle avec les types génériques ?
Donné :
struct ComplexObject<T, U> {
id: ID,
owner: address,
metadata: Metadata,
generic_data: T, // <-- generic
more_metadata: String,
another_generic: U, // <-- generic
}
Règle :
generic_data
Vous pouvez désérialiser jusqu'au premier champ générique ().T
Après cela, l'analyse s'arrête à moins que vous ne sachiez comment décoder.
Donc :
T``more_metadata
- Si vous ne savez pas comment analyseranother_generic
, vousne pouvez pasatteindre ou en toute sécurité.
- Vous devez utiliser les
peel_*
fonctions de bas niveau avec précaution si vous souhaitez un accès partiel.
Seul un préfixe non générique peut être analysé en toute sécurité sans connaissance complète du type.
3. @mysten/bcs
Cohérence multilingue avec JavaScript ()
-L'ordre des champs doit correspondre exactement, comme dans Move.
- Réorganisation des champs dans un objet JS →erreur de désérialisation ou données mal interprétées
- Modification de l'ordre des champs de la structure Move après le déploiement →rompt la compatibilité
- Structures imbriquées avec génériques → ne fonctionne que si tous les schémas imbriqués sont correctement enregistrés
##4.Comment les équipes gèrent-elles l'évolution du schéma BCS en production ?
Stratégies communes : -Versionnage du schéma : attachez le numéro de version aux données sérialisées -Structures immuables : une fois déployées, ne réorganisez ni ne supprimez jamais de champs
- Évitez de réorganiser, de renommer ou de supprimer des champs après le déploiement
- Utilisez des champs réservés/de remplissage pour permettre une expansion future
- Introduisez de nouvelles structures au lieu de modifier les anciennes
- Préférez les types concrets dans les structures multilingues
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.

- 0xduckmove... SUI+88
1
- harry phan... SUI+61
2
- MiniBob... SUI+57
3
- ... SUIHaGiang+56
- ... SUIRogue+47
- ... SUIRogueRig+44
- ... SUIPeera Admin+25
- ... SUIVens.sui+20
- ... SUIMarlKey+20
- ... SUIdudley_smith+16