SC-21-Move-Sui-Python - Move sur Sui

<< Bitcoin Scripting | Solana & Anchor >>


Objectifs d’apprentissage

  1. Decouvrir le langage Move (Meta/Diem)
  2. Comprendre le modèle objet de Sui
  3. Créer un module Move simple
  4. Utiliser les abilities (key, store, copy, drop)

Prerequis

  • SC-3 a SC-6 completes (concepts)
  • Notions de programmation orientee ressources
  • Sui CLI installe (voir instructions dans le notebook)

Duree estimee : 50 minutes


1. Introduction a Move

Move est un langage concu pour les smart contracts avec une sémantique de ressources.

# Comparaison Move vs Solidity
print("""
MOVE vs SOLIDITY

| Caracteristique    | Solidity         | Move                |
|-------------------|------------------|---------------------|
| Paradigme         | Comptes          | Objets              |
| Ressources        | Non-lineaires    | Lineaires (assets)  |
| Ownership         | Implicit         | Explicit (abilities)|
| Generique         | Non              | Oui                 |
| Bytecode          | EVM              | Move VM             |

ABILITIES (Capacites):
- key    : Peut etre un identifiant global
- store  : Peut etre stocke dans un objet
- copy   : Peut etre copie
- drop   : Peut etre detruit implicitement
""")

MOVE vs SOLIDITY

| Caracteristique    | Solidity         | Move                |
|-------------------|------------------|---------------------|
| Paradigme         | Comptes          | Objets              |
| Ressources        | Non-lineaires    | Lineaires (assets)  |
| Ownership         | Implicit         | Explicit (abilities)|
| Generique         | Non              | Oui                 |
| Bytecode          | EVM              | Move VM             |

ABILITIES (Capacites):
- key    : Peut etre un identifiant global
- store  : Peut etre stocke dans un objet
- copy   : Peut etre copie
- drop   : Peut etre detruit implicitement

Interpretation : Move vs Solidity - Paradigmes Différent

Ce tableau compare les différents paradigmes de programmation entre Solidity (EVM) et Move (Sui/Aptos).

Caractéristique Solidity (EVM) Move (Sui) Impact pratique
Paradigme Comptes (account-based) Objets (object-based) Solidity : etat global dans le contrat; Move : objets independants avec proprietaires
Ressources Non-lineaires (copiables) Lineaires (uniques) Move : impossible de copier un asset sans autorisation explicite
Ownership Implicite (mapping address → uint) Explicite (abilities key/store) Move : le compilateur verifie qui peut posseder quoi
Generiques Non Oui (types parametrables) Move : Coin<T> est un type generique (fonctionne pour n’importe quel token)
Bytecode EVM (stack-based) Move VM (registres, bytecodes) Move : plus proche de machine native, optimisations possibles

Points cles : 1. Abilities (capacites) : En Move, chaque struct declare explicitement ce qu’elle peut faire (key, store, copy, drop) 2. Ressources lineaires : Un token Coin est une ressource qui ne peut pas etre copiee (pas de double-spend possible) 3. Modèle objet : Sur Sui, chaque NFT, chaque token est un objet avec son propre UID (pas d’index dans un mapping) 4. Parallelisme : Les objets Move peuvent etre modifiés en parallele s’ils sont independants (pas de contention globale)

Exemples d’abilities : - key : L’objet peut etre stocke globalement (possede par une adresse) - store : L’objet peut etre stocke dans un autre objet (nesting) - copy : L’objet peut etre copie (par defaut : non, pour securite) - drop : L’objet peut etre detruit implicitement (sinon : destruction manuelle obligatoire)

Implications pour les developpeurs : - Sur Ethereum : on deploye un contrat avec des mappings globaux (balances, allowances) - Sur Sui : on deploie un module qui définit des types d’objets (NFTs, coins, escrows) - Le modèle Move est plus verifiable par le compilateur (moins de bugs de runtime)

Note technique : Le terme “lineaire” en Move signifie que la valeur ne peut etre utilisee qu’une seule fois. Quand une fonction consomme une ressource lineaire, elle doit la “detruire” ou la “transferer”.


2. Module Move Simple

Exemple d’un token simple sur Sui.

