Smart Contract Security Audits — Complete Guide
This tutorial teaches you smart contract security audits — you will learn the end-to-end audit Process, how to identify common vulnerabilities using automated tools and manual review, and how to prepare your Solidity code for a professional security assessment.
Why It Matters
Smart contracts are immutable and manage real value. A single vulnerability can result in millions of dollars in losses — the DAO hack ($60M in 2016), Parity multisig freeze ($280M in 2017), and Ronin bridge exploit ($620M in 2022). Professional security audits are the primary defense against catastrophic losses.
Real-World Use
A DeFi protocol pays $100,000+ for a full audit before launching its $500M TVL pool. An NFT marketplace uses automated scanning in CI to catch regressions after every PR. A cross-chain bridge undergoes three independent audits before mainnet deployment.
flowchart LR
A[Code Complete] --> B[Automated Analysis]
B --> C[Slither + Mythril + Echidna]
C --> D[Manual Review]
D --> E[Attack Vector Analysis]
E --> F[Report Generation]
F --> G[Fix and Remediate]
G --> H[Re-audit]
H --> I[Mainnet Deploy]
style A fill:#4CAF50,color:#fff
style C fill:#FF9800,color:#fff
style D fill:#2196F3,color:#fff
style I fill:#4CAF50,color:#fff
Automated Analysis with Slither
Slither is a Static Analysis framework that detects common vulnerabilities without running the code.
# Install Slither
pip3 install slither-analyzer
# Run Slither on a Solidity file
slither src/contracts/Vault.sol --print human-summary
# Run specific detectors
slither src/contracts/ --detect reentrancy-eth,reentrancy-no-eth \
--print contract-summary
Expected output:
INFO:Slither:Running detectors...
INFO:Slither:Vault.sol analyzed (1 contracts)
Reentrancy (ETH) vulnerability detected in Vault.withdraw(uint256):
- State variables updated after external call at line 45
- Recommendation: apply Checks-Effects-Interactions pattern
Reentrancy (No ETH) vulnerability detected in Vault.collectFees():
- External call to IERC20.transfer at line 78 before state update
Slither can detect reentrancy, access control issues, uninitialized state variables, unused return values, and hundreds of other issue types in seconds.
Fuzz Testing with Echidna
Echidna is a fuzzer that generates random inputs to find invariant violations.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
contract Vault {
mapping(address => uint256) public balances;
uint256 public totalDeposits;
function deposit() public payable {
balances[msg.sender] += msg.value;
totalDeposits += msg.value;
}
function withdraw(uint256 amount) public {
require(balances[msg.sender] >= amount, "Insufficient balance");
balances[msg.sender] -= amount;
totalDeposits -= amount;
(bool sent, ) = msg.sender.call{value: amount}("");
require(sent, "Transfer failed");
}
}
// Echidna test: totalDeposits should always equal contract balance
contract VaultTest is Vault {
function echidna_test_total_deposits_match() public view returns (bool) {
return totalDeposits == address(this).balance;
}
}
# Run Echidna fuzzer
echidna-test src/test/VaultTest.sol \
--contract VaultTest \
--config echidna.yaml
Expected output:
[exploit] Echidna found an assertion violation:
Call sequence:
- VaultTest.deposit() value=100
- VaultTest.withdraw(200)
- Assertion: totalDeposits_balance_matches() failed
Reason: withdraw(200) succeeded even though the user
only deposited 100, because the test contract starts
with ETH from the test framework.
Echidna caught that the invariant fails because the test setup (funding the contract) bypasses the deposit function. This would not be caught by unit tests.
Manual Review Checklist
Automated tools cannot catch logic errors or economic attacks. Manual review is essential.
# audit_checklist.py — track manual review progress
checks = {
'Access Control': [
'Admin functions have onlyOwner or onlyRole modifier',
'No backdoor functions accessible to non-privileged users',
'Constructor properly initializes owner/roles',
'Selfdestruct cannot be called by arbitrary users'
],
'Arithmetic': [
'Solidity 0.8+ overflow checks enabled',
'Unchecked blocks explicitly justified and safe',
'Division rounding handled correctly',
'No precision loss in critical calculations'
],
'Reentrancy': [
'Checks-Effects-Interactions pattern followed everywhere',
'ReentrancyGuard used on cross-contract functions',
'No callback functions that can manipulate state',
'Flash loan attack surface analyzed'
],
'Economic': [
'Oracle price manipulation resistance',
'MEV attack surface (sandwich, frontrun) analyzed',
'Liquidation logic tested with edge cases',
'Fee calculation cannot be exploited for infinite mint'
]
}
for category, items in checks.items():
print(f"\n=== {category} ===")
for item in items:
status = input(f"[PASS/FAIL/SKIP] {item}: ") or "SKIP"
print(f" {status}: {item}")
Expected usage pattern:
=== Access Control ===
[PASS/FAIL/SKIP] Admin functions have onlyOwner modifier: PASS
[PASS/FAIL/SKIP] No backdoor functions: PASS
[PASS/FAIL/SKIP] Constructor properly initializes owner: PASS
Writing an Audit Report
const vulnerabilities = [
{
id: 'CRIT-001',
title: 'Reentrancy in withdraw()',
severity: 'Critical',
file: 'Vault.sol:42',
description: 'The withdraw() function sends ETH before updating the user balance, enabling reentrancy attack.',
impact: 'An attacker can drain the entire contract balance.',
recommendation: 'Apply the Checks-Effects-Interactions pattern: update balances[msg.sender] before transferring ETH.',
status: 'Fixed',
},
{
id: 'MED-001',
title: 'Unused return value',
severity: 'Medium',
file: 'Vault.sol:78',
description: 'Return value of IERC20.transfer() is not checked.',
impact: 'If the transfer silently fails, the accounting state diverges from actual balances.',
recommendation: 'Use OpenZeppelin\'s SafeERC20 or revert on false return.',
status: 'Acknowledged',
}
];
console.log('=== Smart Contract Audit Report ===');
console.log(`Project: DeFi Vault v1.0.0`);
console.log(`Auditor: DodaTech Security`);
console.log(`Date: 2026-06-23`);
console.log(`\nVulnerabilities Found: ${vulnerabilities.length}`);
console.log(`Critical: ${vulnerabilities.filter(v => v.severity === 'Critical').length}`);
console.log(`Medium: ${vulnerabilities.filter(v => v.severity === 'Medium').length}`);
console.log(`\n--- Findings ---`);
vulnerabilities.forEach(v => {
console.log(`\n[${v.id}] [${v.severity}] ${v.title}`);
console.log(` File: ${v.file}`);
console.log(` Impact: ${v.impact}`);
console.log(` Status: ${v.status}`);
});
Expected output:
=== Smart Contract Audit Report ===
Project: DeFi Vault v1.0.0
Auditor: DodaTech Security
Date: 2026-06-23
Vulnerabilities Found: 2
Critical: 1
Medium: 1
--- Findings ---
[CRIT-001] [Critical] Reentrancy in withdraw()
File: Vault.sol:42
Impact: An attacker can drain the entire contract balance.
Status: Fixed
[MED-001] [Medium] Unused return value
File: Vault.sol:78
Impact: If the transfer silently fails, the accounting state diverges.
Status: Acknowledged
Common Errors
- Only relying on automated tools — Tools find simple bugs but miss logic errors, economic attacks, and business logic flaws. Always add manual review.
- Auditing without tests — Auditors need test coverage to understand expected behavior. Provide unit tests, integration tests, and specification documents.
- Ignoring dependency risk — OpenZeppelin, Uniswap routers, and oracles are trusted dependencies, but version mismatches or deprecated functions introduce risk.
- Skipping invariant testing — Most critical bugs come from unexpected states, not straightforward function calls. Invariant tests (e.g., totalSupply = sum of balances) catch these.
- Fixing without re-auditing — A rushed fix can introduce new vulnerabilities. All critical fixes require re-audit before deployment.
Practice Questions
What is the difference between Static Analysis and fuzz testing? Static Analysis (Slither) examines source code patterns without execution, finding structural issues like reentrancy or missing access control. Fuzz testing (Echidna) executes the contract with random inputs to find state violations invariant tests define.
Why should a developer provide test coverage before an audit? Tests document the intended behavior. An auditor can identify where edge cases are missed by reviewing test coverage. Missing tests for liquidation thresholds, oracle price boundaries, or reentrancy scenarios highlight risk areas.
What is an economic audit and how does it differ from a code audit? A code audit checks for implementation bugs. An economic audit analyzes tokenomics, incentive alignment, oracle manipulation resistance, and MEV exposure. Economic audits require domain expertise in DeFi mechanisms, not just Solidity.
Frequently Asked Questions
{{< faq question="How much does a professional smart contract audit cost?">}} Costs vary by contract complexity and auditor reputation. A simple ERC-20 token audit starts at $5,000-$15,000. Complex DeFi protocols with multiple interacting contracts cost $50,000-$200,000+. Top-tier firms (Trail of Bits, OpenZeppelin) charge $100-$500 per hour. Consider community audits (Code4rena, Sherlock) for cost-effective alternatives — they pay finders fees rather than charging upfront. {{< /faq >}}
{{< faq question="How long does a typical audit take?">}} Small contracts (1-2 files, under 500 lines) take 1-2 weeks. Medium projects (5-10 files, 500-2000 lines) take 2-4 weeks. Large protocols (multiple interacting contracts, 2000+ lines) take 4-8 weeks. The timeline includes automated analysis, manual review, remediation, and re-audit. Rushed audits are dangerous — schedule audits before your planned launch date. {{< /faq >}}
{{< faq question="Can I bypass a full audit if my contract is simple?">}}
Simple contracts have fewer attack surfaces but are not immune. A token with a single transfer function still needs access control review — a missing onlyOwner modifier on mint could let anyone create unlimited tokens. At minimum, use automated analysis tools (Slither, Mythril) in CI and publish the results. If user funds are involved, a professional audit is strongly recommended regardless of contract size.{{< /faq >}}
Next Steps
Testing Smart Contracts with Hardhat — Write comprehensive tests before your audit.
Solidity Design Patterns — Apply secure patterns that minimize audit findings.
Cross-Chain Bridges and Interoperability — Understand additional attack surfaces in bridge architectures.
Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro