The 14-step pre-audit checklist we run on every smart contract before mainnet.

Audits are expensive. Findings are more expensive. Use this list before you book one. If you can tick all 14, your audit will be cheaper, faster, and cleaner — and your contract will not show up on the list of hacks next month.

Code editor showing Solidity contract beside a 14-step pre-audit checklist.

Every Web3 protocol that gets hacked makes one of two mistakes. Either it skipped an audit entirely, or it walked into the audit with code that needed weeks of cleanup before the auditors could even start. The second mistake is the more expensive one. You pay senior auditors at $400 an hour to point out things any junior dev could have caught.

This is the checklist we run on every contract before it leaves our studio. Tick all 14 and your audit will go faster, cost less, and come back with fewer findings. Skip any of them at your own risk.

01 Why pre-audit matters

Audit firms charge by complexity and by how much fixing they have to do. A clean codebase that just needs a security review costs less. A messy codebase where the auditor finds basic issues on day one costs more — partly because they have to write up findings, partly because every fix needs a re-review, and partly because they quote higher rates for code they expect to be a mess.

More importantly: a clean codebase finds fewer real issues. The kind of bugs that lose money to attackers tend to hide behind sloppy patterns. Tighten the patterns first, and the dangerous bugs are easier to spot.

02 The 14 checks, in the order we run them

We do these in roughly this order because each one makes the next one easier. Skipping ahead works, but you will end up redoing earlier ones.

  1. 01

    Compiler version pinned

    Use pragma solidity 0.8.24; not ^0.8.0. A floating compiler version means the contract auditors test is not necessarily the one that gets deployed. Pin to an exact patch version. This is a thirty-second change that auditors flag as a finding if you skip it.

  2. 02

    Checked math (Solidity 0.8+ default, but verify)

    Solidity 0.8 and up has built-in overflow checks, so SafeMath is no longer needed. But check that you are not using unchecked { } blocks unless you have a real reason. Every unchecked block needs a comment explaining why it is safe.

  3. 03

    Reentrancy guards on every external call

    Any function that transfers ETH, calls another contract, or performs a token transfer needs to be protected. Use nonReentrant from OpenZeppelin’s ReentrancyGuard, and pair it with the CEI pattern (check 05). Reentrancy is still the most common cause of real-money exploits.

  4. 04

    Access control on every privileged function

    Walk every function that changes state. Ask: should anyone be able to call this? If the answer is no, it needs an onlyOwner or a role-based modifier. Missing access control is the easiest hack on earth, and it still happens to live protocols every quarter.

  5. 05

    CEI pattern (Checks-Effects-Interactions)

    Every function that does external calls follows the same order: check requirements first, update state second, call external contracts last. If you call an external contract before updating your own state, you are giving an attacker a window to re-enter and double-spend. CEI closes that window.

  6. 06

    Custom errors instead of revert strings

    Replace require(condition, "error message") with if (!condition) revert ErrorName();. Custom errors cost less gas and read better. Every modern Solidity codebase should be on custom errors. Auditors expect it.

  7. 07

    Slither and Mythril clean

    Run static analysis tools before you book the audit. Slither and Mythril are free and catch the obvious problems that auditors should not be wasting time on. Aim for zero high-severity findings on Slither before sending the code to a paid firm.

  8. 08

    100% branch coverage in tests

    Line coverage is not enough. Branch coverage tracks every if branch and every conditional path. Use forge coverage or Hardhat’s coverage plugin. Anything below 100% on critical paths means you have code that has never been executed. That is where bugs live.

  9. 09

    Fuzz tests on input-bounded functions

    Use Foundry’s built-in fuzzing or Echidna for property-based testing. Throw a few thousand random inputs at every function that takes user input. Fuzzing finds the edge cases your unit tests forgot about. One hour of fuzz setup saves you from a $5M exploit.

  10. 10

    Invariant tests for protocol-level rules

    Invariants are statements that should be true forever regardless of how the contract is used. Examples: total supply equals the sum of balances, the contract never holds less ETH than it owes users. Foundry has good invariant test tooling. Every protocol should have at least three.

  11. 11

    Gas snapshot in CI

    Track gas costs per function in your test suite. Commit a snapshot file. If a code change blows up gas usage by 30%, your CI should fail. Gas regressions are sometimes accidental and sometimes intentional — either way you want to see them before they hit mainnet.

  12. 12

    Storage layout documented

    For upgradable contracts especially, the storage layout matters more than the code. Document which slot holds what. Use OpenZeppelin’s upgrade plugin to detect storage collisions. A storage collision on an upgrade can brick the contract permanently.

  13. 13

    Deployment scripts archived and source verified

    Save your deployment script in the repo, with the exact constructor arguments and network used. Verify the source on Etherscan or whatever block explorer applies. Unverified contracts make users nervous and make incident response harder. There is no good reason to skip verification.

  14. 14

    Pause and upgrade plan documented

    Write down what happens when something breaks. Who can pause the contract? Who can upgrade it? What is the timelock? What is the multisig threshold? If your only answer is “the founder will fix it,” that is a finding. Document the plan, even if the plan is “immutable, no upgrades possible” — that is also a valid plan.

03 What this saves you in dollars

A typical Web3 audit for a mid-complexity protocol runs $25,000 to $80,000. The variation is mostly driven by code quality going in. We have seen the same firm quote $40,000 for a clean codebase and $90,000 for a messy one of the same size, with the messy one also taking three weeks longer.

Beyond the audit cost itself, the bigger savings come post-launch. Each finding that lands in the public audit report is a piece of ammunition for users to question the protocol. Each fix between audits costs another round of review. Each missed bug becomes the news story. Pre-audit work pays for itself ten times over.

04 What auditors actually look for, in order

For context, here is what most reputable audit firms check first when they receive a codebase. If you are missing the items at the top, fix those before you send anything:

All seven of those map directly to the 14 checks above. The checklist is not arbitrary. It is the same list auditors run, just run earlier and by you.

05 Tools we actually use

None of these are exotic. They are all free or very cheap.

06 How long does this take?

On a fresh contract written with these patterns from day one, the full checklist takes about a week of focused work after the feature code is done. On a legacy contract being prepped for audit, it can take three weeks. Either way, that is time you would have spent anyway — just before the audit, instead of during it at four times the hourly rate.

Worth every hour.

— If you are about to ship a contract

We do this work for protocols every quarter.

If you have a smart contract heading for an audit, we can run this checklist and the cleanup before you book the audit firm. Faster audit, fewer findings, lower total cost. Worth a call.

See our Web3 services Or just say hello