Deep EVM #3 : Comprendre le gas — Pourquoi votre contrat coûte ce qu'il coûte
Engineering Team
Pourquoi le gas existe
Le gas résout deux problèmes fondamentaux dans un système de calcul décentralisé :
- Le problème de l’arrêt — Il est mathématiquement impossible de déterminer si un programme arbitraire se terminera. Le gas impose une limite supérieure : si vous manquez de gas, l’exécution s’arrête.
- La tarification des ressources — Chaque nœud du réseau doit exécuter votre transaction. Le gas tarifie équitablement le calcul, le stockage et la bande passante que vous consommez.
EIP-1559 : le marché du gas moderne
Depuis la mise à jour London (août 2021), Ethereum utilise un mécanisme de frais à deux composantes :
- Frais de base (baseFee) — Déterminé algorithmiquement par le protocole. Augmente quand les blocs sont remplis à plus de 50 %, diminue quand ils sont en dessous. Ce montant est brûlé.
- Frais de priorité (tip) — Pourboire payé directement au validateur. Incite l’inclusion de votre transaction.
coût_total = gas_utilisé * (baseFee + maxPriorityFeePerGas)
Votre transaction spécifie maxFeePerGas (maximum que vous êtes prêt à payer) et maxPriorityFeePerGas (pourboire). Si baseFee + tip dépasse maxFeePerGas, la transaction attend dans le mempool.
Anatomie du coût d’une transaction
Décomposons le coût total d’un transfert ERC-20 :
- Gas intrinsèque : 21 000 gas (coût fixe de toute transaction)
- Calldata : 4 gas par octet nul, 16 par non-nul. Un appel
transfer(address,uint256)encode ~68 octets. - Exécution : SLOAD pour les soldes (2100 froid), SSTORE pour mettre à jour deux soldes (5000-22100 chacun), LOG pour émettre l’événement Transfer.
- Overhead du contrat : Dispatch de fonction, vérification SafeMath, encodage ABI.
Total typique : ~65 000 gas pour un transfert ERC-20 standard.
Techniques d’optimisation du gas
1. Packing de variables de stockage
Solidity empaquette les variables de stockage qui tiennent dans un seul slot de 32 octets :
// Mauvais : 3 slots (3 * SLOAD = 6300 gas froid)
uint256 a;
uint256 b;
uint256 c;
// Bon : 1 slot (1 * SLOAD = 2100 gas froid)
uint128 a;
uint64 b;
uint64 c;
2. Utiliser calldata au lieu de memory
Pour les paramètres de fonction que vous ne modifiez pas :
// Coûteux : copie les données en mémoire
function process(uint256[] memory data) external { ... }
// Économique : lit directement depuis calldata
function process(uint256[] calldata data) external { ... }
3. Erreurs personnalisées vs require avec message
// Coûteux : stocke la chaîne dans le bytecode
require(balance >= amount, "Insufficient balance");
// Économique : seulement 4 octets de sélecteur
error InsufficientBalance();
if (balance < amount) revert InsufficientBalance();
4. Opérations non vérifiées
// Quand vous savez que le débordement est impossible :
unchecked {
i++; // Économise ~80 gas par itération
}
Gas et MEV
Pour les bots MEV, chaque unité de gas économisée est un gain direct de profit. Un bot d’arbitrage qui utilise 200 000 gas au lieu de 300 000 peut surenchérir ses concurrents avec un frais de priorité plus élevé tout en restant rentable.
Les techniques avancées incluent :
- Écrire en Yul ou Huff pour un contrôle total
- Préchauffer les slots avec des listes d’accès EIP-2930
- Utiliser des tables de saut au lieu de dispatchers if-else
- Minimiser les appels inter-contrats
Conclusion
Le gas est le langage économique de l’EVM. Comprendre pourquoi chaque opération coûte ce qu’elle coûte — et comment réduire systématiquement ces coûts — est une compétence fondamentale pour tout développeur de smart contracts.
Dans le prochain article, nous explorerons les primitives de sécurité de l’EVM : msg.sender, le contrôle d’accès et la protection contre la réentrance.