XCM: Le Cross-Consensus Message Format

xcRom1.dot ⭕️
Polkadot Francophonie
24 min readDec 22, 2021

Source officiel : XCM: The Cross-Consensus Message Format

Alors que la version finale de Polkadot 1.0, complète avec les Parachains, se rapproche, le format Cross-Consensus Messaging, XCM en abrégé, approche de sa première version prête pour la production. Il s’agit d’une introduction au format, à ses objectifs, à son fonctionnement et à la façon dont il peut être utilisé pour réaliser des tâches cross-chain typiques.

Un fait amusant pour commencer… XCM est le format de messagerie “cross-consensus”, plutôt que simplement “cross-chain”. Cette différence est un signe des objectifs du format qui est conçu pour communiquer les types d’idées envoyées non seulement entre les chaînes, mais aussi les smart-contracts et les palettes, et sur les bridges et les enclaves sharded comme Polkadot’s Spree.

🤟 Un format, pas un protocole

Pour mieux comprendre XCM, il est important de connaître ses limites et de savoir où il se situe dans la pile technologique de Polkadot. XCM est un format de messagerie. Il ne s’agit pas d’un protocole de messagerie. Il ne peut pas être utilisé pour “envoyer” réellement un message entre des systèmes ; son utilité consiste uniquement à exprimer ce qui doit être fait par le récepteur.

Sans compter les bridges et la palette de contrats, Polkadot est livré avec trois systèmes distincts pour communiquer réellement des messages XCM entre les chaînes qui le composent : UMP, DMP et XCMP. UMP (Upward Message Passing) permet aux parachains d’envoyer des messages à la relay chain. DMP (Downward Message Passing) permet à la relay chain de transmettre des messages à l’une de ses parachains. XCMP, qui est peut-être le plus connu d’entre eux, permet aux parachains d’envoyer des messages entre eux. XCM peut être utilisé pour exprimer la signification des messages sur chacun de ces trois canaux de communication.

Outre l’envoi de messages entre chaînes, XCM est également utile dans d’autres contextes, pour effectuer des transactions avec une chaîne dont vous ne connaissez pas nécessairement le format de transaction à l’avance. Avec les chaînes dont la logique commerciale change peu (par exemple Bitcoin), le format de transaction — ou le format utilisé par les portefeuilles pour envoyer des instructions à la chaîne — a tendance à rester exactement le même, ou du moins compatible, indéfiniment. Avec les chaînes hautement évolutives basées sur un métaprotocole, comme Polkadot et les parachains qui la composent, la logique commerciale peut être mise à niveau sur l’ensemble du réseau en une seule transaction. Cela peut tout changer, y compris le format de la transaction, ce qui introduit un problème potentiel pour les mainteneurs de portefeuilles, en particulier pour les portefeuilles qui doivent être maintenus hors ligne (comme Parity Signer). Puisque XCM est bien versionné, abstrait et général, il peut être utilisé comme un moyen de fournir un format de transaction durable que les portefeuilles peuvent utiliser pour créer de nombreuses transactions communes.

🥅 Objectifs

XCM a pour but d’être un langage permettant de communiquer des idées entre les systèmes de consensus. Il doit être suffisamment général pour être correctement utilisé dans un écosystème en pleine expansion. Il doit être extensible. Puisque l’extensibilité impliquera inévitablement des changements, il doit également être à l’épreuve du futur et compatible avec l’avenir. Enfin, il doit être suffisamment efficace pour fonctionner sur la chaîne et, éventuellement, dans un environnement avec des mesures.

Comme pour tous les langages, certaines personnes auront tendance à utiliser certains éléments plus que d’autres. Le XCM n’est pas conçu de manière à ce que chaque système qui le prend en charge soit capable d’interpréter tous les messages XCM possibles. Certains messages n’auront pas d’interprétations raisonnables sous certains systèmes. D’autres pourraient être raisonnables, mais toujours intentionnellement non pris en charge par l’interpréteur en raison de contraintes de ressources ou parce que le même contenu peut être exprimé d’une manière plus claire et plus canonique. Les systèmes ne prendront inévitablement en charge qu’un sous-ensemble de messages possibles. Les systèmes soumis à de fortes contraintes de ressources (comme les contrats intelligents) peuvent ne supporter qu’un “dialecte” très limité.

Cette généralité s’étend même jusqu’à des concepts comme le paiement de frais pour l’exécution du message XCM. Comme nous savons que XCM peut être utilisé sur divers systèmes, y compris une plateforme de contrats intelligents pour le gas et des parachains communautaires, jusqu’à des interactions de confiance entre les parachains du système et la relay chain, nous ne voulons pas intégrer des éléments tels que le paiement de frais trop profondément et de manière irréversible dans le protocole.

😬 Pourquoi ne pas simplement utiliser le format de message natif ?