# Module Move: Coin natif
MOVE_COIN = '''
/// Module: my_coin.move
module my_package::my_coin {
    use sui::coin::{Self, Coin};
    use sui::transfer;
    use sui::object::{Self, UID};
    use sui::tx_context::{Self, TxContext};

    /// Erreur: montant insuffisant
    const EInsufficientBalance: u64 = 0;

    /// Structure du token (capability pour mint)
    public struct AdminCap has key {
        id: UID,
    }

    /// One-Time Witness pour le coin
    public struct MY_COIN has drop {}

    /// Initialisation du module
    fun init(witness: MY_COIN, ctx: &mut TxContext) {
        // Creer la capability admin
        transfer::transfer(
            AdminCap { id: object::new(ctx) },
            tx_context::sender(ctx)
        );

        // Enregistrer le coin
        coin::create_currency(witness, 9, b"MYC", b"My Coin", b"", ctx);
    }

    /// Minter de nouveaux tokens
    public fun mint(
        _: &AdminCap,
        amount: u64,
        recipient: address,
        ctx: &mut TxContext
    ) {
        let coin = coin::mint(amount, ctx);
        transfer::public_transfer(coin, recipient);
    }
}
'''

print("Module Move - Coin:")
print(MOVE_COIN)
Module Move - Coin:

/// Module: my_coin.move
module my_package::my_coin {
    use sui::coin::{Self, Coin};
    use sui::transfer;
    use sui::object::{Self, UID};
    use sui::tx_context::{Self, TxContext};

    /// Erreur: montant insuffisant
    const EInsufficientBalance: u64 = 0;

    /// Structure du token (capability pour mint)
    public struct AdminCap has key {
        id: UID,
    }

    /// One-Time Witness pour le coin
    public struct MY_COIN has drop {}

    /// Initialisation du module
    fun init(witness: MY_COIN, ctx: &mut TxContext) {
        // Creer la capability admin
        transfer::transfer(
            AdminCap { id: object::new(ctx) },
            tx_context::sender(ctx)
        );

        // Enregistrer le coin
        coin::create_currency(witness, 9, b"MYC", b"My Coin", b"", ctx);
    }

    /// Minter de nouveaux tokens
    public fun mint(
        _: &AdminCap,
        amount: u64,
        recipient: address,
        ctx: &mut TxContext
    ) {
        let coin = coin::mint(amount, ctx);
        transfer::public_transfer(coin, recipient);
    }
}

Interpretation : Module Move - Coin Natif Sui

Ce module presente la creation d’un token natif sur Sui en utilisant le système de coin built-in.

Composant Rôle Details
AdminCap Capability de mintage Objet key transférable, represente l’autorite de mint
MY_COIN One-Time Witness Type avec drop qui n’existe qu’en un seul exemplaire (pour coin::create_currency)
init() Initialisation du module Appelé automatiquement a la première publication, créé la capability et enregistre le coin
mint() Creation de tokens Consomme la capability, mint des coins vers un recipient

Points cles : 1. One-Time Witness (OTW) : MY_COIN est un type qui ne peut etre instancié qu’une seule fois (garanti par le compilateur Move) 2. coin::create_currency(witness, 9, b"MYC", ...) : enregistre le coin avec 9 decimales, symbole “MYC” 3. coin::mint(amount, ctx) : créé amount tokens du coin (utilise le witness enregistre) 4. transfer::public_transfer(coin, recipient) : transfere les coins nouvellement mints vers le destinataire

Abilities Move : - key : l’objet peut etre un identifiant global (adresse, objet own) - store : l’objet peut etre stocke dans un autre objet (nesting) - copy : l’objet peut etre copie (par defaut, les structs ne peuvent pas etre copiees) - drop : l’objet peut etre detruit implicitement (sinon, doit etre explicitement detruit)

Différence avec Solidity : - Solidity : ERC-20 est un contrat avec mapping(address => uint256) balances - Move : Coin est un objet lineaire (ressource) qui existe en un seul exemplaire par unite

Avantages du modèle Move : - Lineaire : les coins ne peuvent pas etre copies ou doubles (garanti par le type system) - Pas de double-spend : impossible de depenser les mêmes coins deux fois (le compilateur l’interdit) - Performance : les objects sont des ressources uniques (pas de mapping global a scanner)

