Carriere·9 min de lecture·Par Solingo

How to Win Smart Contract Audit Contests

A practical, repeatable workflow for Code4rena and Sherlock style contests: how to scope fast, hunt high-severity bugs, recognize the bug classes that actually score, and write reports judges reward.

# How to Win Smart Contract Audit Contests

Public audit contests are the fastest, most brutal feedback loop in security. You read a codebase the whole world is also reading, you have a few days, and you only get paid for unique, valid, high-severity findings. This guide gives you a workflow that turns scattered effort into consistent placements, from your first contest to your first leaderboard finish.

Set Expectations Before You Start

Most newcomers quit after one or two contests because they expected to win money immediately. The reward distribution is heavily skewed: a single High can outweigh ten Lows, and duplicates split the pot. Your real goal in the first ten contests is calibration: learning which findings are valid, which are noise, and how judges weigh severity. Treat early contests as paid practice.

Severity, in most contest frameworks, is a function of impact times likelihood:

  • High: direct loss or locking of funds, or a clear protocol-breaking state, with a plausible path.
  • Medium: loss only under specific conditions, or broken core functionality without direct theft.
  • Low / Informational: code quality, no real fund or logic impact. These rarely pay.

If a bug cannot end with "and therefore funds are lost, stuck, or stolen", it is probably not a High.

Phase 1: Scope Fast (First Two Hours)

Do not start reading line one of the largest file. Build a map first.

  • Read the README and the docs. The protocol tells you what it is supposed to do. Bugs live in the gap between intended and actual behavior.
  • Count the lines in scope. Tools like cloc or solidity-metrics show you where the complexity is.
  • List the actors and the money paths. Who deposits, who withdraws, who is privileged, who can be liquidated. Funds move along these edges, and so do bugs.
  • Diff against known forks. Many protocols fork Uniswap, Compound, or an OpenZeppelin base. Comparing against the original surfaces the custom changes, which is exactly where the auditable risk lives.
  • # quick complexity map
    

    cloc src/ --by-file

    # count external and public functions, the attack surface

    grep -rEn 'function .*\b(external|public)\b' src/ | wc -l

    By the end of phase one you should be able to say, in two sentences, how the protocol makes and moves money. If you cannot, keep mapping.

    Phase 2: Hunt High-Severity Bugs

    Generality beats line-by-line reading. Walk the codebase through the lens of recurring bug classes, because the same mistakes recur across protocols.

    Bug classes that score

    • Accounting and rounding. Division before multiplication, fee math that rounds in the wrong direction, share-price manipulation in vaults (the classic first-depositor inflation attack on ERC-4626).
    • Access control gaps. A privileged function missing a modifier, an initializer callable twice, a role check that compares the wrong address.
    • Reentrancy, including read-only reentrancy. State read mid-callback before it is updated. Cross-function and cross-contract variants are where the real money is, not the textbook single-function case.
    • Oracle and price manipulation. Spot price from a single AMM pool, no TWAP, no staleness check on a price feed.
    • Unchecked external calls and return values. A token that returns false instead of reverting, a low-level call whose success flag is ignored.
    • Slippage and deadline. Swaps with no minimum-out or a hardcoded zero, leaving users open to sandwiching.

    Consider this realistic vault pattern. The bug is subtle and high-severity:

    // SPDX-License-Identifier: MIT
    

    pragma solidity ^0.8.20;

    contract Vault {

    mapping(address => uint256) public shares;

    uint256 public totalShares;

    IERC20 public immutable asset;

    constructor(IERC20 _asset) {

    asset = _asset;

    }

    function deposit(uint256 amount) external returns (uint256 minted) {

    uint256 totalAssets = asset.balanceOf(address(this));

    // when totalShares == 0, shares are minted 1:1

    minted = totalShares == 0

    ? amount

    : (amount * totalShares) / totalAssets;

    shares[msg.sender] += minted;

    totalShares += minted;

    asset.transferFrom(msg.sender, address(this), amount);

    }

    }

    The finding: an attacker deposits 1 wei, receives 1 share, then transfers a large amount of the asset directly to the vault. Now totalAssets is huge while totalShares is 1. The next honest depositor computes minted = (amount * 1) / totalAssets, which rounds down to zero shares for a real deposit, donating their funds to the attacker. The fix is to mint dead shares at construction or to track totalAssets internally rather than reading balanceOf.

    How to actually find these

    • For every external function, ask: what happens if I call this with zero, with max uint, twice in the same transaction, or out of the intended order?
    • Follow each transfer and call. Confirm the return value is checked and the state is updated before the external call.
    • Write a property and try to break it with a fuzzer. Foundry invariant tests are excellent for accounting bugs:
    function invariant_totalSharesBackedByAssets() public view {
    

    // total redeemable value should never exceed assets held

    assertLe(vault.totalShares(), asset.balanceOf(address(vault)) + 1);

    }

    A passing proof-of-concept test transforms a "maybe" into a High. Judges reward findings they can run.

    Phase 3: Write Reports Judges Reward

    A correct bug with a weak writeup gets downgraded or marked a duplicate of a better-written one. Structure every finding the same way:

  • Title: severity plus the one-line impact, for example "High: first depositor can steal subsequent deposits via share inflation".
  • Summary: two or three sentences a busy judge can grasp.
  • Impact: what is lost, stuck, or stolen, and under what conditions.
  • Proof of concept: a runnable test or a precise numeric walkthrough. This is the single biggest differentiator.
  • Recommended mitigation: the concrete fix, ideally a diff.
  • Write for a tired reader. State the impact in the first line, never bury it. Avoid speculation: if you cannot show the loss, say so honestly and downgrade yourself rather than risk an "invalid" mark that hurts your accuracy score.

    Time Management Across the Contest Window

    A week-long contest is a marathon with a sprint at the end.

    • Day 1: scope and mapping only. No findings yet.
    • Days 2 to 4: deep dives along the money paths, one subsystem at a time. Log every suspicion in a scratch file, even half-formed ones.
    • Day 5: build proof-of-concept tests for your strongest leads. Drop the weak ones.
    • Final day: polish writeups, double-check severities, submit. Do not chase a new lead in the last hours; finish what you can prove.

    Protect your attention. Two hours of focused review on one subsystem beats a full day of skimming everything.

    Leveling Up From Beginner

    • Read judged reports. After each contest, study the Highs that placed. You will see the same patterns again.
    • Keep a personal bug-class checklist and run every contract through it. Your edge is pattern recognition, and a checklist makes it systematic.
    • Specialize. Pick a niche (vaults, lending, bridges, AMMs) and go deep. Specialists out-find generalists in their domain.
    • Build proof-of-concept muscle. Speed at writing Foundry tests is a direct multiplier on how many findings you can confirm before the deadline.

    Practice This Hands-On

    The fastest way to internalize these bug classes is to exploit them deliberately in a safe environment, then fix them. On app.solingo-blockchain.xyz you can work through audit-style exercises, write proof-of-concept exploits, and check your severity reasoning against worked solutions. Start with the accounting and access-control tracks, then move to reentrancy and oracle manipulation. Read code, break it, write the report, repeat.

    Prêt à mettre en pratique ?

    Applique ces concepts avec des exercices interactifs sur Solingo.

    Commencer gratuitement