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.

Crypto Training2026-01-082 min read

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 invariantStrong replacement
balance >= 0total assets >= total liabilities + realized payouts
withdraw does not revertwithdraw reduces user claim and preserves conservation
oracle price > 0state transitions are blocked when freshness/liveness checks fail

Example: split-merge equivalence invariant#

SOLIDITY
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#

  1. Handlers that revert too often create false confidence.
  2. Unrealistic bounds hide edge behavior.
  3. Missing malicious mocks removes real attack surfaces.

Foundry structure that scales#

BASH
forge test --match-path test/invariants/*.t.sol -vvvv
forge test --match-test invariant_ -vvvv
SOLIDITY
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#

WeekDeliverable
1threat model + invariant shortlist
2executable invariants for top 5 risk flows
3malicious mocks + sequence fuzzing
4CI gating + regression library

Further reading#