Smart contracts automate value transfer with transparent rules, yet small design mistakes can cause large losses. This guide explains the Top 10 Smart Contract Security Vulnerabilities with clear language for beginners and enough depth for advanced readers. You will learn how each weakness appears, why attackers target it, and the practical steps to reduce risk without blocking product goals. We cover code patterns, protocol economics, and operational controls that work together as a complete security program. Use these sections as a checklist during design reviews, audits, and incident drills so your teams ship safer upgrades and maintain user trust.
#1 Reentrancy and external call risks
Reentrancy happens when a contract sends control to an external address that calls back before state is updated. This lets an attacker withdraw many times or bypass limits. Typical triggers include sending value before reducing balances, and calling untrusted contracts inside loops. Defend with the checks effects interactions pattern, pull based withdrawals, and a reentrancy guard. Place state updates before external calls and validate return values. Limit gas forwarded and avoid callbacks when not required. Use audits and property tests to verify no path allows reentry, and document integration assumptions for every external call.
#2 Arithmetic overflows and underflows
Arithmetic bugs can change balances, bypass caps, or freeze accounts. Older compilers allowed silent wraparound, while modern versions revert on overflow but still risk rounding errors and unsafe casts. Pin the compiler version and prefer libraries that implement checked math for ints and decimals. Define units for every variable, such as token amounts, basis points, or wad precision, and convert in one place. Validate user inputs and enforce minimums and maximums. Avoid mixing token decimals and percentage math in the same expression. Add invariant tests that compare supply, balances, and fees so math remains consistent under stress.
#3 Access control and authorization flaws
Many hacks begin with missing or misconfigured roles that grant powerful actions to any caller. Protect sensitive functions such as upgrades, pausing, minting, and parameter changes with least privilege roles. Separate owner, admin, and operator duties, and require multisig approvals for critical changes. Use timelocks so the community can review upgrades before they take effect. Lock initializer functions in upgradeable contracts and restrict only once setup flows. Emit events for all administrative actions and monitor them. Regularly rotate keys, use hardware wallets, and restrict emergency controls to scoped actions with clear rollback plans.
#4 Oracle manipulation and price feed issues
Protocols that trust a single, thinly traded price can be manipulated during short windows. Attackers push the price with swaps or loans, then trigger liquidations or mint against the fake rate. Use time weighted averages, medianized multi source feeds, and deviation checks before using a fresh update. Prefer independent providers with heartbeat and threshold settings that you can verify on chain. Cap per transaction price impact and add circuit breakers when deviation exceeds limits. For long tail assets, lengthen windows or require multiple confirmations. Test oracle outages and stale data so the protocol fails safe during stress.
#5 Front running and MEV extraction
Because pending transactions are public, adversaries can reorder or insert their own to capture value. Common attacks include sandwiching swaps, backrunning liquidations, and priority gas bidding races. Reduce exposure with commit reveal flows, sealed bid auctions, or batch matching that randomizes settlement. Support private order routing when appropriate and avoid revealing sensitive values on chain before finalization. Require slippage limits and deadlines on user operations. For oracle updates, liquidations, or arbitrage hooks, restrict who can call functions or gate them through auctions. Monitor mempool patterns and simulate routes so new features do not create easy extraction points.
#6 Denial of service and griefing vectors
DoS bugs let an attacker block progress or raise costs for honest users. Causes include unbounded loops, storage bloat, revert on callback patterns, and reliance on a single keeper. Keep loops bounded, avoid iterating over dynamic arrays in critical paths, and paginate operations. Prefer pull based collection to reduce forced sends. Design alternative completion paths when external calls fail, and add retries with backoff. Cap storage growth and add incentives to clean abandoned positions. Introduce rate limits and per account quotas. Use gas efficient patterns and test worst case datasets so functions remain callable under load.
#7 Upgradeability and proxy misconfigurations
Proxy patterns allow fixes and features without moving user deposits, but they add new failure modes. Unprotected initialize functions and incorrect admin slots can hand control to attackers. Follow reviewed standards, separate admin and implementation, and lock initialization after deployment. Add access controlled upgrade functions behind a timelock and publish audit trails on chain. Verify storage layout compatibility before every upgrade and include gap variables for future fields. Write migration tests that persist state across versions. Document who can upgrade, how alerts are posted, and how to roll back safely if a release misbehaves.
#8 Signature replay and permit misuse
Off chain approvals reduce gas cost, yet weak nonce rules or domain separation allow replay across chains or deployments. Implement EIP 712 typed data and include chain id, contract address, and user specific nonces in the digest. Invalidate signatures on first use and set reasonable expiration times. Harden relayer logic to prevent reordering or reuse of authorizations. Validate recovered signer, allowance, and deadline checks on chain. Use tested ECDSA libraries that handle malleability. Publish clear wallet prompts that show what is being approved and provide an easy way to revoke outstanding permits.
#9 Insecure randomness and predictability
Random values that depend on blockhash, timestamp, or balances are predictable and sometimes controllable by validators. For lotteries, mints, and allocations, use verifiable randomness from a dedicated provider or combine user commitments with delayed reveals. Delay settlement until randomness is finalized and verified. Prevent early reveals and duplicates with commitments bound to senders. For cross chain systems, verify proofs before consuming results. Model failure cases when randomness is unavailable and decide whether to pause, refund, or fall back. Include tests that simulate miner influence and confirm that no caller can bias outcomes within reasonable cost.
#10 Gas griefing and economic design flaws
Secure code can still fail if incentives are wrong or if gas costs make rescue actions impossible. Attackers exploit underpriced operations, refund mechanics, and fee sharing to drain or stall systems. Use bounded complexity so worst case paths remain callable. Price operations according to storage and computation, and avoid designs that depend on refunds or selfdestruct. Simulate markets with adversarial agents to test equilibria under stress. Add cooldowns that slow risky actions and fair fees that discourage spam without blocking real users. Publish runbooks for recovery so responders can act quickly during volatile conditions.