Note technique : L’OTW (One-Time Witness) est un pattern unique a Move. Le type porte le même nom que le module (MY_COIN dans my_coin), ce qui permet au compilateur de garantir l’unicite.


3. Modèle Objet Sui

Chaque objet a un proprietaire unique.

# NFT simple sur Sui
MOVE_NFT = '''
module my_package::my_nft {
    use sui::object::{Self, UID};
    use sui::transfer;
    use sui::tx_context::{Self, TxContext};
    use sui::url::Url;

    /// Structure NFT
    public struct NFT has key, store {
        id: UID,
        name: String,
        description: String,
        url: Url,
    }

    /// Admin capability pour minter
    public struct AdminCap has key { id: UID }

    /// Initialisation
    fun init(ctx: &mut TxContext) {
        transfer::transfer(
            AdminCap { id: object::new(ctx) },
            tx_context::sender(ctx)
        );
    }

    /// Minter un nouveau NFT
    public fun mint(
        _: &AdminCap,
        name: String,
        description: String,
        url: Url,
        recipient: address,
        ctx: &mut TxContext
    ) {
        let nft = NFT {
            id: object::new(ctx),
            name,
            description,
            url,
        };
        transfer::public_transfer(nft, recipient);
    }

    /// Transferer un NFT
    public fun transfer(nft: NFT, recipient: address) {
        transfer::public_transfer(nft, recipient);
    }

    /// Burn un NFT
    public fun burn(nft: NFT) {
        let NFT { id, name: _, description: _, url: _ } = nft;
        object::delete(id);
    }
}
'''

print("Module Move - NFT:")
print(MOVE_NFT)
Module Move - NFT:

module my_package::my_nft {
    use sui::object::{Self, UID};
    use sui::transfer;
    use sui::tx_context::{Self, TxContext};
    use sui::url::Url;

    /// Structure NFT
    public struct NFT has key, store {
        id: UID,
        name: String,
        description: String,
        url: Url,
    }

    /// Admin capability pour minter
    public struct AdminCap has key { id: UID }

    /// Initialisation
    fun init(ctx: &mut TxContext) {
        transfer::transfer(
            AdminCap { id: object::new(ctx) },
            tx_context::sender(ctx)
        );
    }

    /// Minter un nouveau NFT
    public fun mint(
        _: &AdminCap,
        name: String,
        description: String,
        url: Url,
        recipient: address,
        ctx: &mut TxContext
    ) {
        let nft = NFT {
            id: object::new(ctx),
            name,
            description,
            url,
        };
        transfer::public_transfer(nft, recipient);
    }

    /// Transferer un NFT
    public fun transfer(nft: NFT, recipient: address) {
        transfer::public_transfer(nft, recipient);
    }

    /// Burn un NFT
    public fun burn(nft: NFT) {
        let NFT { id, name: _, description: _, url: _ } = nft;
        object::delete(id);
    }
}

Interpretation : NFT sur Sui avec Move

Ce module montre comment créer et gerer un NFT sur Sui en utilisant le modèle objet de Move.

Composant Rôle Abilities
struct NFT Representation du NFT key (objet global), store (stockable dans d’autres objets)
struct AdminCap Capability pour minter key (objet transférable, represente l’autorite)
mint() Creation d’un NFT Consomme la capability, créé un objet NFT
transfer() Transfert de propriete Deplace l’objet NFT vers un nouveau proprietaire
burn() Destruction du NFT Supprime l’objet et libere l’UID

Points cles : 1. has key, store : le NFT peut etre un objet global (proprietaire = adresse) et etre stocke dans d’autres objets 2. object::new(ctx) : créé un nouvel objet avec un UID unique (identifiant global sur Sui) 3. transfer::public_transfer(nft, recipient) : transfere la propriete de l’objet (atomic, sans approve prealable) 4. burn() utilise le pattern “destructuring” : let NFT { id, name: _, ... } = nft; extrait l’UID pour suppression

Différence Solidity vs Move : - Solidity : mapping(uint256 => address) ownerOf; (le NFT est un index dans un mapping) - Move : Le NFT est un objet avec son propre UID, possede par une adresse (pas de mapping global)

