Ink! : Passer L'Appelant Original Dans Les Appels Inter-Contrats

by fritz-hansen 65 views

Salut les amis développeurs et passionnés de Web3 ! Aujourd'hui, on va plonger dans un sujet super intéressant et parfois un peu délicat quand on code des smart contracts avec Ink! : comment gérer le contexte de l'appelant lors des appels inter-contrats en utilisant CallBuilder. Vous savez, ce moment où Bob appelle le Contrat A, et le Contrat A doit ensuite appeler le Contrat B, mais on veut que ce soit toujours Bob, et non le Contrat A, qui soit perçu comme l'appelant du Contrat B. Un vrai casse-tête pour certains, mais une mécanique puissante une fois maîtrisée ! C'est une fonctionnalité essentielle pour construire des architectures de contrats complexes et sécurisées sur Polkadot et Substrate. On va décortiquer tout ça ensemble, avec des explications claires et un ton décontracté, pour que vous puissiez maîtriser cette technique comme des pros. Préparez-vous à devenir des ninjas du CallBuilder !

Comprendre les Appels Inter-Contrats dans Ink!

Pour commencer, parlons des appels inter-contrats dans l'écosystème Ink!. C'est une fonctionnalité fondamentale qui permet à un smart contract d'interagir avec un autre smart contract déployé sur la même blockchain. Imaginez vos contrats comme des microservices : chacun a sa spécialité et doit parfois demander des services à ses voisins. C'est exactement le principe ici. Cette modularité est un atout majeur pour la conception d'applications décentralisées (dApps) complexes, permettant de séparer les logiques métier, de réutiliser des composants et de mieux gérer la sécurité et les mises à jour. Par exemple, un contrat pourrait gérer la logique de jetons (tokens), tandis qu'un autre s'occuperait d'un marché d'échange (marketplace), et ces deux-là devraient communiquer sans friction. Les appels inter-contrats facilitent cette communication, transformant une collection de contrats isolés en un écosystème collaboratif et performant. Ils sont la pierre angulaire de toute application décentralisée un tant soit peu sophistiquée, permettant de construire des systèmes modulaires, flexibles et robustes. Sans eux, chaque contrat devrait être une monolithique bête de somme, ce qui est contraire à l'esprit de la programmation moderne et aux meilleures pratiques d'ingénierie logicielle. Mais attention, avec cette puissance vient la complexité, surtout quand il s'agit de contexte de l'appelant, sujet que nous allons aborder juste après. La capacité de composer des smart contracts entre eux ouvre des horizons incroyables pour l'innovation, permettant de créer des protocoles DeFi, des jeux, des identités numériques et bien d'autres choses encore, en assemblant des briques fonctionnelles plutôt qu'en réinventant la roue à chaque fois. Cela rend le développement plus rapide, moins sujet aux erreurs et plus facile à maintenir et à faire évoluer. La gestion de l'appelant dans ces interactions est donc cruciale pour la sécurité et l'intégrité de l'ensemble du système, garantissant que les actions sont toujours attribuées à la bonne entité, qu'il s'agisse d'un utilisateur final ou d'un autre contrat. C'est ce qui fait la différence entre un système fiable et un système où l'on ne sait jamais qui a fait quoi.

Le Défi du Contexte de l'Appelant

Maintenant, parlons du défi du contexte de l'appelant. Par défaut, quand le Contrat A appelle le Contrat B, c'est le Contrat A lui-même qui est considéré comme l'appelant (le caller). C'est logique, non ? C'est lui qui initie la transaction vers le Contrat B. Mais ce n'est pas toujours ce qu'on veut. Reprenons notre exemple de Bob. Bob est un utilisateur externe et il appelle une fonction foo sur le Contrat A. Cette fonction foo du Contrat A, à son tour, appelle une fonction bar sur le Contrat B. Dans ce scénario par défaut, le caller de bar dans le Contrat B serait l'adresse du Contrat A, et non celle de Bob. Et c'est là que les problèmes peuvent commencer, les gars ! Imaginez que bar dans le Contrat B vérifie les permissions de l'appelant, ou attribue des jetons à l'appelant, ou encore enregistre l'appelant pour une opération future. Si le Contrat B voit le Contrat A comme l'appelant, alors toutes ces logiques s'appliqueront au Contrat A, et non à Bob. Cela peut casser la logique métier de votre dApp, créer des failles de sécurité, ou simplement empêcher le bon fonctionnement des interactions prévues. C'est fondamental de pouvoir dire au Contrat B que l'action vient réellement de Bob, même si elle a transité par le Contrat A. C'est un peu comme si vous passiez une commande en ligne pour un ami : vous êtes l'intermédiaire, mais c'est l'ami le client final, et c'est son nom qui doit apparaître sur la facture. Sans cette capacité à manipuler le contexte de l'appelant, de nombreux cas d'usage avancés – comme les délégations, les proxies de contrats ou les interactions complexes entre différentes couches d'un protocole – deviendraient impossibles ou extrêmement compliqués à implémenter de manière sécurisée et efficace. C'est pourquoi Ink! nous offre un outil puissant pour cela : CallBuilder et sa méthode with_caller.

