Opcode Summary

PropertyValue
Opcode0x01
MnemonicADD
Gas3
Stack Inputa, b
Stack Output(a + b) % 2^256
BehaviorUnsigned 256-bit addition. Result wraps modulo 2^256 on overflow. No overflow flag or exception.

Threat Surface

ADD is the most fundamental arithmetic opcode and one of the most historically exploited. Its threat surface centers on a single critical property: the EVM has no overflow detection. When a + b >= 2^256, the result silently wraps to (a + b) - 2^256. There is no status flag, no exception, no revert. The caller has no way to know overflow occurred unless they explicitly check for it.

This behavior, combined with Solidity’s historical lack of built-in overflow protection (pre-0.8.0), made integer overflow the single largest class of smart contract vulnerabilities from 2016 to 2020, responsible for hundreds of millions in losses.


Smart Contract Threats

T1: Integer Overflow / Wrapping Arithmetic (Critical)

The EVM’s ADD wraps silently: type(uint256).max + 1 == 0. Any contract that adds user-controlled values without overflow checking is vulnerable. Common patterns:

  • Balance accumulation: balances[attacker] += amount where amount is chosen to wrap the balance
  • Total supply manipulation: totalSupply += mintAmount that wraps to a small value
  • Batch operations: total = count * valuePerItem (uses MUL but often combined with ADD in loops)
  • Fee calculations: amountWithFee = amount + fee where attacker controls amount

Pre-Solidity 0.8.0: No automatic overflow check. Developers had to use SafeMath or manual checks.

Solidity 0.8.0+: Automatic revert on overflow via compiler-inserted checks. However, developers can explicitly opt out with unchecked { } blocks for gas savings, reintroducing the vulnerability.

T2: Unchecked Blocks in Modern Solidity (High)

Solidity >= 0.8.0 provides unchecked { } blocks that disable overflow/underflow checks for gas optimization. This is commonly used in:

  • Loop counters (unchecked { ++i; } in for-loops)
  • Known-safe arithmetic where the developer “proves” overflow is impossible
  • Gas-optimized DeFi math (AMM curves, interest calculations)

The danger: developer assumptions about safety may be wrong, especially after code modifications, or edge cases may exist that weren’t considered. The unchecked block silently removes the safety net.

// Common "safe" pattern -- but is it always safe?
unchecked {
    uint256 result = a + b;  // Developer assumes a + b < 2^256
    // What if a future code change makes 'a' or 'b' user-controlled?
}

T3: Precision Loss and Operation Ordering (Medium)

While not an overflow per se, the order of ADD and DIV operations affects precision in fixed-point arithmetic:

// Loss of precision: division truncates before addition
uint256 bad = (a / PRECISION) + (b / PRECISION);
 
// Better: add first, then divide
uint256 better = (a + b) / PRECISION;  // But now a + b might overflow

DeFi protocols performing token price calculations, interest accrual, and liquidity math must balance overflow risk against precision loss. Getting this wrong leads to exploitable rounding errors.

T4: Signed Arithmetic Misinterpretation (Medium)

ADD treats all values as unsigned 256-bit integers. When used with values that represent signed numbers (int256 in Solidity, which uses two’s complement), the wrapping behavior can cause unexpected sign flips:

  • Adding a large positive number to a negative value (represented as a large uint) can produce incorrect results if the developer doesn’t account for two’s complement semantics.
  • Solidity’s int256 type handles this at the compiler level, but inline assembly or raw opcode usage can bypass these protections.

T5: Accumulator/Counter Overflow in Long-Running Contracts (Low)

Contracts that accumulate values over time (total fees collected, cumulative interest, reward counters) can theoretically overflow uint256 after sufficient time. While 2^256 is astronomically large, contracts using smaller types (uint128, uint96, uint64) for gas packing are more vulnerable:

struct Accumulator {
    uint96 totalRewards;    // Max ~79 billion with 18 decimals -- can overflow
    uint64 lastUpdateTime;  // Overflows in year 2554
    uint96 rewardRate;
}

Protocol-Level Threats

P1: No DoS Vector (Low)

ADD costs a fixed 3 gas with no dynamic component. It cannot be used for gas griefing. It operates purely on the stack with no memory or storage access.

P2: Consensus Safety (Low)

ADD is trivially deterministic: a + b mod 2^256 is unambiguous. All EVM client implementations agree on its behavior. No known consensus divergence has occurred due to ADD.

P3: No State Impact (None)

ADD modifies only the stack. It cannot cause state bloat, storage writes, or memory expansion.

P4: Compiler Optimization Divergence (Low)

Different Solidity compiler versions emit different overflow-checking patterns around ADD. A contract compiled with solc 0.7.x has no checks; the same source compiled with solc 0.8.x adds JUMPI-based overflow detection. This means the same Solidity source can have different security properties depending on compiler version — relevant for auditors reviewing bytecode.


Edge Cases

