Sécurité·10 min de lecture·Par Solingo

Overflow d'Entiers Avant et Après Solidity 0.8 — Ce Qui a Changé

Découvrez comment Solidity 0.8 a révolutionné la sécurité des smart contracts en intégrant des checks d'overflow natifs, et quand utiliser unchecked.

# Overflow d'Entiers Avant et Après Solidity 0.8 — Ce Qui a Changé

L'overflow d'entiers a été l'une des vulnérabilités les plus coûteuses de l'histoire des smart contracts. Puis, en décembre 2020, Solidity 0.8.0 a tout changé. Retour sur cette révolution silencieuse.

L'Ère Pré-0.8 : Le Far West des Overflows

Avant Solidity 0.8, les opérations arithmétiques "wrappaient" silencieusement :

// Solidity 0.7.6

contract UnsafeArithmetic {

uint8 public value = 255;

function increment() public {

value = value + 1; // ❌ Becomes 0 (silent overflow!)

}

function decrement() public {

uint8 zero = 0;

zero = zero - 1; // ❌ Becomes 255 (silent underflow!)

}

}

Ce comportement venait de l'EVM elle-même : l'opcode ADD ne revert pas en cas d'overflow, il wrappe modulo 2^256.

Le Hack DAO 2016 : Pas un Overflow, Mais Presque

Bien que le fameux hack du DAO ne soit pas strictement un overflow, il illustre le danger des uint non vérifiés :

// Simplified vulnerable reentrancy + unchecked balance

mapping(address => uint) public balances;

function withdraw(uint amount) public {

// ❌ No underflow check!

balances[msg.sender] -= amount;

msg.sender.call{value: amount}("");

}

Si amount > balances[msg.sender], le solde devenait astronomique (wrap autour de 2^256).

L'Exploit BEC Token 2018 : Le Vrai Overflow

En avril 2018, le token BeautyChain (BEC) a été exploité via un overflow classique :

// Vulnerable code (simplified)

function batchTransfer(address[] _receivers, uint256 _value) public {

uint256 cnt = _receivers.length;

// ❌ VULNERABLE: amount can overflow!

uint256 amount = uint256(cnt) * _value;

require(_value > 0 && balances[msg.sender] >= amount);

balances[msg.sender] -= amount;

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

balances[_receivers[i]] += _value;

}

}

L'exploit :

  • cnt = 2
  • _value = 2^255 (la moitié de uint256)
  • amount = 2 * 2^255 = 2^256 = 0 (overflow!)
  • Le check balances[msg.sender] >= 0 passe toujours
  • L'attaquant crée des tokens à l'infini

Impact : BEC a perdu 100% de sa valeur en quelques heures.

La Solution Pré-0.8 : SafeMath

OpenZeppelin a publié SafeMath, utilisé par 90% des projets :

library SafeMath {

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 overflow");

return a - b;

}

function mul(uint256 a, uint256 b) internal pure returns (uint256) {

if (a == 0) return 0;

uint256 c = a * b;

require(c / a == b, "SafeMath: multiplication overflow");

return c;

}

}

contract SafeToken {

using SafeMath for uint256;

mapping(address => uint256) public balances;

function transfer(address to, uint256 amount) public {

balances[msg.sender] = balances[msg.sender].sub(amount); // ✅ Reverts on underflow

balances[to] = balances[to].add(amount); // ✅ Reverts on overflow

}

}

Problèmes de SafeMath :

  • Verbeux (chaque opération devient un appel de fonction)
  • Coût en gas (+20-50 gas par opération)
  • Oublier .add() une seule fois = vulnérabilité

Solidity 0.8.0 : Checked Arithmetic par Défaut

Le 16 décembre 2020, Solidity 0.8.0 introduit les checks automatiques :

// Solidity 0.8.0+

contract ModernArithmetic {

uint8 public value = 255;

function increment() public {

value = value + 1; // ✅ Reverts with "Arithmetic operation underflow or overflow"

}

function safeAdd(uint256 a, uint256 b) public pure returns (uint256) {

return a + b; // ✅ Automatic overflow check

}

}

Le compilateur insère automatiquement des checks après chaque opération arithmétique.

Bytecode Comparison

Regardons le bytecode généré :

// Solidity 0.7.6 (no SafeMath)

function add(uint a, uint b) public pure returns (uint) {

return a + b;

}

// Bytecode: ADD (1 opcode, 3 gas)

// Solidity 0.7.6 (with SafeMath)

function add(uint a, uint b) public pure returns (uint) {

return a.add(b);

}

// Bytecode: ~15 opcodes, ~50 gas (function call overhead)

// Solidity 0.8.0+

function add(uint a, uint b) public pure returns (uint) {

return a + b;

}