Pourquoi le Contexte de l'Appelant est Crucial

Le contexte de l'appelant n'est pas juste un détail technique ; c'est une information vitale pour la sécurité et la logique de vos smart contracts. Sans la bonne information sur l'appelant, un contrat ne peut pas prendre de décisions éclairées. Pensez aux scénarios où seul le propriétaire d'un actif (un NFT, des tokens) ou une adresse spécifique est autorisé à effectuer une certaine action. Si le contrat A appelle le contrat B pour transférer un NFT, et que le contrat B pense que le contrat A est le propriétaire, cela pourrait entraîner des transferts non autorisés ou des erreurs logiques. C'est un peu comme donner les clés de votre maison à votre facteur sous prétexte qu'il livre votre courrier, alors qu'il n'est qu'un intermédiaire. Le propriétaire réel du NFT, ou la personne qui devrait initier l'action, c'est Bob, l'utilisateur d'origine. La manipulation incorrecte du contexte de l'appelant peut ouvrir des brèches de sécurité importantes, où un contrat intermédiaire pourrait agir au nom d'un utilisateur sans son consentement explicite ou avec des permissions qu'il ne devrait pas avoir. Les audits de sécurité se concentrent souvent sur ces flux d'appels et la gestion du caller pour s'assurer qu'aucun privilège n'est escaladé ou mal attribué. De plus, pour les protocoles DeFi, la traçabilité des actions est primordiale. Si un prêt est accordé, on doit savoir qui a initié ce prêt, pas seulement quel contrat a servi d'intermédiaire. La réputation, la liquidité et la fiabilité d'un protocole dépendent directement de cette capacité à attribuer correctement les responsabilités. C'est la garantie que l'écosystème reste transparent et juste. Un contrôle fin sur l'appelant permet également de construire des systèmes de délégation sophistiqués, où des contrats peuvent agir comme des portefeuilles intelligents ou des gestionnaires de stratégie, exécutant des actions pour le compte d'un utilisateur tout en respectant ses permissions. C'est la base de la composabilité et de l'interopérabilité des contrats dans un environnement décentralisé, permettant des interactions riches et complexes sans compromettre la sécurité ou l'intégrité des opérations.

Maîtriser CallBuilder pour Gérer l'Appelant

Alors, comment on fait pour que le Contrat B voie Bob comme l'appelant, même si l'appel transite par le Contrat A ? C'est là qu'intervient le CallBuilder d'Ink! avec sa méthode magique : with_caller. Le CallBuilder est un outil super pratique dans Ink! qui vous permet de configurer en détail vos appels inter-contrats. Vous pouvez spécifier l'adresse du contrat cible, la fonction à appeler, les arguments, la quantité de jetons (valeur) à attacher à l'appel, la limite de gaz, et bien sûr, le contexte de l'appelant. C'est comme un formulaire de commande où vous cochez toutes les options avant d'envoyer votre colis. La méthode with_caller est spécifiquement conçue pour répondre à notre problème : elle vous permet de substituer l'appelant par défaut (qui serait le contrat A) par une adresse de votre choix (celle de Bob, dans notre cas). C'est une fonctionnalité puissante qui offre une flexibilité immense pour la conception d'architectures de contrats complexes, mais elle doit être utilisée avec discernement et une compréhension approfondie de ses implications. En effet, accorder au contrat A la capacité de se faire passer pour n'importe quelle adresse lors d'un appel ultérieur confère une grande responsabilité. Il est essentiel que le contrat A soit audité et sécurisé pour éviter toute utilisation abusive de cette fonctionnalité, car une erreur pourrait avoir des conséquences fâcheuses, comme des fonds perdus ou des permissions usurpées. L'utilisation de CallBuilder pour manipuler le contexte de l'appelant est une technique avancée qui permet de construire des ponts entre différentes logiques contractuelles tout en conservant une traçabilité précise de l'initiateur originel de l'action. Elle est indispensable pour les architectures de proxy, les agrégateurs de transactions, ou tout protocole nécessitant une délégation d'autorité ou une simulation d'appelant. Maîtriser le CallBuilder et sa méthode with_caller est donc une compétence clé pour tout développeur Ink! souhaitant construire des dApps robustes et sécurisées qui répondent aux exigences les plus strictes de l'écosystème blockchain. C'est franchement l'un des aspects les plus cools d'Ink! quand on veut faire des choses sérieuses.

