Sui.

Publication

Partagez vos connaissances.

article banner.
harry phan.
Apr 24, 2025
Article

La logique qui lie l'âme et le modèle de reçu de retour

#Logique qui lie l'âme et modèle de reçu de retour

Passons maintenant à un modèle de conception spécial qui utilise le mécanisme parent-enfant :objets liés à l'âme. Un objet lié à une âme est destiné à être lié à un propriétaire spécifique et à ne pas être transférable de façon permanente. Cependant, vous souhaiterez peut-être autoriser temporairement quelqu'un à y accéder au cours d'une seule transaction, par exemple, pour effectuer une opération dessus, tout en vous assurant qu'il vous soit renvoyé à la fin de cette transaction. Ceci est réalisé grâce à ce que nous appelons lemodèle ReturnReceipt, une variante du modèle « patate chaude ».

###Le motif des patates chaudes à Sui

Le motif « patate chaude » doit son nom au jeu pour enfants : si on vous donne une patate chaude 🌡️🥔, vous ne pouvez pas la conserver ; vous devez la transmettre rapidement. En termes de Move, une patate chaude est généralement un objet ou une ressource qui doit être consommé ou transmis lors d'une même transaction, sinon la transaction échouera. Ceci est souvent mis en œuvre en ne permettant pas de supprimer l'objet (vous ne pouvez donc pas simplement l'ignorer ou le supprimer) et en concevant la logique de telle sorte que la seule façon de s'en débarrasser soit d'effectuer l'action requise (comme le remboursement d'un prêt).

Un cas d'utilisation courant est unprêt flash : vous empruntez des pièces lors d'une transaction (vous obtenez un objet de prêt, plus un jeton de « dette »). Si vous ne remboursez pas avant la fin de la transaction, vous ne pourrez pas la finaliser car vous détenez toujours ce jeton de dette que vous n'êtes pas autorisé à abandonner, ce qui vous oblige à rembourser le prêt ou à l'annuler.

###Exemple d'objet lié à l'âme

Supposons que nous ayons un objet SoulBound que nous voulons toujours conserver avec l'adresse de son propriétaire d'origine. Nous pouvons faire en sorte que si quelqu'un l' « emprunte » (en tant qu'objet enfant auprès d'un parent), il doive le retourner lors de la même transaction. Comment ? En utilisant un accusé de réception.

Vous trouverez ci-dessous une version simplifiée inspirée de l'exemple de documentation de Sui sur les objets liés à l'âme

module demo::soul_bound {
    use sui::transfer::{Self, Receiving};
    use sui::object::UID;
    use sui::transfer;

    const EWrongObject: u64 = 0;

    /// A soul-bound object that cannot be permanently transferred.
    /// It has only `key` ability (no `store`), to tighten transfer/receive rules [oai_citation_attribution:40‡docs.sui.io](https://docs.sui.io/concepts/transfers/transfer-to-object#:~:text=%2F%2F%2F%20This%20object%20has%20,id%3A%20UID%2C).
    public struct SoulBound has key {
        id: UID,
        data: u64
    }

    /// A receipt that proves a SoulBound object must be returned.
    /// No abilities: cannot be copied, dropped, or stored (implicitly).
    public struct ReturnReceipt {
        /// The object ID of the soul-bound object that must be returned.
        object_id: UID,
        /// The address (or object ID) it must be returned to (the original owner).
        return_to: address
    }

    /// Allows the owner of `parent` to retrieve their SoulBound child.
    /// Returns the SoulBound object *and* a ReturnReceipt that compels return.
    public fun take_soul_bound(parent: &mut UID, sb_ticket: Receiving<SoulBound>): (SoulBound, ReturnReceipt) {
        let sb = transfer::receive(parent, sb_ticket);  // receive SoulBound (only this module can do it, since SoulBound has no store) [oai_citation_attribution:41‡docs.sui.io](https://docs.sui.io/concepts/transfers/transfer-to-object#:~:text=%2F%2F%2F%20,to_address%28%29%2C%20object_id%3A%20object%3A%3Aid%28%26soul_bound%29%2C) 
        let receipt = ReturnReceipt {
            object_id: sb.id,
            return_to: parent.to_address()
        };
        (sb, receipt)
    }

    /// Return a SoulBound object using the ReturnReceipt.
    /// This must be called in the same transaction after take_soul_bound.
    public fun return_soul_bound(sb: SoulBound, receipt: ReturnReceipt) {
        // Verify the receipt matches this SoulBound object
        assert!(sb.id == receipt.object_id, EWrongObject);
        // Send the SoulBound object back to the original owner address
        transfer::transfer(sb, receipt.return_to);
        // (ReturnReceipt is consumed/destroyed here as function param)
    }
}

Dans ce design :

  • SoulBound est un objet clé uniquement. En ne lui donnant pas de stockage, nous empêchons l'utilisation de public_transfer ou public_receive dessus, ce qui signifie que le seul moyen de le transférer ou de le recevoir est d'utiliser les fonctions de son module de définition (en veillant à ce que notre logique personnalisée soit utilisée).
  • take_soul_bound (analogue à « get_object » dans la documentation) est une fonction que le propriétaire appellerait pour retirer son objet lié à l'âme d'un parent. Il appelle transfer : :receive pour obtenir l'objet SoulBound. Comme SoulBound n'a pas de magasin, cet appel n'est autorisé que dans son module (ce qui est bien). Il crée ensuite une structure ReturnReceipt contenant l'ID de l'objet lié à l'âme et l'adresse à laquelle retourner (l'adresse du parent). Nous retournonsdeuxl'objet et le reçu.
  • ReturnReceipt ne permet pas de supprimer, de stocker ou de copier (nous n'en avons déclaré aucune, elle est donc traitée comme une ressource qui ne peut pas être supprimée ou dupliquée). Cela signifie que la transactiondoiten faire quelque chose ; sinon, il s'agira d'une ressource restante et la machine virtuelle ne laissera pas la transaction se terminer correctement. Le ReturnReceipt est essentiellement notre jeton Hot Potato 🔥🥔.
  • La seule chose valide à faire avec un ReturnReceipt est d'appeler return_soul_bound (ou une fonction désignée similaire) dans la même transaction. Dans return_soul_bound, nous vérifions que l'identifiant de l'objet SoulBound correspond à l'enregistrement du reçu (pour éviter que quelqu'un n'essaie de renvoyer un autre objet avec un reçu erroné). Ensuite, nous appelons transfer : :transfer (sb, receipt t.return_to), qui renvoie l'objet SoulBound à l'adresse indiquée sur le reçu (le propriétaire d'origine). Cela redonne efficacement à l'objet lié à l'âme sa juste place. Le ReturnReceipt est consommé comme argument (donc détruit).
  • Si l'utilisateur essaie de terminer la transaction sans appeler return_soul_bound, il conservera toujours le ReturnReceipt (issu de la sortie take_soul_bound). Comme ReturnReceipt n'a pas de drop, la machine virtuelle Move refusera de terminer la transaction (dans Move, vous ne pouvez pas simplement supprimer une ressource ; elle doit être utilisée ou stockée quelque part). La transaction serait abandonnée ou considérée comme non valide, ce qui signifierait que l'ensemble de l'opération serait annulé.**Résultat :**vous ne pouvez pas simplement partir avec l'objet lié à l'âme ; si vous ne le renvoyez pas, le tx échoue et il reste avec le parent.

À partir du moment où vous appelez take_soul_bound jusqu'au moment où vous appelez return_soul_bound dans la transaction, la valeur de l'objet SoulBound est disponible, probablement pour effectuer certaines opérations autorisées dessus (peut-être la lire ou l'utiliser si nécessaire). Mais vousdezle retourner avant la fin de la transaction, grâce à l'application du reçu.

Ce schéma est souvent décrit comme « lié à l'âme = ne peut pas quitter son propriétaire », mais plus précisément, il peut partir en une seule transaction en tant qu'objet enfant, avec la garantie qu'il reviendra. C'est comme consulter un livre de bibliothèque : vous devez le rendre avant la fermeture de la bibliothèque pour la journée 😅.

Pourquoi ne pas tout simplement ne jamais le transférer du tout ? Dans certains scénarios, le transfert temporaire d'un objet vers un autre objet (ou contexte) est utile. Un exemple est le contexte d'unobjet partagé : peut-être que l'élément SoulBound est conservé sous un objet partagé pendant une opération et doit être retiré par son propriétaire d'origine. Une autre solution consiste à permettre une composition contrôlée. Vous pouvez laisser un module fonctionner sur votre objet en le donnant à l'objet de ce module, puis en le récupérant.

En faisant de SoulBound une clé uniquement, nous avons veillé à ce qu'aucun public_receive externe ne puisse la récupérer sans l'intervention de notre module. En utilisant le reçu, nous forçons le respect de la politique de retour. Le commentaire de l'exemple de code de Sui indique même que le ReturnReceipt empêche l'échange : si deux objets liés à une âme ont été retirés en une seule transaction, chaque reçu porte un identifiant d'objet spécifique, vous ne pouvez donc pas les mélanger et renvoyer le mauvais pour tricher.

**Généraliser l'idée :**Le modèle ReturnReceipt peut être utilisé chaque fois que vous souhaitez imposer la restitution d'un objet. Les prêts flash utilisent un concept similaire (jeton de prêt et jeton de dette qui doit être remboursé). Chaque fois que vous avez un invariant « l'objet X doit se retrouver à l'adresse Y avant la fin de tx », vous pouvez créer une ressource de réception pour le confirmer.

###Récapitulatif rapide :

-Objets liés à l'âme : clés uniquement, récupérés uniquement par la logique de leur module. -ReturnReceipt : une ressource fictive renvoyée à côté de l'objet pour garantir son retour. Comme il ne s'agit pas d'une fonction drop, l'utilisateur doit appeler la fonction de retour ou échouer. -Patate chaude : Si vous êtes en possession d'un reçu de retour, vous feriez mieux de le manipuler (retournez l'objet) avant que la « musique ne s'arrête » (la transaction se termine).

  • Si l'objet n'est pas renvoyé, la transaction ne sera pas exécutée correctement. Vos modifications seront annulées, appliquant ainsi la règle de l'âme.

C'est ainsi que nous avons abordé les principaux concepts des relations parent-enfant dans Sui Move !

##5. Structure du projet et stratégie de test (compatible avec GitHub)

Pour consolider ces concepts, vous pouvez créer un projet Sui Move et jouer avec les exemples. Voici une présentation de projet suggérée qui inclut des modules pour les exemples ci-dessus. Vous pouvez compiler et exécuter des tests unitaires ou utiliser la CLI Sui pour interagir avec eux.

parent_child_demo/
├── Move.toml
├── sources/
│   ├── toy_box.move        (Parent & child in same module example)
│   ├── parcel.move         (Child module for Parcel)
│   ├── warehouse.move      (Parent module for Warehouse, uses public_receive)
│   └── soul_bound.move     (SoulBound and ReturnReceipt module)
└── tests/
    └── parent_child_test.move   (Integration tests for the modules)

**Move.toml :**Assurez-vous d'inclure le framework Sui et toutes les adresses de votre package. Par exemple :

[package]
name = "parent_child_demo"
version = "0.0.1"
dependencies = [ 
    { name = "Sui", git = "https://github.com/MystenLabs/sui.git", subdir = "crates/sui-framework", rev = "devnet" }
]

[addresses]
demo = "0x0"
  • (Utilisez une adresse correcte au lieu de 0x0 si Sui l'exige, ou une adresse nommée que vous attribuerez lors de la publication. ) *

Dans le répertoire des sources :

-toy_box.movepourrait contenir le module example : :toy_box de la partie 2, avec les définitions de Toy et Box et la fonction take_toy. -parcel.moveetwarehouse.movecontiendront les modules de la partie 3. -soul_bound.movecontiendra le module de la partie 4.

Chaque module doit utiliser sui : :... selon les besoins pour le transfert, l'objet, le tx_context, etc., comme indiqué dans les exemples. Le tests/parent_child_test.move peut ensuite importer ces modules et simuler des scénarios :

module demo::parent_child_test {
    use 0xYourAddr::toy_box;
    use 0xYourAddr::warehouse;
    use 0xYourAddr::parcel;
    use 0xYourAddr::soul_bound;
    use sui::tx_context::TxContext;

    #[test]
    fun test_toy_withdraw() {
        let ctx = TxContext::new(); // pseudo, context is usually provided
        let my_box = toy_box::Box { id: object::new(&mut ctx) };
        let my_toy = toy_box::Toy { id: object::new(&mut ctx), name: b"Ball".to_vec() };
        // Transfer toy into the box
        transfer::transfer(my_toy, my_box.id); 
        // Now simulate receiving it back
        let ticket = transfer::Receiving<toy_box::Toy>{ /*...*/ }; // In real test, use transfer::make_receiver or a transaction call
        let returned_toy = toy_box::take_toy(&mut my_box, ticket);
        assert!(returned_toy.name == b"Ball".to_vec());
    }

    // Additional tests for warehouse/parcel and soul_bound can be written similarly.
}

Ce qui précède est une illustration conceptuelle. En pratique, le contexte de test de Sui peut différer : vous devrez peut-être utiliser transfer : :public_transfer dans les tests lors du transfert vers un objet (s'il s'agit d'un module externe), et utiliser le contexte de réception depuis la transaction. Sui fournit un transfer : :make_receiver (fonction de test uniquement) qui peut fabriquer un récepteur à partir d'un ID et d'une version, ce qui est utile dans les tests unitaires pour simuler la saisie d'un objet enfant.

**Exécution de tests :**Vous pouvez exécuter sui move test qui exécutera les fonctions # [test]. Ou déployez le package sur un réseau Sui local et appelez les fonctions d'entrée via l'interface de ligne de commande ou un SDK pour observer le comportement :

  • Créez un colis, créez un entrepôt, appelez transfer : :public_transfer via la CLI pour placer le colis dans l'entrepôt, puis appelez withdraw_parcel.
  • Créez un objet SoulBound (peut-être en rendant une entrée amusante pour en créer un), transférez-le vers un parent (ou un objet partagé), puis, en une seule transaction, appelez take_soul_bound et omettez return_soul_bound pour voir la transaction échouer (prévu), au lieu d'inclure le retour pour voir le succès de la transaction.

Chaque volet de ce projet aborde l'un des sujets suivants :

  • toy_box.move : parent/enfant de base et récepteur.
  • parcel.move & warehouse.move : enfants intermodules et public_receive.
  • soul_bound.move : Soul-bound avec ReturnReceipt.

En les expérimentant, vous approfondirez votre compréhension du modèle d'objet de Sui. Le code est structuré de manière pédagogique et ne doit pas être utilisé tel quel pour la production sans examens de sécurité, mais il constitue un point de départ solide.


**Conclusion :**La fonctionnalité d'objet parent-enfant de Sui Move est puissante. Il vous permet de créer des structures de données complexes en chaîne (comme des inventaires, des portefeuilles, des collections) avec un contrôle d'accès précis. La combinaison de transfer : :receive/public_receive et du système de types de Move garantit que seul le code autorisé peut récupérer les objets enfants, et des modèles tels que ReturnReceipt permettent d'appliquer des règles de propriété temporelles (liaison d'âme, prêts flash, etc.). Nous avons ajouté quelques analogies et émojis amusants, mais en fin de compte, ce sont des outils robustes pour les constructeurs. Maintenant, vas-y et crée de la magie des objets imbriqués ! 🚀🔥

Lorsqu'un objet est transféré vers un autre objet au lieu d'une adresse de compte, il forme unerelation parent-enfant. Vous pouvez le voir comme ceci :

-Parent= objet contenant -Enfant= objet placé à l'intérieur

Move ne se soucie pas de savoir si vous effectuez un transfert vers un compte ou un identifiant d'objet. Il ne fait que bouge.

public struct Parent has key {
    id: UID,
    name: String,
}

public struct Child has key {
    id: UID,
    description: String,
}

##Création d'objets pour parents et enfants

Le parent doit être mutable et doit exister. On ne peut pas mettre des choses dans une boîte immuable !

  • Sui
  • Architecture
4
Partager
Commentaires
.

Sui is a Layer 1 protocol blockchain designed as the first internet-scale programmable blockchain platform.

610Publications1335Réponses
Sui.X.Peera.

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.

Campagne de RécompensesJuillet