Testing engagement · From $4,000 · 1–3 weeks

Invariant & Fuzz Testing Suite Development

An audit is a snapshot. An invariant suite is a standing sentry. We elicit the properties your protocol must never violate, encode them as tests with handlers that reach deep state, and leave them running in your CI long after the engagement ends.

Unit tests check that a function does what you expected on inputs you thought of. That is a description of your assumptions, not a search for their failure. Invariant testing inverts it: you state what must always be true, and a fuzzer spends millions of random action sequences trying to make it false.

The hard part is not the tooling — it is the properties. Most teams can name two or three obvious ones and then stall. Eliciting the real set is a design conversation: what does solvency mean here exactly, what is allowed to make the share price fall, which accounting identities must survive a partial liquidation? We run that conversation as a workshop, and it frequently surfaces design ambiguity before any test is written.

The second hard part is reachability. A naive fuzzer bounces off the surface of a protocol, spending its budget on reverted calls. Good handlers — bounded inputs, realistic actor sets, ghost variables tracking expected state — are what let the search reach the deep states where bugs live.

Scope of review

What we look at, and what we're looking for.

01

Property elicitation

A working session to turn 'it should be solvent' into precise, testable statements, with the edge cases that make them interesting written down explicitly.

02

Handler design

Actor models, input bounding and call sequencing that let the fuzzer reach deep protocol state instead of reverting on the first call.

03

Ghost variables

Shadow accounting maintained alongside the protocol so the test can assert what should have happened, not just what did.

04

Foundry invariants

stateful invariant tests with targeted selectors, fail-on-revert configuration and shrinking so a failure arrives as a minimal reproduction.

05

Echidna / Medusa campaigns

Coverage-guided property fuzzing with corpus reuse across runs, so each CI run continues the previous search rather than restarting it.

06

CI integration

GitHub Actions workflows with time-boxed runs on every PR, longer nightly campaigns, and failure artefacts you can replay locally.

Illustrative example

In practice

A handler is what makes fuzzing work

Code on this page is written to illustrate a technique or a finding class. It is not taken from any client engagement.

See a full sample report

Foundry handler — illustrative
// ILLUSTRATIVE — bounded actions and ghost accounting
contract VaultHandler is Test {
    Vault vault;
    address[] actors;
    uint256 public ghost_depositSum;      // what users put in
    uint256 public ghost_withdrawSum;     // what users took out

    // Bound the input so the fuzzer spends its budget on
    // reachable states instead of reverted calls.
    function deposit(uint256 amount, uint256 actorSeed) public {
        address actor = actors[bound(actorSeed, 0, actors.length - 1)];
        amount = bound(amount, 1, 1_000_000e18);
        deal(address(token), actor, amount);

        vm.startPrank(actor);
        token.approve(address(vault), amount);
        vault.deposit(amount);
        vm.stopPrank();

        ghost_depositSum += amount;       // shadow accounting
    }
}

// The invariant then compares the protocol against the ghost state:
//   assertGe(handler.ghost_depositSum(),
//            handler.ghost_withdrawSum());   // no value created

FAQ

Questions we get about this engagement.

More in pricing and the methodology.

A property that must hold after any sequence of user actions — for example, 'the vault always holds at least the sum of what shares can redeem'. The framework generates random call sequences and tries to falsify it. Where a unit test checks one path you thought of, an invariant test searches paths you did not.

Unit tests encode your expectations. Invariant tests encode your requirements and let a machine attack them. Both matter, but only one of them finds the sequence of individually-valid operations whose composition breaks your accounting — and that composition is where a large share of DeFi losses live.

They answer different questions. An audit tells you what was wrong on the day it was read. An invariant suite keeps testing every future commit against the same properties, which is what protects you between audits — and it is the single highest-leverage artefact you can keep from a security engagement.

Foundry for stateful invariants that live alongside your existing tests, Echidna or Medusa for longer coverage-guided campaigns with corpus reuse. Usually both: Foundry in the PR path for fast feedback, a fuzzer running nightly for depth. We match the toolchain to what your team already runs.

One to three weeks depending on protocol complexity and the number of properties. The property workshop is typically half a day; most of the remaining time goes into handlers, because reachability is what determines whether the suite actually finds anything.

You get a minimal reproduction as a runnable test, a written explanation of the sequence and why the property broke, and a severity assessment. If it is exploitable we treat it exactly like an audit finding, including telling you the same day.

From $4,000

Send the repo. Scoping is free.

Under two working days to a written scope, a fixed quote and a review plan.