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.
// 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:
// 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:
round up in this example).Real-world protocols should pair such logic with formal reasoning and invariants (e.g., through formal verification or property-based tests).
mint() function—integer division rounding down—caused a mismatch between recorded and actual values. A withdrawal that should have burned 1.5 tokens rounded down to 1.0, allowing attackers to artificially inflate the lending_accumulator via repeated deposits/withdrawals and drain ~$9.5M.
totalAssets vs. sum of user balances, or share/value consistency after operations.