Skip to content

How to read a smart contract audit

A smart contract audit is a bounded review of specified code, builds, deployments, assumptions and properties; its findings and fixes must be reconciled with the live system.

Updated

For educational purposes only; not investment or security advice. An audit report, tool result, resolved finding or matching source file is not certification, insurance, compensation or proof that a live deployment is safe.

Direct answer

A smart contract audit is a bounded examination of specified requirements, source code, build inputs, deployment logic and security assumptions during a stated period. Reviewers use complementary methods to identify defects, demonstrate exploit paths, evaluate impact and check proposed fixes. The conclusion applies only to the engagement snapshot and evidence described in the report.

The snapshot should pin the repository and commit or tree hash, submodules and dependency locks, compiler and settings, generated code, deployment scripts, target chains and addresses, proxy, implementation or beacon, constructor or initializer data, libraries, administrators, timelocks and a block or time. Explicit exclusions matter as much as inclusions: a frontend, keeper, oracle, bridge, governance process or off-chain signer can dominate risk while remaining outside the audit.

An audit needs a threat model and a specification. Identify assets, actors, privileged roles, trust boundaries, attacker capabilities, ordering and reorganization assumptions, external dependencies, state transitions and precise safety and liveness properties. An invariant without units, preconditions, quantifiers and exceptions can prove or test the wrong behavior perfectly.

Use four ledgers: scope, build and deployment identity; requirements, threats and invariants; findings, evidence and retest; and residual risk, acceptance and disclosure. A report with no critical finding is not a security certificate, a finding marked resolved is not necessarily deployed, and a proof covers only its encoded property, model and assumptions.

How it works

Manual review traces architecture, fund flow, cross-function state and economic intent. Static analysis finds patterns and data flows but can produce false positives and false negatives. Unit, integration, fork and differential tests compare concrete behavior. Stateful fuzz and invariant tests explore generated sequences, but results depend on handlers, selectors, seeds, corpus, runs, depth and the modeled environment.

Symbolic execution and formal verification can establish selected assertions under supported semantics and assumptions. A solver result of unknown, timeout or unsupported behavior is not a proof. Even a proved property can omit oracle economics, governance, deployment configuration, chain behavior or the requirement the team actually intended. Human review owns the specification and interpretation; an AI-generated observation is not a separate assurance method.

Each finding should identify the affected artifact and deployment, prerequisite, minimal proof, exploit path, reachability, privileges, attacker capital, repeatability, economic impact, severity rubric and recommendation. Exploitability or likelihood and impact are separate dimensions. A theoretical maximum, vulnerability name or tool label does not establish executable loss.

Statuses such as open, acknowledged, risk accepted, partially fixed, resolved and retested are not universal standards. A defensible closure binds the original issue to an exact fix commit, records the changed and adjacent paths tested, and states who retested what and when. Accepted risk remains risk, and a limited retest does not extend the original scope to all new code.

Upgradeable deployments require special reconciliation. Resolve proxy, implementation or beacon and admin slots; verify initializer and reinitializer behavior, implementation locking, storage compatibility, upgrade authorization, timelock or emergency bypass, migration and rollback. Reproduce creation and runtime bytecode from the audited build, then compare linked libraries, parameters, roles and initialized state on every chain.

The final report should state revision, auditors and dates, exact scope, methods and configurations, limitations, findings, evidence, remediation status, unresolved or accepted risks and disclosure terms. After release, monitor implementation hashes, roles, parameters, dependencies and incidents. Any material change creates a new delta requiring review; a badge attached to an old report does not follow future code automatically.

Use this workflow:

  1. Freeze the engagement manifest: repository, commit, dependencies, compiler and settings, generated and deployment code, chains, addresses, proxy stack, parameters, block, report revision, inclusions and exclusions.
  2. Define assets, actors, privileged roles, trust boundaries, attacker capabilities, lifecycle, ordering and liveness assumptions, external dependencies and measurable invariants.
  3. Reproduce the build and map architecture, storage, data, funds and control; reconcile source, artifacts, libraries, creation and runtime bytecode, initializer, roles and live deployment.
  4. Run complementary manual, static, unit, integration, fork, differential, fuzz, invariant, symbolic or formal methods with recorded tool versions, configurations, seeds, corpus, coverage, timeouts and unknowns.
  5. Record each issue’s affected artifact, prerequisite, proof, exploitability, impact, severity method, deployment exposure, recommendation and confidential evidence without treating a tool label as judgment.
  6. Freeze the fix commit and retest the issue, adjacent paths and invariants; validate proxy storage, initialization, migration, rollback, reproducible build and deployment receipts, then assign an evidence-backed status.
  7. Publish scope, methods, limits and residual risks; reconcile audited artifacts to every live chain and keep monitoring, disclosure, incident response and bug-bounty controls current as the system changes.

