# Building Your First Smart Contract Audit Report — A Step-by-Step Guide
Whether you're transitioning into security auditing or a developer preparing for an audit, understanding how to structure and write an audit report is essential. This guide walks you through creating a professional-grade audit report from scratch.
What Is an Audit Report?
A smart contract audit report is a comprehensive document that:
Top audit firms like Trail of Bits, OpenZeppelin, and Cyfrin publish reports that become public trust signals.
Report Structure
A professional audit report follows this structure:
1. Executive Summary
Scope & Methodology
Severity Classification
Findings
- Critical
- High
- Medium
- Low
- Informational / Gas Optimizations
Recommendations
Appendices
- Tools Used
- Test Results
- Code Quality Assessment
Let's build each section.
1. Executive Summary
The executive summary is for non-technical stakeholders (founders, investors, community).
Example:
## Executive Summary
Project: DeFiVault Protocol
Audit Date: March 15-25, 2026
Auditor: [Your Name / Firm]
Commit Hash: abc123def456
Overview
DeFiVault is a yield aggregation protocol that automatically compounds rewards across multiple DeFi protocols. The audit covered 8 smart contracts totaling 1,247 lines of Solidity code.
Key Findings
- Critical: 1 finding (reentrancy in withdraw function)
- High: 2 findings (access control, oracle manipulation)
- Medium: 4 findings (gas griefing, precision loss)
- Low: 6 findings (missing events, naming conventions)
- Informational: 12 gas optimizations
Recommendations
Immediate Action Required: Fix critical reentrancy vulnerability before deployment
Before Mainnet: Address all High severity findings
Post-Deployment: Implement Medium findings in next version
Future Improvements: Consider gas optimizations for user experience
Risk Assessment
Current Risk Level: HIGH
Risk After Fixes: MEDIUM
The protocol should NOT be deployed until the Critical and High severity issues are resolved.
2. Scope & Methodology
Define what was audited and how.
Example:
## Scope
In-Scope Contracts
| Contract | Lines of Code | Purpose |
|----------|---------------|---------|
| Vault.sol | 234 | Main vault logic |
| Strategy.sol | 189 | Strategy interface |
| RewardDistributor.sol | 156 | Reward distribution |
| GovernanceToken.sol | 98 | Protocol governance |
| ... | | |
Total: 1,247 lines of Solidity
Solidity Version: 0.8.20
Repository: https://github.com/project/defi-vault
Commit: abc123def456
Out of Scope
- Frontend / off-chain components
- Third-party integrations (assumed secure)
- Economic attack vectors
- Centralization risks (acknowledged by team)
Methodology
Manual Review
- Line-by-line code review
- Architecture analysis
- Access control verification
- Business logic validation
Automated Analysis
- Slither: Static analysis
- Aderyn: Rust-based analyzer
- Mythril: Symbolic execution
- Echidna: Fuzzing
Testing
- Foundry test suite review (92% coverage)
- Custom exploit POCs for findings
- Mainnet fork testing
Timeline
- March 15-18: Automated scanning & test review
- March 19-22: Manual review
- March 23-24: POC development
- March 25: Report writing
3. Severity Classification
Establish a clear severity system.
Example:
## Severity Classification
We use the CVSS-inspired classification system:
Critical
- Impact: Direct loss of funds or protocol brick
- Likelihood: Easily exploitable
- Examples: Reentrancy allowing fund drainage, unauthorized mint
High
- Impact: Significant fund loss or protocol disruption
- Likelihood: Exploitable with moderate effort
- Examples: Access control bypass, oracle manipulation
Medium
- Impact: Limited fund loss or degraded functionality
- Likelihood: Requires specific conditions
- Examples: Precision loss, gas griefing, MEV vulnerabilities
Low
- Impact: Minimal user impact
- Likelihood: Edge cases or theoretical
- Examples: Missing events, poor error messages
Informational
- Impact: Code quality, gas optimization, best practices
- Likelihood: N/A
- Examples: Magic numbers, redundant checks
4. Findings Format
Each finding should follow a consistent template:
## [C-1] Reentrancy Vulnerability in withdraw() Allows Fund Drainage
Severity: Critical
Status: Unresolved
File: src/Vault.sol
Lines: 145-152
Description
The withdraw() function makes an external call before updating the user's balance, allowing a reentrancy attack that can drain all vault funds.
Vulnerable Code
solidity
function withdraw(uint256 amount) external {
require(balances[msg.sender] >= amount, "Insufficient balance");
// VULNERABLE: external call before state update
(bool success, ) = msg.sender.call{value: amount}("");
require(success, "Transfer failed");
// State updated AFTER external call
balances[msg.sender] -= amount;
totalDeposits -= amount;
}
### Attack Scenario
Attacker deposits 1 ETH
Attacker calls withdraw(1 ether)
In the receive function, attacker calls withdraw(1 ether) again
Since balance hasn't been updated yet, the check passes
Repeat until vault is drained
Proof of Concept
solidity
contract ReentrancyAttack {
Vault public vault;
uint256 public attackCount;
function attack() external payable {
vault.deposit{value: 1 ether}();
vault.withdraw(1 ether);
}
receive() external payable {
if (attackCount < 10 && address(vault).balance >= 1 ether) {
attackCount++;
vault.withdraw(1 ether);
}
}
}
Test Output:
Vault balance before: 100 ETH
Vault balance after: 0 ETH
Attacker profit: 100 ETH
### Impact
Complete loss of all funds in the vault. Given the vault currently holds $2.5M in TVL on testnet, this represents total protocol failure.
Recommendation
Implement the checks-effects-interactions pattern:
solidity
function withdraw(uint256 amount) external {
require(balances[msg.sender] >= amount, "Insufficient balance");
// Update state BEFORE external call
balances[msg.sender] -= amount;
totalDeposits -= amount;
// External call AFTER state updates
(bool success, ) = msg.sender.call{value: amount}("");
require(success, "Transfer failed");
emit Withdrawal(msg.sender, amount);
}
Alternatively, use OpenZeppelin's ReentrancyGuard:solidity
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
contract Vault is ReentrancyGuard {
function withdraw(uint256 amount) external nonReentrant {
// ... rest of code
}
}
### References
Client Response
[To be filled after remediation]
Real-World Finding Examples
High Severity: Access Control Bypass
## [H-1] Missing Access Control Allows Anyone to Update Oracle Price
Severity: High
File: src/PriceOracle.sol
Lines: 67-72
Description
The updatePrice() function lacks access control, allowing any address to manipulate price feeds.
Vulnerable Code
solidity
function updatePrice(address token, uint256 price) external {
prices[token] = price;
lastUpdate[token] = block.timestamp;
emit PriceUpdated(token, price);
}
### Impact
Attacker can:
Set artificially low prices to drain vault during withdrawals
Set artificially high prices to borrow beyond collateral value
Cause liquidations by manipulating collateral prices
Recommendation
Add onlyOwner or role-based access control:
solidity
function updatePrice(address token, uint256 price) external onlyOracle {
require(price > 0, "Invalid price");
require(block.timestamp >= lastUpdate[token] + MIN_UPDATE_DELAY, "Too soon");
prices[token] = price;
lastUpdate[token] = block.timestamp;
emit PriceUpdated(token, price);
}
Medium Severity: Precision Loss
## [M-1] Integer Division Truncation Causes Reward Loss
Severity: Medium
File: src/RewardDistributor.sol
Lines: 89
Description
Reward calculation uses integer division, causing precision loss for users with small stakes.
Vulnerable Code
solidity
function calculateReward(address user) public view returns (uint256) {
uint256 userShare = balances[user] / totalStaked;
return rewardPool * userShare;
}
### Impact
For small stakes:
User stake: 100 tokens
Total staked: 1,000,000 tokens
userShare = 100 / 1,000,000 = 0 (truncated!)
Reward = rewardPool * 0 = 0
Users lose 100% of rewards if stake < 0.01% of total.
Recommendation
Use fixed-point math:
solidity
uint256 constant PRECISION = 1e18;
function calculateReward(address user) public view returns (uint256) {
uint256 userShare = (balances[user] * PRECISION) / totalStaked;
return (rewardPool * userShare) / PRECISION;
}
Low Severity: Missing Events
## [L-1] Critical State Changes Lack Event Emission
Severity: Low
File: Multiple
Description
Several critical functions don't emit events, making off-chain tracking difficult.
Examples
solidity
// src/Governance.sol:45
function updateQuorum(uint256 newQuorum) external onlyOwner {
quorum = newQuorum;
// Missing: emit QuorumUpdated(oldQuorum, newQuorum);
}
// src/Vault.sol:123
function pause() external onlyOwner {
paused = true;
// Missing: emit Paused(msg.sender);
}
### Recommendation
Add events for all state changes:
solidity
event QuorumUpdated(uint256 oldQuorum, uint256 newQuorum);
event Paused(address indexed by);
function updateQuorum(uint256 newQuorum) external onlyOwner {
uint256 oldQuorum = quorum;
quorum = newQuorum;
emit QuorumUpdated(oldQuorum, newQuorum);
}
5. Tools to Use
Static Analyzers
# Slither
slither . --exclude-dependencies
# Aderyn
aderyn . --output slither-report.md
# Mythril
myth analyze src/Vault.sol
Fuzzing
# Foundry
forge test --fuzz-runs 10000
# Echidna
echidna-test . --contract VaultTest --config echidna.yaml
Coverage
forge coverage
Manual Review Checklist
- [ ] Reentrancy vulnerabilities
- [ ] Access control on all state-changing functions
- [ ] Integer overflow/underflow (pre-0.8)
- [ ] Unchecked external calls
- [ ] Frontrunning / MEV vulnerabilities
- [ ] Oracle manipulation
- [ ] Denial of service vectors
- [ ] Gas griefing attacks
- [ ] Centralization risks
- [ ] Upgrade mechanism safety (if upgradeable)
6. Example Report Template
Here's a minimal complete report:
# Smart Contract Audit Report
Project: [Name]
Date: [Date Range]
Auditor: [Your Name]
Commit: [hash]
---
Executive Summary
[2-3 paragraphs summarizing findings]
Severity Breakdown:
- Critical: X
- High: X
- Medium: X
- Low: X
---
Scope
[List of contracts and LOC]
---
Findings
Critical
[C-1] Title
[Finding details using template above]
High
[H-1] Title
[...]
Medium
[M-1] Title
[...]
Low
[L-1] Title
[...]
---
Recommendations
[Priority 1]
[Priority 2]
[Priority 3]
---
Appendix
Tools Used
- Slither v0.9.3
- Foundry 2026-03-20
- Manual review
Test Coverage
92% line coverage
Code Quality: B+
- Well-structured
- Good documentation
- Some areas need refactoring
Tips for Your First Audit
Career Path
Top auditors earn $200k-$500k+ annually. The barrier to entry is skill, not credentials.
Conclusion
Writing audit reports is a learnable skill. Start with simple contracts, use the templates in this guide, and iterate. Every report you write makes you a better security researcher.
Your first audit report won't be perfect — but it will be the start of a valuable career in blockchain security.
Happy auditing.
```
---