Integer Overflow et Underflow en Solidity : Guide Complet

Integer Overflow et Underflow en Solidity : Guide Complet

oct., 30 2026

Imaginez que vous comptiez des dollars sur une calculatrice qui ne peut afficher que trois chiffres. Si vous ajoutez 1 à 999, l'écran n'affiche pas 1000, mais revient bizarrement à 000. C'est exactement ce qui se passe dans les smart contracts Ethereum lorsque l'on parle d'integer overflow. Ce phénomène, autrefois responsable de pertes financières massives dans la DeFi, est devenu un classique de la sécurité blockchain. Mais avec les mises à jour récentes de Solidity, beaucoup de développeurs pensent que le problème est résolu. La réalité est plus nuancée. Comprendre comment fonctionnent ces dépassements reste crucial pour écrire du code robuste, surtout si vous touchez encore aux versions anciennes ou utilisez des blocs unchecked.

Pourquoi les entiers posent-ils problème ?

Contrairement au Python ou au JavaScript où les nombres peuvent devenir infiniment grands (au prix de la performance), la Ethereum Virtual Machine (EVM) utilise des types de données de taille fixe. Cela signifie qu'il y a une limite absolue à la valeur qu'une variable peut stocker. En Solidity, vous avez principalement deux familles de types : les entiers non signés (uint) et les entiers signés (int). Chaque type a une taille précise, exprimée en bits.

Par exemple, un uint8 occupe 8 bits. Il peut donc représenter $2^8 = 256$ valeurs différentes, allant de 0 à 255. Si votre contrat ajoute 1 à une variable contenant déjà 255, le résultat mathématique attendu est 256. Mais comme 256 ne tient pas dans 8 bits, le bit de débordement est perdu, et la variable "boucle" (wrap around) pour revenir à 0. C'est l'overflow. À l'inverse, si vous retirez 1 à une variable contenant 0, elle ne devient pas -1 (car c'est un type non signé), mais saute directement à 255. C'est l'underflow.

Ce comportement binaire est logique pour la machine, mais catastrophique pour la logique métier. Pensez à un système de vote où chaque vote vaut 1 point. Si un attaquant réussit à faire déborder le compteur de votes, il pourrait transformer un score de 255 en 0, annulant ainsi tout le travail précédent, ou inversement, créer une pénurie artificielle de jetons.

L'ère pré-Solidity 0.8.0 : Le règne de SafeMath

Jusqu'à la fin 2020, Solidity ne protégeait pas automatiquement contre ces erreurs. Les développeurs devaient être extrêmement vigilants. Pour éviter les désastres, la communauté s'est tournée vers des bibliothèques externes, dont la plus célèbre est OpenZeppelin's SafeMath.

SafeMath fournissait des fonctions comme safeAdd(), safeSub() et safeMul(). Avant d'effectuer une opération, ces fonctions vérifiaient si le résultat dépassait les limites du type. Si oui, la transaction était annulée (revert). Cette approche était efficace, mais elle avait un coût : elle consommait plus de gas (frais de réseau) car chaque opération arithmétique nécessitait plusieurs instructions supplémentaires pour les vérifications. De plus, cela rendait le code moins lisible, transformant un simple a + b en a.add(b).

Comparaison des approches de gestion des entiers
Caractéristique Solidity < 0.8.0 (sans protection) Solidity < 0.8.0 (avec SafeMath) Solidity >= 0.8.0 (natif)
Comportement en cas de dépassement Boucle silencieuse (Wrap-around) Revert de la transaction Revert de la transaction
Coût en Gas Faible Élevé (+30-50 gas/opération) Moyen (optimisé par le compilateur)
Lisibilité du code Simple mais risqué Verbeux (a.add(b)) Simple et sûr (a + b)
Niveau de risque Très élevé Faible (si utilisé correctement) Faible (sauf usage de unchecked)
Scène cauchemardesque représentant la lourdeur et les risques du SafeMath avant Solidity 0.8.

La révolution Solidity 0.8.0 : Protection native

En décembre 2020, Solidity a introduit une amélioration majeure : la vérification automatique des overflows et underflows. Désormais, si vous écrivez c = a + b; et que le résultat dépasse la capacité de c, la transaction échoue automatiquement. Plus besoin d'importer SafeMath pour la plupart des cas simples. Le compilateur injecte le code de vérification nécessaire.

Cette mise à jour a simplifié le développement et réduit les risques liés à l'oubli des vérifications manuelles. Cependant, cette protection n'est pas magique. Elle repose sur des instructions EVM spécifiques qui consomment du gas. Dans certains scénarios critiques où le gas est précieux, les développeurs peuvent vouloir contourner cette vérification. C'est là qu'intervient le mot-clé unchecked.