S’appuyer sur le format de message/transaction natif d’une chaîne ou d’un contrat intelligent peut être utile dans certaines circonstances, mais présente quelques inconvénients majeurs qui le rendent moins utile pour les objectifs de XCM. Tout d’abord, il y a un manque de compatibilité entre les chaînes, donc un système qui a l’intention d’envoyer des messages à plus d’une destination devra comprendre comment créer un message pour chacune d’entre elles. À ce propos, même une seule destination peut modifier son format natif de transaction/message au fil du temps. Les contrats intelligents peuvent être mis à niveau, les blockchains peuvent introduire de nouvelles fonctionnalités ou modifier les fonctionnalités existantes et, ce faisant, changer leur format de transaction.

Deuxièmement, les cas d’utilisation courants des chaînes ne s’intègrent pas facilement dans une seule transaction ; des astuces particulières peuvent être nécessaires pour retirer des fonds, les échanger et ensuite déposer le résultat, le tout dans une seule transaction. Les notifications de transferts vers l’amont, nécessaires pour un cadre cohérent de réserves et d’actifs, n’existent pas dans les chaînes qui n’en connaissent pas d’autres.

Troisièmement, les opérations telles que le paiement des frais ne s’intègrent pas facilement dans un modèle qui suppose que le paiement des frais a déjà été négocié comme les messages des contrats intelligents. Les enveloppes de transaction, en comparaison, fournissent un certain système pour le paiement des traitements, mais sont aussi généralement conçues pour contenir une signature, ce qui n’est pas quelque chose qui a du sens lorsqu’on communique entre systèmes de consensus.

🎬 Quelques cas d’utilisation

Bien que l’objectif de XCM soit d’être général, flexible et à l’épreuve du temps, il existe bien sûr des besoins pratiques auxquels il doit répondre, notamment le transfert de jetons entre chaînes. Le paiement optionnel de frais (peut-être en utilisant ces jetons) en est un autre, tout comme une interface générale pour la conduite d’un service d’échange, commun à tout le monde du DeFi. Enfin, il devrait être possible d’utiliser le langage XCM pour effectuer une action spécifique à une plateforme ; par exemple, au sein d’une chaîne Substrate, il peut être souhaitable d’envoyer un appel à distance dans l’une de ses palettes pour accéder à une fonctionnalité de niche.

En plus de cela, il existe de nombreux modèles de transfert de jetons que nous voudrions prendre en charge : Nous pourrions vouloir simplement contrôler un compte sur une chaîne distante, permettant à la chaîne locale d’avoir une adresse sur la chaîne distante pour recevoir des fonds et éventuellement transférer ces fonds qu’elle contrôle vers d’autres comptes sur cette chaîne distante.

Nous pouvons avoir deux systèmes de consensus, qui sont tous deux des foyers natifs pour un jeton particulier. Imaginez un jeton tel que USDT ou USDC, qui possède des instances — toutes parfaitement fongibles — sur plusieurs chaînes différentes. Il devrait être possible de brûler un tel jeton sur une chaîne et de mint un jeton correspondant sur une autre chaîne prise en charge. Dans le langage de XCM, nous appelons cela la téléportation en raison de l’idée que le mouvement apparent d’un actif se produit en fait en le détruisant d’un côté et en créant un clone de l’autre côté.

Enfin, il peut y avoir deux chaînes qui souhaitent désigner une troisième chaîne, une chaîne sur laquelle un actif pourrait être considéré comme natif, pour être utilisée comme réserve pour cet actif. La forme dérivée de l’actif sur chacune de ces chaînes serait entièrement garantie, permettant à l’actif dérivé d’être échangé contre l’actif sous-jacent sur la chaîne de réserve qui le garantit. Cela peut être le cas lorsque les deux chaînes ne se font pas nécessairement confiance, mais (au moins en ce qui concerne l’actif en question) sont prêtes à faire confiance à la chaîne native de l’actif. Par exemple, nous avons plusieurs parachains communautaires qui souhaitent s’envoyer des DOT entre eux. Ils ont chacun une forme locale de DOT qui est entièrement soutenue par le DOT contrôlé par le parachain sur la chaîne Statemint (un hub natif pour le DOT). Lorsque la forme locale de DOT est envoyée entre les chaînes, en arrière-plan le “vrai” DOT se déplace entre les comptes des parachains sur Statemint.

Même ce niveau de fonctionnalité apparemment modeste comporte un nombre relativement important de configurations dont l’utilisation pourrait être souhaitable et nécessite une conception intéressante pour éviter les overfitting.

🫀 L’anatomie du XCM

Au cœur du format XCM se trouve le XCVM. Contrairement à ce que l’on pourrait croire, il ne s’agit pas d’un chiffre romain (valide) (bien que si c’était le cas, cela signifierait probablement 905). Il s’agit en fait de l’abréviation de Cross-Consensus Virtual Machine. Il s’agit d’un ordinateur non complet de Turing de très haut niveau dont les instructions sont conçues pour être à peu près au même niveau que les transactions.

Un “message” dans XCM est en fait juste un programme qui s’exécute sur le XCVM. Il s’agit d’une ou plusieurs instructions XCM. Le programme s’exécute jusqu’à ce qu’il arrive à la fin ou qu’il rencontre une erreur, auquel cas il se termine (je laisse ce terme intentionnellement inexpliqué pour le moment) et s’arrête.

