Solidity Pre-Audit Preparation: A Checklist for Web3 Teams
Prepare a Solidity audit package with frozen code, documentation, tests, self-review findings, defined scope, and time for remediation.
Prepare the code, tests, architecture notes, and review scope before a Solidity audit begins. Auditors need to know the intended behavior, trust assumptions, exact compiler and dependencies, and known limitations.
Solidity Pre-Audit Preparation Checklist
Code Quality and Documentation
Lock your compiler version
Use a fixed Solidity version in every file (pragma solidity 0.8.24; not ^0.8.0). The caret syntax allows the contract to compile under future compiler versions with potentially different behavior. Auditors want to know exactly what compiler was used.
Write NatSpec comments for every external and public function
NatSpec is Ethereum's documentation standard. Every external and public function should have @notice (what it does for users) and @dev (technical implementation notes). Every parameter should be documented with @param and return values with @return. The comments give auditors a statement of intended behavior to compare with the implementation.
Write an architecture overview document
One or two pages covering:
- What the protocol does and its economic design
- How the contracts relate to each other (a diagram helps)
- What external protocols are integrated and why
- What the key invariants of the system are (e.g., "the sum of all user balances should always equal the total supply token held by the contract")
- Known trade-offs or design decisions that are intentional
Define scope explicitly
List every file that is in scope and every file that is out of scope, with brief justification. If a library file is an unmodified copy of a previously audited dependency, say so.
Code Cleanliness
Remove all TODO and FIXME comments
If there are unresolved questions or incomplete implementations in the code, resolve them before the audit. Auditors who find TODOs may not know which are genuine planned work and which represent security gaps.
Remove unused imports, variables, and functions
Dead code creates noise and costs auditor time. Run a linter (Solhint or Slither's unused variable checks) and remove everything that is not needed.
Use named constants for magic numbers
Replace inline numbers with named constants that communicate intent. LIQUIDATION_THRESHOLD = 8000 is auditable; 8000 in the middle of a calculation is not.
Consistent error messages
Use custom errors rather than string revert messages where possible (cheaper gas, clearer intent). Custom errors should have descriptive names: InsufficientBalance() is auditable; E1() is not.
Testing
Achieve meaningful unit test coverage
Not necessarily 100%, but every critical path — deposits, withdrawals, liquidations, reward calculations — should have unit tests. Auditors use test suites to understand intended behavior.
Write at least basic invariant tests
Foundry's invariant testing feature and Echidna both let you define properties that should always hold and fuzz the contract looking for violations. Key invariants to cover:
- Token accounting: total supply equals sum of all balances
- Fund custody: ETH or tokens held by the contract match what accounting says should be there
- Authorization: no unauthorized address can trigger a privileged operation
Include negative test cases
Test that unauthorized callers are rejected. Test that operations fail correctly under invalid inputs. These tests document expected behavior under failure conditions and give auditors confidence that error handling is intentional.
Run all tests on the exact codebase being submitted
Do not submit code that does not pass its own test suite. Failing tests during an audit engagement are a red flag and waste auditor time.
Security Self-Review
Run Slither and address output
Slither is a free static analyzer that catches many common vulnerability patterns. Run it against your codebase and triage all findings before the audit. For findings you disagree with or have intentionally accepted, document your reasoning. Auditors who receive a list of pre-triaged Slither findings with explanations spend less time on known patterns and more time on novel issues.
Verify all external calls follow CEI
Check every external call in the codebase — ETH transfers, ERC-20 transfers, calls to external contracts. Confirm that all state updates happen before the external call. This is the Checks-Effects-Interactions pattern. Flag any place where this order is not followed and document why.
Verify access control on every function
Generate a list of all external and public functions and their access modifiers. Every function that should be restricted should have an explicit modifier enforcing that restriction. Functions with no access restriction should be deliberately public — not accidentally so.
Check for hardcoded addresses
Hardcoded addresses for token contracts, oracle feeds, or external protocols should be documented. If these are expected to change, the mechanism for updating them should be access-controlled.
Audit Engagement Logistics
Designate a technical point of contact
The auditing firm will have questions. A single senior developer who can respond within hours rather than days keeps the engagement moving and prevents delays.
Allocate time for remediation
Build calendar time into the release schedule for the remediation phase after the initial audit report. Fixing findings and having them retested takes time. Protocols that rush remediation introduce new bugs.
Budget for a retest
Confirm upfront that the engagement includes retesting of critical and high findings after remediation. A report with no retest is not a complete engagement.
Keep the exact reviewed revision, scope, findings, and retest results together. Preparation supports the audit but does not guarantee its coverage or outcome.
For help preparing a Solidity codebase for independent review, contact LinkIT.