Edge CaseBehaviorSecurity Implication
MAX_UINT256 + 1Returns 0Classic overflow; balance/supply wraps to zero
MAX_UINT256 + MAX_UINT256Returns MAX_UINT256 - 1Double-max wraps to near-max
0 + 0Returns 0Safe; no issue
a + 0Returns aIdentity; no issue
Negative numbers (two’s complement)Wraps as unsignedSign confusion if treating result as int256
a + b where b = 2^256 - aReturns 0Attacker can zero out any accumulator
Very large values near 2^255Wraps into “negative” range if interpreted as int256Sign bit flips

Real-World Exploits

Exploit 1: BeautyChain (BEC) “batchOverflow” — Token Value Destroyed (April 2018)

CVE: CVE-2018-10299

Root cause: Integer overflow in batchTransfer() function. The vulnerable line computed uint256 amount = uint256(cnt) * _value using MUL, but the pattern is directly analogous to ADD-based overflow and the overflow allowed bypassing an ADD-based balance check (balances[msg.sender] >= amount).

Details: The batchTransfer function multiplied the number of recipients (cnt) by the value per recipient (_value). An attacker passed _value = 0x8000...0000 (2^255) with 2 recipients, causing the multiplication to overflow to 0. The subsequent balance check require(balances[msg.sender] >= 0) trivially passed. The function then added _value to each recipient’s balance:

// Vulnerable code (simplified)
uint256 amount = uint256(cnt) * _value;        // Overflows to 0
require(balances[msg.sender] >= amount);        // 0 >= 0, passes
balances[msg.sender] = balances[msg.sender].sub(amount);  // Subtracts 0
for (uint i = 0; i < cnt; i++) {
    balances[_receivers[i]] = balances[_receivers[i]].add(_value);  // Adds 2^255 each
}

Impact: Attacker received ~1.16 * 10^59 BEC tokens (effectively infinite). All ERC-20 token trading was suspended across major exchanges (OKEx, Poloniex). Dozens of similar tokens were found vulnerable. CVE was assigned to the vulnerability class.

ADD’s role: The actual balance inflation happened via ADD (balances[receiver] += _value), adding an astronomically large value that was “authorized” by the overflowed MUL check.

References:


Exploit 2: SmartMesh (SMT) “proxyOverflow” — 65 Septillion Fake Tokens (April 2018)

CVE: CVE-2018-10376

Root cause: Integer overflow in transferProxy() function where _fee + _value overflowed, bypassing balance checks.

Details: The transferProxy function accepted _value and _fee parameters. The contract checked require(balances[_from] >= _fee + _value). An attacker chose _fee and _value such that _fee + _value overflowed to a small number, passing the balance check. The contract then individually added _value to the recipient and _fee to the fee collector — both enormous numbers.

// Vulnerable code pattern
require(balances[_from] >= _fee + _value);  // _fee + _value overflows to small number
balances[_to] += _value;                     // Adds enormous value
balances[msg.sender] += _fee;                // Adds enormous value
balances[_from] -= _fee + _value;            // Subtracts small overflowed value

Impact: ~65.1 septillion (6.5 * 10^25) counterfeit SMT tokens created. 16.6 million traded on exchanges before discovery. Trading suspended on Huobi, OKEx, Gate.io.

ADD’s role: The overflow occurred directly in the ADD operation (_fee + _value), and the inflated balances were created via ADD to recipient balances.

References:


Exploit 3: Truebit Protocol — $26.6M Stolen (January 2026)

Root cause: Integer overflow in getPurchasePrice() on a contract compiled with Solidity 0.6.10 (pre-0.8.0, no automatic overflow checks).

Details: The Truebit token purchase contract calculated price using arithmetic that could overflow when given crafted token amounts. The getPurchasePrice() function returned 0 ETH for non-zero (extremely large) token quantities. The attacker called buy() with msg.value == 0, received 240 trillion TRU tokens, then sold them for ETH, draining 8,535 ETH ($26.6M) from the contract.

Key facts:

  • Contract deployed in 2021, compiled with Solidity 0.6.10
  • Never underwent a third-party security audit
  • Largely dormant for years before exploitation
  • No flash loans, oracle manipulation, or MEV required — just a simple overflow

ADD’s role: The price calculation involved addition and multiplication operations that wrapped to zero, allowing free token minting.

References:


Exploit 4: Yearn Finance yETH — $9M Stolen (November 2025)

Root cause: Unsafe arithmetic in the invariant solver’s _calc_supply() function, with rounding-down failures that zeroed critical product terms and underflow that wrapped values to enormous positive integers.

Details: The yETH Weighted Stable Pool used arithmetic without overflow/underflow protection in its bonding curve calculations. Rounding errors in the invariant solver could zero out product terms, and subsequent underflow operations wrapped values to extremely large numbers, allowing an attacker to drain ~0.9M through a non-disabled bootstrap path.

ADD’s role: Underflow (the inverse of overflow) in subtraction combined with accumulation via addition produced incorrect pool invariant values that the attacker exploited.