La XCVM comprend un certain nombre de registres, ainsi qu’un accès à l’état global du système de consensus qui l’héberge. Les instructions peuvent changer un registre, elles peuvent changer l’état du système de consensus ou les deux.

Un exemple d’une telle instruction serait TransferAsset qui est utilisée pour transférer un actif à une autre adresse sur le système distant. Il faut lui indiquer quel(s) actif(s) transférer et à qui/où l’actif doit être transféré. En Rust, elle est déclarée comme ceci :

enum Instruction {
TransferAsset
{
assets: MultiAssets,
beneficiary: MultiLocation,
}
/* snip */
}

Comme vous pouvez le deviner, les assets sont le paramètre qui exprime quels actifs doivent être transférés, et le beneficiary indique à qui/où ils doivent être mis. Il nous manque bien sûr un autre élément d’information, à savoir à qui/où les actifs doivent être pris. Cette information est automatiquement déduite du registre d’origine. Au début du programme, ce registre est généralement défini en fonction du système de transport (le bridge, XCMP ou autre) pour refléter la provenance réelle du message, et il s’agit du même type d’information que le beneficiary. Le registre d’origine fonctionne comme un registre protégé — le programme ne peut pas le définir arbitrairement, bien qu’il existe deux instructions qui peuvent être utilisées pour le modifier de certaines manières.

Les types utilisés sont des idées tout à fait fondamentales dans XCM : les actifs, représentés par MultiAsset et les lieux-dans-le-consensus, représentés par MultiLocation. Le registre d’origine est un MultiLocation facultatif (facultatif, car il peut être entièrement effacé si on le souhaite).

📍 Emplacements dans XCM

Le type MultiLocation identifie tout emplacement unique qui existe dans le monde du consensus. Il s’agit d’une idée assez abstraite qui peut représenter toutes sortes de choses qui existent au sein du consensus, d’une blockchain multi-shard évolutive telle que Polkadot jusqu’à un modeste compte d’actifs ERC-20 sur un parachain. En termes d’informatique, il s’agit tout simplement d’une structure de données unique globale, quelle que soit sa taille ou sa complexité.

MultiLocation exprime toujours un emplacement relatif à l’emplacement actuel. Vous pouvez l’imaginer un peu comme un chemin de système de fichiers, mais où il n’y a aucun moyen d’exprimer directement la “root” de l’arbre du système de fichiers. Ceci pour une raison simple : dans le monde de Polkadot, les blockchains peuvent être fusionnées et séparées d’autres blockchains. Une blockchain peut commencer sa vie seule, et finalement être élevée pour devenir une parachain au sein d’un consensus plus large. Si cela se produisait, la signification de “root” changerait du jour au lendemain et cela pourrait entraîner le chaos pour les messages XCM et tout ce qui utilise MultiLocation. Pour garder les choses simples, nous excluons complètement cette possibilité.

Les localisations dans XCM sont hiérarchisées ; certains lieux du consensus sont entièrement encapsulés dans d’autres lieux du consensus. Un parachain de Polkadot existe entièrement dans le consensus global de Polkadot et nous l’appelons un emplacement intérieur. En termes plus stricts, nous pouvons dire que chaque fois qu’il existe un système de consensus dont tout changement implique un changement dans un autre système de consensus, alors le premier système est intérieur au second. Par exemple, un contrat intelligent Canvas est intérieur à la palette de contrats qui l’héberge. Un UTXO dans Bitcoin est intérieur à la blockchain Bitcoin.

Cela signifie que XCM ne fait pas de distinction entre les deux questions “qui ?” et “où ?”. Du point de vue de quelque chose d’assez abstrait comme XCM, la différence n’est pas vraiment importante — les deux se confondent et deviennent essentiellement la même chose.

Les MultiLocation sont utilisées pour identifier les endroits où envoyer des messages XCM, les endroits qui peuvent recevoir des ressources et peuvent même aider à décrire le type de ressource lui-même, comme nous allons le voir. Des choses très utiles.

Lorsqu’ils sont écrits dans un texte comme cet article, ils sont exprimés sous la forme d’un certain nombre de composants ..(ou “parent”, le système de consensus encapsulant) suivis d’un certain nombre de jonctions, tous séparés par /. (Ce n’est pas ce qui se passe généralement lorsque nous les exprimons dans un langage comme Rust, mais cela a du sens à l’écrit car cela ressemble beaucoup aux chemins de répertoire familiers qui sont largement utilisés). Les jonctions identifient un emplacement intérieur dans son système de consensus encapsulant. S’il n’y a pas de parents/jonctions du tout, nous disons simplement que l’emplacement est Ici.

Quelques exemples :

  • ../Parachain(1000): Evalué à l’intérieur d’un parachain, ceci identifierait notre parachain d’indice 1000. (En Rust, nous écririons ParentThen(Parachain(1000)).into().)
  • ../AccountId32(0x1234...cdef): Évalué dans un parachain, ceci identifierait le compte 0x1234…cdefde 32 octets sur la relay chain.
  • Parachain(42)/AccountKey20(0x1234...abcd): Évalué sur une relay chain, ceci identifierait le compte de 20 octets 0x1234…abcdsur le parachain numéro 42 (vraisemblablement quelque chose comme Moonbeam qui héberge des comptes compatibles avec Ethereum).