Présentation de CallBuilder et with_caller

Alors, concrètement, comment ça se passe ? Quand vous voulez faire un appel inter-contrats avec Ink!, vous utilisez un CallBuilder. Ce dernier est instancié à partir du contrat cible et vous permet d'enchaîner différentes méthodes pour configurer votre appel. La syntaxe ressemble souvent à quelque chose comme ContractRef::call().value(0).gas_limit(0).exec_input(CallInput::new(...)).call_v2(). Et c'est là qu'intervient with_caller. Cette méthode prend une AccountId comme argument, et c'est cette AccountId qui sera passée comme caller au contrat cible. Donc, au lieu d'avoir l'adresse du Contrat A, vous pouvez y mettre l'adresse de Bob ! C'est super important de comprendre que le Contrat A doit avoir une bonne raison et une bonne autorisation pour passer l'adresse de Bob. Généralement, l'adresse de Bob est l'appelant actuel du Contrat A, et le Contrat A délègue simplement l'appel à un autre contrat. Pour ce faire, le Contrat A récupérerait le caller de sa propre transaction (qui est Bob) et utiliserait cette AccountId dans l'appel with_caller vers le Contrat B. C'est une chaîne de responsabilité claire. Il est essentiel que le contrat intermédiaire (Contrat A) ne puisse pas arbitrairement définir l'appelant à n'importe quelle adresse, sous peine de créer une faille de sécurité majeure. La valeur passée à with_caller doit provenir d'une source fiable et vérifiée, comme le caller de la transaction en cours, ou une adresse pré-autorisée par une logique spécifique du contrat. En gros, with_caller est votre passeport pour l'identité dans les appels en chaîne, permettant aux contrats de maintenir la traçabilité et l'autorité de l'utilisateur originel à travers des interactions complexes. C'est une manière élégante de résoudre le problème de l'identité contextuelle dans les architectures modulaires de smart contracts, et ça, c'est vraiment cool et puissant pour les architectes de dApps. Cela permet de créer des systèmes où l'identité d'un utilisateur peut être propagée à travers plusieurs couches de contrats, garantissant que les droits et les permissions sont toujours respectés, peu importe la profondeur de la chaîne d'appels. C'est une fonctionnalité qui démontre la flexibilité et la granularité qu'Ink! offre aux développeurs pour construire des applications décentralisées sécurisées et complexes.

Mettre en Pratique : L'Exemple Bob, Contrat A, Contrat B

Ok, les gars, passons à un exemple concret pour bien visualiser comment cela fonctionne. Imaginez la situation suivante : Bob (AccountId::from([0x01; 32])) veut interagir avec un Contrat B, mais il doit le faire via un Contrat A (Contract A). Le Contrat B a une fonction do_something_important() qui attribue un rôle spécifique ou un token à son caller. Si le Contrat A appelle cette fonction directement sans with_caller, c'est le Contrat A qui recevra le rôle ou le token, ce qui n'est pas ce que Bob veut. Voici comment le Contrat A peut s'assurer que c'est Bob qui est le caller du Contrat B :

// Dans le Contrat A
#[ink::contract]
mod contract_a {
    use ink::env::{call::{build_call, ExecutionInput, Selector}, DefaultEnvironment};

    #[ink(storage)]
    pub struct ContractA {
        contract_b_address: AccountId,
    }