Le piège des blocs unchecked

Le bloc unchecked permet de dire au compilateur : "Je sais que mon opération ne va pas déborder, ne perds pas de temps à vérifier." Cela économise du gas, mais c'est une arme à double tranchant. Si vous faites une erreur de calcul dans un bloc unchecked, vous retrouvez le comportement dangereux de l'ancien Solidity : le chiffre boucle silencieusement.

Par exemple, dans un contrat de yield farming complexe impliquant des multiplications de taux d'intérêt, on pourrait utiliser unchecked pour optimiser. Mais si un utilisateur malveillant fournit un input extrême, la multiplication pourrait déborder sans lever d'erreur, corrompant l'état du contrat sans que personne ne s'en rende compte immédiatement. Les audits montrent que 15 à 20 % des vulnérabilités restantes dans les contrats modernes proviennent d'un mauvais usage de ces blocs ou d'interactions complexes entre contrats externes.

Figure humaine face à un bloc unchecked instable et dangereux dans une ambiance sombre.

Audits et outils de détection

Même avec Solidity 0.8+, la sécurité ne doit jamais être prise pour acquise. Des outils statiques comme Slither ou Mythril analysent le bytecode et le code source pour détecter les patterns à risque. Ils sont excellents pour trouver les usages suspects de unchecked ou les opérations arithmétiques sur des variables contrôlées par l'utilisateur.

Il est important de noter que ces outils produisent parfois des faux positifs. Un audit manuel reste indispensable. Les auditeurs professionnels cherchent spécifiquement les cas où les bornes mathématiques sont ambiguës. Par exemple, une division entière en Solidity tronque toujours le résultat. Si vous divisez 5 par 2, vous obtenez 2, pas 2.5. Bien que ce ne soit pas un overflow stricto sensu, c'est une perte de précision qui peut mener à des bugs logiques similaires, souvent regroupés sous la même catégorie de risques arithmétiques.

Meilleures pratiques pour 2026

Si vous démarrez un nouveau projet aujourd'hui, voici la marche à suivre :

  • Utilisez Solidity 0.8.0 ou supérieur. C'est la base non négociable.
  • Évitez les types trop petits sauf nécessité absolue. Préférez uint256 pour les montants financiers importants, car la probabilité d'atteindre $2^{256}-1$ est astronomiquement faible.
  • Soyez prudent avec unchecked. N'utilisez ce bloc que si vous pouvez prouver mathématiquement que le débordement est impossible. Documentez pourquoi.
  • Validez les entrées utilisateurs. Ne laissez jamais un utilisateur envoyer un nombre arbitraire sans vérifier qu'il est dans une plage raisonnable avant les calculs.
  • Testez les cas limites. Utilisez des frameworks comme Hardhat ou Foundry pour tester explicitement les additions et soustractions aux bornes maximales et minimales de vos variables.

Les pertes historiques dues aux overflows ont servi de leçon coûteuse. Aujourd'hui, grâce aux améliorations du langage et aux meilleures pratiques, le risque est bien maîtrisé, mais il exige toujours une attention constante de la part des développeurs.

Qu'est-ce qu'un integer overflow exactement ?

Un integer overflow se produit lorsqu'une opération arithmétique produit un résultat supérieur à la valeur maximale que le type de données peut stocker. Dans Solidity, le résultat "boucle" alors vers la valeur minimale (par exemple, 255 + 1 devient 0 pour un uint8).

Dois-je encore utiliser SafeMath avec Solidity 0.8 ?

Non, pour la plupart des projets nouveaux. Solidity 0.8 inclut nativement la protection contre les overflows. SafeMath est désormais considéré comme redondant et inutilement coûteux en gas, sauf dans des cas très spécifiques d'optimisation avancée.

Comment fonctionne le mot-clé unchecked ?

Le bloc unchecked { ... } désactive la vérification automatique des overflows pour les opérations arithmétiques à l'intérieur. Cela réduit la consommation de gas, mais expose le contrat aux erreurs de wrap-around si le développeur fait une erreur de calcul.

Pourquoi les divisions posent-elles aussi problème ?

Bien que distinct de l'overflow, la division entière en Solidity tronque les décimales (ex: 5/2 = 2). Cette perte de précision peut causer des bugs logiques graves dans les calculs financiers, souvent confondus ou traités simultanément avec les problèmes d'overflow lors des audits.

Les contrats écrits avant 2021 sont-ils tous vulnérables ?

Pas nécessairement. Beaucoup utilisaient déjà des bibliothèques comme SafeMath ou avaient implémenté des vérifications manuelles. Cependant, ils nécessitent une revue de code attentive car ils ne bénéficient pas de la protection native du compilateur moderne.