Il existe de nombreux types de jonctions pour identifier les endroits que vous pourriez trouver sur la chaîne de toutes sortes de façons, comme les clés, les indices, les blobs binaires et la pluralité des descriptions.

💰 Les actifs dans XCM

Lorsque l’on travaille en XCM, il est souvent nécessaire de faire référence à un actif quelconque. En effet, pratiquement toutes les blockchains publiques existantes s’appuient sur un actif numérique natif pour fournir l’épine dorsale de leur économie interne et de leur mécanisme de sécurité. Pour les blockchains Proof-of-Work telles que le bitcoin, l’actif natif (BTC) est utilisé pour récompenser les mineurs qui développent la blockchain et empêchent les doubles dépenses. Pour les blockchains Proof-of-Stake telles que Polkadot, l’actif natif (DOT) est utilisé comme une forme de garantie, où les gardiens du réseau (appelés stakers) doivent le risquer afin de générer des blocs valides et d’être récompensés en nature.

Certaines blockchains gèrent plusieurs actifs, par exemple le framework ERC-20 d’Ethereum permet de gérer de nombreux actifs différents sur la chaîne. Certaines gèrent des actifs qui ne sont pas fongibles, comme l’ETH d’Ethereum, mais qui sont plutôt non fongibles, c’est-à-dire des instances uniques ; Crypto-kitties était un exemple précoce de ces jetons non fongibles ou NFT.

XCM est conçu pour être capable de gérer tous ces actifs sans se fatiguer. Pour cela, il existe le type de données MultiAsset ainsi que ses types associés MultiAssets, WildMultiAsset et MultiAssetFilter. Examinons MultiAsset en Rust :

struct MultiAsset {
id: AssetId,
fun: Fungibility,
}

Il y a donc deux champs qui définissent notre actif : id et fun, ce qui est assez révélateur de la façon dont XCM aborde les actifs. Tout d’abord, une identité globale de l’actif doit être fournie. Pour les actifs fongibles, cela identifie simplement l’actif. Pour les NFTs, cela identifie la “classe” globale de l’actif — différentes instances de l’actif peuvent faire partie de cette classe.

enum AssetId {
Concrete(MultiLocation),
Abstract(BinaryBlob),
}

L’identité de l’actif est exprimée de deux manières : Concrete ou Abstract. Abstract n’est pas vraiment utilisé, mais il permet de spécifier les ID de biens par leur nom. C’est pratique, mais cela suppose que le récepteur interprète le nom de la manière attendue par l’expéditeur, ce qui n’est pas toujours facile. Concrete est d’usage général et utilise un emplacement pour identifier un bien sans ambiguïté. Pour les actifs natifs (comme le DOT), l’actif tend à être identifié comme la chaîne qui mint l’actif (la chaîne de relais Polkadot dans ce cas, qui serait l’emplacement .. d’une de ses parachains). Les actifs qui sont principalement administrés dans la palette d’une chaîne peuvent être identifiés par un emplacement comprenant leur indice dans cette palette. Par exemple, le parachain Karura peut faire référence à un bien de la parachain Statemine avec l’emplacement ../Parachain(1000)/PalletInstance(50)/GeneralIndex(42).

enum Fungibility {
Fungible(NonZeroAmount),
NonFungible(AssetInstance),
}

Deuxièmement, ils doivent être soit fongibles, soit non fongibles. S’ils sont fongibles, il doit y avoir un montant associé non nul. S’ils ne sont pas fongibles, au lieu d’un montant, il faut indiquer de quelle instance il s’agit. Ceci est généralement exprimé par un index, mais XCM permet également d’utiliser d’autres types de données tels que des tableaux et des blobs binaires.

Ceci couvre le MultiAsset, mais il existe trois autres types associés que nous utilisons parfois. MultiAsset est l’un d’eux et signifie simplement un ensemble d’éléments MultiAsset. Ensuite, il y a WildMultiAsset, un caractère générique qui peut être utilisé pour trouver une correspondance avec un ou plusieurs éléments MultiAsset. Il n’y a en fait que deux types de caractères génériques qu’il prend en charge : All (qui correspond à tous les actifs) et AllOf qui correspond à tous les actifs ayant une identité ( AssetId) et une fongibilité particulières. Dans ce dernier cas, il n’est pas nécessaire de spécifier le montant (dans le cas de biens fongibles) ou l’instance (pour les biens non fongibles), et tous les biens sont comparés.

Enfin, il y a MultiAssetFilter. Il est le plus souvent utilisé et n’est en fait qu’une combinaison de MultiAsset et WildMultiAsset permettant de spécifier soit des caractères générique, soit une liste d’actifs définis (c’est-à-dire pas de caractères générique).

