Smart Contract Vulnerability Types: A Reference Guide for Web3 Teams
A clear reference of smart contract vulnerability types — from reentrancy to oracle manipulation — with how each works and how it is prevented. Built for Web3 teams.
Understanding the specific categories of smart contract vulnerabilities is the prerequisite for preventing them, auditing for them, and evaluating audit reports that reference them. This reference covers the vulnerability classes that appear most frequently in production exploits, organized by category.
Execution Flow Vulnerabilities
Reentrancy
A reentrancy attack occurs when an external call — most commonly an ETH transfer — triggers code in a recipient contract before the calling contract has finished updating its state. The recipient calls back into the original contract, finding state that still reflects the pre-call values, and exploits the discrepancy.
Prevention: Checks-Effects-Interactions (CEI) pattern — update all state before making external calls. Apply nonReentrant guards from OpenZeppelin's ReentrancyGuard on all value-transferring functions.
Denial of Service (DoS)
Several patterns cause smart contract functions to become permanently uncallable:
- A loop that iterates over an unbounded array — the gas cost eventually exceeds the block limit
- A function that depends on a
pushtransfer to a list of recipients — if any recipient reverts, the entire call fails - A function that requires an external call to succeed — if that external protocol is bricked or the call is blocked, the function is stuck
Prevention: Design for pull rather than push payments. Bound loops or paginate them. Avoid single points of external dependency in critical paths.
Arithmetic Vulnerabilities
Integer Overflow and Underflow
Before Solidity 0.8, arithmetic operations could silently wrap around — adding 1 to the maximum uint256 value produced 0. After 0.8, arithmetic reverts on overflow by default. The remaining risk is in explicit unchecked blocks, where arithmetic checks are disabled for gas savings.
Prevention: Keep unchecked blocks narrow and only around arithmetic that is demonstrably safe. Review every unchecked block in an audit.
Truncation via Type Casting
Casting a larger integer type to a smaller one silently discards the high bits. A uint256 value of 300 cast to uint8 produces 44, not 300, with no revert.
Prevention: Use OpenZeppelin's SafeCast library for all downcast operations where the value's range is not provably within bounds.
Precision Loss and Rounding
Integer division in Solidity truncates toward zero. Division before multiplication in a compound expression loses precision. In protocols where rounding direction is economically significant — fee calculations, reward distributions — accumulated rounding errors can be exploited.
Prevention: Multiply before dividing. Document intended rounding direction. For protocols where precision matters at scale, use fixed-point libraries.
Access Control Vulnerabilities
Missing Function Guards
A function that modifies sensitive state or transfers value with no access control is callable by anyone. This is one of the most frequently exploited classes in DeFi — consistently appearing in exploit post-mortems.
Prevention: Every privileged function must have an enforcing modifier. Maintain an explicit list of all restricted functions and their guards.
Unprotected Initializers
Upgradeable contracts use an initialize function instead of a constructor. Without the initializer modifier and _disableInitializers() in the implementation constructor, anyone can reinitialize the contract and take ownership.
Prevention: Use OpenZeppelin's Initializable. Always call _disableInitializers() in implementation constructors.
Unsafe Privilege Transfer
Single-step ownership transfer allows transferring control to an address without confirming that address can accept it. Sending ownership to a wrong address or a contract that cannot call acceptOwnership permanently bricks admin access.
Prevention: Use Ownable2Step for all ownership transfers.
Oracle and Price Vulnerabilities
Spot Price Manipulation
Protocols that read a DEX spot price within a transaction to determine exchange rates or collateral values can have that price manipulated within the same transaction via flash loans. An attacker borrows large amounts, moves the spot price, exploits the protocol, and repays — all in one transaction.
Prevention: Use time-weighted average prices (TWAPs) for longer windows of protection, or use aggregated off-chain oracle feeds like Chainlink. Understand the specific trade-offs of each.
Stale Oracle Data
Oracle price feeds can fail to update during periods of network congestion or oracle node issues. A protocol that consumes a stale price without checking the last-updated timestamp may liquidate positions incorrectly or accept undercollateralized positions.
Prevention: Check oracle heartbeat and last-update timestamps. Have a defined behavior for when the oracle is stale — typically a circuit breaker or pause.
Logic and Design Vulnerabilities
Front-Running
Transactions on public blockchains are visible in the mempool before they are included in a block. Attackers — or searchers running MEV bots — can observe pending transactions and submit their own transactions with higher gas fees to be processed first, extracting value.
This affects protocols with predictable economic outcomes: batch auctions, NFT mints with uniform pricing, DEX trades with large slippage.
Prevention: Commit-reveal schemes, slippage parameters on trades, batch settlement with uniform clearing prices, or relying on private mempools where applicable.
tx.origin Authorization
Using tx.origin for authorization checks whether the original signer of a transaction is a specific address, but it does not verify the immediate caller. A phishing contract can initiate a transaction through a victim's account and pass a tx.origin check.
Prevention: Always use msg.sender for authorization. tx.origin is only appropriate for specific, narrow use cases, never as a primary authorization check.
Signature Replay
If a contract accepts signed messages as authorization, those signatures can be replayed — used again in a different context or on a different chain — unless the signing scheme includes a nonce, contract address, and chain ID in the signed payload.
Prevention: Follow EIP-712 for structured data signing. Include chainId, contract address, a nonce, and a message expiry in every signed payload. Validate and increment nonces on use.
Upgradeability Vulnerabilities
Storage Collision
Upgradeable proxy contracts separate storage (in the proxy) from logic (in the implementation). If a new implementation version introduces a storage variable at a slot already used by the proxy itself, reading or writing that variable corrupts the proxy's own state — including the address of the implementation.
Prevention: Use OpenZeppelin's Upgradeable contracts and storage gap patterns. Append-only storage in upgrade paths. Run storage layout checks between implementation versions.
Delegatecall to Untrusted Contracts
delegatecall executes code from another contract in the context of the calling contract — using the calling contract's storage. A delegatecall to a malicious or compromised contract gives it full read and write access to the calling contract's state.
Prevention: Only delegatecall contracts you control and have reviewed. Never delegatecall to user-supplied addresses.
This list covers the most common classes, not every possible vulnerability. Auditors work against a more expansive mental model developed through reviewing dozens or hundreds of contracts. Using this reference for code review and test case development is useful; it does not substitute for an expert review.
Clixo engineers build Web3 protocols with these patterns in mind from the first line of code. For protocol teams that want a build partner who knows what auditors look for, reach out.