Smart Contract Security Best Practices
Immutable code managing billions of dollars requires a fundamentally different security approach. This guide covers the most critical smart contract vulnerabilities and how to prevent them.

Why Smart Contract Security Is Uniquely Hard
Smart contracts are programs deployed to a blockchain. They are immutable (in most cases), publicly readable, and manage real economic value — often enormous amounts of it. When they fail, the failure is instant, transparent, and frequently irreversible.
The combination of these properties creates a security environment unlike any other. There are no patches. There is no rollback (except on chains with upgrade mechanisms, which introduce their own risks). The attacker can read your code before attacking it. And the reward for finding a vulnerability can be worth hundreds of millions of dollars.
DeFi exploits have cost the industry over $7 billion since 2020. The vast majority trace to a small set of well-understood vulnerability classes. Understanding them is the foundation of writing secure contracts.
The Most Critical Vulnerability Classes
Reentrancy
Reentrancy is the vulnerability that enabled the 2016 DAO hack — the most consequential smart contract exploit in history. It occurs when a contract makes an external call before updating its internal state, allowing the called contract to re-enter the original function and drain funds.
Vulnerable pattern:
function withdraw(uint256 amount) external {
require(balances[msg.sender] >= amount);
// External call before state update — VULNERABLE
(bool success, ) = msg.sender.call{value: amount}("");
require(success);
balances[msg.sender] -= amount; // Too late
}
Secure pattern (checks-effects-interactions):
function withdraw(uint256 amount) external {
require(balances[msg.sender] >= amount);
balances[msg.sender] -= amount; // Update state first
(bool success, ) = msg.sender.call{value: amount}("");
require(success);
}
Always follow the checks-effects-interactions pattern. Consider OpenZeppelin's ReentrancyGuard for additional protection.
Integer Overflow and Underflow
Prior to Solidity 0.8.0, arithmetic operations could silently overflow or underflow. A balance of 0 minus 1 would wrap to 2^256 - 1.
If you are working with Solidity 0.8.0+, overflow and underflow revert by default. For older contracts or unchecked blocks, use OpenZeppelin's SafeMath library.
Always audit unchecked arithmetic blocks specifically — they are a common source of subtle bugs even in modern contracts.
Access Control Failures
Many exploits trace to missing or incorrect access control. Common patterns:
- Missing
onlyOwnermodifiers on privileged functions - Improper role initialization (deployer forgetting to set themselves as admin)
- Transferring ownership to address(0) by mistake
- Publicly callable functions intended to be internal
Use OpenZeppelin's Ownable and AccessControl contracts. Audit every externally callable function: who should be allowed to call it? What are the consequences of an unauthorized call?
Flash Loan Attacks
Flash loans allow anyone to borrow enormous sums of capital within a single transaction, with no collateral, as long as the funds are repaid by the end of the transaction. They are legitimate DeFi primitives — and devastating attack amplifiers.
Flash loans enable attackers to:
- Manipulate price oracles that rely on spot prices
- Meet minimum balance requirements for governance votes
- Trigger under-collateralized liquidations
Defense: Never rely on spot prices from AMMs as oracles. Use time-weighted average prices (TWAPs) or decentralized oracle networks like Chainlink. Never allow governance actions within the same block as flash loan borrowing.
Oracle Manipulation
Price oracles are critical infrastructure for DeFi protocols. They are also a frequent attack vector.
On-chain oracles that read prices from a single DEX pool are vulnerable to manipulation within a single transaction. An attacker with sufficient capital (often borrowed via flash loan) can move the pool price, trigger a protocol action at the manipulated price, and profit.
Use Chainlink or TWAP oracles for price data. For TWAP oracles, longer averaging windows provide more manipulation resistance at the cost of price freshness.
Front-Running and MEV
Ethereum's mempool is public. Any pending transaction can be observed before it is included in a block. Miners (or validators) and MEV bots can reorder, insert, or suppress transactions to extract value.
Common MEV forms:
- Sandwich attacks: A bot sees your DEX trade, places a buy order before it (driving price up), lets your trade execute at the higher price, then sells.
- Front-running: Bot copies a profitable transaction and submits it with higher gas priority.
- Back-running: Bot inserts a transaction immediately after a known event (arbitrage after a price update).
Complete MEV prevention is impossible on public blockchains. Mitigations include commit-reveal schemes for sensitive operations, slippage limits for trades, and using private mempools (Flashbots Protect, MEV Blocker).
Signature Replay Attacks
If your contract validates off-chain signatures, ensure each signature can only be used once and only on the intended chain.
Include in every signed message:
- The contract address
- The chain ID
- A nonce (per-user counter or timestamp-based)
Use EIP-712 for structured data signing and EIP-2612 for permit-based approvals.
Delegatecall Vulnerabilities
delegatecall executes another contract's code in the calling contract's storage context. It is the mechanism behind upgradeable proxy patterns — and a significant footgun.
Common issues:
- Uninitialized proxy implementations that can be self-destructed
- Storage layout collisions between proxy and implementation contracts
- Malicious implementations (in cases where implementation can be set by untrusted parties)
If using upgradeable contracts, follow the transparent proxy or UUPS patterns from OpenZeppelin. Ensure implementation initialization functions are called once and are access-controlled.
Secure Development Practices
Use Audited Libraries
Do not write your own ERC-20, ERC-721, or access control implementations. Use OpenZeppelin's battle-tested contracts. Complexity is the enemy of security — minimize it.
Formal Verification
For high-value contracts, formal verification — mathematically proving that code satisfies specified properties — provides the strongest security guarantees. Tools include:
- Certora Prover: Specification language for EVM contracts
- Halmos: Symbolic testing via fuzzing
- Echidna: Property-based fuzzing
Formal verification is expensive but warranted for core protocol logic.
Testing: Beyond Unit Tests
Unit tests verify that your code does what you think. They do not verify that what you think is correct.
- Fuzzing: Automatically generate inputs to discover edge cases. Foundry's built-in fuzzer is excellent.
- Invariant testing: Define properties that should always hold ("total supply equals sum of all balances") and verify them under adversarial conditions.
- Fork tests: Test against mainnet state to simulate realistic conditions including interactions with live protocols.
Pre-Deployment Checklist
Before deploying to mainnet:
- [ ] All tests passing, including fuzz and invariant tests
- [ ] Static analysis with Slither (run
slither . --print human-summary) - [ ] Manual review of all external calls
- [ ] Review of all access-controlled functions
- [ ] Review of all arithmetic, especially in
uncheckedblocks - [ ] Oracle and price feed review
- [ ] Emergency pause mechanism (if appropriate)
- [ ] At least one independent security audit
- [ ] Bug bounty program launched
Security Audits
A security audit is not a guarantee of safety — it is a systematic review by experienced eyes. The best audits combine automated scanning with deep manual analysis by experts who understand both Solidity semantics and DeFi mechanics.
When selecting an auditor:
- Review their published audit reports (quality and depth matter, not just audit count)
- Ensure they have experience with your specific protocol type
- Provide a complete test suite — auditors should not be writing your tests
- Allow adequate time (rushing audits produces low-quality results)
- Address all findings before deployment, not after
Emergency Preparedness
Even well-audited protocols get exploited. Have a plan:
- Pause mechanisms: Can you halt the protocol if an attack is detected?
- Emergency withdrawal: Can users exit even if the protocol is paused?
- Incident response contacts: Who do you call? Major security firms offer emergency response retainers.
- Post-incident process: White-hat rescue, community communication, post-mortem analysis
Conclusion
Smart contract security requires constant vigilance. The vulnerability classes documented here have been known for years, yet protocols continue to be drained because of them. Discipline in applying known best practices is the primary defense.
Write less code. Use audited libraries. Test comprehensively. Get audited by experts. Deploy with humility.
Black Unicorn Security provides smart contract audits and Web3 security assessments. Our team combines deep Solidity knowledge with adversarial security expertise.