Your First Audit: A Step-by-Step Guide
Your first competitive audit will be humbling. You will find fewer bugs than you expected, miss findings that seem obvious in the post-contest analysis, and possibly earn nothing if the contest is highly competitive. This is normal and expected — every top auditor went through exactly this. The goal of your first audit is not to earn money. It is to learn the process, calibrate your abilities, and build momentum. This lesson gives you the exact framework to make that first audit as productive as possible.
Top auditors on Code4rena and Sherlock typically found zero to two valid findings in their first contest. Low and Medium findings are entirely normal for a beginner. A single valid High finding in your first audit would be an excellent result. The distribution of earnings is extremely top-heavy — the top 5% of auditors earn 80% of rewards. Getting into that bracket takes 12-24 months of consistent effort, not one lucky contest.
Choosing Your First Contest
// Green flags (choose this contest):
// - Codebase size: < 1000 SLOC (Source Lines of Code)
// - Protocol type you have studied: lending, AMM, ERC20 — not a novel mechanism
// - Good documentation: README, NatSpec, protocol docs available
// - Foundry-based: easier to write PoCs
// - Medium prize pool ($20K-$100K): competitive but not overwhelming
// - Duration: 7-14 days (not 3 days — you need time to learn the codebase)
// Red flags (skip this contest as a beginner):
// - Novel mechanisms you have never seen (ZK circuits, custom VMs, L2 sequencers)
// - >5000 SLOC — overwhelming for first audit
// - No documentation at all
// - Very large prize pool ($500K+) — hundreds of experienced auditors competing
// - Protocol with many external dependencies (10+ integrations)
// How to find contests:
// Code4rena: code4rena.com/contests — shows all active and upcoming
// Sherlock: app.sherlock.xyz/audits — shows active contests
// Cantina: cantina.xyz — higher bar, invite system, some open
// Hats Finance: hats.finance — smaller bug bounties
Setup: Before Day 1
# 1. Fork the contest repository
$ git clone https://github.com/contest-org/contest-repo
$ cd contest-repo
# 2. Install Foundry dependencies
$ forge install
$ forge build
# 3. Run existing tests — make sure they all pass
$ forge test
# If tests fail before you touch anything: note which ones, understand why
# 4. Install analysis tools
$ pip3 install slither-analyzer
$ cargo install aderyn
# 5. Count lines of code in scope
$ cloc src/ --include-lang=Solidity
# or: forge inspect src/Contract.sol:Contract abi | wc -l (rough estimate)
# 6. Set up your notes file
$ touch AUDIT_NOTES.md
# Structure: Per-file sections, Leads, Findings, Questions for team
# 7. Join the contest Discord channel if available
# Read all pinned messages — protocol team often clarifies scope there
Day-by-Day Plan: 7-Day Contest
// DAY 1 GOAL: Understand what the protocol does without reading Solidity
// Morning (4 hours):
// 1. Read the FULL README — not just the first paragraph
// 2. Read all linked documentation (Gitbook, etc.)
// 3. Find and read ALL prior audits
// 4. Look up this protocol on Solodit — any known issues in similar protocols?
// Afternoon (4 hours):
// 5. Run: forge coverage --report summary (understand test coverage)
// 6. Run: slither . --print human-summary (architecture overview)
// 7. Draw the contract architecture on paper:
// - Which contracts call which?
// - Where does ETH/tokens enter and exit?
// - Who is the admin? What can they do?
// 8. Write your initial threat model (5-10 bullet points)
// End of Day 1 deliverable:
// A one-page mental model of the protocol — what it does, who uses it,
// where the money is, and your top 5 suspected attack surfaces
// DAY 2 GOAL: Clear the "automated findings" backlog
// Morning (4 hours):
// 1. Full Slither run, save output: slither . 2>&1 | tee slither.txt
// 2. For each High/Medium finding: investigate, valid or false positive?
// 3. Run aderyn for a second opinion: aderyn . -o aderyn-report.md
// 4. Note any valid-looking Slither findings as "leads" in AUDIT_NOTES.md
// Afternoon (4 hours):
// 5. Generate Slither call graph: slither . --print call-graph
// 6. List ALL external/public functions — this is your work queue
// 7. Mark each function: HIGH/MED/LOW priority for manual review
// HIGH: transfers tokens/ETH, modifies core accounting, access control
// MED: reads from external protocols, governance actions
// LOW: view functions, admin parameter setters within safe bounds
// 8. Order your manual review queue: HIGH functions first
// DAYS 3-4 GOAL: Methodically review all HIGH and MEDIUM priority functions
// For each function in priority order:
// 1. Read it once for understanding — what does it do?
// 2. Apply the 5-question framework:
// WHO can call this? (access control correct?)
// WHEN can this be called? (time-based issues?)
// WHAT state changes? (CEI pattern?)
// WHAT external calls? (return values? reentrancy?)
// WHAT inputs are not validated? (edge cases?)
// 3. Apply token handling checks (fee-on-transfer? SafeERC20?)
// 4. Check oracle usage if present
// 5. Note anything suspicious — even small things
// Reading tips for speed:
// - Read math twice — write it out algebraically if needed
// - Trace every require() — what condition triggers it?
// - Follow every storage variable — where is it written? where read?
// - For every external call: Ctrl+Click to the called contract
// DAYS 5-6 GOAL: Prove each lead is exploitable, draft reports
// For each "lead" in AUDIT_NOTES.md:
// 1. Write a failing test that proves normal operation works
// 2. Write a test that exercises the suspicious edge case
// 3. Does it reproduce the suspected vulnerability?
// YES: This is a finding — start the report
// NO: Was your hypothesis wrong? Or just the test?
// UNSURE: Spend max 2 more hours before moving on
function testLead_MissingSlippageOnSwap() public {
// Fork mainnet state
vm.createSelectFork("mainnet");
// Setup: simulate sandwich attacker
address sandwicher = makeAddr("sandwicher");
deal(address(USDC), sandwicher, 1_000_000e6);
// Step 1: Sandwicher front-runs
vm.prank(sandwicher);
// ... buy ETH before victim ...
// Step 2: Protocol executes vulnerable swap
protocol.rebalance(); // calls router.swap() with amountOutMin=0
// Step 3: Sandwicher back-runs
vm.prank(sandwicher);
// ... sell ETH ...
// Assert sandwicher profited at protocol's expense
assertGt(USDC.balanceOf(sandwicher), 1_000_000e6, "Sandwicher should profit");
}
// Draft your report while the finding is fresh in your mind
// Title + Severity + 3-sentence impact + PoC test + mitigation
Day 7: Finalize and Submit
Submitting known issues: Read the contest README for "known issues" — submitting these wastes your time and may penalize you on Sherlock.
Wrong severity: A DoS that requires admin action is not Critical. A theoretical issue requiring 12 simultaneous conditions is not High. Overclaiming severity is a common beginner mistake that damages credibility with judges.
No PoC: On Code4rena and Sherlock, a Medium/High finding without a PoC is often downgraded or invalidated. Always include a working test.
Gas-only findings when time is short: If you are running out of time, skip gas optimization findings. Submit everything ≥ Low severity first.
How to Ask Clarifying Questions
// ❌ BAD: Reveals your finding
// "Is it intentional that an attacker can drain the vault via reentrancy?"
// → Now every auditor in the channel knows there's a reentrancy bug
// ❌ BAD: Shows you haven't read the docs
// "What does the withdraw() function do?"
// → Read the README first
// ✅ GOOD: Asks about behavior without revealing vulnerability
// "Is it expected behavior that deposit() accepts fee-on-transfer tokens?"
// "What is the intended behavior of rebalance() if the oracle reverts?"
// "Is the admin key in a hardware wallet / what's the multisig threshold?"
// "Is this contract expected to receive ETH via selfdestruct?"
// Rule of thumb: ask about INTENT and SCOPE, not about vulnerabilities
// "What happens if X" is usually safe
// "Did you know X is exploitable" is always wrong in a public channel
After the Contest: Building on Your First Result
| Result | What It Means | What to Do Next |
|---|---|---|
| 0 valid findings | Normal for first audit; your scope of knowledge doesn't yet match the codebase type | Study the winning reports; identify which patterns you missed; read one lesson per day |
| 1-3 Low/Informational | Good start — you understand code quality issues and Solidity patterns | Focus next audit on finding Medium-level issues; study oracle and token handling |
| 1-2 Medium findings | Excellent for a first audit — you have a solid foundation | Start aiming for Highs; study the DeFi protocol type you like most in depth |
| 1 High finding | Outstanding first result — top 5-10% for beginners | Write a public post-mortem; keep that momentum; try a more complex contest next |
After every contest, read the published winning reports carefully. For every finding you missed: (1) Understand exactly what the vulnerability was. (2) Ask yourself: "At what point in my review did I see the relevant code? Why did I not flag it?" (3) Add this pattern to your personal checklist. This retrospective habit is what separates auditors who improve quickly from those who repeat the same mistakes.
Handling Scope: What Is and Is Not Fair Game
// Scope document example (from a typical Code4rena contest):
// In scope:
// - src/Vault.sol
// - src/Strategy.sol
// - src/Oracle.sol
// Out of scope:
// - lib/openzeppelin-contracts/ (standard libraries)
// - test/ (test files)
// - scripts/ (deployment scripts)
// Gray areas (always check contest README for clarification):
// - Bugs in out-of-scope files that affect in-scope contracts?
// - Centralization risks (admin can rug)?
// - Gas optimizations?
// - Oracle manipulation if Chainlink goes down?
// General rules for scope ambiguity:
// 1. If the vulnerability only exists in out-of-scope code → don't submit
// 2. If out-of-scope code AFFECTS in-scope code behavior → may be valid
// But note clearly: "This issue is in [out-of-scope file] but affects [in-scope file]"
// 3. If unsure → submit and let the judge decide
// Submitting an invalid-but-honest finding rarely penalizes you
// 4. "Known issues" listed in README → NEVER submit these
// On Sherlock, submitting known issues reduces your accuracy rate
Building Momentum After Your First Contest
// Week 1 (immediately after contest ends):
// - Write a post-mortem for yourself: what did I find? what did I miss?
// - Read all published High/Critical findings you did not find
// - For each missed finding: trace through the code yourself
// - Add missed patterns to your personal checklist
// Week 2:
// - Write a public writeup of your best finding (even if it was Low)
// - Post the writeup on your platform (GitHub Pages, Hashnode, etc.)
// - Tweet the writeup with relevant hashtags: #smartcontractsecurity #web3security
// Week 3:
// - Start your next audit contest
// - Apply lessons from Week 1 explicitly
// - Test: does your updated checklist catch what you missed last time?
// Week 4:
// - Complete the next contest
// - Community engagement: respond to 3 Twitter posts from security researchers
// - Join one discussion thread in Code4rena or Secureum Discord
// After 3 months of this cycle:
// - 3+ public writeups on your profile
// - 6+ contests completed
// - Personalized checklist with 20+ items
// - Clear pattern: which vulnerability categories you find well, which to study more
First Payout: Practical Details
Code4rena and Sherlock pay in USDC, typically 2-6 weeks after the contest ends (judging takes time). You need to provide a wallet address during registration. Tax treatment varies by jurisdiction — in most countries, contest winnings are taxable income at your marginal rate. Keep records of all payments received. There are no minimum withdrawal thresholds, but gas costs for small amounts may make claiming impractical on Ethereum mainnet — check if the platform supports Polygon or Arbitrum for lower fees.
Reading Protocol Tests as a Security Signal
// Tests reveal the DEVELOPER'S mental model of the protocol
// Things tested = things the developer thought about
// Things NOT tested = blind spots and potential vulnerabilities
// Example: Vault contract test suite
// What IS tested:
// testDeposit() — happy path
// testWithdraw() — happy path
// testYieldAccrual() — basic yield calculation
// What is NOT tested (and likely not considered by developer):
// - deposit() with a fee-on-transfer token
// - withdraw() with a blacklisted USDC recipient
// - deposit() and immediate withdraw() in the same block
// - withdraw() when the vault is at maximum utilization
// - What happens if the oracle reverts during withdraw()
// Each of these is a potential finding
// How to systematically find test gaps:
// 1. Run: forge coverage --report lcov
// 2. Open lcov report in browser: genhtml lcov.info -o coverage/
// 3. Find all RED lines (uncovered code)
// 4. Each red line in a security-critical function = priority investigation
// Grep for untested require() statements:
$ grep -n "require(" src/Vault.sol | wc -l # Total require count: 23
$ grep -n "expectRevert" test/Vault.t.sol | wc -l # Tested reverts: 8
// 15 untested require() paths → 15 leads to investigate
Disputing Judge Decisions After Contest
After contest results are published, you can often dispute judge decisions within a defined window (usually 24-72 hours). Dispute only when: (1) you have a clear technical argument that the judge made an error, (2) you have evidence the judge misunderstood your finding, or (3) a duplicate grouping is incorrect. Do NOT dispute because you disagree with severity (this almost never succeeds). Write a short, professional dispute explaining the technical error clearly. Aggressive or personal disputes are always counterproductive — judges remember repeat offenders.
// ❌ BAD DISPUTE: Emotional, vague, unhelpful
// "This is clearly a Critical finding and should not be marked as a duplicate.
// The judge made a mistake. My finding is different and should be rewarded fully."
// ✅ GOOD DISPUTE: Technical, specific, respectful
// "I believe my finding #47 (missing slippage in rebalance()) was incorrectly
// grouped as a duplicate of finding #23 (missing slippage in harvest()).
// The root causes are different:
// - Finding #23: protocol calls swap() without amountOutMin (line 456)
// - Finding #47: protocol passes 0 to existing amountOutMin parameter (line 891)
// The fix for each is different: #23 needs a new parameter, #47 needs to use
// the expected output from an oracle instead of hardcoded 0.
// These are two separate issues with two separate fixes."
// What makes a dispute succeed:
// - Clear technical distinction (not just "they're different, trust me")
// - Reference specific line numbers
// - Explain what the correct fix would be for EACH finding separately
// - Short (3-5 sentences) — judges read hundreds of disputes
Tracking Your Progress Over Time
| Metric | How to Track | Target After 6 Months | Target After 12 Months |
|---|---|---|---|
| Valid findings per contest | Spreadsheet: contest name, findings count, severity | 3-5 Low/Med per contest | 1+ High per contest |
| Total earnings | Spreadsheet: contest, payout date, USDC amount | $500-$5K cumulative | $5K-$30K cumulative |
| Findings missed | After each contest: count published Highs you did not find | Missing <80% of Highs | Missing <50% of Highs |
| Contest participation rate | Track: audits started vs audits completed | 3-4 contests per month | 2-3 focused contests per month |
| Leaderboard position | Monthly screenshot of C4/Sherlock leaderboard | Top 200 | Top 50 |
Scope Ambiguity and Out-of-Scope Findings
Every audit has a defined scope — specific contracts and commit hashes. Sometimes you find a critical vulnerability that is technically outside the scope but closely related. Handling this correctly protects your reputation and maximizes your payout.
// Scenario 1: Vulnerability in an out-of-scope contract
// The Vault.sol (in scope) calls ExternalOracle.sol (out of scope)
// ExternalOracle has a critical bug that allows price manipulation
// What to do:
// Submit a finding with title: "Vault.sol trusts ExternalOracle.sol which has X vulnerability"
// Frame it as an IN-SCOPE issue: the in-scope vault's reliance on a vulnerable external contract
// Most judges will accept this framing if you focus on the in-scope contract's failure
// Scenario 2: Finding in a library used by in-scope contracts
// Libraries are usually implicitly in scope if the in-scope contracts use them
// Check the scope description: "all contracts in src/" usually includes libraries in src/lib/
// When in doubt: ask in Discord (ask about scope, NOT about the vulnerability)
// Scenario 3: Known issues from previous audits
// Many contest scope descriptions include: "The following known issues are out of scope:"
// Read this list carefully BEFORE you start — don't waste time on acknowledged issues
// If you find the same issue via a different attack path → submit with that distinction
// Scenario 4: Governance or admin attack vectors
// Sherlock specifically excludes "admin private key compromise" as a valid finding
// But "admin can rug users without timelock" IS valid if admin trust is not accepted
// Read platform-specific judging rules before the contest, not after
The Post-Contest Learning Protocol
When a contest ends and findings are published, block 3 hours for a structured post-mortem. This is the highest-leverage learning time available to an auditor. Compare every published High and Critical finding against your notes. For every finding you missed: identify (1) what pattern you did not recognize, (2) what code path you did not trace, and (3) what test you could have written to find it mechanically. Then add that pattern to your personal checklist. Over six months, this systematic practice compounds dramatically.
// Post-contest retrospective template (run after every contest)
// 1. FINDINGS I SUBMITTED (count each by severity)
// Critical: 0 High: 1 Medium: 2 Low: 4 Info: 2
// 2. FINDINGS I MISSED (from published report)
// H-1: Flash loan reentrancy in stake()
// → Why I missed: Did not trace the callback path in ERC777 transfer
// → Pattern to add to checklist: "ERC777 tokensReceived callback = reentrancy surface"
// → Test I should have written: deploy ERC777 token, hook tokensReceived, call stake()
// H-2: TWAP manipulation window = only 1 block
// → Why I missed: I saw "TWAP" and assumed it was safe, did not check window length
// → Pattern to add: "Always check TWAP window in seconds, not just 'is it a TWAP'"
// → Test I should have written: oracle.getTWAP() on block 0 vs block 1
// 3. FINDINGS I FOUND THAT OTHERS MISSED
// M-3: Missing deadline check on swaps (only 3 others found this)
// → What made me find it: my checklist item "all swaps need deadline"
// → Reinforce: this checklist item is high-value, keep it prominent
// 4. PATTERN LIBRARY UPDATE
// New patterns added this contest: 2
// Checklist items added: 2
// Total checklist items: 73
// 5. NEXT CONTEST FOCUS AREA
// I consistently miss: token callback reentrancy patterns
// Before next contest: read TokenSecurity lesson + Euler Finance post-mortem
Your First Payout: Practical Considerations
Most competitive audit platforms pay in USDC or ETH. Code4rena requires a Polygon address for payouts. Sherlock pays on Ethereum mainnet. Immunefi pays directly from the protocol to an address you provide. You will need to provide wallet information, and some platforms require KYC verification before releasing significant payouts (>$5K on some platforms). Set up a dedicated auditor wallet — never use your primary wallet for public platform profiles. Tax implications vary by jurisdiction: consult a tax professional before your first significant payout, as crypto audit income is generally treated as self-employment income.
Key Takeaways
- Your first audit goal is learning the process, not earning money — calibrate expectations accordingly and judge success by what you learned, not what you earned.
- Choose contests with codebases under 1000 SLOC in protocol categories you have already studied — familiarity with the domain dramatically increases finding rate.
- The 7-day plan (Day 1: context, Day 2: automated, Days 3-4: manual, Days 5-6: PoC, Day 7: submit) gives your audit structure and prevents the common mistake of spending too much time on one area.
- Untested
require()paths in the test suite are a map of the developer's blind spots — count them and prioritize investigation accordingly. - Submit everything ≥ Low severity — even a Low finding demonstrates you read the code carefully and understand Solidity best practices.
- Disputes should be short, technical, and focused on factual errors — never dispute severity disagreements alone.
- Never ask questions in public channels that reveal your findings — ask about intent and scope, not about vulnerabilities.
- Post-contest retrospective analysis of winning reports is the single highest-leverage learning activity between contests — schedule this time explicitly.