References:


Attack Scenarios

Scenario A: Classic Balance Overflow (Pre-0.8.0)

// Solidity < 0.8.0, no SafeMath
contract VulnerableToken {
    mapping(address => uint256) public balances;
    
    function transfer(address to, uint256 amount) external {
        require(balances[msg.sender] >= amount);
        balances[msg.sender] -= amount;
        balances[to] += amount;  // If balances[to] is near MAX_UINT256, this wraps to ~0
    }
}

Attack: If balances[to] is already 2^256 - 1 and amount = 1, the recipient’s balance wraps to 0. The attacker loses 1 token; the victim loses their entire balance.

Scenario B: Fee + Value Overflow Bypass

function transferWithFee(address to, uint256 value, uint256 fee) external {
    uint256 total = value + fee;  // Overflow: value + fee wraps to small number
    require(balances[msg.sender] >= total);  // Passes with small 'total'
    balances[msg.sender] -= total;           // Subtracts small amount
    balances[to] += value;                   // Adds enormous value
    balances[feeCollector] += fee;           // Adds enormous value
}

Attack: Choose value and fee such that value + fee > 2^256, wrapping total to nearly 0.

Scenario C: Unchecked Block in Modern Solidity

// Solidity 0.8.x with unchecked
function accumulateRewards(uint256 newReward) internal {
    unchecked {
        totalRewards += newReward;  // Developer assumes this can't overflow
        // But if called millions of times with large values...
    }
}

Scenario D: Operation Order Precision Attack

// AMM price calculation
function getPrice(uint256 reserveA, uint256 reserveB, uint256 amount) external view returns (uint256) {
    // Attacker manipulates reserves so that (reserveA + amount) causes
    // different rounding than expected, extracting value
    return (reserveB * amount) / (reserveA + amount);
}

Mitigations

ThreatMitigationImplementation
T1: Integer overflowUse Solidity >= 0.8.0 (automatic overflow checks)Default behavior; no action needed
T1: Legacy contractsUse OpenZeppelin SafeMath libraryusing SafeMath for uint256; a.add(b)
T2: Unchecked blocksMinimize use; formal verification of safety invariantsRestrict unchecked to provably safe operations (e.g., loop counters)
T2: Unchecked auditFlag all unchecked blocks during security reviewAutomated tooling: Slither, Mythril detect unchecked arithmetic
T3: Precision lossMultiply before dividing; use higher-precision intermediates(a * b) / c instead of (a / c) * b
T3: Rounding attacksRound against the attacker (favor the protocol)Use mulDivUp / mulDivDown from Solmate or OpenZeppelin Math
T4: Signed confusionUse Solidity’s int256 type; avoid raw assembly for signed mathLet the compiler handle two’s complement
T5: Accumulator overflowUse uint256 for accumulators; audit packed struct sizesVerify maximum possible accumulated value fits the type

Compiler/EIP-Based Protections

  • Solidity 0.8.0+ (2020): Automatic revert on overflow/underflow for all arithmetic operations. This single change eliminated the most exploited vulnerability class in smart contract history.
  • SafeMath (OpenZeppelin): Pre-0.8.0 library that wraps ADD/SUB/MUL with overflow checks. Became the de facto standard from 2017-2020.
  • Static analysis tools: Slither, Mythril, and Securify can detect unchecked arithmetic and potential overflow patterns.

Overflow Detection at EVM Level

The standard pattern for detecting ADD overflow at the opcode level:

// Check: did (a + b) overflow?
// If a + b < a, overflow occurred (since b >= 0)
PUSH a
PUSH b
ADD        // result = (a + b) mod 2^256
DUP1       // result, result
PUSH a
LT         // result < a? If yes, overflow

This is exactly what Solidity 0.8.0+ emits after every ADD instruction.


Severity Summary

Threat IDCategorySeverityLikelihoodReal-World Precedent
T1Smart ContractCriticalHigh (pre-0.8) / Low (post-0.8)BEC, SMT, Truebit, hundreds of tokens
T2Smart ContractHighMediumYearn yETH ($9M)
T3Smart ContractMediumMediumVarious DeFi rounding exploits
T4Smart ContractMediumLowAssembly-level bugs
T5Smart ContractLowLowTheoretical for uint256; real for packed types
P1ProtocolLowN/A
P2ProtocolLowN/A

OpcodeRelationship
SUB (0x03)Inverse operation; underflow is the mirror of overflow (same vulnerability class)
MUL (0x02)Multiplication overflow is even more dangerous (larger jumps); often combined with ADD in exploits
ADDMOD (0x08)Addition with modulus — avoids overflow by design but introduces modular arithmetic concerns
EXP (0x0A)Exponentiation can overflow extremely quickly
SIGNEXTEND (0x0B)Relevant when ADD results are interpreted as signed values
ISZERO (0x15)Used in overflow detection patterns (check if result wrapped to 0)
LT (0x10)Used in the standard overflow detection pattern: (a + b) < a implies overflow