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 Solidityprint("""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 natifMOVE_COIN ='''/// Module: my_coin.movemodule 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 SuiMOVE_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 conditionnelclass 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 uniquespassdef 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 creeTODO etudiant : generer un UID unique et stocker l'objet """# TODO etudiant : implementez create_objectreturn""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 reussiTODO etudiant : verifier que sender est bien le proprietaireTODO etudiant : mettre a jour le proprietaire """# TODO etudiant : implementez transferreturnFalsedef 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_objectreturnFalsedef 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_objectreturn {}# Test apres implementationsim = 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 validesim.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 objetsim.share_object(uid2)print(f"Objet partage : {sim.get_object(uid2)}")print("Exercice a completer : SuiObjectSimulator")
# Commandes Sui CLIprint("""INSTALLATION:cargo install --locked --git https://github.com/MystenLabs/sui.git --branch mainnet suiCONFIGURATION:sui client active-address # Voir l'adresse activesui client active-env # Voir l'environnementsui client switch --env devnet # Changer d'environnementPROJET:sui move new my_package # Creer un nouveau projetcd my_packagemkdir -p sources testsCOMPILATION:sui move build # Compiler le moduleTESTS:sui move test # Executer les tests unitairesDEPLOIEMENT:sui client publish --gas-budget 100000000APPELS: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_abilitiesreturn {"is_valid": False, "is_asset": False, "warnings": [],"missing_for_object": set(), "category": "invalid"}# Tests avec differents structs Movetest_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")
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 Moveclass 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 existantspassdef 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 """passdef 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 priseTODO etudiant : verifier que le parent existeTODO etudiant : verifier que la cle n'existe pas dejaTODO etudiant : ajouter la paire cle-valeur """# TODO etudiant : implementez add_fieldreturnFalsedef get_field(self, parent_uid: str, key):""" Lit la valeur d'un champ dynamique. Returns: La valeur du champ, ou None si introuvableTODO etudiant : implementez get_field """# TODO etudiant : implementez get_fieldreturnNonedef remove_field(self, parent_uid: str, key) ->bool:""" Supprime un champ dynamique. Returns: bool: True si supprime, False si introuvableTODO etudiant : implementez remove_field """# TODO etudiant : implementez remove_fieldreturnFalsedef 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_countreturn0# Test apres implementationexplorer = DynamicFieldExplorer()explorer.register_object("obj_001")explorer.register_object("obj_002")# Ajout de champsexplorer.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)")# Lectureprint(f"Champ 'name' : {explorer.get_field('obj_001', 'name')}")print(f"Champ 0 : {explorer.get_field('obj_001', 0)}")# Suppressionexplorer.remove_field("obj_001", 1)print(f"Apres suppression cle 1 : {explorer.field_count('obj_001')} champs")# Objet inexistantprint(f"Champ sur obj_inexistant : {explorer.get_field('obj_999', 'name')}")print("Exercice a completer : DynamicFieldExplorer")
# Exercice: Escrow simpleEXERCISE_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.
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).