Reentrancy Attacks
Reentrancy is the most famous smart contract vulnerability — the attack that drained The DAO in 2016. It occurs when a contract calls an external contract before updating its own state, allowing the external contract to call back into the original contract before the first call completes. The classic example: a withdrawal function that sends ETH before updating the user's balance, allowing the attacker to withdraw repeatedly.
The fix: the checks-effects-interactions pattern. Validate all conditions first (checks), update all state variables second (effects), and make external calls last (interactions). OpenZeppelin's ReentrancyGuard modifier provides an additional safety net by using a mutex to prevent reentrant calls entirely.
// Vulnerable: external call before state update
function withdraw(uint amount) public {
require(balances[msg.sender] >= amount);
(bool success,) = msg.sender.call{value: amount}("");
balances[msg.sender] -= amount; // Too late!
}
// Fixed: state update before external call
function withdraw(uint amount) public nonReentrant {
require(balances[msg.sender] >= amount);
balances[msg.sender] -= amount; // Effects first
(bool success,) = msg.sender.call{value: amount}("");
require(success);
}
Access Control Patterns
Access control vulnerabilities expose functions that should be restricted. Every public and external function should have explicit access control — either onlyOwner, role-based access (onlyRole(MINTER_ROLE)), or no restriction (intentionally public). The mistake is forgetting to add access control to a new function during development. OpenZeppelin's AccessControl contract provides role-based access with admin roles that control who can grant and revoke roles.
Integer Overflow and Underflow
Before Solidity 0.8, integer arithmetic could silently overflow — adding 1 to uint256's max value wraps to 0. Since Solidity 0.8, arithmetic operations revert on overflow by default. However, if you use unchecked blocks for gas optimization (a common pattern), you've opted back into overflow behavior. Every unchecked block should be reviewed to confirm that overflow is impossible given the constraints.
Oracle Price Manipulation
DeFi protocols that read prices from DEX pools are vulnerable to oracle manipulation — an attacker can manipulate the pool's price in a single transaction, then exploit the manipulated price in the same transaction. The fix: use time-weighted average prices (TWAP) over multiple blocks, or use dedicated oracle services (Chainlink) that aggregate prices from multiple sources. Never use a single spot price from a single liquidity pool.
Flash Loan Attacks and Gas-Limit DoS
Flash loan attacks deserve separate treatment from ordinary oracle manipulation because they remove the capital constraint that used to make manipulation expensive. A flash loan lets an attacker borrow millions of dollars with no collateral, as long as the loan is repaid within the same transaction. This means an attacker can temporarily control enough capital to move a thin liquidity pool's price, exploit that price in your protocol, and repay the loan — all in one atomic transaction, at essentially zero cost if it fails. The defense is the same TWAP and multi-source oracle pattern used against ordinary manipulation, but the threshold for "thin liquidity" that's exploitable is much lower than most teams assume once flash loans are in scope. We stress-test every price-dependent function against a hypothetical eight-figure flash loan before mainnet deployment, regardless of the protocol's actual TVL at launch.
Denial-of-service through gas limits is a subtler but real production issue. A function that iterates over an unbounded array — all token holders, all pending withdrawals — will eventually exceed the block gas limit as the array grows, permanently bricking that function. We avoid unbounded loops entirely in production contracts, favoring a pull-based pattern where each user claims their own funds individually rather than the contract pushing funds to everyone in a single transaction.
Upgradeable Contract Patterns
Immutable smart contracts are secure by design — what's deployed is what runs forever. But production contracts sometimes need upgrades (bug fixes, feature additions). The proxy pattern (UUPS or Transparent Proxy) separates storage (proxy contract) from logic (implementation contract). Upgrades deploy a new implementation and update the proxy's pointer. The critical security concern: storage layout compatibility between versions. Adding a new state variable in the wrong position corrupts existing storage. Tools like OpenZeppelin's Upgrades plugin validate storage layout compatibility automatically.
Front-Running, MEV, and Signature Replay
Front-running and MEV (miner/validator extractable value) affect any contract where transaction ordering matters — DEX trades, NFT mints, and auction settlements are the classic cases. A pending transaction is visible in the mempool before it's confirmed, and a validator or bot can insert their own transaction ahead of it to profit from the price impact. Mitigations include commit-reveal schemes, where users submit a hashed commitment first and reveal it in a second transaction, using a private transaction relay like Flashbots Protect to bypass the public mempool, or designing the mechanism so front-running doesn't produce an advantage in the first place, as batch auctions do.
Signature replay is the last pattern worth calling out specifically: any contract that accepts an off-chain signature — for gasless approvals, meta-transactions, or off-chain order matching — must include a nonce and, ideally, an expiration timestamp in the signed message. Without a nonce, a valid signature can be resubmitted indefinitely; we've seen this exact gap in third-party contracts we were asked to audit before a client integrated with them.
The Pre-Deployment Audit Checklist
Before any production deployment, we run through a 40-point checklist as part of our smart contract development process. The critical items: all external calls use checks-effects-interactions pattern, all access control is explicit, all arithmetic is overflow-safe, all oracle inputs use TWAP or aggregated sources, all upgradeable contracts pass storage layout validation, all functions have gas limits that prevent DoS, and all edge cases (zero addresses, empty arrays, maximum values) are tested. After internal review, we engage a professional audit firm (Trail of Bits, OpenZeppelin, or Consensys Diligence) for contracts handling more than $1M in value — standard practice across our blockchain development engagements.