📋 What Is It?
Integer Overflow and Underflow occur when an arithmetic operation produces a result outside the range a fixed-size integer can hold, so the value wraps around modulo 2ⁿ instead of being the mathematically correct number. On the EVM every integer is fixed-width-uint256 holds 0 to 2²⁵⁶−1, uint8 holds 0 to 255-with no arbitrary-precision fallback. Underflow subtracts past zero (uint8(0) - 1 wraps to 255), so balance - amount can mint a near-infinite balance. Overflow adds or multiplies past the maximum (uint8(255) + 1 wraps to 0), so a supply counter or cap check can silently reset and let an attacker slip past a limit.
⚠️ Common Exploits
- Balance underflow:
balance - amountwhereamount > balancewraps to ~2²⁵⁶ - mint from nothing - Multiplication overflow:
count * amountwraps a batch total small so a balance check passes (batchOverflow class) - Supply / cap overflow: overflowing total slips under a
require(total <= CAP)check - Downcast truncation:
uint64(amount)silently drops high bits - never checked, any version - unchecked block: gas optimisation re-enables wrapping on attacker-controlled input
- Inline assembly: Yul
add/mul/subnever check for overflow at any version - Multiply-before-divide: intermediate product wraps (pre-0.8) or reverts as a DoS (0.8+)
🔴 Attack Flow
↓
2. Model the wrap - choose an input past 0 or past 2ⁿ−1
↓
3. Call the function with the boundary value in one transaction
↓
4. Balance/total wraps; invariant the code trusted is now false
↓
5. IMPACT: Unlimited minting, cap bypass, pool drained!
❌ Vulnerable Code
✅ Secure Code
✓ Prevention Checklist
- Compile with Solidity ≥ 0.8.0 so checked arithmetic is the default
- Pin the pragma to a fixed, recent compiler version
- Wrap every add/sub/mul in SafeMath for legacy <0.8 code
- Treat every
unchecked { }block as a proof obligation - Never
uncheckedraw attacker input - only bounded quantities - Range-check or use OpenZeppelin
SafeCastbefore any downcast - Add manual overflow checks to any inline assembly arithmetic
- Validate and bound inputs so values can't reach the extremes
- Order operations to avoid intermediate overflow; use
Math.mulDiv - Add boundary tests at
0andtype(uint256).max - Fuzz arithmetic invariants with Foundry / Echidna
- Make "is this arithmetic protected?" a review line item
🔍 Detection & Tools
Analysis Tools:
Secure Libraries:
How to Test:
- Run Slither to flag overflow-prone patterns, dangerous casts, and risky
unchecked - Run Mythril / MythX symbolic execution for arithmetic edge cases
- Fuzz invariants in Foundry: e.g. transfers never change total supply
- Assert Echidna properties: sum of balances == total supply; no mint past cap
- Add explicit boundary tests: adding to
type(uint256).maxmust revert - Push inputs to
0and the type maximum that unit tests never reach
🌍 Real-World Incident Classes
- batchTransfer overflow class (2018): ERC-20 tokens computed
amount * countand checked it against the sender's balance. On a pre-0.8 compiler with no SafeMath, a craftedamountoverflowed the product to a tiny value, so the balance check passed while each recipient was credited the full enormous amount - minting far beyond supply. Widely referred to as the batchOverflow class; it affected multiple tokens and forced exchanges to suspend deposits and trading. - proxyOverflow ERC-20 class (2018): a related family exposed transfer paths where overflow in amount/allowance arithmetic let attackers credit balances the supply accounting never authorised - again prompting exchanges to halt trading of affected tokens.
- Underflow-based balance exploits: a recurring pre-0.8 pattern where a
transferorburnsubtracts an attacker-controlled amount without guaranteeing the balance is large enough. Subtracting past zero wraps the balance to a near-maximum value, effectively minting unlimited supply.
Note: these are described as classes of incident. The durable lesson is the mechanism - unchecked fixed-width arithmetic in value-bearing bookkeeping - not any single token's exact numbers. This is why SafeMath became ubiquitous and why checked arithmetic was baked into Solidity at 0.8.0.
📌 Quick Tips
- DO NOT assume 0.8 protects casts,
unchecked, or assembly - DO NOT
uncheckedraw attacker-controlled input - DO NOT downcast a wide value without a range check
- DO compile with Solidity ≥ 0.8 (or SafeMath below it)
- DO use SafeCast for narrowing conversions
- DO check caps before mutating, and bound inputs
- DO fuzz the extremes with Foundry / Echidna