Tutoriel·7 min de lecture·Par Solingo

Écrire des Boucles Gas-Efficient en Solidity

Optimisez vos smart contracts en maîtrisant les patterns de boucles qui réduisent drastiquement les coûts de gas.

# Écrire des Boucles Gas-Efficient en Solidity

Les boucles sont l'une des principales sources de consommation de gas dans les smart contracts. Une boucle mal optimisée peut rendre votre contrat inutilisable en production.

Le Problème des SLOAD Répétés

Chaque lecture depuis le storage coûte 2100 gas (cold) ou 100 gas (warm). Dans une boucle, cela s'accumule rapidement :

// ❌ Mauvais : SLOAD à chaque itération

uint256 sum;

for (uint256 i = 0; i < values.length; i++) {

sum += values[i];

}

Pattern 1 : Cache la Longueur du Tableau

// ✅ Bon : cache la longueur

uint256 length = values.length;

for (uint256 i = 0; i < length; i++) {

sum += values[i];

}

Économie : ~2000 gas pour 100 itérations.

Pattern 2 : Incrémentation Non-Checkée

Depuis Solidity 0.8.0, les débordements sont vérifiés par défaut. Dans une boucle avec un maximum connu, désactivez-les :

// ✅ Optimal

uint256 length = values.length;

for (uint256 i = 0; i < length;) {

sum += values[i];

unchecked { ++i; }

}

Économie : ~40 gas par itération.

Pattern 3 : Préférez ++i à i++

// ✅ ++i est plus économe (pas de variable temporaire)

unchecked { ++i; }

Pattern 4 : Évitez les Boucles Dynamiques

Ne bouclez JAMAIS sur un tableau dont la taille est contrôlée par les utilisateurs :

// ❌ DANGER : DoS possible

function distributeRewards(address[] calldata recipients) external {

for (uint256 i = 0; i < recipients.length; i++) {

// Un attaquant envoie 10000 adresses → out of gas

}

}

Solution : pagination ou pull pattern.

Pattern 5 : Accumulez Puis Écrivez

// ❌ Mauvais : SSTORE à chaque itération (20000 gas × n)

for (uint256 i = 0; i < length; i++) {

totalBalance += balances[i];

}

// ✅ Bon : une seule écriture finale

uint256 temp;

for (uint256 i = 0; i < length; i++) {

temp += balances[i];

}

totalBalance = temp;

Exemple Complet : Token Batch Transfer

function batchTransfer(

address[] calldata recipients,

uint256[] calldata amounts

) external {

require(recipients.length == amounts.length, "Length mismatch");

require(recipients.length <= 100, "Batch too large");

uint256 length = recipients.length;

uint256 totalAmount;

// Première passe : calcul du total

for (uint256 i = 0; i < length;) {

totalAmount += amounts[i];

unchecked { ++i; }

}

require(balanceOf[msg.sender] >= totalAmount, "Insufficient balance");

balanceOf[msg.sender] -= totalAmount;

// Deuxième passe : distribution

for (uint256 i = 0; i < length;) {

balanceOf[recipients[i]] += amounts[i];

unchecked { ++i; }

}

}

Benchmark

Pour un batch de 50 transferts :

  • Version naïve : ~1.2M gas
  • Version optimisée : ~450K gas
  • Économie : 62%

Quand NE PAS Optimiser

Si votre boucle itère sur moins de 10 éléments fixes, l'optimisation ne vaut souvent pas la perte de lisibilité.

Ressources

  • Testez avec forge snapshot --diff

Maîtrisez ces patterns et vos utilisateurs vous remercieront avec leurs wallets.

Prêt à mettre en pratique ?

Applique ces concepts avec des exercices interactifs sur Solingo.

Commencer gratuitement