rounded

Rédaction : Certik

Les frais de Gaz sur le réseau principal d'Ethereum ont toujours été un problème majeur, surtout en période de congestion. En période de pointe, les utilisateurs doivent souvent payer des frais de transaction très élevés. Ainsi, il est particulièrement important d'optimiser les frais de Gaz lors de la phase de développement des contrats intelligents. Optimiser la consommation de Gaz peut non seulement réduire efficacement les coûts de transaction, mais aussi améliorer l'efficacité des transactions, offrant ainsi une expérience blockchain plus économique et efficace aux utilisateurs.

Cet article résume le mécanisme des frais de Gaz de la machine virtuelle Ethereum (EVM), les concepts fondamentaux liés à l'optimisation des frais de Gaz, ainsi que les meilleures pratiques pour optimiser les frais de Gaz lors du développement de contrats intelligents. Nous espérons que ces contenus apporteront de l'inspiration et une aide pratique à la communauté des développeurs, tout en aidant les utilisateurs ordinaires à mieux comprendre le fonctionnement des frais de Gaz dans l'EVM, afin de relever ensemble les défis de l'écosystème blockchain.

Aperçu du mécanisme des frais de Gaz de l'EVM

Dans les réseaux compatibles avec l'EVM, le terme « Gaz » fait référence à l'unité de mesure de la puissance de calcul requise pour exécuter une opération spécifique.

Le graphique ci-dessous illustre la structure de l'EVM. Dans le graphique, la consommation de Gaz est divisée en trois parties : exécution des opérations, appels de messages externes, et lecture/écriture en mémoire et stockage.

Source : site officiel d'Ethereum [1]

Chaque transaction nécessitant des ressources de calcul est soumise à des frais afin d'éviter les boucles infinies et les attaques par déni de service (DoS). Les frais nécessaires pour terminer une transaction sont appelés « frais de Gaz ».

Depuis l'entrée en vigueur de l'EIP-1559 (hard fork de Londres), les frais de Gaz sont calculés selon la formule suivante :

Frais de Gaz = unités de Gaz utilisées * (frais de base + frais prioritaires)

Les frais de base seront détruits, tandis que les frais prioritaires serviront d'incitation pour encourager les validateurs à inclure les transactions dans la blockchain. En définissant des frais prioritaires plus élevés lors de l'envoi de transactions, on peut améliorer les chances que la transaction soit incluse dans le prochain bloc. Cela ressemble à un « pourboire » que l'utilisateur paie au validateur.

1. Comprendre l'optimisation des Gaz dans l'EVM

Lorsque les contrats intelligents sont compilés avec Solidity, ils sont transformés en une série d'« opcodes ».

Chaque opcode (par exemple, créer un contrat, faire un appel de message, accéder au stockage des comptes et exécuter des opérations sur la machine virtuelle) a un coût de consommation de Gaz reconnu, ces coûts étant enregistrés dans le livre jaune d'Ethereum[2].

Après plusieurs modifications d'EIP, certains coûts en Gaz des opcodes ont été ajustés et peuvent différer de ceux du livre jaune. Pour des informations détaillées sur les coûts les plus récents des opcodes, veuillez consulter ici[3].

2. Concepts de base de l'optimisation des Gaz

Le concept central de l'optimisation des Gaz est de choisir en priorité des opérations à coût efficace sur la blockchain EVM, évitant ainsi les opérations coûteuses en Gaz.

Dans l'EVM, les opérations suivantes coûtent moins cher :

  • Lire et écrire des variables de mémoire

  • Lire des constantes et des variables immuables

  • Lire et écrire des variables locales

  • Lire des variables calldata, telles que des tableaux et des structures calldata

  • Appels de fonctions internes

Les opérations coûteuses comprennent :

  • Lire et écrire des variables d'état stockées dans le stockage du contrat

  • Appels de fonctions externes

  • Opérations en boucle

Meilleures pratiques pour l'optimisation des frais de Gaz dans l'EVM

Sur la base de ces concepts fondamentaux, nous avons élaboré une liste de meilleures pratiques pour l'optimisation des frais de Gaz à l'intention de la communauté des développeurs. En suivant ces pratiques, les développeurs peuvent réduire la consommation de Gaz de leurs contrats intelligents, diminuer les coûts de transaction et créer des applications plus efficaces et conviviales.

1. Minimiser l'utilisation du stockage

