Qu'est-ce que le HyVM et pourquoi est-il si puissant pour la DeFi ?
Le HyVM est un contrat intelligent écrit en Huff qui agit comme un émulateur EVM construit sur l'EVM. Découvrez comment il va changer la DeFi.

Bienvenue dans le premier article de notre série technique explorant le fonctionnement interne de l'application Mass et ce qui la rend si unique. Pour commencer, nous avons le HyVM, le cœur battant de Mass.
En résumé : Si vous êtes uniquement intéressé par une explication technique de la mise en œuvre du HyVM, rendez-vous directement à la section "Plongée approfondie dans le HyVM".
🚀 Un peu de contexte
Il existe de nombreux protocoles DeFi incroyables. Les initiés de la DeFi les connaissent et les utilisent au quotidien. MAIS :
Interagir avec eux nécessite d'apprendre comment ils fonctionnent.
Interagir avec plusieurs protocoles (si ce n'est pas impossible) est souvent difficile en une seule transaction.
Une fois que vous avez mis en place une stratégie complexe, il est fastidieux de rétro-ingénier ce que vous avez fait et d'en sortir manuellement.
Pour les non-initiés à la DeFi, les stratégies complexes sont tout simplement hors de portée, même lorsque le schéma est clairement expliqué par quelqu'un en qui ils ont confiance. Il y a simplement trop de choses à apprendre, trop de plateformes à comprendre pour gagner la confiance de se lancer seuls.
Chez Mass, nous visons à offrir une plateforme tout-en-un pour tous les protocoles DeFi et à abstraire cette complexité.
Cela nous amène à nous demander :
Comment pouvons-nous offrir une flexibilité maximale et des fonctionnalités sans avoir à auditer, déployer et entretenir des contrats intelligents cauchemardesques censés tout faire ?
C'est pourquoi nous avons créé le HyVM.
❓ Qu'est-ce que le HyVM ?
Le HyVM est un contrat intelligent qui agit comme une machine virtuelle. Il est capable d'exécuter dynamiquement des instructions (bytecode EVM) sans avoir besoin de déployer un contrat dédié pour les exécuter.
En termes plus simples, le HyVM évite le besoin de créer des contrats pour des cas d'utilisation temporaires et évolutifs tout en offrant plus de flexibilité !
Il est particulièrement adapté aux contrats avec des appels de fonction peu fréquents.
Le HyVm résout un problème majeur auquel de nombreuses dApps sont confrontées chaque jour.
Bien sûr, autoriser les utilisateurs à exécuter un bytecode arbitraire sur un contrat partagé par de multiples utilisateurs ne serait pas une solution réalisable en raison des risques de sécurité. C'est pourquoi vous aurez besoin d'un modèle de sécurité basé sur la ségrégation des utilisateurs (qui empêche l'utilisation d'une telle approche pour les protocoles nécessitant que les actifs de tous les utilisateurs soient stockés dans un seul contrat, par exemple).
Cependant, si votre cas d'utilisation permet une configuration où les utilisateurs disposent de leur propre contrat privé, vous obtiendrez une flexibilité maximale avec un entretien minimal on-chain en implémentant le HyVm.
🔐 Modèle de sécurité de Mass
Nous avons atteint la ségrégation des actifs des utilisateurs via une approche simple. Lorsqu'un utilisateur souhaite interagir avec Mass, voici ce qui se passe :
Chaque utilisateur crée son propre espace de stockage isolé appelé Mass Smart Account (MSA). Chaque MSA agit comme un portefeuille détenu par un seul utilisateur.
Chaque utilisateur crée sa propre copie du HyVM déployé, qui est un proxy vers une seule implémentation.
La copie du HyVM fraîchement créée ne peut être appelée que par son MSA associé.
Le HyVM est le seul contrat autorisé à être appelé en délégué par le MSA.
Voyons maintenant un exemple concret :
🔁 Sans le HyVM vs Avec le HyVM
Sans le HyVM
Lorsque l'utilisateur n'utilise pas un MSA exécutant le HyVM, il peut appeler directement les fonctions de contrat ou effectuer un multicall avec l'aide d'un contrat auxiliaire. Cependant, il peut y avoir certaines limitations à prendre en compte lors de l'utilisation de ces fonctions.
Par exemple, dans ce scénario, l'utilisateur souhaite effectuer les actions suivantes :
Approuver le routeur UniswapV3 à dépenser la moitié de son USDT.
Échanger USDT en WETH.
Approuver les contrats de Morpho pour fournir du WETH.
Fournir du WETH dans Morpho.
L'utilisateur a deux options pour exécuter ces actions sans le HyVM :
Effectuer 4 appels séparés - 4 transactions (2 approbations + 2 appels).
Effectuer un seul délégué call vers un contrat qui détient la logique pour les quatre étapes - 2 transactions à signer (1 approbation + 1 délégué call).
Maintenant, sans le HyVM, si nous voulons refaire cette transaction mais que nous voulons dépenser 1/4 de notre USDT à l'étape 1, nous devrions mettre à jour et déployer un nouveau contrat pour contenir la logique mise à jour.
Avec le HyVM
C'est là que le HyVM, associé au MSA, est si puissant. L'utilisateur peut fournir la logique en tant que paramètre.
L'utilisateur peut passer un code précompilé en tant que paramètre de la fonction au lieu de déployer un nouveau contrat. En conséquence, les possibilités d'interaction avec les contrats intelligents en utilisant le MSA sont presque infinies et d'une flexibilité illimitée.
📚 Quelques prérequis sur l'EVM
EVM, contrats, langages, bytecode
Si vous avez appris à programmer des contrats intelligents, vous l'avez probablement fait en utilisant Solidity. Cependant, les contrats intelligents et Solidity NE SONT PAS la même chose.
Vous avez peut-être rencontré l'acronyme barbare "EVM" (pour Ethereum Virtual Machine)... il s'agit du moteur d'exécution des contrats intelligents qui alimente toutes les blockchains compatibles avec l'EVM.
Solidity est simplement un langage de programmation (comme Vyper ou Huff, pour en citer quelques-uns) qui est compilé en "bytecode".
Ce "bytecode" est exécuté par l'EVM, qui ne sait rien de Solidity.
Ne plongeons pas dans les détails de comment déployer un contrat (c'est-à-dire le constructeur en Solidity, qui exécute du code pendant le déploiement). Pour simplifier, pour être exécuté, ce bytecode doit être déployé à une adresse statique. Toute personne appelant cette adresse pourra ensuite l'exécuter.
Voici comment fonctionne l'exécution sur l'EVM
Note: Si vous savez comment fonctionnent d'autres VM (machines virtuelles), comme le CLR (la VM derrière des langages comme C# ou F#) ou le JVM (derrière Java ou Kotlin), vous vous sentirez comme chez vous : le bytecode EVM est très similaire au MSIL ou au bytecode JVM, bien que beaucoup plus simple.
Un bytecode est un flux d'instructions, chacune étant représentée par un octet (sauf push1...32 qui en prennent 2...33).
Vous pouvez trouver une liste complète de toutes les instructions sur evm.codes
Chaque instruction a un comportement bien défini, qui peut effectuer une ou plusieurs des trois choses suivantes :
Consommer des valeurs de "la pile."
Pousser une valeur sur "la pile."
Affecter l'environnement : lire/écrire des données depuis "la mémoire", lire/écrire des données depuis "le stockage", lire des données depuis les données d'appel, appeler d'autres contrats, obtenir des données depuis d'autres sources (Nous n'entrerons pas dans les détails).
"la pile" est, comme son nom l'indique, simplement une pile de valeurs de 32 octets que vous manipulez avec des instructions. Vous donnez des instructions sur ce qu'il faut faire via cette pile. Par exemple, le calcul de 40+2 serait effectué via ce flux d'instructions :
push1 40 👉 poussera la valeur "40" sur la pile push1 2 👉 poussera la valeur "2" sur la pile add 👉 dépopera deux valeurs de la pile et poussera le résultat de leur addition sur la pile
"la mémoire" est simplement ce à quoi vous vous attendez : un espace linéaire où vous pouvez stocker des données pour les récupérer ultérieurement lors de l'exécution. Plusieurs instructions permettent d'écrire dans la mémoire.
"le stockage" est une paire clé-valeur où vous pouvez stocker des données. Il est persistant tout au long des exécutions.
Cette section n'a pas pour but d'être un tutoriel complet sur les subtilités de l'EVM. J'en resterai là. Cela devrait suffire à vous donner des indications sur son fonctionnement ou ses relations avec d'autres machines virtuelles que vous connaissez déjà. Je vous suggère de creuser davantage dans des ressources dédiées si vous souhaitez en savoir plus.
Maintenant, plongeons dans le vif du sujet.
🔍 Plongée approfondie dans le HyVM
Le HyVM est une VM écrite en Huff.
Lorsque l'on traite avec des VM, on rencontre souvent les concepts suivants, qui seront expliqués dans cette section :
Le programme "hôte" est responsable de l'exécution de l'environnement virtualisé. Dans notre cas, c'est le contrat HyVM (fonctionnant lui-même à l'intérieur de l'EVM).
Le programme "invité" est le contrat que l'hôte exécute sans qu'il puisse remarquer qu'il ne s'exécute pas dans un environnement "normal" (il ne remarquera pas qu'il ne s'agit pas d'un contrat déployé). Ici, notre programme invité est transmis en tant que données d'appel au HyVM.
Ségrégation de la mémoire de l'hôte et de l'invité : l'hôte et l'invité ont besoin de stocker des informations en mémoire, mais ils ne peuvent pas se connaître mutuellement.
Un mot à propos de la mémoire
Un contrat EVM classique sera capable d'allouer des adresses mémoire de 0 à l'infini. Cela signifie que lorsqu'il s'exécute en tant qu'invité dans notre VM, il DOIT être capable de le faire si nous voulons qu'il s'exécute comme prévu.
Cela dit, l'hôte a également besoin de stocker des données en mémoire, qui ne doivent pas être accessibles à l'invité. Cela soulève la question : Où stockons-nous la mémoire de l'hôte lorsque les invités ont besoin d'avoir accès à [0 ; +∞] ?
Pour résoudre ce problème, nous devons savoir combien d'espace mémoire notre hôte a besoin (cela doit être une quantité fixe) et décaler la mémoire de l'invité de cette quantité. En d'autres termes, l'adresse 0x00 de l'invité sera en réalité stockée à une adresse plus élevée. Ensuite, nous corrigerons les adresses poussées aux instructions accédant à la mémoire de cette quantité (plus d'informations à ce sujet plus tard).
Fun fact : Les systèmes d'exploitation (Linux, Windows, etc.) isolent la mémoire du noyau de la mémoire de l'utilisateur en utilisant le même type de technique.
Découpage de la base de code du HyVM
Il y a plusieurs parties remarquables :
La macro MAIN()... le point d'entrée
La macro CONTINUE()... la macro "exécutez l'opcode suivant"
Toutes les étiquettes d'implémentation des instructions (étiquettes commençant par op_)
La macro FIX_MEMOFFSET()... responsable de la correction des adresses mémoire de l'invité
Plongeons dans cela.
1️⃣ MAIN() ne fait que deux choses :
Configure la mémoire de l'hôte en chargeant une "table de sauts", qui est une correspondance entre les opcodes et leurs étiquettes d'implémentation
Démarre l'exécution en appelant le premier CONTINUE()
2️⃣ CONTINUE() est responsable de l'exécution de l'opération suivante
Il charge le "pointeur d'exécution" depuis la mémoire (c'est-à-dire notre curseur d'exécution, l'adresse des instructions suivantes à exécuter dans notre bytecode)
Ensuite, il déplace ce pointeur d'exécution vers l'instruction suivante
Enfin, il saute vers la bonne "implémentation d'instruction" en utilisant la table de sauts chargée par MAIN()
3️⃣ Les implémentations d'instructions
C'est là que se déroulera réellement l'exécution de l'invité.
La chose clé à comprendre est que lors du saut vers une implémentation d'exécution, l'hôte n'aura laissé aucune de ses propres valeurs sur la pile => Toutes les valeurs sur la pile auront été poussées par le contrat invité.
La mise en œuvre de la plupart des opérations sera aussi simple que d'exécuter l'instruction EVM correspondante, puis de passer à la suivante. C'est pourquoi la plupart des implémentations d'opérations semblent stupidement simples et sont presque toujours les mêmes. Par exemple, add :
op_add: add 👈 ajoute simplement les deux valeurs sur la pile CONTINUE() 👈 saut vers l'instruction suivante
Cela dit, comme indiqué dans la section "Un mot sur la mémoire", toutes les instructions qui lisent depuis ou écrivent dans la mémoire doivent être corrigées pour tenir compte de la mémoire de l'hôte. Cela se fait en exécutant la macro FIX_MEMOFFSET() sur toutes les valeurs de la pile qui font référence à un pointeur mémoire.
Par exemple, mload :
op_mload: FIX_MEMOFFSET() 👈 corrige le pointeur mémoire en haut de la pile mload 👈 exécute l'instruction mload réelle à l'adresse corrigée CONTINUE() 👈 saut vers l'instruction suivante
De même, d'autres instructions plus anecdotiques comme PC ou codecopy seront implémentées différemment car leur valeur sous-jacente est virtualisée (PC accède au compteur de programme, qui est en réalité stocké en mémoire, et codecopy devra charger le code à partir des données d'appel, pas à partir du code réel — qui est le code HyVM)

4️⃣ FIX_MEMOFFSET()
Une fois que vous avez compris tout ce qui précède, son implémentation est assez simple, donc je ne vais pas plonger dans plus de détails.
Des questions ?
Nous allons bientôt lancer notre offre DeFi en tant que service (DaaS), où d'autres projets pourront utiliser l'ensemble de notre pile technologique pour construire des protocoles financiers Lego nécessitant le plus haut degré de composabilité.
Voici quelques protocoles qui pourraient bénéficier du HyVM :
Aggrégateurs DEX (Paraswap, 0x, 1inch, etc.)
Aggrégateurs Perps (Mux)
Facilitateurs (InstaDapp, DeFiSaver, Rhino),
Protocoles de rendement (StakeDAO, Convex, Yearn)
Tout sera bien sûr open-source.
Vous voulez en savoir plus sur le HyVM ? Souhaitez-vous collaborer ? Vous voulez aider à changer la DeFi ?
Nous aimerions discuter ! Passez sur notre serveur Discord (https://discord.com/invite/MassDotMoney?ref=blog.mass.money) et dites bonjour dans le canal #Mass-Tech-Talk !
Last updated