Avantages du modèle Move : - Pas d’approbation prealable : le proprietaire peut transferer directement (pas de approve/setApprovalForAll) - Typage statique : le compilateur verifie que les ressources ne sont pas copiees ou detruites par erreur - Parallelisme : chaque objet peut etre modifie independamment (pas de contention globale)

Note technique : L’ability store permet au NFT d’etre stocke dans d’autres objets (ex: une collection contient plusieurs NFTs). Sans store, le NFT ne peut etre possede que par une adresse.

Exercice : Modèle objet Sui – Transfert conditionnel

Le modèle objet de Sui permet des transferts complexes via des règles de propriete. Completez la classe SuiObjectSimulator qui simule le transfert d’objets entre adresses avec des règles de type “owned object” et “shared object”.

Objectifs : 1. Simuler la creation et le transfert d’objets Sui entre adresses 2. Implementer le transfert conditionnel (l’objet ne peut etre transfere que si le proprietaire actuel signe) 3. Gerer les objets partages (shared objects) accessibles par tous

Indices : - Chaque objet Sui a un UID unique, un proprietaire (adresse), et un type (struct) - Un “owned object” ne peut etre modifie que par son proprietaire - Un “shared object” peut etre lu par tous mais modifie selon des règles spécifiques - Utiliser un dictionnaire objects: dict[str, dict] ou la cle est l’UID

# Exercice : Modele objet Sui -- Transfert conditionnel
class SuiObjectSimulator:
    """Simule le modele objet de Sui avec transferts et objets partages."""

    def __init__(self):
        # TODO etudiant : initialiser le dictionnaire d'objets {uid: {owner, type, data}}
        # TODO etudiant : initialiser un compteur pour generer des UIDs uniques
        pass

    def create_object(self, owner: str, obj_type: str, data: dict) -> str:
        """
        Cree un nouvel objet possede par owner.

        Args:
            owner: adresse du proprietaire
            obj_type: type de l'objet (ex: "Coin", "NFT")
            data: donnees de l'objet

        Returns:
            str: UID de l'objet cree

        TODO etudiant : generer un UID unique et stocker l'objet
        """
        # TODO etudiant : implementez create_object
        return ""

    def transfer(self, sender: str, obj_uid: str, recipient: str) -> bool:
        """
        Transfere un objet owned de sender vers recipient.

        Args:
            sender: adresse du proprietaire actuel
            obj_uid: UID de l'objet a transferer
            recipient: adresse du nouveau proprietaire

        Returns:
            bool: True si le transfert a reussi

        TODO etudiant : verifier que sender est bien le proprietaire
        TODO etudiant : mettre a jour le proprietaire
        """
        # TODO etudiant : implementez transfer
        return False

    def share_object(self, obj_uid: str) -> bool:
        """
        Transforme un objet owned en objet shared (accessible par tous).

        TODO etudiant : changer le proprietaire en "SHARED"
        """
        # TODO etudiant : implementez share_object
        return False

    def get_object(self, obj_uid: str) -> dict:
        """
        Retourne les informations d'un objet.

        TODO etudiant : retourner l'objet depuis le dictionnaire
        """
        # TODO etudiant : implementez get_object
        return {}


# Test apres implementation
sim = SuiObjectSimulator()
uid1 = sim.create_object("0xAlice", "Coin", {"amount": 1000})
uid2 = sim.create_object("0xAlice", "NFT", {"name": "CryptoPunk #42"})
print(f"Objet cree : uid={uid1}, {sim.get_object(uid1)}")

# Transfert valide
sim.transfer("0xAlice", uid1, "0xBob")
print(f"Apres transfert : {sim.get_object(uid1)}")

# Transfert invalide (mauvais proprietaire)
result = sim.transfer("0xAlice", uid1, "0xCharlie")
print(f"Transfert invalide : {result} (attendu False)")

# Partager un objet
sim.share_object(uid2)
print(f"Objet partage : {sim.get_object(uid2)}")
print("Exercice a completer : SuiObjectSimulator")
Objet cree : uid=, {}
Apres transfert : {}
Transfert invalide : False (attendu False)
Objet partage : {}
Exercice a completer : SuiObjectSimulator

4. Commandes Sui CLI