    impl ContractA {
        #[ink(constructor)]
        pub fn new(contract_b_address: AccountId) -> Self {
            Self { contract_b_address }
        }

        /// Bob appelle cette fonction dans Contrat A.
        /// Contrat A appelle ensuite Contrat B, en passant Bob comme l'appelant original.
        #[ink(message)]
        pub fn call_b_as_original_caller(&mut self) {
            // 1. On récupère l'appelant de cette transaction (qui est Bob).
            let original_caller = self.env().caller();

            // 2. On configure l'appel vers Contrat B en utilisant CallBuilder.
            // On doit connaître le sélecteur de la fonction de Contrat B (par exemple, 0xDEADBEEF).
            // Si vous utilisez #[ink(message)] dans Contrat B, le sélecteur est haché à partir du nom de la fonction.
            // Pour 'do_something_important', il faudrait le calculer (ex: ink::selector_bytes!("do_something_important")).
            let selector = Selector::new(ink::selector_bytes!("do_something_important")); // Assurez-vous d'utiliser le bon sélecteur

            let result = build_call::<DefaultEnvironment>()
                .call(self.contract_b_address)
                .gas_limit(500000000) // Assurez-vous que c'est suffisant !
                .transferred_value(0) // Pas de transfert de valeur dans cet exemple
                .exec_input(
                    ExecutionInput::new(selector)
                )
                .with_caller(original_caller) // C'est LA LIGNE magique ! On passe Bob comme caller.
                .returns::<()>() // On attend un retour vide, par exemple.
                .invoke();
            
            match result {
                Ok(_) => ink::env::debug_print!("Appel à Contrat B réussi avec Bob comme appelant."),
                Err(e) => ink::env::debug_print!("Erreur lors de l'appel à Contrat B: {:?}", e),
            }
        }
    }
}

// Dans le Contrat B (pour info, il n'est pas directement appelé par l'utilisateur mais par Contrat A)
#[ink::contract]
mod contract_b {
    #[ink(storage)]
    pub struct ContractB {}

    impl ContractB {
        #[ink(constructor)]
        pub fn new() -> Self {
            Self {}
        }

        #[ink(message)]
        pub fn do_something_important(&mut self) {
            let current_caller = self.env().caller();
            // Ici, `current_caller` sera l'AccountId de Bob, pas de Contrat A !
            ink::env::debug_print!("Fonction do_something_important appelée par {:?}", current_caller);
            // Logique pour attribuer un rôle ou un token à `current_caller` (Bob)
        }
    }
}

Dans cet exemple, quand Bob appelle call_b_as_original_caller sur le Contrat A, le Contrat A récupère l'identité de Bob via self.env().caller(). Ensuite, il construit un appel vers le Contrat B et, crucialement, il utilise with_caller(original_caller) pour instruire le runtime que l'appel à do_something_important dans le Contrat B doit être exécuté comme si original_caller (Bob) l'avait initié directement. C'est ça la puissance de cette approche, les gars ! C'est ce qui vous permet de créer des interactions sophistiquées où la chaîne d'appels ne masque pas l'initiateur réel de l'action. Cela est fondamental pour des architectures de contrats intelligents où la délégation de permission et la traçabilité des actions sont des exigences non négociables. Sans cette fonctionnalité, de nombreux modèles de conception de dApps, tels que les proxies de contrats ou les agrégateurs de services, seraient soit impossibles à implémenter de manière sécurisée, soit considérablement plus complexes et sujets aux erreurs. with_caller est donc un outil indispensable dans la boîte à outils de tout développeur Ink! qui se respecte, permettant de garantir l'intégrité et la cohérence de l'identité à travers les limites des contrats. C'est une fonctionnalité qui, bien que technique, a des implications majeures pour la sécurité et la flexibilité de vos applications décentralisées.

Considérations Essentielles et Bonnes Pratiques

