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

Cet article décrira le mécanisme des frais de Gas de la machine virtuelle Ethereum (EVM), les concepts clés liés à l'optimisation des frais de Gas, ainsi que les meilleures pratiques pour optimiser les frais de Gas lors du développement de contrats intelligents. Nous espérons qu'à travers ce contenu, nous pourrons fournir inspiration et aide pratique aux développeurs, tout en aidant les utilisateurs ordinaires à mieux comprendre le fonctionnement des frais de Gas de l'EVM, afin d'affronter ensemble les défis de l'écosystème blockchain.

Aperçu des mécanismes de frais de Gas de l'EVM

Dans les réseaux compatibles avec l'EVM, le terme « Gas » fait référence à l'unité utilisée pour mesurer la capacité de calcul nécessaire pour exécuter une opération spécifique.

Le graphique ci-dessous illustre la disposition structurelle de l'EVM. Dans le graphique, la consommation de Gas se divise en trois parties : exécution des opérations, appels de messages externes et lectures/écritures de mémoire et de stockage.

Source : site officiel d'Ethereum[1]

Puisque l'exécution de chaque transaction nécessite des ressources de calcul, des frais sont appliqués pour éviter les boucles infinies et les attaques par déni de service (DoS). Les frais nécessaires pour compléter une transaction sont appelés « frais de Gas ».

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

Frais de Gas = unités de gas utilisées * (frais de base + frais de priorité)

Les frais de base seront détruits, tandis que les frais de priorité serviront d'incitation aux validateurs pour ajouter des transactions à la blockchain. En fixant des frais de priorité plus élevés lors de l'envoi d'une transaction, il est possible d'augmenter 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 Gas dans l'EVM

Lors de la compilation de contrats intelligents avec Solidity, le contrat est transformé en une série d'« opcodes », c'est-à-dire d'opcodes.

