Crypto Training
Invariant-First Auditing: Closing the 40% Gap in Automated Coverage
Static checks and fuzzing are necessary but insufficient. Invariant-first auditing makes protocol safety properties explicit and testable across long adversarial sequences.
Tooling catches many bugs, not all of them.
The misses are usually compositional, economic, or state-transition failures.
Invariant-first auditing is the practical way to target that gap.
flowchart LR
A[Threat model] --> B[Safety property]
B --> C[Executable invariant]
C --> D[Fuzz / invariant test harness]
D --> E[Counterexample trace]
E --> F[Patch + regression invariant]
What makes a strong invariant#
A strong invariant is:
- global (covers system behavior, not one function),
- adversarially meaningful,
- inexpensive enough to run continuously.
| Weak invariant | Strong replacement |
|---|---|
balance >= 0 | total assets >= total liabilities + realized payouts |
withdraw does not revert | withdraw reduces user claim and preserves conservation |
oracle price > 0 | state transitions are blocked when freshness/liveness checks fail |
Example: split-merge equivalence invariant#
function invariant_splitMergeEquivalence() public {
uint256 x = bound(rand(), 1e6, 1e24);
uint256 a = x / 3;
uint256 b = x - a;
uint256 outDirect = model.simulateSinglePath(x);
uint256 outSplit = model.simulateSplitPath(a, b);
// allow explicit fee tolerance only
assertLe(_absDiff(outDirect, outSplit), model.maxRoundingBudget());
}
This catches rounding composition bugs that single-call unit tests miss.
Multi-step adversarial state transitions#
stateDiagram-v2
[*] --> Empty
Empty --> Deposited: deposit
Deposited --> Levered: borrow
Levered --> Distressed: price move
Distressed --> Repaired: repay
Distressed --> Liquidated: keeper action
Repaired --> Levered
Liquidated --> Empty
Every edge should preserve a declared global safety property.
Harness design pitfalls#
- Handlers that revert too often create false confidence.
- Unrealistic bounds hide edge behavior.
- Missing malicious mocks removes real attack surfaces.
Foundry structure that scales#
forge test --match-path test/invariants/*.t.sol -vvvv
forge test --match-test invariant_ -vvvv
contract InvariantHandler {
function act_deposit(uint256 amt) external { /* bounded adversarial input */ }
function act_withdraw(uint256 shares) external { /* ... */ }
function act_rebalance(bytes calldata path) external { /* external-call boundary */ }
}
Practical rollout plan#
| Week | Deliverable |
|---|---|
| 1 | threat model + invariant shortlist |
| 2 | executable invariants for top 5 risk flows |
| 3 | malicious mocks + sequence fuzzing |
| 4 | CI gating + regression library |