Smart Contract Audit Checklist (Before You Ship to Mainnet)

Smart contract audits aren’t a ceremonial rubber stamp—they’re a structured way to reduce existential risk. On-chain bugs don’t just crash a service; they can permanently leak funds, corrupt protocol state, and destroy user trust in minutes. The best teams treat “audit readiness” as an engineering discipline: clear invariants, aggressive testing, minimized trust, and deployment hygiene.

Below is a pragmatic checklist you can use whether you’re preparing for a third‑party audit or running an internal security review. It’s biased toward what actually breaks in production: permissions, accounting, upgradeability, and integration edges.

1) Scope, threat model, and invariants

Before anyone reads line 1 of code, lock down what you’re auditing and what “correct” means.

  • Define scope: contracts, libraries, proxies, deployment scripts, and off-chain components (keepers, oracles, relayers). Excluding “just one” helper contract is how vulnerabilities slip through.
  • Document trust assumptions: who can pause, upgrade, mint, whitelist, set fees, change oracle sources, or sweep funds.
  • Write invariants (must always hold):
    • Total assets = sum of user balances (minus fees) for vaults.
    • Shares ↔ assets conversions are monotonic and cannot be manipulated via rounding.
    • No user can withdraw more than they deposited + yield.
    • Protocol parameters remain within safe bounds.

If you can’t articulate invariants, you can’t meaningfully audit. You’ll only spot syntactic issues—not economic or accounting failures.

2) Access control and governance

Most real-world exploits are permission misconfigurations or admin key abuse pathways.

  • Enumerate privileged roles (owner, admin, governor, pauser, operator, upgrader) and list what each can do.
  • Enforce least privilege: split roles; don’t let a single key both upgrade and drain.
  • Two-step ownership transfers: require pendingOwner acceptance to prevent accidental transfers.
  • Timelocks + multisig: production systems should route sensitive actions through a timelock and multisig, not EOAs.
  • Parameter bounds: hard-cap fees, slippage settings, leverage, LTV, or reward rates. Auditors love seeing require(newFee <= MAX_FEE_BPS).

Practical example: fee setters without upper bounds have repeatedly enabled “soft rug” scenarios (set fee to 100%, users can’t exit without losing everything).

3) Upgradeability and proxy hygiene

Upgradeability is a security tradeoff. If you use it, do it correctly.

  • Identify proxy pattern: UUPS vs Transparent vs Beacon. Verify it matches your tooling.
  • Initializer safety:
    • Use initializer/reinitializer correctly.
    • Disable initializers on implementation contracts (_disableInitializers()), or attackers can initialize the logic contract and hijack roles.
  • Storage layout:
    • Check storage gaps and layout compatibility across upgrades.
    • Avoid changing variable order/types in upgradeable contracts.
  • Upgrade authorization: confirm _authorizeUpgrade() is restrictive and cannot be bypassed.

Opinionated guidance: if you don’t need upgrades, don’t ship them. A simple, immutable contract with good escape hatches often beats a complex upgrade surface.

4) Core logic, state transitions, and accounting

This is the heart of most audits: verifying the contract does what it claims—especially around balances.

  • Review state transitions: deposits, withdrawals, liquidations, claims, swaps, rebases—each must be consistent.
  • Check for rounding and precision issues:
    • Prefer consistent math libraries.
    • Validate conversions (shares/assets) don’t allow sandwich-style dilution.
  • Validate edge cases:
    • 0 amount operations
    • first depositor / empty pool
    • max values
    • fee-on-transfer tokens
    • rebasing tokens
  • Confirm event correctness: events should reflect real state changes; index key parameters for monitoring.

Classic pitfall: “share price manipulation” in vault-like contracts when share minting uses a naive formula and can be influenced by donations or small deposits.

5) External calls, reentrancy, and composability

If your contract calls out to tokens, routers, bridges, or oracles, you’re in adversarial territory.

  • Reentrancy:
    • Apply checks-effects-interactions.
    • Use ReentrancyGuard where appropriate, but don’t rely on it to fix broken logic.
    • Watch for reentrancy through ERC777 hooks, fallback functions, or callback-based AMMs.
  • ERC20 interaction safety:
    • Use SafeERC20.
    • Handle non-standard returns.
    • Ensure allowances aren’t left dangerously high for third parties.
  • Call failure handling: decide whether a failed external call reverts or is tolerated; be explicit.

6) Oracles, pricing, and economic attack surfaces

Many “smart contract hacks” are actually oracle or market-structure exploits.

  • Oracle source validation:
    • Use time-weighted pricing (TWAP) when relevant.
    • Avoid spot price from a low-liquidity pool.
    • Check staleness, heartbeat, and decimals.
  • Manipulation resistance:
    • Model flash-loan price manipulation.
    • Add circuit breakers (max deviation, min liquidity thresholds).
  • Economic invariants:
    • Confirm liquidation math can’t be gamed.
    • Ensure fees don’t create perverse incentives (e.g., profitable self-liquidation loops).

Real example pattern: protocols using DEX spot prices for collateral valuation get drained when attackers move the price briefly, borrow against inflated collateral, then unwind.

7) Denial of service and gas griefing

Funds can be safe yet unusable. That’s still a failure.

  • Unbounded loops: iterating over user arrays or positions can brick functions as the protocol grows.
  • Block gas limits: ensure critical functions remain callable under worst-case conditions.
  • DoS via revert: if a single failing transfer blocks an entire batch process, you have a hostage problem.
  • Rate limiting: protect against spam on costly state-changing endpoints.

8) MEV and transaction-order dependence

If your protocol touches swaps, auctions, liquidations, or mint/redeem mechanics, assume MEV bots are your most active users.

  • Slippage and deadline checks on swaps.
  • Commit-reveal or batch auctions for sensitive pricing.
  • Anti-sandwich measures where feasible (e.g., TWAP, min-out, max-in).
  • Transaction ordering assumptions: ensure correctness doesn’t depend on “this runs first.” It won’t.

9) Testing, verification, and audit artifacts

Auditors move faster—and find deeper issues—when you provide strong artifacts.

  • Unit + integration tests for every external function and major path.
  • Property-based/fuzz testing for invariants (Foundry fuzzing is a workhorse here).
  • Static analysis: Slither, Mythril, Semgrep rules, and custom checks.
  • Formal verification (selectively): focus on the vault math, liquidation logic, or upgrade authorization—small critical kernels.
  • Reproducible builds: pinned compiler versions, deterministic deployments, clear CI.

10) Deployment, configuration, and operational security

A perfect codebase can still ship insecurely.

  • Verify constructor/initializer parameters: token addresses, oracle feeds, admin roles, fee recipients.
  • Environment separation: testnet vs mainnet addresses and configs must be isolated.
  • Key management:
    • multisig signers, hardware wallets
    • no private keys in CI logs
    • rotate compromised keys with a playbook
  • Monitoring and incident response:
    • alerts on upgrades, admin actions, large transfers
    • pause mechanism tested (yes, tested)
    • runbooks for oracle outages, chain halts, and bridge issues

Conclusion: audits are a process, not an event

A smart contract audit checklist is less about “finding bugs” and more about proving your protocol’s assumptions are defensible under adversarial conditions. If you do the work—clear invariants, tight permissions, robust oracle design, and serious testing—you’ll not only get more value from external auditors, you’ll ship a protocol that survives contact with mainnet reality.

If you want one rule of thumb: minimize trust and complexity, then test the remaining complexity until it’s boring. Boring is good. Boring is safe.