Examples

  • A vault inflation path needs a complete ledger. An attacker deposits 1 asset through the first-deposit branch and receives 1 share, then donates 1,000,000 assets; totals become 1,000,001 assets and 1 share. A victim deposits 500,000 assets, and an unsafe floor calculation gives floor(500,000 * 1 / 1,000,001) = 0 shares. If zero-share deposits are accepted, the vault holds 1,500,001 assets; the attacker redeems them and gains 500,000 assets over its 1,000,001-asset contribution. If the implementation reverts on zero shares, this exact loss path does not execute.
  • File coverage is not deployment coverage. A manifest contains 24 source units, 4 deployment scripts and 3 keeper services, or 31 items. The engagement includes 20 source units and 2 scripts, so count coverage is 22 / 31 = 70.96774194%; the other 9 items are excluded. If the live proxy points to an implementation built from an excluded unit, coverage of that live implementation is 0% despite the 70.96774194% headline.
  • Fuzz observations are not an absence proof. A stateful run executes 2,000 sequences * 64 calls = 128,000 calls; an invariant fails in 3 sequences, an observed 3 / 2,000 = 0.15% of generated sequences. After a fix, 10,000 sequences * 64 calls = 640,000 calls produce zero failures. That is zero in this corpus, not proof; under an independence and stable-generator teaching assumption, the rule-of-three approximate 95% upper bound is 3 / 10,000 = 0.03% per generated sequence.
  • Finding closure and deployment identity are independent. A report has 12 findings: 2 critical, 3 high, 4 medium and 3 low. Retest closes 2 + 2 + 3 + 2 = 9, so count closure is 9 / 12 = 75%; one high, one medium and one low remain. Audited runtime hash is H1, but the live implementation is H2, so deployment verification fails regardless of the closure rate. Replacing it with exact H1 and matching proxy slot, initializer and roles proves identity only at the checked block.

Risks

  • The repository, commit, submodule or generated source is stale or ambiguous.
  • Compiler version, optimizer settings, libraries or dependencies are not pinned.
  • Deployment scripts, constructor data, initializer or CREATE2 salt are excluded.
  • The wrong chain, address, proxy, beacon or implementation is inspected.
  • Source, artifact, creation bytecode and runtime bytecode do not reconcile.
  • The threat model omits an actor, privilege, asset or trust boundary.
  • The specification or invariant has wrong units, preconditions or exceptions.
  • Admin, guardian, timelock, pause, upgrade or migration paths are missed.
  • Oracle, token, bridge, keeper, governance or chain assumptions fail.
  • Static analysis produces an untriaged false positive.
  • Manual review, testing or fuzzing misses an ungenerated path.
  • Fuzz harness, selector, seed, corpus, depth or state model is biased.
  • Solver timeout, unsupported semantics or unknown is mistaken for proof.
  • A correct proof formalizes the wrong requirement or incomplete system.
  • Severity is anchored on a vulnerability name instead of exploitability and impact.
  • Theoretical value at risk is mistaken for reachable loss or attacker profit.
  • A fix introduces an adjacent regression or breaks an economic invariant.
  • Proxy storage, initializer, upgrade or migration corrupts live state.
  • An accepted, open or partially fixed issue is hidden by a passed-audit badge.
  • The report is treated as insurance, certification, compensation or permanent coverage.

Common misconceptions

  • “No critical findings means the contract is secure.” It describes reported findings within a bounded scope, time and method set, not all possible defects.
  • “High test coverage or zero fuzz failures proves there are no bugs.” Those are measurements of selected code and generated paths, not absence proofs.
  • “Formal verification proves the whole protocol safe.” It proves encoded properties of a model under assumptions; specifications and surrounding systems can still be wrong.
  • “Resolved means every live deployment is fixed.” Closure requires independent retest plus build, bytecode, proxy, parameter and role reconciliation for each deployment.
  • “A reputable auditor guarantees compensation or future upgrades.” Liability depends on the engagement contract, users may not be beneficiaries, and later code or configuration falls outside the old snapshot.

Sources

Navigation

Search the wiki...