Compilation et deploiement.

# Commandes Sui CLI
print("""
INSTALLATION:
cargo install --locked --git https://github.com/MystenLabs/sui.git --branch mainnet sui

CONFIGURATION:
sui client active-address     # Voir l'adresse active
sui client active-env        # Voir l'environnement
sui client switch --env devnet  # Changer d'environnement

PROJET:
sui move new my_package    # Creer un nouveau projet
cd my_package
mkdir -p sources tests

COMPILATION:
sui move build            # Compiler le module

TESTS:
sui move test             # Executer les tests unitaires

DEPLOIEMENT:
sui client publish --gas-budget 100000000

APPELS:
sui client call --package 0x... --module my_coin \
    --function mint --args 1000 0x... --gas-budget 10000000
""")

INSTALLATION:
cargo install --locked --git https://github.com/MystenLabs/sui.git --branch mainnet sui

CONFIGURATION:
sui client active-address     # Voir l'adresse active
sui client active-env        # Voir l'environnement
sui client switch --env devnet  # Changer d'environnement

PROJET:
sui move new my_package    # Creer un nouveau projet
cd my_package
mkdir -p sources tests

COMPILATION:
sui move build            # Compiler le module

TESTS:
sui move test             # Executer les tests unitaires

DEPLOIEMENT:
sui client publish --gas-budget 100000000

APPELS:
sui client call --package 0x... --module my_coin     --function mint --args 1000 0x... --gas-budget 10000000

Interpretation : Commandes Sui CLI

Ces commandes couvrent le cycle de vie complet de développement d’un module Move sur Sui.

Commande Action Usage typique
sui move new my_package Créer un nouveau projet Initialise la structure de base (sources/, tests/, Move.toml)
sui move build Compiler le module Move Verifier la syntaxe et les types
sui move test Executer les tests unitaires Validation de la logique avant deploiement
sui client publish Deploier le module sur Sui Publication du bytecode sur devnet/mainnet
sui client call Appeler une fonction publique Interaction avec le module deploye

Points cles : 1. --gas-budget est obligatoire sur Sui pour limiter le cout maximum d’une transaction (en Mist = 10^-9 SUI) 2. sui client active-address montre l’adresse actuellement utilisee pour signer les transactions 3. sui client switch --env devnet change entre devnet, testnet et mainnet (config dans ~/.sui/sui_config/client.yaml) 4. L’adresse du package (0x...) retournee par publish est necessaire pour les appels ulterieurs

Workflow typique :

sui move new mon_nft      # Créer le projet
# Editer sources/mon_nft.move
sui move build            # Compiler (verifier la syntaxe)
sui move test             # Tester (unite tests)
sui client publish --gas-budget 100000000  # Deploier
sui client call --package <addr> --module mon_nft --function mint --args ...  # Appeler

Différences avec Solidity : - Sui n’a pas de “deployement de contrat” classique : on publie un package avec plusieurs modules - Les modules ne sont pas deployes separement : tout le package est deploie en une transaction - Le package ID est l’identifiant unique de toutes les fonctions du module

Note technique : Le gas budget est en Mist (1 SUI = 1,000,000,000 Mist). Un budget de 100,000,000 Mist = 0.1 SUI. Si la transaction coute moins, la différence est remboursee.

Exercice : Analyseur d’abilities Move

Le système d’abilities de Move (key, store, copy, drop) est fondamental pour la securite des ressources. Completez la fonction analyze_abilities qui verifie la validite d’une combinaison d’abilities et identifie les problemes potentiels.

Objectifs : 1. Verifier qu’une combinaison d’abilities est valide selon les règles Move 2. Identifier les abilities manquantes pour un cas d’usage donne 3. Determiner si un struct est un asset (ne peut pas etre copie) ou une valeur (peut etre copie)

Indices : - Un struct avec key doit aussi avoir store (un objet global doit pouvoir etre stocke) - Un struct sans drop ne peut pas etre detruit implicitement : il faut le consommer explicitement - Un “asset” Move n’a PAS copy ni drop (comme un token : unique, ne peut pas etre duplique ou perdu) - Un struct avec copy + drop est une valeur ordinaire (comme un entier)