// Bytecode: ADD DUP1 DUP3 LT ISZERO PUSH2 JUMPI INVALID

// (7 opcodes, ~25 gas)

Résultat : 0.8.0+ est 2x plus rapide que SafeMath, mais un peu plus lent que 0.7.6 sans check.

Le Bloc unchecked : Quand Désactiver les Checks

Pour les cas où l'overflow est impossible (ou souhaité), utilisez unchecked :

contract GasOptimized {

// Example: loop counter (can't overflow in practice)

function sumArray(uint256[] memory arr) public pure returns (uint256) {

uint256 sum = 0;

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

sum += arr[i];

unchecked { i++; } // ✅ Save ~25 gas per iteration

}

return sum;

}

// Example: known safe arithmetic

function calculateFee(uint256 amount) public pure returns (uint256) {

unchecked {

// Fee is 0.3%, amount is capped at 1e24

// Max result: 1e24 * 3 / 1000 = 3e21 (safe)

return amount * 3 / 1000;

}

}

}

Gas saved : ~25 gas par opération non-checkée.

Cas Dangereux : Quand unchecked Devient une Vulnérabilité

Attention aux pièges :

contract VulnerableUnchecked {

mapping(address => uint256) public balances;

function withdraw(uint256 amount) public {

unchecked {

// ❌ VULNERABLE: can underflow!

balances[msg.sender] -= amount;

}

payable(msg.sender).transfer(amount);

}

}

Si amount > balances[msg.sender], le solde wrappe à un nombre énorme. L'utilisateur peut ensuite retirer tout l'Ether du contrat.

Règle d'or : n'utilisez unchecked que si vous pouvez prouver mathématiquement qu'un overflow est impossible.

Pattern Sûr : Checks Explicites + Unchecked

Le meilleur des deux mondes :

contract SafeUnchecked {

mapping(address => uint256) public balances;

function withdraw(uint256 amount) public {

require(balances[msg.sender] >= amount, "Insufficient balance");

unchecked {

balances[msg.sender] -= amount; // ✅ Safe: checked above

}

payable(msg.sender).transfer(amount);

}

}

Économie : ~20 gas par transaction (le require suffit, pas besoin du check automatique).

Cas Spéciaux : int256 et les Signed Integers

Les signed integers peuvent overflow dans les deux sens :

contract SignedOverflow {

function overflow() public pure returns (int8) {

int8 max = 127;

return max + 1; // ✅ Reverts in 0.8.0+ (was -128 in 0.7.6)

}

function underflow() public pure returns (int8) {

int8 min = -128;

return min - 1; // ✅ Reverts in 0.8.0+ (was 127 in 0.7.6)

}

}

Solidity 0.8 protège aussi contre ces cas.

Migration 0.7 → 0.8 : Checklist

Si vous migrez un projet existant :

  • Retirez SafeMath (sauf si vous utilisez des div/mod, voir ci-dessous)
  • Cherchez les unchecked implicites (ex: compteurs de loop)
  • Testez intensivement : certains contrats comptaient sur l'overflow (hash functions, PRNGs)
  • Vérifiez les divisions : 0.8 ne check PAS la division par zéro pour / et % (par design)
  • // ⚠️ Division by zero still NOT checked!
    

    function divide(uint256 a, uint256 b) public pure returns (uint256) {

    return a / b; // Reverts, but with generic "Division or modulo by zero"

    }

    // Better: explicit check

    function safeDivide(uint256 a, uint256 b) public pure returns (uint256) {

    require(b > 0, "Division by zero");

    return a / b;

    }

    Impact sur l'Écosystème

    Depuis 0.8.0 (décembre 2020) :

    • Baisse de 95% des exploits liés aux overflows
    • Adoption de 0.8+ par 80% des nouveaux projets (source : Etherscan verified contracts)
    • OpenZeppelin 5.0 a retiré SafeMath de son export par défaut

    Les 5% d'exploits restants viennent de :

    • Contrats legacy 0.7.6 non migrés
    • Utilisation incorrecte de unchecked
    • Overflows dans des librairies assembly

    Conclusion : Une Révolution Silencieuse

    Solidity 0.8 a éliminé l'une des classes de bugs les plus dangereuses, sans que les développeurs aient à changer leur code. C'est un exemple rare de "secure by default" qui fonctionne.

    Règles en 2026 :

    • ✅ Utilisez toujours Solidity 0.8.0+
    • ✅ Profitez des checks automatiques gratuits
    • ✅ Utilisez unchecked uniquement avec preuve mathématique
    • ❌ N'importez plus SafeMath (sauf pour div/mod avec messages custom)

    L'ère des overflows silencieux est révolue. Bonne débarras.

    Prêt à mettre en pratique ?

    Applique ces concepts avec des exercices interactifs sur Solingo.

    Commencer gratuitement