Smart contract security and auditing — reentrancy, overflow, access control with vulnerable code demos
- Reentrancy: always update state before external calls AND use OpenZeppelin's ReentrancyGuard — both together.
- Never use DEX spot prices as oracles — use Chainlink or TWAP for manipulation resistance.
- Run Slither + Mythril + fuzz tests as free baseline; get a professional audit for anything handling real funds.
Smart contract security is the most critical skill in blockchain development. Unlike web apps where you can push a hotfix, deployed smart contracts are immutable — bugs become permanent, and exploits can drain millions in a single transaction.
This lesson walks through the most exploited vulnerability patterns with code you can study, test, and learn to prevent.
Smart Contract Security: Reentrancy: the $60M vulnerability
Reentrancy caused the DAO hack in 2016 ($60M lost, led to the Ethereum/Ethereum Classic fork). It exploits the fact that sending ETH triggers code execution in the receiving contract:
// VULNERABLE — sends ETH before updating state
contract VulnerableVault {
mapping(address => uint256) public balances;
function deposit() public payable {
balances[msg.sender] += msg.value;
}
function withdraw() public {
uint256 bal = balances[msg.sender];
require(bal > 0);
(bool ok, ) = msg.sender.call{value: bal}("");
require(ok);
balances[msg.sender] = 0; // TOO LATE — attacker re-entered
}
}
// ATTACKER — re-enters withdraw() via receive()
contract Attacker {
VulnerableVault vault;
constructor(address v) { vault = VulnerableVault(v); }
function attack() external payable {
vault.deposit{value: 1 ether}();
vault.withdraw();
}
receive() external payable {
if (address(vault).balance >= 1 ether) {
vault.withdraw(); // balance still shows 1 ETH!
}
}
}
// FIXED — Checks-Effects-Interactions + reentrancy lock
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
contract SafeVault is ReentrancyGuard {
mapping(address => uint256) public balances;
function withdraw() public nonReentrant {
uint256 bal = balances[msg.sender];
require(bal > 0);
balances[msg.sender] = 0; // state BEFORE call
(bool ok, ) = msg.sender.call{value: bal}("");
require(ok);
}
}Always combine both defenses: update state before external calls (Checks-Effects-Interactions) AND use OpenZeppelin's ReentrancyGuard.
Oracle manipulation and price attacks
DeFi protocols relying on spot prices for oracles can be drained with flash loans:
// VULNERABLE: uses DEX spot price (manipulable with flash loans)
contract VulnerableLending {
IUniswapPair pair;
function getPrice() public view returns (uint256) {
(uint112 r0, uint112 r1, ) = pair.getReserves();
return (uint256(r1) * 1e18) / uint256(r0); // spot price!
}
function borrow(uint256 collateral) public {
uint256 price = getPrice(); // attacker inflated this
uint256 limit = collateral * price / 1e18;
// Attacker borrows max, price reverts, protocol drained
}
}
// SAFE: Chainlink oracle with staleness check
import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol";
contract SafeLending {
AggregatorV3Interface immutable priceFeed;
uint256 constant STALENESS = 3600; // 1 hour max
constructor(address feed) {
priceFeed = AggregatorV3Interface(feed);
}
function getPrice() public view returns (uint256) {
(, int256 price, , uint256 updatedAt, ) = priceFeed.latestRoundData();
require(price > 0, "Invalid price");
require(block.timestamp - updatedAt < STALENESS, "Stale price");
return uint256(price);
}
}Never use DEX spot prices as oracles. Use Chainlink, Uniswap TWAP, or other manipulation-resistant feeds.
Access control and logic bugs
// VULNERABLE: anyone can mint unlimited tokens
contract VulnerableToken {
mapping(address => uint256) public balanceOf;
uint256 public totalSupply;
function mint(address to, uint256 amount) public {
// BUG: no access control! Anyone can call this
balanceOf[to] += amount;
totalSupply += amount;
}
}
// FIXED: proper role-based access control
import "@openzeppelin/contracts/access/AccessControl.sol";
contract SafeToken is AccessControl {
bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
mapping(address => uint256) public balanceOf;
uint256 public totalSupply;
constructor() {
_grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
_grantRole(MINTER_ROLE, msg.sender);
}
function mint(address to, uint256 amount) public onlyRole(MINTER_ROLE) {
balanceOf[to] += amount;
totalSupply += amount;
}
}In 2022, a cross-chain bridge lost $325M because a key verification function was accidentally left public. Always audit every external/public function for proper access control.
Explore Crypto Trading
Cryptocurrency trading is one of the fastest-growing financial markets — open 24/7, globally accessible, and full of opportunity for those who learn the fundamentals first.
- Trade spot, futures & copy-trading on one platform
- Start with as little as $10 — practice risk-free on the testnet
- Built-in TradingView charts, grid bots & DCA tools
Security audit checklist and tools
Before deploying any contract to mainnet:
- Run Slither static analysis:
slither .— catches common vulnerability patterns automatically - Run Mythril symbolic execution:
myth analyze contract.sol— finds reentrancy, overflows, and more - Write fuzz tests with Foundry: throw random inputs at every function to find edge cases
- Manual review: read every function with an attacker mindset — what if msg.sender is a contract? What if values are zero? Max uint?
- Check all external calls follow Checks-Effects-Interactions pattern
- Verify every admin function has proper access control (onlyOwner, onlyRole, etc.)
- Ensure events are emitted for all state changes (enables monitoring)
- Test with multiple accounts in different orderings and edge cases
- Get a professional audit for anything handling real money ($10K-$500K depending on complexity)
# Install Slither
pip3 install slither-analyzer
# Run on your project
cd my-token
slither .
# Output shows:
# - Reentrancy vulnerabilities
# - Unused return values
# - Missing zero-address checks
# - Dangerous delegatecall usage
# - Low-level calls without checks
# Color-coded by severity: red (high), yellow (medium), green (info)When evaluating smart contract security, start with the fundamentals: what problem it solves, who it serves, and how it compares to the alternatives buyers consider first.
Start with the fundamentals. Define who you are serving, the single outcome you want, and the budget you are comfortable committing before you compare any options.
A practical rule is to favour clarity over cleverness. The choice that is easiest to understand, spell, remember and act on tends to win over the long run.
Context changes the answer. What is optimal for a fast-moving startup is rarely optimal for an established brand, so weigh your own stage, market and ambitions.
Avoid the common mistakes: rushing the decision, copying competitors blindly, and ignoring the total cost of ownership beyond the very first year of use.
Think in terms of trade-offs rather than a single perfect answer. Every option trades memorability, cost, reach and flexibility in a slightly different mix.
Document your reasoning. Writing down why you chose one path makes it far easier to revisit the decision later and to explain it to partners and investors.
Test with real people. A quick check with a handful of your actual customers will surface problems that no amount of internal debate ever reveals.
Plan for growth. The option that fits today should still make sense when your audience, catalogue or team is several times larger than it is right now.
Finally, revisit the decision periodically. Markets, tools and best practices move quickly, and a choice that was ideal last year may deserve a fresh look today.
WSOT 2026: Squad Leader Slots Are Open — Up to $128,000 per Leader
Join NowFrequently asked questions
How much does a smart contract audit cost?
Simple contracts: $5,000-$20,000 from smaller firms. Complex DeFi protocols: $50,000-$500,000+ from top firms (Trail of Bits, OpenZeppelin, Spearbit). Audit competitions on Code4rena offer crowd-sourced auditing for $50K-$200K.
Does an audit guarantee safety?
No. Audits significantly reduce risk but are not guarantees. Many exploited protocols had been audited. The vulnerability may have been in a dependency, an upgrade, or an economic design flaw. Multiple independent audits are better than one.
What free tools should every developer use?
Slither (static analysis), Mythril (symbolic execution), Foundry fuzzing, and Echidna (property testing) are all free and open-source. They catch the majority of common vulnerabilities and should be part of every developer workflow.
Free Courses — Learn New Skills
No sign-up · No fees · Learn at your own pace
Start Trading
Trade crypto and premium domains — digital assets with real opportunities.
📝 Need a custom article?
Our editorial team writes it for you — carefully crafted, SEO-optimized, about your topic, your domain name or your service. Multiple languages available. From $15 per language.
Find your name on Atom
DominantBrand curates the best premium, brandable names from Atom.com — the marketplace with a free AI appraisal, a USPTO trademark check, and secure escrow. Every listing even ships with a designed logo.