def analyze_abilities(struct_name: str, abilities: set) -> dict:
    """Analyser les abilities d'un struct Move et detecter les problemes.

    Args:
        struct_name: nom du struct
        abilities: ensemble d'abilities (parmi {"key", "store", "copy", "drop"})

    Returns:
        dict avec :
            - "is_valid": True si la combinaison est valide en Move
            - "is_asset": True si c'est un asset (pas copy, pas drop)
            - "warnings": liste de warnings (combinaisons douteuses)
            - "missing_for_object": abilities manquantes pour etre un objet Sui valide
            - "category": "asset", "value", "object", ou "invalid"

    TODO etudiant : implementez analyze_abilities.
    Etape 1 : verifier que key implique store (regle Move)
    Etape 2 : determiner si c'est un asset (pas copy, pas drop)
    Etape 3 : ajouter des warnings si la combinaison est inhabituelle
    Etape 4 : determiner la categorie
    """
    # TODO etudiant : implementez analyze_abilities
    return {"is_valid": False, "is_asset": False, "warnings": [],
            "missing_for_object": set(), "category": "invalid"}


# Tests avec differents structs Move
test_cases = [
    ("Coin<SUI>", {"key", "store"}),       # Token natif : asset
    ("NFT", {"key", "store"}),              # NFT : asset
    ("AdminCap", {"key"}),                  # Capability : invalide sans store
    ("MY_COIN", {"drop"}),                  # One-Time Witness : valide
    ("UserInfo", {"store", "copy", "drop"}), # Valeur ordinaire
    ("TreasuryCap", {"store"}),             # Capability interne
]

for name, abilities in test_cases:
    result = analyze_abilities(name, abilities)
    print(f"{name:20s} {abilities} -> {result['category']:10s} valid={result['is_valid']} asset={result['is_asset']}")
    for w in result.get("warnings", []):
        print(f"  Warning : {w}")
print("Exercice a completer : analyze_abilities")
Coin<SUI>            {'store', 'key'} -> invalid    valid=False asset=False
NFT                  {'store', 'key'} -> invalid    valid=False asset=False
AdminCap             {'key'} -> invalid    valid=False asset=False
MY_COIN              {'drop'} -> invalid    valid=False asset=False
UserInfo             {'drop', 'copy', 'store'} -> invalid    valid=False asset=False
TreasuryCap          {'store'} -> invalid    valid=False asset=False
Exercice a completer : analyze_abilities

Exercice : Explorateur de champs dynamiques Move

Les dynamic fields de Sui permettent d’ajouter dynamiquement des paires cle-valeur a un objet, sans les declarer dans le struct. Ce mécanisme est essentiel pour les collections (ex: un objet Collection contenant N objets Item). Completez la classe DynamicFieldExplorer qui simule ce comportement.

Objectifs : 1. Ajouter, lire et supprimer des champs dynamiques sur un objet parent 2. Verifier que les cles sont uniques dans un objet donne 3. Calculer la taille totale (nombre de champs) d’un objet

Indices : - Utilisez un dictionnaire imbrique : {parent_uid: {key: value}} - Les cles peuvent etre des strings ou des entiers (utilisez str(key) pour uniformiser) - Un champ dynamique ne peut pas exister si l’objet parent n’existe pas - La suppression d’un champ inexistant doit echouer silencieusement (retourner False)

