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 +
nonReentrantguard. No exceptions. - Overflow: default to checked arithmetic; document every
uncheckedblock. - 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.