Smart contract exploits cost the DeFi ecosystem billions every year — and the same vulnerability classes appear again and again. Understanding them is the first step to writing code that doesn't become a cautionary tale.
This list is drawn from real exploit data across Ethereum, BSC, Polygon, and Solana in 2024–2025. Each entry includes how the vulnerability works, a minimal code example, and what to do instead.
Is Your Contract Vulnerable?
Paste your contract address for an instant AI-powered scan — checks all 10 vulnerability classes below and more.
#1 — Reentrancy
Severity: Critical | Responsible for hundreds of millions in losses including The DAO ($60M).
A reentrancy attack occurs when an external contract is called before the caller's state is updated, allowing the external contract to recursively re-enter the function.
// VULNERABLE
function withdraw(uint amount) external {
require(balances[msg.sender] >= amount);
(bool ok,) = msg.sender.call{value: amount}(""); // external call first
require(ok);
balances[msg.sender] -= amount; // state updated AFTER — too late
}
// SAFE: Checks-Effects-Interactions pattern
function withdraw(uint amount) external {
require(balances[msg.sender] >= amount);
balances[msg.sender] -= amount; // state updated FIRST
(bool ok,) = msg.sender.call{value: amount}("");
require(ok);
}
Prevention: Always follow the Checks-Effects-Interactions (CEI) pattern. Update all state variables before any external calls. Additionally, use OpenZeppelin's ReentrancyGuard modifier as a second layer of defense.
#2 — Broken Access Control
Severity: Critical | The #1 vulnerability by total funds lost in 2024.
Functions that should be restricted to the contract owner or specific roles are left unprotected — or protected with flawed logic that can be bypassed.
// VULNERABLE: anyone can call mint
function mint(address to, uint amount) external {
_mint(to, amount);
}
// SAFE
function mint(address to, uint amount) external onlyOwner {
_mint(to, amount);
}
More subtle access control bugs include: using tx.origin instead of msg.sender for authentication (can be bypassed via phishing contracts), or missing role checks on initializer functions in upgradeable contracts.
#3 — Integer Overflow and Underflow
Severity: High | Largely mitigated in Solidity 0.8+, but still common in older contracts and assembly blocks.
Before Solidity 0.8, arithmetic operations would silently wrap around. A balance of 0 minus 1 would become 2²⁵⁶-1 — maximum possible value.
// VULNERABLE (Solidity < 0.8)
uint8 x = 255;
x += 1; // wraps to 0
// In Solidity 0.8+, this reverts automatically.
// But in unchecked{} blocks, overflow is re-enabled:
unchecked {
uint8 y = 255;
y += 1; // wraps to 0 — no revert!
}
Prevention: Use Solidity 0.8+ and avoid unchecked blocks unless you've formally proven the operation cannot overflow. Use SafeMath for any legacy 0.7.x code.
#4 — Oracle Manipulation
Severity: Critical | Flash loan-amplified oracle attacks have stolen over $500M.
Contracts that use on-chain price sources — particularly AMM spot prices — can be manipulated within a single transaction using flash loans to artificially move the price, exploit the contract, then restore the price.
getReserves() on a Uniswap pair directly and uses it to calculate collateral values, liquidation thresholds, or mint quantities — it is vulnerable to oracle manipulation.
Prevention: Use time-weighted average prices (TWAPs) from Uniswap v3, Chainlink price feeds, or Band Protocol. Never trust spot prices from AMMs for anything financially significant.
#5 — Unchecked Return Values
Severity: High | Commonly missed in code reviews because the code looks correct.
Low-level calls (call, delegatecall, send) return a boolean indicating success or failure. Ignoring this return value means a failed transfer is treated as successful.
// VULNERABLE: send() return value ignored
function pay(address payable recipient) external {
recipient.send(1 ether); // if this fails, execution continues!
}
// SAFE: check the return value
(bool success,) = recipient.call{value: 1 ether}("");
require(success, "Transfer failed");
Similarly, some ERC-20 tokens (notably USDT on mainnet) do not return a boolean from transfer(). Use OpenZeppelin's SafeERC20 library to handle non-standard tokens.
#6 — Flash Loan Attack Vectors
Severity: Critical | Amplifies almost every other vulnerability class.
Flash loans allow borrowing unlimited funds with zero collateral — as long as the loan is repaid in the same transaction. Attackers use them to amplify oracle attacks, governance attacks, and collateral manipulation.
Flash loans themselves aren't a vulnerability — but any contract that makes security decisions based on state that can be temporarily manipulated within one transaction is at risk.
Prevention: Design contracts so that critical state cannot be meaningfully changed and exploited within a single transaction. Use commit-reveal schemes, time delays, and TWAP oracles.
#7 — Front-Running and MEV
Severity: Medium–High | Affects DEXes, NFT mints, and any first-come-first-served mechanism.
Miners (and MEV bots) can see pending transactions in the mempool and insert their own transactions ahead of them — or manipulate transaction ordering. This allows them to sandwich trades, snipe NFT mints, and extract arbitrage.
// VULNERABLE: naïve DEX with predictable slippage
function swap(uint amountIn) external {
uint price = getSpotPrice(); // bot reads this before tx lands
// bot sandwiches: buy → your tx (higher price) → sell
}
Prevention: For DEXes, add slippage tolerance parameters that users set explicitly. For competitive auctions, use commit-reveal schemes. For sensitive operations, consider using Flashbots Protect RPC.
#8 — Improper Initialization
Severity: Critical | Unique to upgradeable contracts (proxy pattern).
Upgradeable contracts use an initialize() function instead of a constructor. If this function is not protected with an initializer modifier, anyone can call it after deployment and take ownership.
// VULNERABLE: unprotected initializer
function initialize(address _owner) public {
owner = _owner; // anyone can call this!
}
// SAFE: use OpenZeppelin's Initializable
function initialize(address _owner) public initializer {
owner = _owner;
}
Additionally, implementation contracts (the logic behind a proxy) must be initialized immediately at deployment — otherwise attackers can initialize them and use delegatecall to destroy the proxy.
#9 — Denial of Service (DoS)
Severity: Medium–High | Can permanently freeze funds or break contract mechanics.
Several patterns can cause a contract to become permanently non-functional:
- Unbounded loops — iterating over a user-controlled array that grows until gas limit is hit
- Unexpected revert in a loop — if any iteration reverts (e.g., a failed ETH transfer), the entire batch fails
- Block gas limit — a function that worked with 100 users fails with 10,000
// VULNERABLE: loop over user array
function distributeRewards() external {
for (uint i = 0; i < users.length; i++) { // will fail if users grows large
payable(users[i]).transfer(rewards[users[i]]);
// if any transfer fails, entire distribution reverts
}
}
Prevention: Use pull payment patterns (let users withdraw their own funds) instead of push payments. Avoid unbounded loops over user-controlled data structures.
#10 — Timestamp Manipulation
Severity: Low–Medium | Relevant for time-locked mechanisms.
Validators (formerly miners) have limited ability to manipulate block.timestamp — typically within a 15-second window. Contracts that use timestamps for randomness sources or fine-grained time locks are vulnerable.
// VULNERABLE: timestamp as randomness source
function getWinner() external view returns (address) {
uint random = uint(keccak256(abi.encode(block.timestamp)));
return participants[random % participants.length]; // manipulable
}
Prevention: Never use block.timestamp or block.number as a randomness source. Use Chainlink VRF for verifiable randomness. For time locks, a 15-second tolerance window is acceptable for most use cases.
Quick Reference: Risk Matrix
| # | Vulnerability | Severity | Funds at Risk |
|---|---|---|---|
| 1 | Reentrancy | Critical | All contract ETH/tokens |
| 2 | Broken Access Control | Critical | All funds + admin rights |
| 3 | Integer Overflow | High | Balance manipulation |
| 4 | Oracle Manipulation | Critical | All collateral/liquidity |
| 5 | Unchecked Return Values | High | Silent fund loss |
| 6 | Flash Loan Vectors | Critical | Amplified losses |
| 7 | Front-Running / MEV | Medium–High | User trade value |
| 8 | Improper Initialization | Critical | Full ownership takeover |
| 9 | Denial of Service | Medium–High | Frozen funds |
| 10 | Timestamp Manipulation | Low–Medium | Lottery/randomness bias |
How to Check Your Contract for These Issues
The fastest way to identify which of these 10 vulnerabilities exist in your contract is an automated scan. Quantum Audit checks all of the above patterns — plus dozens more — and delivers a risk score from 0–100 with a full PDF report in under 60 seconds.