Dans l’API Rust XCM, nous fournissons de nombreuses conversions pour rendre le travail avec ces types de données aussi facile que possible. Par exemple, pour spécifier le MultiAsset fongible qui équivaut à 100 unités indivisibles de l’actif DOT (Planck, pour ceux qui le savent) lorsque nous sommes sur la Relay Chain Polkadot, nous utiliserons (Here, 100).into().

👉 Le registre de détention

Examinons une autre instruction XCM : WithdrawAsset. À première vue, cette instruction ressemble un peu à la première partie de TransfertAsset : elle retire des actifs du compte du lieu spécifié dans le registre des origines. Mais qu’en fait-on ? — S’ils ne sont déposés nulle part, c’est sûrement une opération plutôt inutile. Regardons sa déclaration Rust :

WithdrawAsset(MultiAssets),

Il n’y a donc qu’un seul paramètre cette fois (de type MultiAssets et qui dicte quels actifs doivent être retirés de la propriété du registre des origines). Mais il n’y a pas d’emplacement spécifié dans lequel placer les actifs.

Les avoirs retirés et non dépensés sont temporairement conservés dans ce que l’on appelle le registre d’attente — (“attente” parce qu’ils sont dans une position temporaire qui ne peut pas durer indéfiniment). Il existe un certain nombre d’instructions qui fonctionnent sur le registre d’attente. L’une d’entre elles, très simple, est l’instruction DepositAssets. Examinons-la :

enum Instruction {
DepositAsset {
assets: MultiAssetFilter,
max_assets: u32,
beneficiary: MultiLocation,
},
/* snip */
}

Le lecteur avisé verra que cela ressemble plutôt à la moitié manquante de l’instruction TransferAsset. Nous avons le paramètre assets qui spécifie quels actifs doivent être retirés du registre des avoirs pour être déposés sur la chaîne. max_assets permet à l’auteur du XCM d’informer le récepteur du nombre d’actifs uniques qui doivent être déposés. (Cette information est utile pour calculer les frais avant de connaître le contenu du registre d’attente, car le dépôt d’un actif peut être une opération coûteuse). Enfin, il y a le bénéficiaire, qui est le même paramètre que nous avons rencontré précédemment dans l’opération TransferAsset.

Il existe de nombreuses instructions qui expriment des actions à effectuer sur le registre des participations, et DepositAsset est l’une des plus simples. Certaines autres sont plutôt plus sophistiquées 😬.

🤑 Paiement des frais dans XCM

Le paiement des frais dans XCM est un cas d’utilisation assez important. La plupart des parachains de la communauté Polkadot exigent de leurs interlocuteurs qu’ils paient pour toutes les opérations qu’ils souhaitent effectuer, de peur de s’exposer au “spam de transaction” et à une attaque par déni de service. Il existe des exceptions à cette règle lorsque les chaînes ont de bonnes raisons de croire que leur interlocuteur se comportera bien — c’est le cas lorsque la chaîne de relais Polkadot correspond à la chaîne de common-good Polkadot Statemint. Cependant, dans le cas général, les frais sont un bon moyen de s’assurer que les messages XCM et leurs protocoles de transport ne sont pas surutilisés. Voyons comment les frais peuvent être payés lorsque les messages XCM arrivent à Polkadot.

Comme déjà mentionné, XCM n’inclut pas l’idée de frais et de paiement de frais comme un citoyen de première classe : Contrairement, par exemple, au modèle de transaction Ethereum, le paiement de frais n’est pas quelque chose d’intégré au protocole que les cas d’utilisation qui n’en ont pas besoin doivent contourner ostensiblement. Comme Rust avec ses abstractions à coût nul, le paiement des frais n’entraîne pas de frais généraux importants dans XCM.

Pour les systèmes qui exigent le paiement de frais, XCM offre la possibilité d’acheter des ressources d’exécution avec des actifs. En gros, cela consiste en trois parties :

  • Tout d’abord, il faut fournir certains atouts.
  • Deuxièmement, l’échange d’actifs contre du temps de calcul (ou du poids, dans le jargon des Substrats) doit être négocié.
  • Enfin, les opérations XCM seront effectuées selon les instructions.

La première partie est gérée par l’une des nombreuses instructions XCM qui fournissent des actifs. Nous connaissons déjà l’une d’entre elles (WithdrawAsset), mais il en existe plusieurs autres que nous verrons plus tard. Les actifs résultants dans le registre de détention seront bien sûr utilisés pour payer les frais associés à l’exécution de la XCM. Tous les actifs non utilisés pour payer les frais seront déposés sur un compte de destination. Pour notre exemple, nous supposerons que le XCM a lieu sur la chaîne de relais Polkadot et qu’il porte sur 1 DOT (soit 10 000 000 000 d’unités indivisibles).

Jusqu’à présent, notre instruction XCM ressemble à ceci :

WithdrawAsset((Here, 10_000_000_000).into()),

Cela nous amène à la deuxième partie, l’échange de (certains de) ces actifs contre du temps de calcul pour payer notre XCM. Pour cela, nous avons l’instruction XCM BuyExecution. Jetons-y un coup d’oeil :