# Exercice : Explorateur de champs dynamiques Move
class DynamicFieldExplorer:
    """Simule les dynamic fields de Sui : paires cle-valeur attachees dynamiquement a un objet."""

    def __init__(self):
        # TODO etudiant : initialiser les structures de donnees
        # Indice : dict {parent_uid: {key: value}} pour les champs
        # Indice : set pour suivre les objets existants
        pass

    def register_object(self, uid: str) -> None:
        """
        Enregistre un objet comme parent potentiel pour des champs dynamiques.

        TODO etudiant : ajouter uid au set d'objets et initialiser son dict de champs
        """
        pass

    def add_field(self, parent_uid: str, key, value) -> bool:
        """
        Ajoute un champ dynamique a un objet.

        Args:
            parent_uid: UID de l'objet parent
            key: cle du champ (string ou int)
            value: valeur du champ

        Returns:
            bool: True si ajoute, False si parent inexistant ou cle deja prise

        TODO etudiant : verifier que le parent existe
        TODO etudiant : verifier que la cle n'existe pas deja
        TODO etudiant : ajouter la paire cle-valeur
        """
        # TODO etudiant : implementez add_field
        return False

    def get_field(self, parent_uid: str, key):
        """
        Lit la valeur d'un champ dynamique.

        Returns:
            La valeur du champ, ou None si introuvable

        TODO etudiant : implementez get_field
        """
        # TODO etudiant : implementez get_field
        return None

    def remove_field(self, parent_uid: str, key) -> bool:
        """
        Supprime un champ dynamique.

        Returns:
            bool: True si supprime, False si introuvable

        TODO etudiant : implementez remove_field
        """
        # TODO etudiant : implementez remove_field
        return False

    def field_count(self, parent_uid: str) -> int:
        """
        Retourne le nombre de champs dynamiques d'un objet.

        TODO etudiant : implementez field_count
        """
        # TODO etudiant : implementez field_count
        return 0


# Test apres implementation
explorer = DynamicFieldExplorer()
explorer.register_object("obj_001")
explorer.register_object("obj_002")

# Ajout de champs
explorer.add_field("obj_001", "name", "Ma collection")
explorer.add_field("obj_001", 0, {"type": "Item", "rarity": "legendary"})
explorer.add_field("obj_001", 1, {"type": "Item", "rarity": "common"})
print(f"obj_001 champs : {explorer.field_count('obj_001')}")

# Doublon (cle 0 deja prise)
result = explorer.add_field("obj_001", 0, "duplicate")
print(f"Doublon cle 0 : {result} (attendu False)")

# Lecture
print(f"Champ 'name' : {explorer.get_field('obj_001', 'name')}")
print(f"Champ 0 : {explorer.get_field('obj_001', 0)}")

# Suppression
explorer.remove_field("obj_001", 1)
print(f"Apres suppression cle 1 : {explorer.field_count('obj_001')} champs")

# Objet inexistant
print(f"Champ sur obj_inexistant : {explorer.get_field('obj_999', 'name')}")
print("Exercice a completer : DynamicFieldExplorer")
obj_001 champs : 0
Doublon cle 0 : False (attendu False)
Champ 'name' : None
Champ 0 : None
Apres suppression cle 1 : 0 champs
Champ sur obj_inexistant : None
Exercice a completer : DynamicFieldExplorer

5. Exemples guidés

# Exercice: Escrow simple
EXERCISE_ESCROW = '''
module my_package::escrow {
    use sui::object::{Self, UID};
    use sui::transfer;
    use sui::coin::Coin;
    use sui::sui::SUI;
    use sui::tx_context::{Self, TxContext};

    /// Erreurs
    const ENotSeller: u64 = 0;
    const ENotCompleted: u64 = 1;

    /// Escrow object
    public struct Escrow has key {
        id: UID,
        seller: address,
        buyer: address,
        amount: u64,
        completed: bool,
    }

    /// Creer un escrow
    public fun create(
        seller: address,
        buyer: address,
        payment: Coin<SUI>,
        ctx: &mut TxContext
    ): Escrow {
        Escrow {
            id: object::new(ctx),
            seller,
            buyer,
            amount: coin::value(&payment),
            completed: false,
        }
    }

    /// Completer l'escrow
    public fun complete(
        escrow: &mut Escrow,
        sender: address,
        payment: Coin<SUI>,
        ctx: &mut TxContext
    ) {
        assert!(sender == escrow.buyer, ENotSeller);
        escrow.completed = true;
        transfer::public_transfer(payment, escrow.seller);
    }
}
'''
print("Exercice Escrow:")
print(EXERCISE_ESCROW)
Exercice Escrow:

module my_package::escrow {
    use sui::object::{Self, UID};
    use sui::transfer;
    use sui::coin::Coin;
    use sui::sui::SUI;
    use sui::tx_context::{Self, TxContext};

    /// Erreurs
    const ENotSeller: u64 = 0;
    const ENotCompleted: u64 = 1;

    /// Escrow object
    public struct Escrow has key {
        id: UID,
        seller: address,
        buyer: address,
        amount: u64,
        completed: bool,
    }

