Skip to content
Back to Insights
Smart Contracts Solidity DeFi Security Blockchain

Smart Contract Security: 7 Vulnerabilities We Found in Production DeFi Protocols

We have audited dozens of DeFi protocols and the same classes of vulnerability appear repeatedly — sometimes in protocols that had already been audited by others. This post walks through the seven most impactful vulnerabilities we found in production, with real Solidity examples and the fixes.

Codecanis Admin

11 min read
Blockchain code on screen
Audit findings from a DeFi protocol — three of the seven vulnerabilities below were caught here.

Smart contract security is different from application security in one fundamental way: bugs are permanent. You cannot patch a deployed contract. Every vulnerability is a potential permanent loss of user funds. This concentrates the mind wonderfully.

What follows are the seven vulnerability classes we've found most impactful across our DeFi audits. For each one, we show a simplified version of the vulnerable code, explain why it's exploitable, and show the fix.

1. Reentrancy

Reentrancy is the vulnerability behind the 2016 DAO hack and it still appears in production code in 2025. A contract is vulnerable when it calls an external contract before updating its own state — giving the external contract an opportunity to call back in and re-execute logic with stale state.

// VULNERABLE: External call before state update
function withdraw(uint256 amount) external {
    require(balances[msg.sender] >= amount, "Insufficient balance");

    // External call happens BEFORE state update — attacker re-enters here
    (bool success, ) = msg.sender.call{value: amount}("");
    require(success, "Transfer failed");

    balances[msg.sender] -= amount; // Too late — already re-entered
}
// FIXED: Checks-Effects-Interactions pattern
function withdraw(uint256 amount) external nonReentrant {
    require(balances[msg.sender] >= amount, "Insufficient balance");

    balances[msg.sender] -= amount; // Update state FIRST

    (bool success, ) = msg.sender.call{value: amount}("");
    require(success, "Transfer failed");
}

Always apply the Checks-Effects-Interactions (CEI) pattern and use OpenZeppelin's ReentrancyGuard (nonReentrant modifier) as a belt-and-suspenders defence. CEI is a pattern; nonReentrant is a guard against cross-function reentrancy that CEI alone doesn't cover.

2. Integer Overflow and Underflow

Pre-Solidity 0.8.0, arithmetic operations could silently overflow or underflow. Solidity 0.8+ reverts on overflow by default, but we still find code using unchecked blocks carelessly, or older contracts that were never upgraded.

// VULNERABLE (Solidity < 0.8.0, or unchecked block in >= 0.8.0)
function transfer(address to, uint256 amount) external {
    unchecked {
        // If amount > balances[msg.sender], this wraps to a large number
        balances[msg.sender] -= amount;
        balances[to] += amount;
    }
}

The fix: avoid unchecked blocks unless you have proven the arithmetic cannot overflow, and use SafeMath (or Solidity 0.8+ checked arithmetic) everywhere else. When you do use unchecked for gas optimisation, document exactly why it is safe.

3. Front-Running

Ethereum (and most EVM chains) have public mempools. Any transaction you submit is visible before it's included in a block. Attackers — including MEV bots — can observe your transaction and submit their own with a higher gas price to execute first.

The most common victim: DEX trades. A user submits a swap with 1% slippage tolerance. A bot sees it, front-runs with a buy, lets the user's transaction execute at the inflated price, then immediately sells — classic sandwich attack.

Mitigations depend on context:

  • Commit-reveal schemes for auctions and games: commit a hash of your action, reveal later.
  • Slippage limits + deadline parameters for AMM interactions (already standard in Uniswap v3).
  • Private mempools (Flashbots Protect, MEV Blocker) for high-value transactions.
  • Batch auctions (Gnosis Protocol) that eliminate time-ordering advantages.

4. Access Control Failures

This one is embarrassingly common: privileged functions left unprotected, or protected with a modifier that contains a logic error.

// VULNERABLE: Anyone can drain the treasury
function withdrawTreasury(address to, uint256 amount) external {
    // Missing: onlyOwner or equivalent
    token.transfer(to, amount);
}
// ALSO VULNERABLE: Incorrect modifier logic
modifier onlyAdmin() {
    require(admins[msg.sender] = true); // Assignment, not comparison!
    _;
}

Use OpenZeppelin's Ownable and AccessControl contracts rather than rolling your own. For multi-sig governance on treasury functions, use Gnosis Safe with a 2-of-3 or 3-of-5 threshold. Never deploy a contract where a single EOA can drain funds unilaterally.

5. Oracle Manipulation

Price oracles are the interface between on-chain contracts and off-chain reality. If an attacker can manipulate the price feed — even briefly — they can exploit any contract that uses it for collateralisation, liquidation triggers, or settlement.

On-chain DEX spot prices (e.g., a Uniswap v2 pair's current ratio) are trivially manipulable within a single transaction using flash loans. We found a lending protocol using a Uniswap v2 spot price as its sole collateral oracle — a flash loan could move the price enough to borrow against worthless collateral.

Fixes:

  • Use time-weighted average prices (TWAP) over at least 30 minutes, not spot prices.
  • Use Chainlink Data Feeds for production deployments — off-chain aggregated, economically costly to manipulate.
  • Use multiple oracles and revert if they deviate beyond a threshold (circuit-breaker oracle).

6. Flash Loan Attack Vectors

Flash loans themselves are not a vulnerability — they're a feature. But they dramatically change the economics of every other attack by giving any attacker access to large capital within a single transaction at near-zero cost.

Any invariant that your contract assumes holds within a transaction can be broken by a flash loan. A governance contract that uses ERC-20 token balance as voting weight? A flash loan can temporarily give an attacker a supermajority, pass a malicious proposal, and repay the loan — all in one transaction.

Counter-measures:

  • Use snapshot-based voting (token balance at a past block), not real-time balance.
  • Separate proposal and execution phases with a time delay (timelock).
  • Track governance tokens with ERC-20Votes (OpenZeppelin) which snapshots at each block.

7. MEV and Miner Extractable Value

MEV refers to value that validators (formerly miners) or sophisticated bots can extract by reordering, inserting, or censoring transactions within a block. It's not a single vulnerability but a category of economic attacks made possible by transparent mempool ordering.

For protocols where ordering matters (limit order books, liquidation auctions, NFT mints), MEV bots will consistently extract value from your users. Long-term mitigations include:

  • Building on chains with encrypted mempools (Aztec, Shutter Network).
  • Using off-chain order books with on-chain settlement (0x, dYdX v4).
  • Implementing Dutch auctions for liquidations so bots compete on price, not speed.

Key Takeaways

  • Reentrancy: always CEI pattern + nonReentrant guard. No exceptions.
  • Overflow: default to checked arithmetic; document every unchecked block.
  • Front-running: slippage limits and private mempools for DEX integrations.
  • Access control: use OpenZeppelin's audited contracts; multi-sig for treasury.
  • Oracle manipulation: TWAP or Chainlink, never spot price from a DEX.
  • Flash loans break capital-size assumptions — design invariants that hold regardless of caller wealth.
  • MEV is structural on transparent chains — design around it, not against it.
Let's build something

Want to work together?

If this article made you think about your architecture, your roadmap, or a problem you haven't solved yet — let's talk.