PerspectivesChecklist

Smart contract audit: a pre-deployment checklist

A smart contract audit works when it is the middle of a process, not the end of one. Before it: a threat model, written invariants, tests and fuzzing. After it: time to fix findings, a re-review of the fixes, a rehearsed deployment and controls for running the contract. Schedule backwards from mainnet so remediation fits.

Author
NextSense EngineeringEngineering team
Published
Reading
6 min

Why audits disappoint

Teams commission a smart contract audit expecting it to certify that the code is safe. What they receive is a report on one commit, written by reviewers who had a few weeks to understand a system its authors spent months building. When the audit is booked for the week before launch, the findings arrive with no time to fix them properly, fixes go in without review, and the contract that reaches mainnet is not the one that was audited.

An audit works when it is the middle of a process rather than the end of one. The work before it determines what the auditors can find; the work after it determines whether their findings are fixed without introducing new faults. This checklist covers both, sequenced backwards from the mainnet date so that each step has the time it needs.

Work backwards from mainnet

Fix the launch date, then count back. A realistic external audit takes two to four weeks for a contract system of moderate size, and auditors are often booked well in advance. Remediation and re-review need at least another two weeks. The preparation that makes the audit worth its cost — a threat model, written invariants, a complete test suite and fuzzing — needs to be finished before the audit starts, which puts code freeze four to six weeks before launch and design decisions earlier still.

If the date cannot accommodate that sequence, move the date or reduce the scope. Compressing the sequence is how the unaudited fix reaches mainnet.

The checklist

Checklist

When

Check

What it catches

Six to eight weeks out

Threat model: actors, assets, trust assumptions, privileged roles

Attacks nobody thought to test for

Six to eight weeks out

Invariants written in plain language, such as "total supply equals the sum of balances"

Logic errors that pass every example-based test

Six to eight weeks out

Access control and upgrade model decided and documented

Admin keys holding more power than anyone intended

Six to eight weeks out

Oracle and price inputs reviewed for manipulation

Flash-loan and price-manipulation attacks

Four to six weeks out

Unit and integration tests, including every revert path

Regressions and unhandled edge cases

Four to six weeks out

Invariant and fuzz testing against the written invariants

State sequences no one wrote a test for

Four to six weeks out

Static analysis run and every finding triaged

Known vulnerability patterns

Four to six weeks out

Audit scope frozen at a commit hash, with documentation

Auditors reviewing a moving target

Audit window

Auditors given the threat model, invariants and test suite

Audit time spent rediscovering what the team knew

Remediation

One commit per finding, each with a regression test

Fixes that introduce new faults

Remediation

Auditors re-review the fixes before deployment

Unreviewed code reaching mainnet

Launch week

Scripted deployment rehearsed on a mainnet fork

Wrong constructor arguments, owners or initialisation

Launch week

Source verified; privileged roles on a multisig behind a timelock

A single key able to move user funds

After launch

Monitoring, a tested pause, an incident runbook and a bug bounty

Slow response when something goes wrong

Before the audit

Write the threat model down

List who interacts with the system, what each of them can do, what is worth stealing or breaking, and which parties the design trusts. Most serious contract failures are failures of a trust assumption nobody stated — a price feed assumed to be honest, an admin assumed never to be compromised, an external contract assumed never to call back. Writing the assumptions down is what makes them testable.

Turn properties into tests

Example-based unit tests check the cases their authors imagined. Invariant testing and fuzzing check properties across thousands of generated sequences, which is where the surprising bugs live. Tools such as Foundry's invariant testing and Echidna make this routine; the hard part is writing invariants worth checking, which is why they belong in the design phase rather than the week before the audit.

Run static analysis and read the results

Static analysers such as Slither catch known patterns — reentrancy, unchecked return values, dangerous delegate calls — quickly and cheaply. Their output is noisy. Triage every finding and record why each one does or does not apply, so that the auditors start from a clean baseline instead of spending their time on issues a tool could have closed.

During and after the audit

Give the auditors everything: the threat model, the invariants, the test suite, and an honest account of the parts the team is least sure of. An audit is a limited amount of expert attention, and it should go where the risk is.

When findings arrive, fix each in its own commit with a regression test that fails before the fix and passes after it. Then have the auditors review the fixes. A fix made under time pressure is new code, and new code is exactly what the audit did not see.

Reading the report

An audit report lists findings by severity, usually from critical to informational, against the commit it reviewed. Read three things before the findings themselves. The commit hash, to confirm it matches what will be deployed. The scope, to see which contracts were reviewed and which were not. And the findings marked acknowledged rather than fixed, because those are the risks the team has decided to carry into production.

Publish the report alongside the deployed addresses so that users and integrators can check the same things. A report that covers a different commit from the one on mainnet, or leaves out a contract the system depends on, tells its readers less than it appears to — and they deserve to know that.

Launching and operating

Deployment is its own source of failure. Script it, rehearse it on a fork of mainnet state, and review every constructor argument and initialisation call — a contract deployed with the wrong owner is as exploitable as one with a bug. Verify the source on the block explorer so that users and monitors can read what is running.

After launch, the privileged roles should sit on a multisig behind a timelock, so no single key can act and every administrative change is visible before it takes effect. A pause mechanism should exist and have been tested. Monitoring should alert on unusual flows, and an incident runbook should name who decides and who acts. A bug bounty invites the scrutiny that continues after the auditors have gone.

Upgradeable contracts deserve particular care. An upgrade is a deployment of new code, and it should pass through the same sequence — threat model, tests, review, rehearsal — compressed if the change is small, but never skipped because the system is already live.

This sequence is how we run contract work in Security Engineering, alongside the protocol design in Blockchain Engineering. The order matters more than any single check: every step exists so that the next one has something reliable to stand on.

Questions, answered

How long before launch should an audit be booked?

Early enough that the audit ends at least two weeks before mainnet, leaving time for remediation and a re-review. With preparation, that usually means code freeze four to six weeks before launch.

Does an audit mean the contract is safe?

No. It means experienced reviewers examined one commit for a limited time. It lowers risk considerably, but safety also depends on testing, deployment, key management and monitoring after launch.

Do fixes need to be audited again?

Yes. A fix written under time pressure is new code the audit did not see. Each fix should carry a regression test, and the auditors should review the fixes before deployment.