BeginnerHigh Severity

Integer Overflow & Underflow

6 min readUpdated Dec 8, 2024By Hexific Team

What is Integer Overflow/Underflow?

Integer overflow occurs when an arithmetic operation produces a value that exceeds the maximum value the data type can hold. Underflow is the opposite - when a value goes below the minimum (usually zero for unsigned integers).

📊 uint256 Limits

Minimum:0
Maximum:2²⁵⁶ - 1

That's 115,792,089,237,316,195,423,570,985,008,687,907,853,269,984,665,640,564,039,457,584,007,913,129,639,935

Good News!

Since Solidity 0.8.0, arithmetic operations revert on overflow/underflow by default. However, you still need to be careful with unchecked blocks and older contracts.

How Overflow/Underflow Works

🔄 Overflow Example (uint8)

uint8 max value: 255

255 + 1 = 0 (wraps around!)

255 + 10 = 9

🔄 Underflow Example (uint8)

uint8 min value: 0

0 - 1 = 255 (wraps around!)

5 - 10 = 251

OverflowUnderflow.sol
1// In Solidity < 0.8.0, this would NOT revert
2contract OverflowExample {
3    // uint8 can hold values 0-255
4    uint8 public value = 255;
5    
6    function increment() public {
7        value += 1;  // Before 0.8: value becomes 0
8                     // After 0.8: REVERTS!
9    }
10}
11
12contract UnderflowExample {
13    uint256 public balance = 100;
14    
15    function withdraw(uint256 amount) public {
16        // Before 0.8: If amount > balance, result wraps to huge number
17        // e.g., 100 - 200 = 2^256 - 100 (a massive number!)
18        balance -= amount;
19    }
20}

Real Attack: BEC Token (2018)

The Beauty Chain (BEC) token had a critical overflow vulnerability in its batch transfer function:

BECVulnerability.sol
1// The vulnerable batchTransfer function (simplified)
2function batchTransfer(address[] _receivers, uint256 _value) public {
3    uint256 cnt = _receivers.length;
4    
5    // VULNERABLE: This multiplication can overflow!
6    uint256 amount = uint256(cnt) * _value;
7    
8    // If overflow happens, 'amount' becomes tiny
9    // but each receiver still gets full '_value'
10    require(_value > 0 && balances[msg.sender] >= amount);
11    
12    balances[msg.sender] -= amount;  // Deducts tiny overflowed amount
13    
14    for (uint256 i = 0; i < cnt; i++) {
15        balances[_receivers[i]] += _value;  // Adds full _value to each!
16    }
17}
18
19// Attack:
20// _receivers.length = 2
21// _value = 2^255 (half of max uint256)
22// amount = 2 * 2^255 = 2^256 = 0 (overflow!)
23// Result: Sender loses 0, receivers gain 2^255 each!
🚨

Impact

This vulnerability allowed attackers to mint tokens out of thin air, crashing the token value to zero. The token was delisted from exchanges.

Solidity 0.8+ Protection

Starting with Solidity 0.8.0, all arithmetic operations check for overflow/underflow and revert if detected. This is built into the compiler.

Solidity08.sol
1// Solidity 0.8.0+
2contract SafeByDefault {
3    uint256 public value = type(uint256).max;  // Maximum value
4    
5    function tryOverflow() public {
6        value += 1;  // REVERTS with Panic(0x11)
7    }
8}
9
10// If you NEED unchecked math (for gas savings), be explicit:
11contract UncheckedMath {
12    function riskyIncrement(uint256 x) public pure returns (uint256) {
13        // WARNING: This can overflow!
14        unchecked {
15            return x + 1;  // Does NOT revert on overflow
16        }
17    }
18    
19    // Common safe use case: loop counters
20    function safeLoop(uint256[] calldata data) public pure returns (uint256 sum) {
21        for (uint256 i = 0; i < data.length;) {
22            sum += data[i];
23            unchecked {
24                ++i;  // Safe: i can't overflow before running out of gas
25            }
26        }
27    }
28}

When You're Still Vulnerable

⚠️ unchecked Blocks

Code inside unchecked { } bypasses overflow protection.

⚠️ Older Contracts

Contracts compiled with Solidity <0.8.0 are still vulnerable.

⚠️ Type Casting

Downcasting (uint256 to uint8) can silently truncate values.

⚠️ Assembly

Inline assembly bypasses all Solidity safety checks.

CastingVulnerability.sol
1// Type casting vulnerability (even in Solidity 0.8+)
2contract CastingIssue {
3    function unsafeCast(uint256 bigNumber) public pure returns (uint8) {
4        // WARNING: This truncates without reverting!
5        // 256 becomes 0, 257 becomes 1, etc.
6        return uint8(bigNumber);
7    }
8    
9    // Safe alternative
10    function safeCast(uint256 bigNumber) public pure returns (uint8) {
11        require(bigNumber <= type(uint8).max, "Value too large");
12        return uint8(bigNumber);
13    }
14}
15
16// OpenZeppelin SafeCast library
17import "@openzeppelin/contracts/utils/math/SafeCast.sol";
18
19contract WithSafeCast {
20    using SafeCast for uint256;
21    
22    function safeCastExample(uint256 value) public pure returns (uint8) {
23        return value.toUint8();  // Reverts if value > 255
24    }
25}

Best Practices

✅ Use Solidity 0.8.0+

Always use the latest Solidity version to get built-in overflow protection.

✅ Use SafeCast for Downcasting

OpenZeppelin's SafeCast library provides safe type conversions.

✅ Review unchecked Blocks Carefully

Only use unchecked when you've mathematically proven overflow is impossible.

✅ Validate Inputs

Check that multiplication/addition results are reasonable before using them.