Opcode Summary
| Property | Value |
|---|---|
| Opcode | 0x01 |
| Mnemonic | ADD |
| Gas | 3 |
| Stack Input | a, b |
| Stack Output | (a + b) % 2^256 |
| Behavior | Unsigned 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] += amountwhereamountis chosen to wrap the balance - Total supply manipulation:
totalSupply += mintAmountthat wraps to a small value - Batch operations:
total = count * valuePerItem(uses MUL but often combined with ADD in loops) - Fee calculations:
amountWithFee = amount + feewhere attacker controlsamount
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 overflowDeFi 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 Case | Behavior | Security Implication |
|---|---|---|
MAX_UINT256 + 1 | Returns 0 | Classic overflow; balance/supply wraps to zero |
MAX_UINT256 + MAX_UINT256 | Returns MAX_UINT256 - 1 | Double-max wraps to near-max |
0 + 0 | Returns 0 | Safe; no issue |
a + 0 | Returns a | Identity; no issue |
| Negative numbers (two’s complement) | Wraps as unsigned | Sign confusion if treating result as int256 |
a + b where b = 2^256 - a | Returns 0 | Attacker can zero out any accumulator |
| Very large values near 2^255 | Wraps into “negative” range if interpreted as int256 | Sign 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 valueImpact: ~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
| Threat | Mitigation | Implementation |
|---|---|---|
| T1: Integer overflow | Use Solidity >= 0.8.0 (automatic overflow checks) | Default behavior; no action needed |
| T1: Legacy contracts | Use OpenZeppelin SafeMath library | using SafeMath for uint256; a.add(b) |
| T2: Unchecked blocks | Minimize use; formal verification of safety invariants | Restrict unchecked to provably safe operations (e.g., loop counters) |
| T2: Unchecked audit | Flag all unchecked blocks during security review | Automated tooling: Slither, Mythril detect unchecked arithmetic |
| T3: Precision loss | Multiply before dividing; use higher-precision intermediates | (a * b) / c instead of (a / c) * b |
| T3: Rounding attacks | Round against the attacker (favor the protocol) | Use mulDivUp / mulDivDown from Solmate or OpenZeppelin Math |
| T4: Signed confusion | Use Solidity’s int256 type; avoid raw assembly for signed math | Let the compiler handle two’s complement |
| T5: Accumulator overflow | Use uint256 for accumulators; audit packed struct sizes | Verify 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 ID | Category | Severity | Likelihood | Real-World Precedent |
|---|---|---|---|---|
| T1 | Smart Contract | Critical | High (pre-0.8) / Low (post-0.8) | BEC, SMT, Truebit, hundreds of tokens |
| T2 | Smart Contract | High | Medium | Yearn yETH ($9M) |
| T3 | Smart Contract | Medium | Medium | Various DeFi rounding exploits |
| T4 | Smart Contract | Medium | Low | Assembly-level bugs |
| T5 | Smart Contract | Low | Low | Theoretical for uint256; real for packed types |
| P1 | Protocol | Low | N/A | — |
| P2 | Protocol | Low | N/A | — |
Related Opcodes
| Opcode | Relationship |
|---|---|
| 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 |