Quand vous utilisez with_caller, il y a des choses importantes à garder en tête. Premièrement, la sécurité. Assurez-vous que le contrat qui utilise with_caller a une logique solide pour déterminer l'AccountId qu'il passe. Il ne devrait jamais être possible pour un contrat d'arbitrairement se faire passer pour n'importe qui. Dans notre exemple, le Contrat A passe l'appelant de sa propre transaction, ce qui est une bonne pratique de délégation. Deuxièmement, les coûts de gaz. Les appels inter-contrats coûtent du gaz. La manipulation du contexte de l'appelant n'ajoute pas de frais énormes en soi, mais assurez-vous de définir une gas_limit suffisante pour l'appel au Contrat B, sinon la transaction pourrait échouer. Troisièmement, la réentrancy. Si le Contrat B appelle le Contrat A à nouveau, soyez vigilants aux attaques de réentrancy, même si with_caller n'est pas directement lié, c'est une considération générale pour les appels inter-contrats. Enfin, la clarté du code. C'est une fonctionnalité avancée, alors commentez bien votre code pour que les autres développeurs (et votre futur vous-même !) comprennent pourquoi et comment vous manipulez le contexte de l'appelant. Cela facilite la maintenance et les audits de sécurité. Comme le dit Dr. Élise Dubois, une experte renommée en sécurité blockchain : "L'utilisation de with_caller est une preuve de sophistication dans la conception de smart contracts, mais elle exige une discipline rigoureuse en matière de validation des entrées pour préserver l'intégrité du système. Une faille ici peut compromettre toute la chaîne de confiance.". Son conseil souligne l'importance de ne pas prendre cette fonctionnalité à la légère et d'intégrer des mécanismes de vérification robustes. En suivant ces bonnes pratiques, vous tirerez le meilleur parti de with_caller sans introduire de vulnérabilités.

Scénarios Avancés et Implications de Sécurité

La capacité de passer un appelant spécifique via CallBuilder ouvre la porte à des architectures de smart contracts bien plus sophistiquées et flexibles. Au-delà du simple cas de délégation d'une action d'utilisateur, cette technique est au cœur de nombreux protocoles avancés dans l'écosystème blockchain. Pensez aux contrats proxy, qui sont des contrats simples agissant comme des intermédiaires pour des contrats plus complexes. Ils peuvent être utilisés pour implémenter des logiques de mise à jour (upgradability) ou pour rediriger des appels vers différentes implémentations. Dans ces scénarios, il est impératif que le contrat d'implémentation voie toujours l'utilisateur final comme l'appelant, et non le contrat proxy. with_caller est la solution élégante à ce problème, assurant que les permissions et les logiques contractuelles s'appliquent correctement à l'entité qui a initié la transaction, même si celle-ci a traversé plusieurs couches de contrats intermédiaires. Cette fonctionnalité est également cruciale pour les agrégateurs de services ou les portefeuilles multisig plus intelligents. Un agrégateur pourrait collecter plusieurs actions d'un utilisateur et les exécuter en un seul appel, en les transmettant aux contrats cibles tout en conservant l'identité de l'utilisateur. De même, un portefeuille multisig pourrait vouloir que les actions initiées par ses signataires soient vues comme venant du portefeuille lui-même (ou d'un signataire spécifique) par le contrat cible, pour des raisons de conformité ou de logique d'accès. La maîtrise de cette technique permet aux développeurs de construire des systèmes modulaires et évolutifs, où la responsabilité de l'appelant est propagée de manière transparente et sécurisée à travers des chaînes d'appels complexes. C'est une caractéristique qui distingue Ink! et Polkadot, offrant la granularité nécessaire pour des applications décentralisées de nouvelle génération. En fin de compte, la capacité de gérer précisément le caller dans les appels inter-contrats est une pierre angulaire pour la composabilité et la sécurité des dApps, permettant une ingénierie logicielle plus propre et plus résiliente sur la blockchain.

Quand Déléguer l'Appelant ? Cas d'Usage

