# Débordement et Sous-débordement d'Entiers en Solidity — Protection des Calculs
Les bugs de débordement d'entiers ont historiquement été l'une des vulnérabilités les plus coûteuses en développement de smart contracts. Bien que Solidity 0.8.0 ait introduit une protection par défaut, comprendre comment et pourquoi ces vulnérabilités se produisent reste essentiel pour tout développeur.
Qu'est-ce qu'un Débordement/Sous-débordement d'Entiers ?
En Solidity, les entiers ont des tailles fixes (uint8, uint256, etc.). Quand un calcul dépasse la valeur maximale ou descend en dessous de la valeur minimale, il "boucle" vers l'autre extrémité.
Débordement (Overflow)
uint8 public maxValue = 255; // Valeur max pour uint8
function overflow() public {
maxValue = maxValue + 1; // Solidity < 0.8.0 : boucle vers 0
}
Avant Solidity 0.8.0 : 255 + 1 = 0
Avec Solidity 0.8.0+ : ❌ Transaction revert
Sous-débordement (Underflow)
uint8 public minValue = 0;
function underflow() public {
minValue = minValue - 1; // Solidity < 0.8.0 : boucle vers 255
}
Avant Solidity 0.8.0 : 0 - 1 = 255
Avec Solidity 0.8.0+ : ❌ Transaction revert
Cas Réel : Exploit BeautyChain (BEC)
En avril 2018, le token BeautyChain (BEC) a subi un débordement catastrophique.
function batchTransfer(address[] _receivers, uint256 _value) public {
uint256 cnt = _receivers.length;
uint256 amount = uint256(cnt) * _value; // ❌ DÉBORDEMENT ICI
require(balances[msg.sender] >= amount);
for (uint256 i = 0; i < cnt; i++) {
balances[_receivers[i]] += _value;
}
balances[msg.sender] -= amount;
}
L'attaque :
// cnt = 2
// _value = 2^255
// amount = 2 * 2^255 = 2^256 (déborde vers 0)
L'attaquant a créé des tokens à partir de rien car amount = 0 a passé le require, mais le transfert réel a transféré 2^255 tokens à chaque destinataire.
Impact : Le trading du token BEC a été suspendu, valeur de marché anéantie.
Protection dans Solidity 0.8.0+
Depuis Solidity 0.8.0, toutes les opérations arithmétiques revert automatiquement en cas de débordement/sous-débordement.
// Solidity ^0.8.0
uint8 x = 255;
x = x + 1; // ❌ Revert : "Arithmetic operation underflowed or overflowed"
Mode Unchecked
Si vous avez besoin d'arithmétique bouclante pour l'optimisation, utilisez unchecked {}.
function safeIncrement(uint8 x) public pure returns (uint8) {
unchecked {
return x + 1; // Boucle vers 0 si x = 255
}
}
⚠️ N'utilisez unchecked que si vous avez vérifié que le débordement est impossible ou intentionnel.
Cas d'usage légitime :
for (uint256 i = 0; i < array.length;) {
// ...
unchecked { ++i; } // Sûr car i ne dépassera jamais uint256.max
}
Protection dans Solidity < 0.8.0 : SafeMath
Avant 0.8.0, les développeurs utilisaient la bibliothèque SafeMath d'OpenZeppelin.
import "@openzeppelin/contracts/utils/math/SafeMath.sol";
contract OldToken {
using SafeMath for uint256;
mapping(address => uint256) public balances;
function transfer(address to, uint256 amount) public {
balances[msg.sender] = balances[msg.sender].sub(amount); // Revert en cas de sous-débordement
balances[to] = balances[to].add(amount); // Revert en cas de débordement
}
}
SafeMath ajoute des vérifications à chaque opération :
function add(uint256 a, uint256 b) internal pure returns (uint256) {
uint256 c = a + b;
require(c >= a, "SafeMath: addition overflow");
return c;
}
function sub(uint256 a, uint256 b) internal pure returns (uint256) {
require(b <= a, "SafeMath: subtraction underflow");
return a - b;
}
Depuis Solidity 0.8.0, SafeMath n'est plus nécessaire car la protection est intégrée.
Types d'Entiers en Solidity
| Type | Taille | Plage (unsigned) | Plage (signed) |
|------|--------|------------------|----------------|
| uint8 / int8 | 8 bits | 0 à 255 | -128 à 127 |
| uint16 / int16 | 16 bits | 0 à 65,535 | -32,768 à 32,767 |
| uint32 / int32 | 32 bits | 0 à 4,294,967,295 | -2,147,483,648 à 2,147,483,647 |
| uint256 / int256 | 256 bits | 0 à 2^256-1 | -2^255 à 2^255-1 |
uint est un alias pour uint256.
Vulnérabilités Communes
1. Multiplication Avant Division
function calculateReward(uint256 amount) public pure returns (uint256) {
return (amount * REWARD_MULTIPLIER) / PRECISION; // ❌ Peut déborder
}
Solution :
function calculateReward(uint256 amount) public pure returns (uint256) {
return (amount / PRECISION) * REWARD_MULTIPLIER; // ✅ Diviser d'abord
}
Ou utilisez des mathématiques de point fixe (bibliothèques comme PRBMath).
2. Cast Non Sécurisé vers un Type Plus Petit
function unsafeCast(uint256 x) public pure returns (uint8) {
return uint8(x); // ❌ Tronque silencieusement
}
// unsafeCast(256) = 0
// unsafeCast(257) = 1
Solution :
function safeCast(uint256 x) public pure returns (uint8) {
require(x <= type(uint8).max, "Value too large");
return uint8(x);
}
Ou utilisez la bibliothèque SafeCast d'OpenZeppelin :
import "@openzeppelin/contracts/utils/math/SafeCast.sol";
function safeCast(uint256 x) public pure returns (uint8) {
return SafeCast.toUint8(x); // Revert si > 255
}
3. Soustraction d'Horodatages
uint256 public lastUpdate;
function timeSinceUpdate() public view returns (uint256) {
return block.timestamp - lastUpdate; // ❌ Sous-déborde si lastUpdate > block.timestamp
}
Solution :
function timeSinceUpdate() public view returns (uint256) {
if (block.timestamp < lastUpdate) return 0;
return block.timestamp - lastUpdate;
}
Tests pour Débordement/Sous-débordement
Avec Foundry
// test/Overflow.t.sol
import "forge-std/Test.sol";
contract OverflowTest is Test {
function testOverflowReverts() public {
uint8 x = 255;
vm.expectRevert(); // Attend un revert
x = x + 1;
}
function testUncheckedOverflow() public {
uint8 x = 255;
unchecked {
x = x + 1;
}
assertEq(x, 0); // Boucle vers 0
}
}
Avec Hardhat
const { expect } = require("chai");
describe("Overflow", function () {
it("Should revert on overflow", async function () {
const Contract = await ethers.getContractFactory("MyContract");
const contract = await Contract.deploy();
await expect(
contract.overflow()
).to.be.revertedWith("Arithmetic operation overflowed");
});
});
Bonnes Pratiques
✅ Utilisez Solidity 0.8.0 ou supérieur — protection automatique
✅ Évitez unchecked sauf cas d'usage prouvé — documentez pourquoi c'est sûr
✅ Utilisez SafeCast pour les conversions de type
✅ Divisez avant de multiplier lorsque c'est possible
✅ Testez les valeurs limites (0, max, max-1, max+1)
✅ Utilisez des bibliothèques de point fixe pour les mathématiques de précision (PRBMath)
✅ Auditez le code legacy < 0.8.0 — ajoutez SafeMath si nécessaire
Outils de Détection
- Slither : Détecte les opérations arithmétiques non vérifiées
- Mythril : Analyse symbolique pour débordements
- Echidna : Fuzzing pour trouver des valeurs d'entrée déclenchant des débordements
slither contracts/MyContract.sol --detect unchecked-lowlevel
Conclusion
Bien que Solidity 0.8.0+ ait largement éliminé les débordements accidentels, comprendre cette vulnérabilité reste crucial pour :
- Auditer du code legacy
- Utiliser
uncheckeden toute sécurité
- Écrire des conversions de type sécurisées
- Comprendre les mathématiques de point fixe
Chez Solingo, vous pratiquez la détection de débordements à travers des exercices interactifs basés sur des exploits réels comme BeautyChain et PoWHC.