Chaque opcode (par exemple, créer un contrat, effectuer 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 Gas reconnu, ces coûts étant documentés dans le livre jaune d'Ethereum[2].

Après plusieurs modifications d'EIP, les coûts en Gas de certaines 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 fondamentaux de l'optimisation des Gas

Le concept central de l'optimisation des Gas est de privilégier les opérations à coût efficace sur la blockchain EVM, tout en évitant les opérations coûteuses en Gas.

Dans l'EVM, les opérations suivantes ont des coûts plus bas :

  • Lire et écrire des variables en 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 les variables d'état stockées dans le stockage du contrat

  • Appels de fonctions externes

  • Opérations de boucle

Meilleures pratiques pour optimiser les frais de Gas de l'EVM

Sur la base des concepts fondamentaux ci-dessus, nous avons compilé une liste de meilleures pratiques pour l'optimisation des frais de Gas destinée à la communauté des développeurs. En suivant ces pratiques, les développeurs peuvent réduire la consommation de frais de Gas 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 Gas est bien plus élevée que 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 Gas.

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 de mémoire. Par exemple, les instructions OPcodesmload et mstore ne consomment que 3 unités de Gas, tandis que les opérations de stockage comme sload et sstore nécessitent au moins 100 unités, même dans les conditions les plus idéales.

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

  • Stocker des 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, et en attribuant les résultats aux variables de stockage une fois tous les calculs terminés.

2. Regroupement de variables

Le nombre d'emplacements de stockage utilisés dans les contrats intelligents et la manière dont les développeurs représentent les données influencent considérablement la consommation de frais de Gas.

Le compilateur Solidity regroupe les variables de stockage consécutives lors du processus de compilation, en utilisant des emplacements de stockage de 32 octets comme unité de base de stockage des variables. Le regroupement de variables consiste à organiser judicieusement les variables pour permettre à plusieurs d'entre elles de 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.

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

Étant donné que chaque emplacement de stockage consomme du Gas, le regroupement de variables optimise l'utilisation du Gas 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 associés à différents types de données varient également. Choisir le bon type de données aide à optimiser l'utilisation du Gas.

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

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

À première vue, l'utilisation de uint256 est moins coûteuse que uint8. Cependant, cela change avec l'optimisation de regroupement de variables que nous avons précédemment recommandée. Si les développeurs peuvent regrouper quatre variables uint8 dans un seul emplacement de stockage, le coût total d'itération sur elles sera inférieur à celui de quatre variables uint256. Ainsi, le contrat intelligent peut lire et écrire un emplacement de stockage une seule fois et placer les quatre variables uint8 dans la mémoire/stockage en une seule opération.

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

Si les données peuvent être contenues dans 32 octets, il est conseillé d'utiliser le type de données bytes32 au lieu de bytes ou de strings. En général, les variables de taille fixe consomment moins de Gas que les variables de taille variable. Si la longueur en octets peut être limitée, essayez de choisir la longueur minimale de bytes1 à bytes32.

5. Mappages et tableaux

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

Les mappages sont généralement plus efficaces et moins coûteux que les tableaux, mais les tableaux sont itérables et supportent le regroupement de types de données. Par conséquent, il est recommandé d'utiliser des mappages pour gérer les listes de données, sauf si une itération est nécessaire ou si une optimisation de consommation de Gas par le regroupement de types de données est possible.

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 entre les deux est que memory peut être modifié par la fonction, tandis que calldata est immuable.

N'oubliez pas ce principe : si les paramètres de fonction sont en lecture seule, il est préférable d'utiliser calldata plutôt que 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 au cours du processus de décodage ABI. Le coût d'exécution de ce bloc de code est de 3 694 unités de Gas.

Exemple 2 : Utiliser calldata

Lors de la lecture de valeurs directement à partir de calldata, sautez les opérations de mémoire intermédiaires. Cette méthode d'optimisation réduit le coût d'exécution à seulement 2 413 unités de Gas, avec une efficacité de Gas augmentée 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, et il est conseillé d'utiliser autant que possible les mots-clés Constant ou Immutable.

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

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

Dans le graphique ci-dessous, sous contrainte conditionnelle i

De plus, les compilateurs de version 0.8.0 et ultérieure n'ont plus besoin d'utiliser la bibliothèque SafeMath, car le compilateur lui-même intègre des protections contre le débordement et le sous-dépassement.

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 Gas. Voici une méthode pour optimiser les coûts en Gas des modificateurs :

Avant optimisation :

Après optimisation :

Dans cet exemple, en restructurant la logique en une fonction interne _checkOwner(), cela permet de réutiliser cette fonction interne dans le modificateur, réduisant ainsi la taille du bytecode et diminuant les coûts en Gas.

10. Optimisation des courts-circuits

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

Pour optimiser la consommation de Gas, les conditions à faible coût de calcul doivent être placées en premier, ce qui permet de sauter potentiellement des calculs coûteux.

Conseils généraux supplémentaires

1. Supprimer le code inutile

Si un contrat contient des fonctions ou des variables inutilisées, il est conseillé de les supprimer. C'est la manière la plus directe de réduire les coûts de déploiement des contrats et de maintenir la taille du contrat petite.

Voici quelques conseils pratiques :

Utilisez les algorithmes les plus efficaces pour effectuer des calculs. Si certaines opérations dans le contrat utilisent directement les résultats de calculs, ces calculs redondants doivent être supprimés. En essence, tout calcul inutilisé doit être éliminé.

Dans Ethereum, les développeurs peuvent obtenir des récompenses en Gas 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 réinitialisée à sa valeur par défaut.

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

2. Utiliser des contrats précompilés

Les contrats précompilés offrent des fonctions de bibliothèque complexes, comme les opérations de cryptage et de hachage. Étant donné que le code n'est pas exécuté sur l'EVM, mais est exécuté localement sur les nœuds clients, il nécessite moins de Gas. Utiliser des contrats précompilés peut économiser du Gas 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 de 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 coûts en Gas et améliorer l'efficacité d'exécution des applications.

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

3. Utiliser le code d'assemblage en ligne

L'assemblage en ligne (in-line assembly) permet aux développeurs d'écrire du code bas niveau mais efficace qui peut ê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, réduisant ainsi davantage les frais de Gas. De plus, l'assemblage en ligne peut exécuter certaines opérations complexes qui seraient difficiles à réaliser uniquement avec Solidity, offrant ainsi plus de flexibilité pour optimiser la consommation de Gas.

Voici un exemple de code montrant comment économiser du Gas avec l'assemblage en ligne :

Comme le montre le graphique ci-dessus, par rapport aux cas d'utilisation standards, le deuxième cas d'utilisation utilisant la technologie d'assemblage en ligne a une efficacité en Gas plus élevée.

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

4. Utiliser des solutions de Layer 2

Utiliser des solutions de Layer 2 peut réduire la quantité de données devant être stockées et calculées sur la chaîne principale d'Ethereum.

Les solutions de Layer 2, telles que les rollups, les chaînes latérales 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 de nombreuses transactions, ces solutions réduisent le nombre de transactions sur la chaîne, ce qui diminue les frais de Gas. Utiliser des solutions de Layer 2 peut également améliorer la scalabilité d'Ethereum, permettant à plus d'utilisateurs et d'applications de participer au réseau sans provoquer de congestion due à une surcharge du réseau.

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

Il existe plusieurs outils d'optimisation disponibles, tels que l'optimiseur solc, l'optimiseur de construction 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. En combinaison avec d'autres bibliothèques d'optimisation du Gas, telles que « solmate », les développeurs peuvent réduire efficacement les coûts de Gas et améliorer l'efficacité des contrats intelligents.

Conclusion

Optimiser la consommation de Gas est une étape importante pour les développeurs, car cela permet de minimiser les coûts de transaction tout en améliorant l'efficacité des contrats intelligents sur des réseaux compatibles avec l'EVM. En priorisant l'exécution d'opérations à coût réduit, 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 Gas 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é. Au cours de l'optimisation du code et de la réduction de la consommation de Gas, il ne faut jamais sacrifier la sécurité inhérente des contrats intelligents.

[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