Defence stack for contracts
In one line
Section titled “In one line”No single layer is enough, so contracts are defended in five: build and test, review, deploy, run, respond. The first two try to stop a bug from shipping. The last three limit what a shipped bug or a stolen key can do.
Why it exists
Section titled “Why it exists”Ordinary software leans on patching and rollback. On-chain, deployed code is immutable, transactions are final, and every bug has a payout. See Basics. That shifts the weight in two directions.
- Before deployment, because there is no quiet hotfix. Hence fuzzing, invariant tests, formal verification and several kinds of external review.
- Around the contract at runtime, because prevention fails anyway. The largest recent losses were key and infrastructure failures, not code bugs (Key and signer attacks). Hence timelocks, pause switches, withdrawal caps and monitoring that pages in minutes. Ronin’s 2022 loss was noticed six days later.
How it works
Section titled “How it works”1. Build and test
Section titled “1. Build and test”- Foundry for unit tests, fuzz tests, invariant tests, and fork tests against mainnet state.
- Static analysis in CI: Slither, Aderyn, Semgrep Solidity rules.
- Fuzzers for longer campaigns: Echidna, Medusa.
- Formal verification for core maths: Certora, Halmos.
An invariant is a statement that must hold after any sequence of calls, for example “no account ends insolvent” or “total assets are at least total claims”. The fuzzer generates random call sequences and tries to break it. Euler’s 2023 loss came from one function that skipped a solvency check the others had, which is what an invariant test exists to find.
2. Review
Section titled “2. Review”- An audit per release, scoped to the diff plus the storage layout.
- Competitive audits (Code4rena, Sherlock, Cantina), where many reviewers compete on findings.
- A standing bug bounty (Immunefi) with a published scope.
Closed source is not much of a control. Bytecode, storage and every transaction are public, and decompilers are good. It slows a casual attacker by days.
3. Deploy
Section titled “3. Deploy”- Deploy from CI with provenance (SLSA, Sigstore), not from a laptop.
- Deterministic addresses (CREATE2), verified source, and on-chain bytecode compared with the build artefact.
- The deployer key has no lasting power. Ownership moves to a multisig plus timelock in the same script.
- Deploy and initialise in one transaction. See Proxies and upgrades.
4. Run
Section titled “4. Run”- Monitoring: Forta, Hypernative, Hexagate, Tenderly alerts.
- What to watch:
Upgraded,DiamondCut,RoleGranted,OwnershipTransferred, oracle deviation, outflows against a baseline, unusual callers on sensitive functions. - Circuit breakers: a pause guardian, per-period withdrawal caps.
- Timelock: a mandatory change window before an admin change applies. It gives watchers time to react, and it turns a bad signature into a visible queued action instead of an instant loss.
- Role separation: a pauser that can only pause, an upgrader behind multisig and timelock. The pause guardian is a separate low-privilege role so pausing is fast.
5. Respond
Section titled “5. Respond”- A runbook: detect, pause, war room, communicate.
- SEAL 911 for live help from trusted security responders. SEAL Safe Harbor is a framework that lets a protocol offer legal protection to white-hats who rescue funds during an active exploit.
- A pre-signed pause is fine. Pre-signed anything else is how Drift lost about $285M in 2026.
Compare
Section titled “Compare”Testing and analysis techniques
Section titled “Testing and analysis techniques”| Technique | What it does | Finds | Misses | Tools |
|---|---|---|---|---|
| Unit test | Checks one function with chosen inputs | Mistakes you thought of | Everything you did not think of | Foundry |
| Fuzz test | Runs one function with many random inputs | Edge values, rounding at tiny amounts | Bugs that need a sequence of calls | Foundry, Echidna, Medusa |
| Invariant test | Runs random sequences of calls and checks a property after each | Missing checks, broken accounting across functions | Properties nobody wrote down | Foundry, Echidna, Medusa |
| Fork test | Runs against a copy of real mainnet state | Integration surprises with real tokens and protocols | Future state | Foundry |
| Static analysis | Scans source for known bad patterns without running it | Reentrancy shapes, tx.origin, unchecked calls |
Logic errors; produces false positives | Slither, Aderyn, Semgrep |
| Formal verification | Proves a property for all inputs | Maths errors no fuzzer reaches | Anything outside the written specification; costly | Certora, Halmos |
Kinds of review
Section titled “Kinds of review”| Review | Who looks | When | Strength | Limit |
|---|---|---|---|---|
| Internal review | The team | Every change | Knows the system | Shares the authors’ blind spots |
| Audit | A hired firm, a few people for a few weeks | Per release | Depth on the scoped diff | Only what was in scope, only at that commit |
| Competitive audit | Many independent researchers | Per release | Breadth; many eyes | Uneven depth; needs triage |
| Bug bounty | Anyone | Continuously, on deployed code | Covers the live system for its whole life | Pays only after something is found; needs a clear scope |
Runtime controls
Section titled “Runtime controls”| Control | What it limits | Cost | Fails when |
|---|---|---|---|
| Timelock | How fast an admin change can take effect | Slower legitimate changes | Nobody watches the queue; delay set to zero |
| Pause guardian | How long an exploit can run | A role that can halt the system | The role is slow to reach, or can do more than pause |
| Withdrawal cap per period | How much can leave in one window | Friction for large legitimate withdrawals | The cap is above what the protocol can afford to lose |
| Multisig on admin roles | Who can change the system | Slower operations | Signers share one UI or one fleet of devices |
| Monitoring and alerts | Time to notice | On-call load, false positives | Alerts go to a channel nobody reads at night |
| Bug bounty | The incentive to exploit instead of report | Payouts | The bounty is far smaller than the money at risk |
What breaks
Section titled “What breaks”| Symptom | Cause | Check |
|---|---|---|
| An exploit used a call sequence no test covered | No invariant tests, or too few runs | forge test --match-test invariant; raise the run count and depth in foundry.toml |
| A bug only appears with a real token or pool | Tests use mocks | forge test --fork-url $RPC |
| Rounding bug at tiny amounts | Fuzz inputs never reached the edge | forge test --fuzz-runs 100000; bound inputs to include 0, 1 and the maximum |
| A known pattern shipped anyway | Static analysis not in CI, or findings ignored | slither . as a required CI step |
| Deployed code differs from the audited commit | Built or deployed by hand | Compare cast code $ADDR with the build artefact; verify source with forge verify-contract |
| Upgrade corrupted state | Storage layout changed | forge inspect MyContract storageLayout, diffed against the previous release |
| An admin change went through unnoticed | No alert on admin events | Alert on Upgraded, DiamondCut, RoleGranted, OwnershipTransferred |
| The pause came too late | Pause needs the same slow multisig as upgrades | A separate pause role, with a pre-signed pause transaction ready |
| The deployer address still owns the contract | Ownership transfer was a manual follow-up step | Transfer ownership in the deploy script; assert the owner at the end |
Incident runbooks worth having ahead of time, one per scenario: oracle failure, liquidation or keeper bot down, key suspected compromised, contract exploit. Each names who may pause, and who to call.
Keywords
Section titled “Keywords”defence in depth, Foundry, forge test, unit test, fuzz test, invariant test, fork test, Echidna, Medusa, Slither, Aderyn, Semgrep, static analysis, formal verification, Certora, Halmos, audit, competitive audit, Code4rena, Sherlock, Cantina, bug bounty, Immunefi, provenance, SLSA, Sigstore, CREATE2, deterministic deployment, verified source, bytecode comparison, deployer key, multisig, timelock, TimelockController, pause, Pausable, pause guardian, circuit breaker, withdrawal cap, rate limit, role separation, AccessControl, monitoring, Forta, Hypernative, Hexagate, Tenderly, Upgraded, DiamondCut, RoleGranted, OwnershipTransferred, oracle deviation, outflow baseline, runbook, war room, SEAL 911, Safe Harbor, white-hat.
Sources
Section titled “Sources”- ethereum.org: Smart contract security
- ethereum.org: Testing smart contracts
- ethereum.org: Formal verification of smart contracts
- Foundry: fuzz testing
- Foundry: invariant testing
- Foundry: fork testing
- Foundry: forge test reference
- Slither
- Echidna
- Medusa
- Aderyn
- Halmos
- Certora documentation
- OpenZeppelin Contracts: Utilities, including Pausable and ReentrancyGuard
- OpenZeppelin Contracts: Governance, including TimelockController
- OpenZeppelin Contracts: Access control
- EIP-1014: CREATE2
- SLSA specification
- Sigstore documentation
- SEAL 911
- SEAL Whitehat Safe Harbor Agreement
- Forta documentation