I have shipped software for 18 years. Roughly 8 of those in crypto. And this week I signed up for my first Sherlock audit contest, and my hands were a little cold when I clicked in. That gap tells you something about how competing in public feels versus building things.
I write secure code for a living. I built spectr-ai, an open-source AI smart contract auditor. I have read more Solidity than I can remember. None of that is the same as putting my name on a public leaderboard next to people who do this full time and win five figures per contest. So this post is me being honest about the setup, the plan, and the fear, before I have any results to brag about or hide.
Why a contest at all
There are a few ways to make money auditing. Firms hire you. Bug bounties pay you when you find a live bug. And contest platforms like Sherlock run time-boxed competitions where a protocol puts its code in scope, a pile of auditors (Watsons, in their language) hunt for bugs at the same time, and the prize pool gets split based on what you find.
The model is the part that pulled me in. Payouts are severity based. A valid High is worth more than a valid Medium, and the pot for a given issue gets shared among everyone who found it. If five people report the same High, they split that issue's reward. So the incentive is not just to find bugs, it is to find the bugs other people miss.
There is also a gate that matters if you care about ranking, not just cash. To climb from the entry tier you generally need to land 2 valid issues and clear a points threshold (around 20% of the top performer on a contest, from what I have read). That is a real bar. It stops the leaderboard from filling up with people who got lucky once. It also means my goal for the first contest is not "win." It is "submit 2 issues I can fully defend." Everything else is noise.
How I prepared
I did not open the code first. I spent the first two days reading old contest reports. Sherlock publishes them, and they are gold. You get to see what actually counted as a High, how judges reasoned about severity, which reports got downgraded to informational and why, and the shape of the mistakes that recur across protocols.
A few patterns jumped out fast:
- Rounding and precision bugs in share/asset math show up constantly.
- Access control that looks fine until you trace who can call an internal admin path through a proxy.
- Oracle assumptions that hold on mainnet but break on an L2 or during sequencer downtime.
- Reentrancy that is not the classic kind, but cross-function or cross-contract.
Reading reports also recalibrated my sense of what a "real" finding looks like. A lot of things I would flag in a code review ("this could be clearer," "add a check here") are not valid contest issues unless I can show funds at risk or an invariant broken. Severity is about impact plus likelihood, not tidiness.
The workflow: AI proposes, Foundry proves
Here is the rule I set for myself, and I am not breaking it: nothing gets submitted without a working Foundry proof of concept.
I use LLMs heavily, but as an idea generator, not an oracle. I run Ollama locally on WSL2 with qwen2.5-coder for the cheap fast passes, and I reach for a bigger model when a path looks promising. The loop looks like this:
- I feed the model a contract plus the invariants I wrote down (more on that in another post) and ask it to enumerate attack paths, not to "find bugs." Open-ended "is this safe" prompts produce garbage. Specific "how could an attacker make totalAssets diverge from the sum of balances" prompts produce leads.
- The model gives me candidate attacks. Most are wrong. Some are hallucinated functions that do not exist. I throw those out immediately.
- For anything that survives, I write a Foundry test that either triggers the bug or fails to. If I cannot make it fail, it is not a finding.
A skeleton PoC looks like this:
// test/PocShareInflation.t.sol
pragma solidity ^0.8.20;
import "forge-std/Test.sol";
import {Vault} from "../src/Vault.sol";
import {MockToken} from "./mocks/MockToken.sol";
contract PocShareInflation is Test {
Vault vault;
MockToken token;
address attacker = makeAddr("attacker");
address victim = makeAddr("victim");
function setUp() public {
token = new MockToken();
vault = new Vault(address(token));
token.mint(attacker, 100 ether);
token.mint(victim, 100 ether);
}
function test_firstDepositorInflation() public {
// attacker deposits 1 wei, mints 1 share
vm.startPrank(attacker);
token.approve(address(vault), type(uint256).max);
vault.deposit(1);
// then donates directly to inflate share price
token.transfer(address(vault), 50 ether);
vm.stopPrank();
// victim deposits and gets rounded down to 0 shares
vm.startPrank(victim);
token.approve(address(vault), type(uint256).max);
vault.deposit(50 ether);
vm.stopPrank();
assertEq(vault.balanceOf(victim), 0, "victim minted zero shares");
// attacker withdraws everything, including victim funds
}
}
If that assert holds, I have something. If it does not, the model was wrong and I move on. The PoC is also what I paste into the report, because a judge should be able to run forge test --match-test test_firstDepositorInflation and watch it happen.
This is the same discipline I use everywhere. spectr-ai flags things, but a flag is a hypothesis, not a verdict. The PoC is the verdict.
The fear, handled honestly
The impostor feeling is real, and pretending it is not would be dishonest. Eighteen years of shipping does not translate to "I will place well against people who audit for a living." Those are different sports.
What settled me down was reframing the goal. I am not trying to top the board. I am trying to submit two issues I can defend line by line, learn how the judging actually works from the inside, and read every other Watson's report after the contest closes to see what I missed. The reports of a closed contest are the best paid course in the space, except it is free.
There is also a floor to the downside. The worst case is I submit nothing valid, lose a week, and learn a lot from the post-contest reports. That is a cheap tuition. The upside is a payout and a data point that I can actually do this.
I picked the contest this week. I am not naming it because I do not want to color anyone else's read of the same code, and honestly because I want to talk about method here, not about one protocol. Next posts will be about how I read the scope and how I turn invariants into findings.
If you have competed on a contest platform, what is the one thing you wish someone had told you before your first one?
Top comments (0)