enum Instruction {
/* snip */
BuyExecution {
fees: MultiAsset,
weight: u64,
},
}

Le premier élément de fees est le montant qui doit être prélevé sur le registre d’attente et utilisé pour le paiement des frais. Il s’agit techniquement du maximum puisque tout solde non utilisé est immédiatement restitué.

Le montant qui sera finalement dépensé est déterminé par le système d’interprétation — les fees ne font que le limiter et si le système d’interprétation doit être payé davantage pour l’exécution souhaitée, l’instruction BuyExecution entraînera une erreur. Le deuxième élément spécifie une quantité de temps d’exécution à acheter. Cette quantité ne doit généralement pas être inférieure au poids total du programme XCM.

Dans notre exemple, nous supposerons que toutes les instructions XCM prennent un million de poids, ce qui fait deux millions pour nos deux éléments jusqu’à présent (WithdrawAsset et BuyExecution) et un autre pour ce qui va suivre. Nous utiliserons simplement tout le DOT dont nous disposons pour payer ces frais (ce qui n’est une bonne idée que si nous avons confiance dans la chaîne de destination pour ne pas avoir de frais excessifs — nous supposerons que c’est le cas). Jetons un coup d’oeil à notre XCM jusqu’à présent :

WithdrawAsset((Here, 10_000_000_000).into()),
BuyExecution {
fees: (Here, 10_000_000_000).into(),
weight: 3_000_000,
},

