Contract Audits and Threat Modeling
Smart contracts hold real value in public, immutable code that anyone can call in any order, with any inputs, funded by flash loans worth millions. A single missed check can drain a protocol in one transaction, and there is rarely a way to roll back. That is why serious teams combine threat modeling — understanding who can do what, which assumptions the system relies on, and where value can be extracted — with audits: structured reviews by people and tools trying to break the code before attackers do.
An audit is not a stamp of safety. Its value depends on how well the team prepares (clear scope, documentation, invariants, tests), how findings are fixed and verified, and what monitoring and response exist after launch.
TL;DR
- Start with a threat model: assets, actors, privileged roles, trust assumptions, external dependencies, and attack surfaces.
- Pay special attention to admin keys, upgradeability, oracles, token integrations, and cross-contract callbacks.
- Write invariants — properties that must always hold — and test them with fuzzing and invariant testing.
- Run static analysis (such as Slither) and fix or justify findings before the audit.
- Freeze a commit, document the system, and define scope; after the audit, provide remediation evidence and get fixes reviewed.
- After deployment, monitor, run a bug bounty, and keep incident response ready (pause mechanisms, communication).
Quick Example
A trust assumptions table — the first artifact auditors ask for:
Invariants expressed as Foundry invariant tests, run against random sequences of calls through a handler contract:
Core Concepts
Threat Modeling for Contracts
A general threat modeling method helps; see Threat Modeling. For contracts, add economic analysis: an attack that costs $1 million in flash-loaned capital is viable if it extracts $2 million.
Privileged Roles and Upgradeability
Admin powers are the largest trust assumption in many protocols. Document every privileged function, require multisig control and timelocks for impactful changes, separate roles (pauser versus upgrader), and make sure emergency powers cannot be abused to steal funds. Upgradeable proxies add risks: unprotected initializers, storage layout collisions between versions, and upgrade keys that can replace all logic.
Common Attack Surfaces
See Smart Contract Security and Solidity.
Testing Before the Audit
- Unit and integration tests for every function and path, including reverts.
- Invariant tests and fuzzing (Foundry, Echidna, Medusa) for properties over random call sequences. See Fuzz Testing and Property-Based Testing.
- Fork tests against mainnet state for integrations with real tokens and protocols.
- Static analysis with tools such as Slither and Aderyn, with findings triaged.
- Formal verification for critical invariants where justified (Certora, Halmos, and others).
The Audit Process
- Scope and freeze: a specific commit, the contracts in scope, and what is out of scope (dependencies, off-chain components).
- Documentation: architecture, roles, trust assumptions, invariants, known issues, and deployment parameters.
- Review: manual review by auditors, supported by tools and their own tests; competitive audit contests add breadth.
- Findings: each with severity (commonly critical, high, medium, low, informational), impact, likelihood, and a proof of concept.
- Remediation: fixes in separate commits, each with a test reproducing the issue.
- Fix review: auditors verify fixes and check for new issues; the final report records the reviewed commit.
- Publication: publish the report and deploy exactly the reviewed code.
Remediation Evidence
For every finding, record the fix commit, the regression test that failed before and passes after, and the auditor's verification. Acknowledged but unfixed issues need a written rationale and, where relevant, mitigations such as parameter limits or monitoring.
After Deployment
- Verify deployments: deployed bytecode matches the audited commit, and initialization parameters match documentation.
- Monitor privileged actions, large flows, oracle deviations, and invariant violations in real time.
- Bug bounty: invite responsible disclosure with rewards proportionate to funds at risk.
- Incident response: tested pause procedures, signer availability, and communication plans. See Security Incident Response.
- Re-audit changes: every upgrade or new integration gets reviewed, not just the initial launch.
Best Practices
Write Invariants First
Clear invariants — solvency, conservation of shares, access rules — guide design, testing, and auditors alike.
Minimize Privilege
Fewer admin powers, behind multisigs and timelocks, mean fewer catastrophic failure modes and more user trust.
Keep Code Small and Reuse Audited Libraries
Use well-reviewed libraries for tokens, access control, and math, and avoid unnecessary complexity in custom logic.
Budget Time for Fixes and Fix Review
Plan launch dates after remediation and re-review, not immediately after the audit report arrives.
Common Mistakes
Auditing a Moving Target
Treating Admin Keys as Out of Scope
Spot Prices as Oracles
FAQ
What is a smart contract audit?
A security review of smart contract code by specialists who analyze design, logic, and implementation for vulnerabilities, report findings with severity ratings, and verify the fixes.
Does an audit guarantee my contracts are secure?
No. Audits reduce risk but are limited by time, scope, and the reviewers' focus. Combine them with thorough testing, invariant fuzzing, minimal privileges, monitoring, bug bounties, and incident response.
What should I prepare before an audit?
A frozen commit, clear scope, architecture and role documentation, trust assumptions, invariants, comprehensive tests, triaged static analysis results, and known issues.
What is threat modeling for smart contracts?
Systematically identifying assets, actors, privileged roles, trust assumptions, and ways value could be stolen or the system disrupted — including economic and ordering attacks — to guide design, testing, and review.
What are invariant tests?
Tests that assert properties which must always hold — such as solvency or conservation of balances — while a fuzzer calls contract functions in random sequences with random inputs.
Related Topics
- Smart Contract Security — Vulnerability classes and defenses
- Solidity — The language most audits review
- Threat Modeling — General threat modeling methods
- Ethereum — The execution environment
- Fuzz Testing — Coverage-guided input generation
- Property-Based Testing — Testing invariants