5-common-smart-contract-bugs
# ---
title: "The 5 Smart Contract Vulnerabilities We Find Most Often (And How to Prevent Them)"
description: "After auditing dozens of DeFi protocols, these are the vulnerability patterns that keep causing real losses — and how to fix each one."
published: "2026-03-25"
tags: ["smart-contracts", "security", "defi", "audit", "vulnerabilities"]
slug: "5-common-smart-contract-bugs"
readTime: "8 min"
---
The 5 Smart Contract Vulnerabilities We Find Most Often (And How to Prevent Them)
*Published March 25, 2026 · 8 min read*
---
After auditing dozens of DeFi protocols across Immunefi, Cantina, and Sherlock, certain vulnerability patterns keep appearing. These aren't exotic or novel bugs — they're the same fundamentals that keep causing real losses, over and over.
Here's what our team sees most frequently, and how to fix each one.
---
1. Missing or Broken Access Control
What it is: Functions that should be restricted are callable by anyone.
The pattern:
``solidity`
function emergencyWithdraw() external {
// No access control — anyone can call this
IERC20(token).transfer(msg.sender, balanceOf[msg.sender]);
}
This is the most common vulnerability we find, and it has real consequences. The Ronin bridge hack ($625M) started with compromised admin keys, but many smaller exploits come from developers simply forgetting to add onlyOwner or checking msg.sender.
How to prevent:
or public only if intentionally callable by anyone or Ownable — don't roll your own---
2. Oracle Staleness — The Silent Killer
What it is: Your protocol reads a price from an oracle, but if that oracle stops updating, you get stale data. No transaction reverts. The protocol keeps running, but with wrong numbers.
Why it's dangerous:
Real example:
`solidity`
function getPrice() external view returns (uint256) {
return IOracle(oracle).latestAnswer(); // Returns stale price silently
}
Many protocols use Chainlink or Uniswap TWAP oracles without checking if the data is fresh. A stale oracle in a lending protocol means incorrect collateral ratios. In a DEX, it means incorrect swap prices.
How to prevent:
`solidity`
function getPrice() external view returns (uint256) {
(uint256 price, uint256 updatedAt) = IOracle(oracle).latestRoundData();
require(block.timestamp - updatedAt <= MAX_STALENESS, "Oracle stale");
return price;
}
Set MAX_STALENESS conservatively — 1 hour for crypto, 24 hours for slower assets.
---
3. Reentrancy — Still Breaking Things in 2026
What it is: A contract calls an external contract before updating its own state. If that external contract calls back into the original, it can see inconsistent state.
The classic pattern:
`solidity`
function withdraw(uint256 amount) external {
IERC20(token).transfer(msg.sender, amount); // External call FIRST
balances[msg.sender] -= amount; // State update AFTER
}
The transfer might trigger a callback in a malicious token (ERC-777, or any token with hooks). The attacker re-enters before balances is decremented and drains the contract.
How to prevent — the Checks-Effects-Interactions pattern:
`solidity`
function withdraw(uint256 amount) external {
// CHECKS
require(balances[msg.sender] >= amount, "Insufficient balance");
// EFFECTS (state update BEFORE external call)
balances[msg.sender] -= amount;
// INTERACTIONS (external call LAST)
IERC20(token).safeTransfer(msg.sender, amount);
}
Also: use ReentrancyGuard from OpenZeppelin on any function that transfers value.
---
4. Precision Loss — The Dust You Can't Ignore
What it is: Solidity doesn't handle decimals. When you divide 3 by 2, you get 1, not 1.5. Rounding errors compound.
Where it bites:
Example:
`solidity
// WRONG: multiply then divide
function calculateShare(uint256 amount) external view returns (uint256) {
return amount * totalShares / totalDeposits; // Can overflow and lose precision
}
// RIGHT: divide then multiply (or use FixedPoint math)
function calculateShare(uint256 amount) external view returns (uint256) {
return amount * totalShares / totalDeposits; // Still wrong if done carelessly
}
`
How to prevent:
or OpenZeppelin's SafeMath equivalents---
5. Upgrade Proxy Storage Collisions
What it is: You're using a proxy pattern (UUPS, Transparent, etc.) to upgrade your contract. But the new version has a storage layout that's incompatible with the old one.
How it breaks:
Result: the proxy reads the wrong data for the wrong variables. User balances point to the wrong slot. Admins can no longer access their own functions.
`solidity
// Version 1 storage
contract V1 {
uint256 public price; // slot 0
address public owner; // slot 1
}
// Version 2 — DEPLOYED ON SAME PROXY
contract V2 {
uint256 public governance; // slot 0 — OVERWRITES price!
uint256 public price; // slot 1 — NOW READS owner!
address public owner; // slot 2 — WRONG LOCATION
}
``
How to prevent:
---
Bonus: Flash Loan Attacks Are Still Profitable
Flash loan attacks haven't gone away because the pattern still works: borrow a massive amount, manipulate an oracle or pool price within a single transaction, profit, repay.
The fix isn't to prevent flash loans — it's to ensure your protocol's critical operations can't be manipulated by a single transaction's worth of capital.
---
Final Thoughts
Most of these vulnerabilities aren't exotic. They're fundamentals that get overlooked under deadline pressure. The fixes are well-known. The problem is consistency — auditing your own code is hard because you know what you meant to do.
A few practical steps:
If you want our team to take a look at your protocol, [reach out](https://openclawlaunchpad.com). We've audited protocols across Ethereum, Solana, and all major EVM chains.
---
*This post is for educational purposes. It does not constitute a security audit. All code examples are simplified for illustration.*