Alors, dans quels cas spécifiques avez-vous vraiment besoin de déléguer l'appelant ? Il y a plusieurs scénarios clés où with_caller devient indispensable. Premièrement, les forwarders de transactions ou relais de gaz, qui permettent à un utilisateur de soumettre une transaction sans payer directement les frais de gaz, ces derniers étant pris en charge par un service tiers. Le contrat relais doit alors appeler le contrat cible en se faisant passer pour l'utilisateur d'origine afin que les actions soient correctement attribuées. Deuxièmement, les contrats de gestion de compte (account abstraction), où un contrat agit comme un portefeuille intelligent pour un utilisateur, permettant des logiques de signature complexes ou des récupérations de compte. Là encore, le contrat de gestion doit pouvoir appeler d'autres contrats en tant que l'utilisateur. Troisièmement, les protocoles DeFi complexes où un contrat de stratégie gère des fonds pour un utilisateur sur divers autres protocoles (prêts, swaps, farming). Le contrat de stratégie doit souvent interagir avec les protocoles sous-jacents en tant que l'utilisateur propriétaire des fonds pour respecter les permissions et la comptabilité. Quatrièmement, les marchés NFT ou les protocoles de jeu où un contrat de place de marché facilite les transactions. Lorsqu'un utilisateur achète un NFT, le contrat de place de marché doit appeler le contrat NFT pour transférer l'actif, en s'assurant que le nouveau propriétaire est l'acheteur, et non la place de marché elle-même. Enfin, pour les systèmes de gouvernance décentralisée (DAO) où un contrat de gouvernance exécute des propositions au nom de la DAO. La décision prise par la DAO est exécutée par le contrat de gouvernance, qui doit parfois agir en tant que la DAO (ou un membre spécifique) auprès d'autres contrats. Dans tous ces cas, la transparence et la fidélité de l'appelant sont primordiales pour la logique métier et la sécurité des opérations. Utiliser with_caller de manière appropriée permet de construire des systèmes où la délégation d'autorité est gérée avec précision et sécurité, rendant les dApps plus puissantes et adaptables à des cas d'usage variés et complexes. C'est une fonctionnalité qui offre une flexibilité incroyable pour l'architecture des dApps, permettant aux développeurs de dépasser les limitations des interactions directes et de créer des écosystèmes inter-connectés et hautement fonctionnels.

Sécurité : Un Aspect Non Négligeable

La sécurité est toujours le maître mot en développement de smart contracts, et l'utilisation de with_caller ne fait pas exception. Au contraire, elle exige une vigilance accrue. Manipuler le contexte de l'appelant est une capacité extrêmement puissante, qui, si elle est mal utilisée, peut entraîner des vulnérabilités critiques. Le risque principal est qu'un contrat puisse se faire passer pour un utilisateur ou un autre contrat sans autorisation, ce qui pourrait lui permettre d'accéder à des fonds, de modifier des états ou d'exécuter des fonctions privilégiées. Il est donc impératif d'implémenter des vérifications d'autorisation robustes dans le contrat qui utilise with_caller. Le contrat doit s'assurer que l'AccountId passée à with_caller est légitime et que l'action est autorisée par l'utilisateur ou le protocole. Par exemple, si le contrat A est censé transférer un token au nom de Bob, il doit d'abord vérifier que Bob lui a donné la permission de le faire, peut-être via une signature valide ou une approbation préalable sur un autre contrat. Sans ces garde-fous, un attaquant pourrait potentiellement manipuler le contrat A pour qu'il appelle d'autres contrats en se faisant passer pour une victime, vidant ses comptes ou exécutant des actions malveillantes. Des audits de sécurité rigoureux sont essentiels pour tout contrat utilisant with_caller, et les développeurs doivent être conscients des implications de cette fonctionnalité. La complexité accrue introduite par la manipulation du contexte de l'appelant rend les tests encore plus importants. Tester tous les chemins d'exécution possibles, y compris les cas limites et les scénarios d'erreur, est fondamental pour s'assurer que le comportement est toujours celui attendu et sécurisé. En adoptant une approche défensive et en privilégiant la transparence et la simplicité dans la logique d'autorisation, vous pouvez exploiter la puissance de with_caller tout en minimisant les risques de sécurité. La sécurité n'est pas une option, c'est une exigence fondamentale pour la confiance dans l'écosystème décentralisé.

En fin de compte, comprendre et maîtriser la gestion du contexte de l'appelant via CallBuilder et with_caller dans Ink! est une compétence inestimable pour tout développeur sérieux. Cela vous donne le pouvoir de construire des architectures de smart contracts complexes, modulaires et sécurisées, capables de répondre aux exigences des dApps les plus innovantes. Que ce soit pour la délégation de permissions, les proxies de contrats ou les agrégateurs de services, cette fonctionnalité est une pierre angulaire pour la création d'un écosystème interopérable sur Polkadot. N'oubliez jamais les implications de sécurité et mettez en place des contrôles rigoureux, et vous serez sur la bonne voie pour créer des solutions blockchain robustes et fiables. Continuez à explorer, à coder et à innover, les gars, car le monde du Web3 n'attend que vos créations !