8 Field Techniques for Smart Contract Auditing — Forged by Refuting My Own Findings
I recently spent 24 focused rounds auditing a lending-and-automation codebase for a
security competition. I opened 7 attack hypotheses. I closed all 7 — six with proofs,
one by refuting my own draft finding at final review, hours before it would have gone
to judges as a "Low severity, high confidence" report.
That last one hurt, and it taught me more than the six clean refutations. Here are
the eight techniques that carried the campaign. Each is small enough to use tomorrow.
1. Rebuild the economic model before reading functions
For mechanism contracts (auctions, leases, escrows), don't read top-to-bottom. First
reconstruct the economy: who pays whom, when, for what right. Attackers fall out of
the mechanism for free — the flash-renter, the buyout-frontrunner, the donation
thief. This halves the reading time because you know which state transitions matter.
2. Follow the patch trail to find the sparse zones
Prior audit fix IDs in comments are a map of where previous reviewers massed their
forces. Dense numbering = picked clean. Sparse numbering plus heavy external
interactions = your hunting ground. Read the fix list first, then go where it isn't.
3. Ask what slice of the loss spectrum a check actually blocks
A require(shares == 0 → revert) looks like inflation protection. It blocks exactly
one point: the victim receiving nothing. Every partial-loss shape passes. For every
boundary check, ask: "which segment of the loss spectrum does this cover?" Pseudo-
protection is more dangerous than no protection because it stops you looking.
4. At every rounding site, ask "who keeps the dust?"
Round directions decide who eats sub-unit remainders. The audit question is
ownership: dust back to the fund's source = safe; to the protocol = QA note; to an
attacker = bug. Trace mulDiv vs mulDivRoundingUp against who supplied the value
being divided.
5. Track invariants by who sets, who burns, who recomputes
For state pairs that must stay ordered (escrow balance vs. reserved minimum), find
the single point where the invariant is established and check that every mutation
preserves it. My favorite case: a mutable self-assessed price that raises a burn
rate — the reserved minimum is set once at install and never recomputed on price
changes, so the ordering holds by construction. Once you see the "no recomputation"
pattern, the underflow fear evaporates.
6. Enumerate callback points as a grid, then verify CEI per cell
Contracts have fewer external callback points than you fear: NFT in, NFT out,
payout transfers, token hooks — usually under five. Enumerate them exhaustively,
then check effects-before-interactions at each. Completeness comes from the
enumeration, not from insight. This closes a reentrancy hypothesis in one sitting.
7. For consume-on-failure designs, ask "can the opponent cause the failure?"
Fail-open flows (trigger consumed, action failed, event emitted) have a griefing
surface: can an adversary deliberately trigger the failure to burn someone's
consumed state? If every failure source is environmental (stale oracle feed, lapsed
time), the design is sound. If any failure is opponent-manufacturable, you have a
DoS vector.
8. Push exact wei-level numbers through your exploit math before believing it
This is the technique that saved my credibility. I had a textbook ERC4626 inflation
finding: "attacker donates 999× the deposit, victim loses 99.9%." Plausible,
standard, wrong for that deposit profile. Pro-rata redemption is self-consistent:
the victim's fraction of the pool is A/(D+2A), and they redeem almost all of A back.
The classic attack harms only deposits that round down to zero shares — and this
contract's deposits were whole-position liquidity, large, with a zero-share check
already present. Analogies are how audit findings die in triage. Wei-level numbers,
end to end, or it didn't happen.
The meta-rule
Hypothesis-driven auditing with mandatory refutation beats bug-shaped skimming. I
submitted nothing that round — and that was the correct outcome. A self-refuted
finding costs an evening; a wrong one costs your reputation with every triager who
remembers your name.
Top comments (0)