Dans Solidity, le stockage est une ressource limitée, dont la consommation de Gaz est bien supérieure à celle de la mémoire. Chaque fois qu'un contrat intelligent lit ou écrit des données à partir du stockage, cela entraîne des coûts élevés en Gaz.

Selon la définition du livre jaune d'Ethereum, le coût des opérations de stockage est plus de 100 fois supérieur à celui des opérations en mémoire. Par exemple, les instructions OPcodes mload et mstore ne consomment que 3 unités de Gaz, tandis que les opérations de stockage telles que sload et sstore nécessitent au moins 100 unités même dans les meilleures conditions.

Les méthodes pour limiter l'utilisation du stockage incluent :

  • Stocker les données non permanentes en mémoire

  • Réduire le nombre de modifications de stockage : en conservant les résultats intermédiaires en mémoire, puis en affectant les résultats aux variables de stockage après que tous les calculs soient terminés.

2. Regroupement de variables

Le nombre d'emplacements de stockage utilisés dans un contrat intelligent et la manière dont les développeurs représentent les données influencent grandement la consommation des frais de Gaz.

Le compilateur Solidity regroupe les variables de stockage continues au cours de la compilation et utilise un emplacement de stockage de 32 octets comme unité de base. Le regroupement de variables consiste à organiser judicieusement les variables afin que plusieurs d'entre elles puissent s'adapter à un seul emplacement de stockage.

À gauche se trouve une méthode d'implémentation moins efficace, qui consommera 3 emplacements de stockage ; à droite se trouve une méthode d'implémentation plus efficace.

