SC07:2026 - Arithmetic Errors (Rounding & Precision)

Description

Arithmetic errors (rounding and precision loss) describe any situation where a smart contract performs integer-based calculations that produce incorrect or exploitable results due to truncation, scaling, or unit conversion. Smart contracts are limited to integer arithmetic; any division, fixed-point scaling, or conversion between units can lose precision, introduce asymmetric rounding, or—when combined with unchecked blocks or non-EVM semantics—cause overflow/underflow (see SC09).

This affects all contract types that compute numeric values: DeFi (share minting/burning, LP tokens, interest accrual, swap outputs, AMM invariant updates), yield vaults and ERC-4626 (asset/share conversions), rebasing tokens, reward distribution, and NFT/token economics. On non-EVM chains (e.g., Move, Rust-based), integer semantics and available precision differ; similar risks apply wherever arithmetic drives economic outcomes.

Few areas to focus on:

Attackers exploit:

When combined with flash loans (SC04) or business logic flaws (SC02), arithmetic errors can be amplified into protocol-draining exploits.

Example (Vulnerable Share Calculation)

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

contract VulnerableShares {
    uint256 public totalAssets;
    uint256 public totalShares;

    mapping(address => uint256) public balanceOf;

    function deposit(uint256 assets) external {
        require(assets > 0, "zero");

        uint256 shares;
        if (totalShares == 0) {
            shares = assets;
        } else {
            // Rounds down and may favor the depositor under certain edge states
            shares = (assets * totalShares) / totalAssets;
        }

        totalAssets += assets;
        totalShares += shares;
        balanceOf[msg.sender] += shares;
    }
}

Issues:

Example (More Robust Arithmetic & Invariant-Aware Design)

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

contract SaferShares {
    uint256 public totalAssets;
    uint256 public totalShares;

    mapping(address => uint256) public balanceOf;

    error ZeroAmount();
    error InvalidState();

    function deposit(uint256 assets) external {
        if (assets == 0) revert ZeroAmount();

        uint256 shares;
        if (totalShares == 0 || totalAssets == 0) {
            // Explicitly define initial conditions
            shares = assets;
        } else {
            // Use rounding strategy intentionally (up or down) and test it
            shares = (assets * totalShares + totalAssets - 1) / totalAssets; // round up
        }

        uint256 newTotalAssets = totalAssets + assets;
        if (newTotalAssets < totalAssets) revert InvalidState(); // overflow guard

        totalAssets = newTotalAssets;
        totalShares += shares;
        balanceOf[msg.sender] += shares;
    }
}

Security Improvements:

Real-world protocols should pair such logic with formal reasoning and invariants (e.g., through formal verification or property-based tests).

2025 Case Studies

Best Practices & Mitigations