A token contract has a batchTransfer function that lets a user send tokens to multiple recipients in one transaction. The function calculates the total amount to deduct: _value × cnt, where cnt is the number of recipients. The contract checks that the sender has enough balance for the total. Then it deducts.
An attacker calls the function with two recipients and a _value of 2^255. The multiplication: 2^255 × 2 = 2^256. But the maximum value of a uint256 is 2^256 - 1. The result wraps around to 0.
The balance check passes — the sender needs 0 tokens, and they have more than 0. The contract then deducts 0 from the sender’s balance and credits 2^255 tokens to each recipient. From nothing, the attacker has created 2^256 tokens.
This is an integer overflow attack. It did not exploit a flash loan, a reentrancy bug, or an oracle manipulation. It exploited a property of arithmetic that most programmers never think about — the fact that numbers in computers have limits, and when you cross those limits, the results are silently wrong.
BLUF: Integer overflow and underflow attacks exploit the finite size of integer types in the Ethereum Virtual Machine (EVM). A
uint256can hold values from0to2^256 - 1. If an operation produces a result larger than the maximum (overflow) or smaller than zero (underflow), the value wraps around silently — the largest number becomes zero, or zero becomes the largest number. Attackers exploit this to bypass balance checks, mint unlimited tokens, or drain contracts. The primary vectors were: (1) unchecked multiplication in batch transfer functions (BEC token, $900M+ inflated supply); (2) underflow in balance tracking allowing negative-equivalent balances; (3) storage slot underflow enabling arbitrary writes. Solidity 0.8.0 (released December 2020) introduced built-in overflow checks that revert on arithmetic errors — but contracts compiled with older versions, contracts usinguncheckedblocks, and custom assembly code remain vulnerable.
What Is Integer Overflow?
Computers store numbers in fixed-size containers. A uint256 — the most common integer type in Ethereum — uses 256 bits of memory. This gives it a range of 0 to 2^256 - 1, which is approximately 1.15 × 10^77.
That is an absurdly large number. It is larger than the estimated number of atoms in the observable universe. For almost any practical purpose, a uint256 cannot overflow through normal addition.
But multiplication changes the math. When you multiply two uint256 values, the intermediate result can exceed the maximum — even if neither input is particularly large. 2^128 × 2^128 = 2^256, which overflows. And in the BEC attack, the overflow was achieved with a value of 2^255 multiplied by 2 (the number of recipients).
When an overflow occurs in the EVM, the result is silent wrapping. The extra bits are discarded, and what remains is the lower 256 bits of the true result. This is modular arithmetic: the result is true_result mod 2^256. For 2^256, the result is 0. For 2^256 + 5, the result is 5.
The critical danger: there is no error, no warning, no exception. The computation proceeds as if nothing happened. The smart contract continues executing with a completely wrong number.
What Is Integer Underflow?
Underflow is the mirror image. If you subtract a larger number from a smaller uint256 — say 0 - 1 — the result wraps around to 2^256 - 1, the maximum possible value.
This is even more dangerous in some contexts. A balance of 0 that underflows becomes 2^256 - 1 tokens. An array index that underflows can point to arbitrary storage slots. A counter that underflows can bypass loop limits.
The Anatomy of an Overflow Attack
Most overflow attacks share a common structure:
| Step | What Happens |
|---|---|
| 1 | The contract performs arithmetic without overflow protection |
| 2 | The attacker crafts inputs that cause the result to wrap |
| 3 | The wrapped result passes a validation check (e.g., balance sufficient) |
| 4 | The contract executes state changes based on the wrong value |
| 5 | The attacker receives tokens, permissions, or assets they should not have |
The key insight: the overflow must occur in a value that is later used for a security-critical check. If the overflow happens in a meaningless variable, it does not matter. But when the overflow affects balance checks, transfer amounts, or access control, it becomes exploitable.
Historical Incidents
BeautyChain (BEC) Token — April 2018
The BEC token incident is the textbook integer overflow attack. The BeautyChain smart contract had a batchTransfer function that allowed sending tokens to multiple addresses in one call:
function batchTransfer(address[] _receivers, uint256 _value) public returns (bool) {
uint cnt = _receivers.length;
uint256 amount = uint256(cnt) * _value;
require(cnt > 0 && cnt <= 20);
require(_value > 0 && balances[msg.sender] >= amount);
// ... transfer logic
}
An attacker called the function with two receiver addresses and _value = 2^255. The multiplication 2 × 2^255 = 2^256 overflowed to 0. The balance check balances[msg.sender] >= 0 passed for anyone. The contract then credited 2^255 BEC tokens to each receiver — creating 57,896,044,618,658,097,711,785,492,504,343,953,926,634,992,332,820,282,019,728,792,003,956,564,819,968 tokens out of thin air.
The BEC token price collapsed. While the attacker could not easily sell all tokens (liquidity was limited), the damage to the project was total. The incident became the most cited example of why overflow protection matters.
SmartMesh (SMT) Token — April 2018
Days after the BEC incident, the SMT token was found to have a similar vulnerability in its proxy contract. An overflow in the token’s transferProxy function allowed an attacker to mint large amounts of SMT tokens. The SMT team detected the anomaly quickly and suspended trading, but the vulnerability demonstrated that overflow bugs were systemic — not a one-off mistake.
Poolz Finance — February 2021
The Poolz incident involved an underflow in a token vesting contract. The contract used an array index that could underflow, allowing the attacker to manipulate storage and mint tokens arbitrarily. This attack was notable because it occurred after SafeMath had become standard practice — the vulnerability was in assembly code and custom logic that bypassed SafeMath protections.
The SafeMath Era
Before Solidity 0.8.0, the standard defense was the SafeMath library — a set of functions that checked for overflow before performing arithmetic:
library SafeMath {
function mul(uint256 a, uint256 b) internal pure returns (uint256) {
if (a == 0) return 0;
uint256 c = a * b;
require(c / a == b, "SafeMath: multiplication overflow");
return c;
}
// ... add, sub, div with similar checks
}
SafeMath was a convention, not a requirement. Developers had to remember to use it for every arithmetic operation. Any operation that forgot SafeMath — even one multiplication in a rarely-called function — was a potential vulnerability. The burden was on the developer to be perfect, everywhere, always.
This is why overflow bugs were so common. Not because developers were careless, but because the language made safety opt-in rather than opt-out.
Solidity 0.8.0: The Game Changer
In December 2020, Solidity 0.8.0 was released with a fundamental change: arithmetic operations now check for overflow and underflow by default. If an overflow occurs, the transaction reverts automatically.
// Solidity >= 0.8.0: overflow reverts automatically
uint256 result = a * b; // reverts if overflow
// To disable checks (for gas optimization in proven-safe code):
unchecked {
uint256 result = a * b; // wraps silently, like old behavior
}
This single change eliminated an entire class of vulnerabilities. Contracts compiled with Solidity 0.8.0 or later are immune to overflow attacks in their standard arithmetic — the checks are built in, free, and cannot be forgotten.
Why Overflows Still Matter
Given that Solidity 0.8.0 solved the problem, why write about it in 2026? Several reasons:
Legacy Contracts
Thousands of smart contracts compiled before Solidity 0.8.0 are still active on Ethereum. These contracts do not have built-in overflow checks. If they did not use SafeMath correctly — or at all — they remain vulnerable. Many token contracts, vesting contracts, and staking contracts deployed in 2017–2020 are still in use.
Assembly Code
Inline assembly (assembly { ... }) bypasses Solidity’s safety checks entirely. A contract compiled with Solidity 0.8.0 can still have overflow vulnerabilities in its assembly blocks. Gas optimization often pushes developers toward assembly, reintroducing the risk.
The unchecked Block
Solidity 0.8.0+ allows developers to opt out of overflow checks using unchecked blocks. While these are intended for gas optimization in code where overflow is provably impossible, a mistake in the proof can reintroduce the vulnerability.
Non-Solidity Languages
Contracts written in Vyper, Yul, or directly in EVM bytecode may not have equivalent overflow protection. Cross-chain smart contracts deployed on alternative VMs (Move, CosmWasm, etc.) have their own arithmetic semantics that may or may not include overflow checks.
Cross-Contract Calls
A contract compiled with 0.8.0 that calls a legacy contract compiled with 0.5.0 may still be affected if the legacy contract’s arithmetic is vulnerable. Your own safety checks do not protect you from the contracts you interact with.
Detection and Defense
For Developers
| Practice | Effectiveness |
|---|---|
| Use Solidity ≥ 0.8.0 | Eliminates overflow in standard arithmetic |
Audit all unchecked blocks | Ensure overflow is truly impossible |
| Audit all inline assembly | Assembly bypasses Solidity checks |
| Fuzz testing | Tools like Echidna and Foundry can find overflow paths |
| Formal verification | Prove mathematically that overflow cannot occur |
| Upgrade legacy contracts | Migrate pre-0.8.0 code to modern Solidity |
For Users and Analysts
When evaluating a smart contract’s security:
- Check the Solidity version — Contracts compiled with
< 0.8.0need careful review of all arithmetic - Look for SafeMath usage — If the contract uses old Solidity, SafeMath should be used consistently
- Examine batch operations — Functions that multiply user input by an array length are classic overflow vectors
- Review assembly blocks — Any
assemblykeyword is a potential blind spot - Check for
uncheckedblocks — These are explicit opt-outs from overflow protection
The Broader Lesson
Integer overflow is not unique to smart contracts. It has caused some of the most famous bugs in computing history:
- Ariane 5 rocket explosion (1996): A 64-bit floating-point number was converted to a 16-bit integer, causing an overflow that crashed the guidance system. The rocket self-destructed 37 seconds after launch. Cost: $370 million.
- Therac-25 radiation overdose (1980s): A race condition involving an integer counter allowed radiation doses 125× stronger than intended. Multiple patients died.
- Gangnam Style YouTube (2014): The view counter exceeded
2^31(the maximum for a 32-bit signed integer), breaking YouTube’s counter.
What makes overflow particularly dangerous in smart contracts is immutability — once deployed, a vulnerable contract cannot be patched without a proxy upgrade pattern. A traditional software bug can be fixed with an update. A smart contract bug can be exploited until funds are drained or the contract is paused.
The EVM’s design choice — silent wrapping rather than error-on-overflow — was made for gas efficiency. Every additional check costs gas. In a system where every operation is priced, removing a check that “almost never matters” seemed reasonable. It took hundreds of millions of dollars in exploits to demonstrate that “almost never” is not “never.”
Conclusion
Integer overflow attacks represent a solved problem for new contracts and an ongoing risk for legacy ones. The lesson is broader than arithmetic: when security is opt-in, it will eventually be opted out of. Solidity 0.8.0’s decision to make overflow checks the default — and require explicit opt-out — followed the principle that safe should be easy and unsafe should require effort. That principle applies far beyond integer math.
For analysts reviewing contracts today, the presence of pre-0.8.0 Solidity is a flag, not a verdict. Many older contracts used SafeMath correctly and are safe. But the absence of built-in checks means the burden of proof is on the code — and every arithmetic operation in a legacy contract deserves scrutiny.