← Back to Journal

5-common-smart-contract-bugs

· 5 min read

# ---
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:

  • Default-deny: every function should be external or public only if intentionally callable by anyone

  • Use OpenZeppelin's AccessControl or Ownable — don't roll your own

  • Audit your modifiers: are they actually being applied to every sensitive function?

  • Add a_natriv checks as a sanity test in production
  • ---

    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:

  • Silent failure — no revert means no alarm

  • Accounting drift accumulates silently

  • By the time you notice, bad debt is already baked in
  • 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:

  • Token transfers with fee-on-transfer tokens

  • Multiplication before division (order matters!)

  • Low-decimal tokens (some tokens have 6 or even 0 decimals)

  • Accrued interest calculations in lending protocols
  • 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:

  • Use Solidity's WadRayMath or OpenZeppelin's SafeMath equivalents

  • Always understand the decimal implications of each token in your system

  • Test with low-decimal tokens (like USDC on some chains: 6 decimals) in your test suite

  • Document rounding direction: does your protocol round up or down, and toward whom?
  • ---

    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:

  • New variable declared before old variable in storage

  • Variable type changed (uint256 → uint128)

  • Gap variable removed or resized
  • 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:

  • Always maintain a storage gap at the end of each contract

  • Never reorder storage variables between versions

  • Never change the type of an existing variable

  • Use storage layout verifiers (OpenZeppelin has a plugin)

  • Always upgrade on a testnet first and verify all state is preserved
  • ---

    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.

  • Use TWAP oracles with sufficient averaging time

  • Check for price manipulation across multiple liquidity sources

  • Implement circuit breakers that pause during anomalous activity
  • ---

    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:

  • Get an external audit before mainnet — don't skip this for "just a small launch"

  • Use automated tools: Slither, Mythril, Certora — they're not a substitute for humans but catch the obvious stuff

  • Run a bug bounty program — even a small one creates economic friction for attackers

  • Monitor your contracts post-launch — Tenderly, Forta, OpenZeppelin Defender
  • 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.*

    Running AI agents for DeFi security research?

    OpenClaw Launchpad — White-glove AI agent setup →