Skip to main content
Sigvex

Integer Overflow Remediation

How to eliminate integer overflow and underflow vulnerabilities in Solidity by using Solidity 0.8+ checked arithmetic and safe unchecked block practices.

Integer Overflow Remediation

Overview

Related Detector: Integer Overflow

Integer overflow and underflow occur when arithmetic exceeds the bounds of the integer type. In Solidity versions before 0.8.0, this silently wraps around. In Solidity 0.8+, arithmetic reverts on overflow — except inside unchecked {} blocks. The primary fix is to upgrade to Solidity 0.8+ and avoid unchecked blocks on user-controlled values.

Before (Vulnerable — Pre-0.8 Solidity)

pragma solidity ^0.6.0;

contract VulnerableToken {
    mapping(address => uint256) balances;

    // VULNERABLE: overflow wraps silently — balances[sender] - amount underflows
    // to 2^256 - 1 if amount > balance
    function transfer(address to, uint256 amount) external {
        balances[msg.sender] -= amount; // Underflow!
        balances[to] += amount;
    }
}

After (Fixed — Solidity 0.8+)

pragma solidity ^0.8.0;

contract SafeToken {
    mapping(address => uint256) balances;

    // FIXED: Solidity 0.8+ automatically reverts on underflow
    function transfer(address to, uint256 amount) external {
        require(balances[msg.sender] >= amount, "Insufficient balance");
        balances[msg.sender] -= amount; // Safe: protected by require above + 0.8+ check
        balances[to] += amount;
    }
}

Alternative Mitigations

1. Safe unchecked Block Usage (Gas Optimization)

When you need gas efficiency and overflow is provably impossible, use unchecked with explicit documentation:

pragma solidity ^0.8.0;

contract OptimizedContract {
    function sumArray(uint256[] calldata values) external pure returns (uint256 total) {
        // SAFE unchecked: loop counter cannot overflow uint256 within block gas limit
        for (uint256 i = 0; i < values.length; ) {
            total += values[i]; // This is NOT safe unchecked without bounds validation
            unchecked { ++i; }  // SAFE: i < values.length guarantees no overflow
        }
    }

    function subtractWithCheck(uint256 a, uint256 b) external pure returns (uint256) {
        // SAFE: explicit comparison before unchecked subtraction
        require(a >= b, "Underflow would occur");
        unchecked { return a - b; } // Safe because a >= b is verified
    }
}

2. OpenZeppelin’s SafeMath (Legacy — for Pre-0.8 Codebases)

For contracts that must support Solidity < 0.8:

pragma solidity ^0.6.0;

import "@audited/math/SafeMath.sol";

contract SafeToken {
    using SafeMath for uint256;

    mapping(address => uint256) balances;

    function transfer(address to, uint256 amount) external {
        balances[msg.sender] = balances[msg.sender].sub(amount, "Insufficient balance");
        balances[to] = balances[to].add(amount);
    }
}

3. Fixed-Point Arithmetic for Financial Calculations

For fractional amounts (interest rates, fees), use fixed-point with explicit scaling:

pragma solidity ^0.8.0;

contract FixedPointFee {
    uint256 private constant PRECISION = 1e18;
    uint256 private constant FEE_RATE = 3e15; // 0.3% = 3/1000 = 3e15 / 1e18

    function calculateFee(uint256 amount) public pure returns (uint256) {
        // Multiply then divide — avoids precision loss from division-first
        // The multiplication is safe because amount is bounded by token supply
        return (amount * FEE_RATE) / PRECISION;
    }
}

4. Use OpenZeppelin’s Math for Checked Multiplication

For products that might overflow before being divided:

import "@audited/utils/math/Math.sol";

// Safe for amounts up to 2^128 (prevents mulDiv overflow)
uint256 result = Math.mulDiv(a, b, denominator);

Common Mistakes

Mistake 1: Unchecked Block on User-Controlled Input

// WRONG: unchecked on user-supplied amount
function deposit(uint256 amount) external {
    unchecked {
        // If amount = 2^256 - balance, totalDeposits wraps to 0
        totalDeposits += amount; // VULNERABLE inside unchecked
    }
}

Mistake 2: Type Casting Truncation

// WRONG: casting larger type to smaller silently truncates
uint256 largeAmount = type(uint256).max;
uint128 truncated = uint128(largeAmount); // Silently becomes type(uint128).max

// FIXED: check before casting
require(largeAmount <= type(uint128).max, "Value too large");
uint128 safe = uint128(largeAmount);

Mistake 3: Multiplication Before Addition in Compound Expressions

// WRONG: intermediate multiplication can overflow even if the final result fits
uint256 result = a * b + c * d; // a * b might overflow before addition

// FIXED: use mulDiv or break into safe steps
uint256 ab = Math.mulDiv(a, b, 1); // Checked multiply
uint256 cd = Math.mulDiv(c, d, 1);
uint256 result = ab + cd;

References