Blockchain3 min read

What to do in the two weeks before a smart-contract audit

Auditors bill for time, and most of it goes on things you could hand over upfront. How to prepare a codebase so the audit finds bugs, not context.

An audit has a fixed budget of auditor attention. Every hour spent working out what a function is supposed to do is an hour not spent looking for the bug in it. Preparation is not about looking good — it is about redirecting that attention toward the code that can lose money.

Here is what is worth doing in the two weeks before the engagement starts.

Freeze the code

The single most expensive thing you can do is keep committing during the audit. Every change invalidates work already done and forces re-review of anything downstream.

Tag the commit you are submitting. If a critical bug is found mid-audit, fix it on a branch and agree with the auditor when it gets folded in. Do not push to the audited branch because a linter complained.

Write down what the system is supposed to do

Auditors find bugs by comparing intent against implementation. With no stated intent, they can only find things that are self-evidently wrong — reentrancy, overflow, missing access control. The subtle bugs, the ones where the code works exactly as written and the logic is wrong, are invisible without a spec.

What is worth handing over:

  • Roles and what each can do. Every privileged function, who can call it, and what happens if that key is compromised.
  • Invariants. Statements that must always hold: total shares equals sum of balances, the pool can never pay out more than it holds, a market cannot settle twice.
  • The money paths. Every way value enters and leaves the system, drawn as a diagram if possible.
  • What you deliberately did not protect against. If you accept the risk of a malicious oracle because the oracle is you, say so. Otherwise the auditor writes it up as a finding and you both spend time on it.

The invariants list is the highest-leverage document in the pack. It tells the auditor exactly what to try to break, and writing it usually surfaces a bug or two on its own.

Make the tests tell the truth

Coverage percentage is close to meaningless — a test that calls a function and asserts nothing counts as covered. What matters is whether the tests encode the invariants above.

Before the audit: get the full suite green, delete or fix skipped tests, and add a fuzz or invariant test for each of the money-path invariants. If a property is worth stating, it is worth having the fuzzer attack it for a while.

Clean the obvious findings yourself

Every audit report has a section of low-severity findings that a static analyzer would have caught. Run Slither, run the compiler with all warnings on, and fix or explicitly justify each result. You are paying senior-engineer rates; do not spend them on unused imports and missing event emissions.

Prepare the deployment story

A surprising share of real incidents come from deployment and upgrade paths rather than contract logic. Have ready: the exact deployment sequence, initialization parameters, who holds which key, whether contracts are upgradeable, and what the upgrade procedure is. If there is a proxy, the auditor needs the storage layout.

Resolution is the hard part of a prediction market

After the report arrives

Fix findings on a branch, and write a response to every single item, including the ones you are not fixing. "Acknowledged, accepted risk, here is why" is a legitimate answer and it is a much better look than a silently unaddressed finding.

Then get the fixes re-reviewed. An unreviewed fix to a critical finding is a new critical finding waiting to happen — patches written under deadline pressure are exactly the code that needs a second pair of eyes.

Sergei Palii

Founder, Sepia Software

About me

Read next

All articles