    /// Creer un escrow
    public fun create(
        seller: address,
        buyer: address,
        payment: Coin<SUI>,
        ctx: &mut TxContext
    ): Escrow {
        Escrow {
            id: object::new(ctx),
            seller,
            buyer,
            amount: coin::value(&payment),
            completed: false,
        }
    }

    /// Completer l'escrow
    public fun complete(
        escrow: &mut Escrow,
        sender: address,
        payment: Coin<SUI>,
        ctx: &mut TxContext
    ) {
        assert!(sender == escrow.buyer, ENotSeller);
        escrow.completed = true;
        transfer::public_transfer(payment, escrow.seller);
    }
}

Interpretation : Exercice Escrow Move

Cet exercice presente un escrow (tiers de confiance) simple sur Sui avec le modèle objet de Move.

Fonction Opération Verification
create Créé un objet escrow avec paiement du vendeur Stocke seller, buyer, amount, completed=false
complete Le buyer complete l’escrow et transfere le paiement Verifie que sender == buyer, marke completed=true

Points cles : 1. L’objet Escrow a l’ability key : il peut etre stocke comme un objet global (pas dans un compte) 2. Le paiement Coin<SUI> est un token lineaire (ressource) : il ne peut pas etre copie, seulement transfere 3. assert!(sender == escrow.buyer, ENotSeller) : verifie que seul le buyer peut completer l’escrow 4. transfer::public_transfer(payment, escrow.seller) : transfere le paiement au vendeur (atomic avec la completion)

Limitation de l’exercice : - La fonction create ne consomme pas le paiement du vendeur (devrait etre transferre dans l’escrow) - L’erreur ENotCompleted est définie mais jamais utilisee - Il manque une fonction cancel pour que le vendeur puisse annuler et recuperer ses fonds - L’objet escrow devrait etre detruit (object::delete) après completion pour nettoyer

Ameliorations possibles : - Ajouter un timeout pour annulation automatique - Permettre au vendeur de recuperer les fonds si le buyer ne complete pas - Detruire l’objet escrow après completion pour liberer l’UID

Note technique : Sur Sui, les objets avec key ability sont des “objets owns”. Ils ont un proprietaire unique (adresse ou autre objet). Quand un objet est transfere, la propriete change instantanement.


6. Resume

Concept Description
module Unite de code Move
struct Structure de données avec abilities
key Peut etre un objet global
store Peut etre stocke dans un autre objet
UID Identifiant unique d’objet
TxContext Contexte de transaction

Notebook suivant : SC-22-Solana-Anchor-Python


<< Bitcoin Scripting | Solana & Anchor >>

Resume et perspectives

Ce notebook a introduit le langage Move et la blockchain Sui comme alternative au paradigme Ethereum/Solidity. Nous avons compare les modèles fondamentaux : la sémantique de ressources lineaires de Move (ou chaque asset existe en un seul exemplaire et ne peut etre duplique) face aux etats globaux mutables de Solidity. Nous avons explore le système d’abilities (key, store, copy, drop) qui permet au compilateur de garantir la securite des ressources des la compilation, implemente un module de token natif avec le pattern One-Time Witness (OTW) pour l’enregistrement de currency, créé un NFT avec transfert et destruction via le modèle objet de Sui, et applique ces concepts a un exercice d’escrow illustrant le verrouillage et la liberation de fonds.

Le modèle objet de Sui represente un changement de paradigme par rapport aux mappings Ethereum : chaque NFT, chaque token est un objet indépendant avec son propre UID et son proprietaire, eliminant le besoin d’index globaux et permettant le parallelisme natif des transactions. Les abilities Move offrent un contrôle granulaire sur le comportement des types : un token (Coin) ne peut pas etre copie (pas de double-spend garantit par le type system), un NFT peut etre detruit explicitement via destructuring pattern (let NFT { id, name: _, ... } = nft), et les capabilities comme AdminCap controlent les droits de mintage par transfert d’objet.

Le notebook suivant, SC-22-Solana-Anchor-Python, poursuit l’exploration des chaînes alternatives avec Solana et le framework Anchor, en etudiant les Program Derived Addresses (PDAs) et les Cross-Program Invocations (CPIs).

Retour au sommet