Avec cet ajustement, les développeurs peuvent économiser 20 000 unités de Gaz (le stockage d'un emplacement de stockage inutilisé consomme 20 000 Gaz), mais maintenant, seulement deux emplacements de stockage sont nécessaires.

Chaque emplacement de stockage consommant des Gaz, le regroupement des variables optimise l'utilisation du Gaz en réduisant le nombre d'emplacements de stockage nécessaires.

3. Optimiser les types de données

Une variable peut être représentée par plusieurs types de données, mais les coûts d'opération varient selon les types de données. Choisir le type de données approprié aide à optimiser l'utilisation de Gaz.

Par exemple, dans Solidity, les entiers peuvent être subdivisés en différentes tailles : uint8, uint16, uint32, etc. Étant donné que l'EVM exécute des opérations par tranches de 256 bits, l'utilisation de uint8 signifie que l'EVM doit d'abord le convertir en uint256, ce qui entraîne une consommation de Gaz supplémentaire.

Nous pouvons comparer les coûts en Gaz de uint8 et uint256 à l'aide du code dans le graphique. La fonction UseUint() consomme 120 382 unités de Gaz, tandis que la fonction UseUInt8() consomme 166 111 unités de Gaz.

Pris isolément, utiliser uint256 est moins cher que uint8. Cependant, cela change si l'on applique l'optimisation de regroupement de variables que nous avons recommandée précédemment. Si les développeurs peuvent regrouper quatre variables uint8 dans un seul emplacement de stockage, le coût total de leur itération sera inférieur à celui de quatre variables uint256. Ainsi, le contrat intelligent peut lire et écrire une fois dans l'emplacement de stockage et placer les quatre variables uint8 dans la mémoire / le stockage en une seule opération.

4. Utiliser des variables de taille fixe au lieu de variables dynamiques

Si les données peuvent être contrôlées dans 32 octets, il est conseillé d'utiliser le type de données bytes32 à la place de bytes ou strings. En général, les variables de taille fixe consomment moins de Gaz que les variables de taille variable. Si la longueur en octets peut être limitée, choisissez autant que possible la longueur minimale allant de bytes1 à bytes32.

5. Mappings et tableaux

Les listes de données de Solidity peuvent être représentées par deux types de données : tableaux (Arrays) et mappings (Mappings), mais leur syntaxe et structure sont complètement différentes.

Les mappings sont généralement plus efficaces et moins coûteux, mais les tableaux sont itérables et supportent le regroupement de types de données. Par conséquent, il est conseillé de privilégier l'utilisation de mappings lors de la gestion de listes de données, sauf si une itération est nécessaire ou si des optimisations de consommation de Gaz peuvent être réalisées par le regroupement de types de données.

6. Utiliser calldata au lieu de memory

Les variables déclarées dans les paramètres de fonction peuvent être stockées dans calldata ou memory. La principale différence est que memory peut être modifié par la fonction, tandis que calldata est immuable.

Rappelez-vous ce principe : si les paramètres de fonction sont en lecture seule, il convient de privilégier l'utilisation de calldata plutôt que de memory. Cela permet d'éviter des opérations de copie inutiles de calldata de fonction vers memory.

Exemple 1 : Utiliser memory

Lors de l'utilisation du mot-clé memory, les valeurs des tableaux sont copiées de calldata codé vers memory lors du décodage ABI. Le coût d'exécution de ce bloc de code est de 3 694 unités de Gaz.

Exemple 2 : Utiliser calldata

Lors de la lecture directe des valeurs depuis calldata, cette optimisation permet de sauter les opérations intermédiaires de mémoire. Cette méthode d'optimisation ramène le coût d'exécution à seulement 2 413 unités de Gaz, améliorant ainsi l'efficacité du Gaz de 35%.

7. Utiliser autant que possible les mots-clés Constant/Immutable

Les variables Constant/Immutable ne sont pas stockées dans le stockage du contrat. Ces variables sont calculées au moment de la compilation et stockées dans le bytecode du contrat. Par conséquent, leur coût d'accès est bien inférieur à celui du stockage, il est donc conseillé d'utiliser les mots-clés Constant ou Immutable autant que possible.

8. Utiliser Unchecked lorsque l'on est sûr qu'il n'y aura pas de dépassement / sous-dépassement

Lorsque les développeurs peuvent s'assurer que les opérations arithmétiques ne provoqueront pas de dépassements ou de sous-dépassements, ils peuvent utiliser le mot-clé unchecked introduit dans Solidity v0.8.0 pour éviter des vérifications superflues, économisant ainsi des frais de Gaz.

Dans le graphique ci-dessous, la variable i est limitée par la contrainte conditionnelle i<length, ce qui signifie qu'elle ne peut jamais dépasser. Ici, length est défini comme uint256, ce qui signifie que la valeur maximale de i est max(uint)-1. Par conséquent, incrémenter i dans un bloc de code non vérifié est considéré comme sûr et économise ainsi du Gaz.

De plus, les compilateurs à partir de la version 0.8.0 n'ont plus besoin d'utiliser la bibliothèque SafeMath, car le compilateur lui-même a intégré des fonctionnalités de protection contre les dépassements et sous-dépassements.

9. Optimiser les modificateurs

Le code des modificateurs est intégré dans les fonctions modifiées, et chaque fois qu'un modificateur est utilisé, son code est copié. Cela augmente la taille du bytecode et augmente la consommation de Gaz. Voici une façon d'optimiser les coûts de Gaz des modificateurs :


Avant optimisation :

Optimisé :

Dans cet exemple, en restructurant la logique en une fonction interne _checkOwner(), permettant de réutiliser cette fonction interne dans le modificateur, on peut réduire la taille du bytecode et diminuer le coût du Gaz.

10. Optimisation par courts-circuits

Pour les opérateurs || et &&, l'évaluation logique est réalisée par évaluation par courts-circuits, c'est-à-dire que si la première condition peut déjà déterminer le résultat de l'expression logique, la deuxième condition ne sera pas évaluée.

Pour optimiser la consommation de Gaz, il est conseillé de placer les conditions à faible coût en premier, afin de sauter potentiellement les calculs coûteux.

Conseils généraux supplémentaires

1. Supprimer le code inutile

S'il existe des fonctions ou des variables inutilisées dans le contrat, il est conseillé de les supprimer. C'est la méthode la plus directe pour réduire les coûts de déploiement du contrat et maintenir la taille du contrat petite.

Voici quelques conseils pratiques :

Utiliser les algorithmes les plus efficaces pour les calculs. Si certains résultats de calcul sont directement utilisés dans le contrat, il convient de supprimer ces calculs redondants. En substance, tout calcul non utilisé doit être supprimé.

Dans Ethereum, les développeurs peuvent obtenir des récompenses en Gaz en libérant de l'espace de stockage. Si une variable n'est plus nécessaire, elle doit être supprimée à l'aide du mot-clé delete ou définie sur une valeur par défaut.

Optimisation des boucles : éviter les opérations de boucle coûteuses, combiner les boucles autant que possible et déplacer les calculs répétés en dehors du corps de la boucle.

2. Utiliser des contrats précompilés

Les contrats précompilés fournissent des fonctions de bibliothèque complexes, telles que les opérations cryptographiques et de hachage. Comme le code n'est pas exécuté sur l'EVM, mais localement sur le nœud client, cela nécessite moins de Gaz. Utiliser des contrats précompilés peut économiser des Gaz en réduisant la charge de calcul nécessaire pour exécuter des contrats intelligents.

Des exemples de contrats précompilés incluent l'algorithme de signature numérique par courbe elliptique (ECDSA) et l'algorithme de hachage SHA2-256. En utilisant ces contrats précompilés dans les contrats intelligents, les développeurs peuvent réduire les frais de Gaz et améliorer l'efficacité de l'exécution des applications.

Pour la liste complète des contrats précompilés pris en charge par le réseau Ethereum, veuillez consulter ici[4].

3. Utiliser du code d'assemblage en ligne

L'assemblage en ligne permet aux développeurs d'écrire du code bas niveau mais efficace, pouvant être exécuté directement par l'EVM, sans avoir à utiliser des opcodes Solidity coûteux. L'assemblage en ligne permet également un contrôle plus précis de l'utilisation de la mémoire et du stockage, ce qui réduit encore les frais de Gaz. De plus, l'assemblage en ligne peut effectuer des opérations complexes que l'utilisation de Solidity rend difficile, offrant ainsi plus de flexibilité pour optimiser la consommation de Gaz.

Voici un exemple de code utilisant l'assemblage en ligne pour économiser du Gaz :

D'après le graphique ci-dessus, par rapport au cas d'utilisation standard, le deuxième cas d'utilisation utilisant la technique d'assemblage en ligne présente une efficacité en Gaz plus élevée.

Cependant, l'utilisation de l'assemblage en ligne peut également comporter des risques et être sujette à des erreurs. Par conséquent, cela doit être fait avec prudence et réservé aux développeurs expérimentés.

4. Utiliser des solutions Layer 2

Utiliser des solutions Layer 2 peut réduire la quantité de données à stocker et à calculer sur la chaîne principale d'Ethereum.

Des solutions Layer 2 telles que les rollups, les sidechains et les canaux d'état peuvent décharger le traitement des transactions de la chaîne principale d'Ethereum, permettant ainsi des transactions plus rapides et moins coûteuses.

En regroupant un grand nombre de transactions, ces solutions réduisent le nombre de transactions sur la chaîne, diminuant ainsi les frais de Gaz. Utiliser des solutions de Layer 2 peut également améliorer l'évolutivité d'Ethereum, permettant à plus d'utilisateurs et d'applications de participer au réseau sans provoquer de congestions dues à une surcharge.

5. Utiliser des outils et bibliothèques d'optimisation

Il existe plusieurs outils d'optimisation disponibles, comme l'optimiseur solc, l'optimiseur de construction de Truffle et le compilateur Solidity de Remix.

Ces outils peuvent aider à minimiser la taille du bytecode, à supprimer le code inutile et à réduire le nombre d'opérations nécessaires pour exécuter des contrats intelligents. Combiné avec d'autres bibliothèques d'optimisation des Gaz, telles que « solmate », les développeurs peuvent réduire efficacement les coûts de Gaz et améliorer l'efficacité des contrats intelligents.

Conclusion

Optimiser la consommation de Gaz est une étape importante pour les développeurs, car cela peut minimiser les coûts de transaction tout en améliorant l'efficacité des contrats intelligents sur les réseaux compatibles avec l'EVM. En priorisant l'exécution d'opérations économes en coûts, en réduisant l'utilisation du stockage, en utilisant l'assemblage en ligne et en suivant d'autres meilleures pratiques discutées dans cet article, les développeurs peuvent réduire efficacement la consommation de Gaz de leurs contrats.

Cependant, il est important de noter qu'au cours du processus d'optimisation, les développeurs doivent agir avec prudence pour éviter d'introduire des vulnérabilités de sécurité. Lors de l'optimisation du code et de la réduction de la consommation de Gaz, la sécurité inhérente des contrats intelligents ne doit jamais être compromise.

[1] : https://ethereum.org/en/developers/docs/gas/

[2] : https://ethereum.github.io/yellowpaper/paper.pdf

[3] : https://www.evm.codes/

[4] : https://www.evm.codes/precompiled