La troisième partie de notre XCM consiste à déposer les fonds restants dans le registre d’attente. Pour cela, nous allons utiliser l’instruction DepositAsset. Nous ne savons pas réellement combien il reste dans le Registre de détention, mais cela n’a pas d’importance puisque nous pouvons spécifier un caractère générique pour le(s) actif(s) à déposer. Nous allons les placer sur le compte souverain de Statemint (qui est identifié commeParachain(1000).

Notre instruction XCM finale ressemble donc à ceci :

WithdrawAsset((Here, 10_000_000_000).into()),
BuyExecution {
fees: (Here, 10_000_000_000).into(),
weight: 3_000_000,
},
DepositAsset {
assets: All.into(),
max_assets: 1,
beneficiary: Parachain(1000).into(),
},

⛓ Déplacement des actifs entre les chaînes dans XCM

L’envoi d’un actif à une autre chaîne est probablement le cas d’utilisation le plus courant de la messagerie inter-chaînes. Permettre à une chaîne d’administrer l’actif natif d’une autre chaîne permet toutes sortes de cas d’utilisation dérivés (sans jeu de mots), le plus simple étant un échange décentralisé mais généralement regroupé sous le nom de finance décentralisée ou DeFi.

D’une manière générale, les actifs circulent entre les chaînes de deux manières différentes, selon que les chaînes ont confiance ou non dans la sécurité et la logique de l’autre.

✨ Téléportation

Pour les chaînes qui se font confiance (par exemple, des shards homogènes sous le même consensus global et le même parapluie de sécurité), nous pouvons utiliser un framework que Polkadot appelle téléportation, qui consiste simplement à détruire un actif du côté de l’émetteur et à le monnayer du côté du récepteur. Cette méthode est simple et efficace : elle ne nécessite que la coordination des deux chaînes et n’implique qu’une seule action de part et d’autre. Malheureusement, si la chaîne réceptrice ne peut pas faire confiance à 100% à la chaîne émettrice pour détruire réellement l’actif qu’elle mint (et même pour ne pas mint des actifs en dehors des règles convenues pour l’actif), alors la chaîne émettrice n’a vraiment aucune base pour mint l’actif au dos d’un message.

Voyons à quoi ressemblerait le XCM qui a téléporté (la majeure partie) de 1 DOT de la chaîne de relais Polkadot vers son compte souverain sur Statemint. Nous supposerons que les frais sont déjà payés du côté de Polkadot.

WithdrawAsset((Here, 10_000_000_000).into()),
InitiateTeleport {
assets: All.into(),
dest: Parachain(1000).into(),
xcm: Xcm(vec![
BuyExecution {
fees: (Parent, 10_000_000_000).into(),
weight: 3_000_000,
},
DepositAsset {
assets: All.into(),
max_assets: 1,
beneficiary: Parent.into(),
},
]),
}

Comme vous pouvez le constater, ce schéma est assez similaire au withdraw-buy-deposit pattern que nous avons vu précédemment. La différence réside dans l’instruction InitiateTeleport qui est insérée autour des deux dernières instructions (BuyExecution et DepositAsset). En coulisse, la chaîne émettrice (Polkadot Relay) crée un tout nouveau message lorsqu’elle exécute l’instruction InitiateTeleport ; elle prend le champ xcm et le place dans un nouveau XCM, ReceiveTeleportedAsset, et envoie ce XCM à la chaîne réceptrice (Statemint). Statemint fait confiance à la chaîne de relais Polkadot pour avoir détruit le 1 DOT de son côté avant d’envoyer le message. (Elle le fait !)

Le beneficiary est indiqué comme Parent.into(), et un lecteur avisé pourrait se demander à quoi cela peut bien faire référence dans le contexte de la chaîne de relais Polkadot. La réponse serait “rien”, mais il n’y a pas d’erreur ici. Tout ce qui figure dans le paramètre xcm est écrit du point de vue de la partie réceptrice. Ainsi, bien qu’il s’agisse d’une partie du XCM global qui est introduit dans la Relay Chain Polkadot, il n’est en fait exécuté que sur Statemint, et c’est donc dans le contexte de Statemint qu’il est écrit.

Lorsque Statemint finit par recevoir le message, il ressemble à ceci :

ReceiveTeleportedAsset((Parent, 10_000_000_000).into()),
BuyExecution {
fees: (Parent, 10_000_000_000).into(),
weight: 3_000_000,
},
DepositAsset {
assets: All.into(),
max_assets: 1,
beneficiary: Parent.into(),
},

Vous remarquerez qu’elle ressemble beaucoup à la précédente WithdrawAsset XCM. La seule différence majeure est qu’au lieu de financer les frais et le dépôt par un retrait d’un compte local, il est “magicked” créé en faisant confiance à la destruction fidèle du DOT du côté de l’envoi (Relay Chain Polkadot) et en honorant le message ReceiveTeleportedAsset.

Notamment, l’identifiant d’actif du 1 DOT que nous avons envoyé sur la Relay Chain Polkadot (ici, en se référant à la Relay Chain elle-même, le foyer natif du DOT) a été automatiquement muté dans sa représentation sur Statemint : Parent.into(), qui est l’emplacement de la Relay Chain dans le contexte de Statemint.

Le beneficiary est également désigné comme étant la Relay Chain Polkadot et son compte souverain (sur Statemint) est donc crédité des 1 DOT nouvellement mint, moins les frais. Le XCM aurait pu facilement nommer un compte ou un autre endroit pour le beneficiary. En l’état actuel des choses, un TransfertAsset envoyé ultérieurement par la Relay Chain pourrait être utilisé pour déplacer ce 1 DOT.

🏦 Réserves

L’autre moyen de transférer des actifs entre chaînes est légèrement plus compliqué. On utilise une tierce partie connue sous le nom de réserve. Le nom vient de la banque de réserve, où les actifs sont détenus “en réserve” pour donner de la crédibilité à l’idée que certaines promesses émises ont de la valeur. Par exemple, si nous pouvons raisonnablement croire qu’exactement 1 DOT “réel” (par exemple Statemint ou Relay Chain) est remboursable pour chaque DOT “dérivé” émis sur un parachain indépendant, alors nous pouvons traiter le DOT du parachain comme étant économiquement équivalent au DOT réel, (la plupart des banques font quelque chose appelé banque de réserve fractionnaire, ce qui signifie qu’elles gardent moins que la valeur nominale en réserve). Cela fonctionne bien jusqu’à ce qu’un trop grand nombre de personnes souhaitent racheter leurs titres, et alors tout peut aller très mal très vite.

Ainsi, la réserve est l’endroit qui stocke les actifs “réels” et, aux fins du transfert, dont la logique et la sécurité font l’objet de la confiance de l’émetteur et du récepteur. Tout actif correspondant du côté de l’émetteur et du récepteur serait alors un produit dérivé, mais il serait adossé à 100% à l’actif “réel” de la réserve. En supposant que le parachain se comporte bien (c’est-à-dire qu’il ne présente aucun bug et que sa gouvernance ne décide pas de s’enfuir avec la réserve), le DOT dérivé aurait plus ou moins la même valeur que le DOT de réserve sous-jacent. Les actifs de réserve sont détenus sur le compte souverain de l’expéditeur/récepteur (c’est-à-dire le compte contrôlable par la chaîne de l’expéditeur ou du récepteur) sur la chaîne de réserve, il y a donc une bonne raison pour que, à moins que quelque chose ne tourne mal avec le parachain, ils soient bien gardés.

Pour en revenir au mécanisme de transfert, l’émetteur demanderait à la réserve de transférer les actifs qu’il possède (et utilise comme réserve pour sa propre version du même actif) sur le compte souverain du récepteur, et la réserve — et non l’émetteur ! — informe le récepteur de son nouveau crédit. Cela signifie que l’émetteur et le récepteur n’ont pas besoin de faire confiance à la logique ou à la sécurité de l’autre, mais seulement à celle de la chaîne utilisée comme réserve. Cependant, cela implique que les trois parties doivent se coordonner, ce qui augmente le coût global, le temps et la complexité.

Examinons le XCM requis. Cette fois, nous allons envoyer 1 DOT de parachain 2000 à parachain 2001, qui utilisent le DOT avec réserve sur parachain 1000. Encore une fois, nous supposerons que les frais sont déjà payés du côté de l’expéditeur.

WithdrawAsset((Parent, 10_000_000_000).into()),
InitiateReserveWithdraw {
assets: All.into(),
dest: ParentThen(Parachain(1000)).into(),
xcm: Xcm(vec![
BuyExecution {
fees: (Parent, 10_000_000_000).into(),
weight: 3_000_000,
},
DepositReserveAsset {
assets: All.into(),
max_assets: 1,
dest: ParentThen(Parachain(2001)).into(),
xcm: Xcm(vec![
BuyExecution {
fees: (Parent, 10_000_000_000).into(),
weight: 3_000_000,
},
DepositAsset {
assets: All.into(),
max_assets: 1,
beneficiary: ParentThen(Parachain(2000)).into(),
},
]),
},
]),
},

C’est un peu plus complexe, comme promis. Faisons le tour. La partie extérieure traite de l’extraction du 1 DOT du côté de l’expéditeur (parachain 2000) et du retrait du 1 DOT correspondant détenu par Statemint (parachain 1000) — elle utilise InitiateReserveWithdraw à cette fin et est assez explicite.

WithdrawAsset((Parent, 10_000_000_000).into()),
InitiateReserveWithdraw {
assets: All.into(),
dest: ParentThen(Parachain(1000)).into(),
xcm: /* snip */
}

Maintenant, nous avons 1 DOT dans le registre d’attente de Statemint. Avant de pouvoir faire quoi que ce soit d’autre, nous devons acheter du temps d’exécution sur Statemint. Ceci, encore une fois, semble assez familier :

/*snip*/
xcm: Xcm(vec![
BuyExecution {
fees: (Parent, 10_000_000_000).into(),
weight: 3_000_000,
},
DepositReserveAsset {
assets: All.into(),
max_assets: 1,
dest: ParentThen(Parachain(2001)).into(),
xcm: /* snip */
},
]),
/*snip*/

Nous utilisons notre 1 DOT pour payer les frais, et nous supposons un million par opération XCM. Une fois cette opération payée, nous déposons le 1 DOT (moins les frais, et comme nous sommes paresseux, nous utilisons simplement All.into()) sur le compte souverain du parachain 2001, mais nous le faisons en tant qu’actif de réserve, ce qui signifie que nous demandons également à Statemint d’envoyer une notification XCM à la chaîne réceptrice pour l’informer du transfert, ainsi que certaines instructions à exécuter sur les actifs dérivés qui en résultent. Les instructions DepositReserveAsset n’ont pas toujours beaucoup de sens ; pour qu’elles aient un sens, le Dest doit être un endroit qui peut raisonnablement détenir des fonds sur la chaîne de réserve, mais aussi un endroit vers lequel la chaîne de réserve peut envoyer un XCM. Les parachutes frères et soeurs répondent parfaitement à ces critères.

/*snip*/
xcm: Xcm(vec![
BuyExecution {
fees: (Parent, 10_000_000_000).into(),
weight: 3_000_000,
},
DepositAsset {
assets: All.into(),
max_assets: 1,
beneficiary: ParentThen(Parachain(2000)).into(),
},
]),
/*snip*/

La dernière partie définit une partie du message qui arrive au parachain 2001. Comme pour le lancement d’une opération de téléportation, DepositReserveAsset compose et envoie un nouveau message, dans ce cas ReserveAssetDeposited. C’est ce message, contenant toutefois le programme XCM que nous définissons, qui arrive au parachain récepteur. Il ressemblera à quelque chose comme ceci :

ReserveAssetDeposited((Parent, 10_000_000_000).into()),
BuyExecution {
fees: (Parent, 10_000_000_000).into(),
weight: 3_000_000,
},
DepositAsset {
assets: All.into(),
max_assets: 1,
beneficiary: ParentThen(Parachain(2000)).into(),
},

(Cela suppose qu’aucun frais n’a été réellement prélevé sur Statemint et que l’ensemble du 1 DOT a été transféré. Ce n’est pas spécialement réaliste, donc la ligne des assets va probablement avoir un chiffre plus bas).

La seule différence significative avec le message ReceiveTeleportedAsset que nous avons vu dans la dernière section est l’instruction de haut niveau ReserveAssetDeposited, qui remplit un objectif similaire, sauf qu’au lieu de signifier “la chaîne émettrice a brûlé des actifs pour que vous puissiez mint des actifs équivalents”, elle signifie “la chaîne émettrice a reçu des actifs et les garde pour vous en réserve, pour que vous puissiez mint des produits dérivés entièrement garantis”. Dans les deux cas, la chaîne de destination les mints dans le registre de détention, et nous les déposons sur le compte souverain de l’expéditeur sur la chaîne de réception. 🎉

🏁 Conclusion

C’est tout pour cet article ; j’espère qu’il a été utile pour expliquer ce qu’est XCM et les principes de base de son fonctionnement. Dans le(s) prochain(s) article(s), nous examinerons plus en détail l’architecture du XCVM, son modèle d’exécution et sa gestion des erreurs, le système de gestion des versions du XCM et la manière dont les mises à jour du format peuvent être gérées dans un écosystème interdépendant bien connecté, ainsi que son système de réponse aux requêtes et le fonctionnement du XCM dans Substrate. Nous aborderons également certaines des orientations futures de XCM, les fonctionnalités prévues et le processus d’évolution du format.

--

--