Deploying a smart contract to mainnet is a one-way door. Unlike traditional software, you can't push a hotfix if a vulnerability is exploited. Every DeFi protocol that lost millions learned the same lesson the hard way.
This checklist covers the 10 most common (and most costly) vulnerability classes every Solidity developer should verify before going live. For each one, we explain what to look for and how automated scanning can help catch it.
1. Reentrancy
What it is: An external contract call is made before internal state is updated, allowing the callee to re-enter the function and drain funds.
Classic pattern:
// VULNERABLE
function withdraw(uint256 amount) external {
require(balances[msg.sender] >= amount);
(bool ok,) = msg.sender.call{value: amount}(""); // external call first
balances[msg.sender] -= amount; // state updated after
}
Fix: Follow the Checks-Effects-Interactions (CEI) pattern. Update state before making any external call.
Detection: Slither's reentrancy-eth and reentrancy-no-eth detectors catch this reliably. Mythril also models it symbolically.
2. Access Control Missing or Misconfigured
What it is: Sensitive functions (mint, pause, upgradeProxy, emergencyWithdraw) lack onlyOwner, onlyRole, or equivalent guards.
Common mistake:
// Anyone can call this
function setTreasury(address newTreasury) external {
treasury = newTreasury;
}
Fix: Use OpenZeppelin's AccessControl or Ownable. Explicitly restrict privileged functions. Consider a multi-sig or timelock for critical operations.
Detection: Slither's missing-zero-check and Semgrep rules for unguarded state-changing functions. AI analysis is particularly effective here for finding logical access control gaps that pattern-matching misses.
3. Integer Overflow / Underflow
What it is: In Solidity < 0.8, arithmetic wraps silently. uint256(0) - 1 becomes 2^256 - 1.
Pre-0.8 risk:
// Solidity 0.7 — wraps to 2^256-1
uint256 public balance;
function decrement() external {
balance -= 1; // underflows if balance == 0
}
Fix: Upgrade to Solidity ≥ 0.8 (overflow/underflow reverts by default), or use SafeMath on older versions.
Detection: Slither's integer-overflow-and-underflow detectors. Also flagged by Aderyn on older compiler targets.
4. Uninitialized Storage Pointers
What it is: A local struct declared without memory or storage keyword points to slot 0 by default, potentially overwriting critical state.
Fix: Always specify memory or storage explicitly for struct/array local variables.
Detection: Slither's uninitialized-storage detector.
5. Tx.origin Authentication
What it is: Using tx.origin for authorization instead of msg.sender makes the contract vulnerable to phishing attacks where a malicious contract relays a transaction.
// VULNERABLE
require(tx.origin == owner, "Not authorized");
// CORRECT
require(msg.sender == owner, "Not authorized");
Detection: Semgrep and Slither both flag tx.origin usage in authentication context.
6. Oracle Price Manipulation
What it is: Relying on a single DEX spot price (e.g., a Uniswap v2 reserve ratio) as a price oracle. Attackers use flash loans to manipulate the price within a single transaction.
High-risk pattern:
// Single-source spot price — manipulable with flash loan
function getPrice() public view returns (uint256) {
(uint112 reserve0, uint112 reserve1,) = pair.getReserves();
return reserve1 * 1e18 / reserve0;
}
Fix: Use TWAP (Time-Weighted Average Price) oracles, Chainlink price feeds, or multi-source aggregation with deviation checks.
Detection: AI-based analysis is best here — static analyzers often miss the semantic risk. Semgrep rules can flag getReserves() calls used for pricing.
7. Unchecked Return Values
What it is: ERC-20 transfer() and approve() don't always revert on failure — some return false instead. Ignoring the return value means a silent failure.
// VULNERABLE — return value ignored
token.transfer(recipient, amount);
// CORRECT
require(token.transfer(recipient, amount), "Transfer failed");
// Or use SafeERC20 from OpenZeppelin
Fix: Use OpenZeppelin's SafeERC20 wrapper, which handles both reverting and non-reverting ERC-20 implementations.
Detection: Slither's unchecked-transfer detector.
8. Delegate Call to Untrusted Contracts
What it is: delegatecall executes code from an external address in the context of the calling contract — using its storage, ETH balance, and msg.sender. Passing user-controlled addresses to delegatecall is extremely dangerous.
Fix: Never delegatecall to addresses that aren't trusted, immutable, or validated. In upgradeable proxies, ensure only authorized parties can change the implementation address.
Detection: Slither's controlled-delegatecall and delegatecall-loop detectors.
9. Timestamp Dependence
What it is: Using block.timestamp for randomness, lock periods, or sensitive logic. Miners (or validators post-merge) can manipulate timestamps within a ~15 second window.
Risky pattern:
// block.timestamp can be slightly manipulated
require(block.timestamp >= unlockTime, "Too early");
uint256 random = uint256(keccak256(abi.encodePacked(block.timestamp, msg.sender)));
Fix: For randomness, use Chainlink VRF. For time-based locks, the 15-second manipulation window is usually acceptable for durations > 1 minute, but never rely on it for precise timing.
Detection: Slither's timestamp detector. AI analysis can identify semantic risk (e.g., is the timestamp used for randomness vs. a reasonable time gate?).
10. Front-Running (MEV) Vulnerabilities
What it is: Transactions in the mempool are public. Attackers (or bots) can observe your transaction and submit their own with higher gas, executing first. This affects token swaps, auctions, NFT mints, and commit-reveal schemes.
Common targets:
- Slippage-free swap calls
- Dutch auction bids
- On-chain reveal of secrets hashed in commit phase
Fix: Implement slippage parameters (minAmountOut), use commit-reveal patterns, or consider private mempools (Flashbots Protect) for sensitive transactions.
Detection: Primarily AI and manual review territory — static analysis has limited visibility into economic MEV dynamics.
How to Run This Checklist
Manually reviewing all 10 categories for a non-trivial contract is time-consuming and error-prone. A good approach:
- Automated scan first — run Slither, Semgrep, Mythril, and Aderyn to catch the mechanical issues (reentrancy, integer overflow, unchecked returns, tx.origin, etc.)
- AI analysis — for semantic issues like oracle manipulation and business logic flaws that pattern-matching misses
- Manual review — especially for access control logic and MEV exposure
- Test suite — property-based and fuzz testing with Foundry or Hardhat
ContractScan runs all five engines (Slither, Semgrep, Mythril, Aderyn, AI) in parallel and produces a structured vulnerability report with severity ratings and suggested fixes. A full scan covers all 10 categories above in under two minutes — no setup, no CLI, no local Solidity toolchain required.
Quick Reference Table
| # | Vulnerability | Severity | Auto-Detectable |
|---|---|---|---|
| 1 | Reentrancy | Critical | Yes (Slither, Mythril) |
| 2 | Missing Access Control | High | Yes (Slither, Semgrep, AI) |
| 3 | Integer Overflow/Underflow | High | Yes (Slither, Aderyn) |
| 4 | Uninitialized Storage | High | Yes (Slither) |
| 5 | Tx.origin Auth | Medium | Yes (Slither, Semgrep) |
| 6 | Oracle Manipulation | Critical | Partial (AI, Semgrep) |
| 7 | Unchecked Return Values | Medium | Yes (Slither) |
| 8 | Dangerous Delegatecall | Critical | Yes (Slither) |
| 9 | Timestamp Dependence | Low-Med | Yes (Slither) |
| 10 | Front-Running / MEV | Varies | Partial (AI, manual) |
Deploying to mainnet before running this checklist is like shipping without tests. Even if you pass every item manually, a second pair of automated eyes is worth the five minutes it takes — because attackers only need to find one gap you missed.
Important Notes
This post is for informational and educational purposes only. It does not constitute financial, legal, or investment advice. The security analysis provided is based on available data and automated tools, which may not capture all potential vulnerabilities. Always conduct a professional audit before deploying smart contracts.