Medusa Fuzzer & Invgen: AI-Generated Invariants

Get Free Crypto Wallets Network

When I first swapped a stubborn property-testing suite over to Medusa, a campaign that had been crawling on a single core suddenly saturated every thread on my machine and surfaced two edge cases within minutes. That's why I keep coming back to the pairing of Medusa and Invgen: one gives you a fast, parallel engine for exercising Solidity contracts, and the other tackles the part of fuzzing developers dread most — actually writing the invariants. Below I walk through what Medusa is, how it differs from Echidna, and how automated invariant generation with Invgen fits into a modern workflow. I'll keep it hands-on, share the commands I run, and be honest about the rough edges.

Table of Contents

What Is Medusa?

Medusa is a parallelized, coverage-guided, mutational fuzzer for Solidity smart contracts, maintained under the crytic organization and powered by go-ethereum. It comes out of the same security-research lineage as Echidna — the team behind it has run countless audits on blockchain systems, and that operational experience is baked into the tool. Where a lot of fuzzers feel academic, Medusa reads like something built by people who fuzz contracts for a living.

Mechanically, a Medusa medusa fuzzer smart contract campaign generates random sequences of transactions against your deployed contract, watches which branches of the bytecode get reached, and mutates the inputs that expanded coverage. It keeps a corpus of the call sequences that discovered new code paths and reuses them to reach deeper states. The first major release brought coverage-guided fuzzing into the core and added an HTML coverage report, so after a run I can see, line by line, what the fuzzer touched and what it never reached. That visibility alone changes how I write harnesses — dead branches jump out immediately.

Two properties make Medusa stand out: it runs multiple workers in parallel, and it exposes both a CLI and a Go API. The CLI is what most teams will use, but the Go API means you can embed the fuzzer in a larger tool or drive it programmatically — handy when you want fuzzing to be part of an automated pipeline rather than a manual chore.

Medusa vs Echidna: What Actually Changed

The medusa vs echidna question comes up constantly, and the honest answer is that they are cousins, not competitors. Echidna is the older, battle-tested property-based fuzzer written in Haskell; Medusa is the newer engine written in Go, built on go-ethereum, and explicitly inspired by Echidna. If you already know how to write Echidna properties, the mental model transfers almost one-to-one.

Here's how I think about the trade-offs:

My practical rule: I reach for Medusa when I want speed, parallelism, and a codebase I can extend, and I keep Echidna around when I need a long track record. Running both against the same invariants is also legitimate — if two independently implemented fuzzers agree a property holds, I trust it more.

Running Medusa: A Practical Setup

Getting a first campaign going is quick. Medusa is configured through a medusa.json file and plays nicely with a Foundry project layout.

# Install (Go toolchain required), then initialize config
medusa init

# Point Medusa at your test contract and run
medusa fuzz --target-contracts "MyInvariants"

A minimal medusa.json looks roughly like this:

{
  "fuzzing": {
    "workers": 8,
    "testLimit": 1000000,
    "callSequenceLength": 100,
    "corpusDirectory": "corpus",
    "coverageEnabled": true
  },
  "testing": {
    "assertionTesting": { "enabled": true },
    "propertyTesting": { "enabled": true, "testPrefixes": ["invariant_", "echidna_"] }
  }
}

A few settings earn their keep. workers controls parallelism — I set it near my core count. corpusDirectory persists interesting call sequences between runs, so a second campaign starts smarter than the first; commit the corpus so CI benefits too. coverageEnabled produces the HTML report. I kick off a short local run to shake out harness bugs, then let a much longer campaign run in CI or overnight where the higher testLimit has room to breathe.

Writing Invariants That Catch Real Bugs

An invariant is simply a statement that must always be true no matter what sequence of calls the fuzzer throws at your contract. The classic examples are conservation properties — the sum of user balances equals total supply, a vault never lets you withdraw more than you deposited, an AMM's k never decreases on a swap. In a Medusa harness you express these as functions the fuzzer must never be able to falsify:

function invariant_totalSupplyMatchesBalances() public view returns (bool) {
    return token.totalSupply() == _sumOfTrackedBalances();
}

The uncomfortable truth is that writing good invariants is harder than running the fuzzer. It demands that you reason about what your contract should guarantee and phrase it as booleans. Weak invariants let real bugs slip through; overly broad ones fire on legitimate behavior and drown you in false positives. This is exactly the bottleneck Invgen sets out to relieve.

Invgen: Automating Invariant Generation

Invgen, from the Fuzzland team, is a tool for invgen automated invariant generation — it uses a large language model to generate invariants for Foundry projects. The philosophy behind it is one I find compelling: decouple invariant writing from harness writing. Developers who understand a protocol are well equipped to reason about its expected behavior, but stitching together a fuzzing harness is tedious plumbing. Automating the plumbing, and even proposing candidate invariants, lets the human focus on judgment rather than boilerplate.

Concretely, Invgen expects a Foundry project with a setup file containing a setUp function that deploys the contract-under-test. From there it analyzes the contract and emits candidate invariants. The output isn't locked to one engine — you can run the invariants with Foundry's own invariant testing or feed them into ItyFuzz, Fuzzland's bytecode-level hybrid fuzzer. That flexibility means Invgen fits whatever engine your team already trusts rather than forcing a migration.

I treat Invgen output as a strong first draft, not a final answer. An LLM can propose accounting relationships I'd have overlooked, but it can also hallucinate properties that sound plausible and aren't true for this specific contract. Every generated invariant goes through my review before it earns a place in the suite.

An Invgen Workflow, Step by Step

Here's the loop I've settled into when I want machine help proposing invariants:

  1. Prepare a Foundry project with a setUp that deploys and wires up the contract-under-test, including dependencies and initial state.
  2. Run Invgen to generate candidate invariants against that setup.
  3. Review each candidate critically. Delete the ones that don't reflect a real guarantee and tighten the ones that are close — this is where domain knowledge is irreplaceable.
  4. Run the survivors under a fuzzer. I execute the same invariants under both Foundry's invariant runner and a coverage-guided engine like Medusa, so a broken assumption surfaces two ways.
  5. Triage failures. A falsified invariant is either a genuine bug or a wrong invariant — decide which, fix, and re-run.
  6. Persist the corpus and promote to CI, so the campaign compounds instead of starting cold each run.

The payoff of this hybrid loop is coverage of the invariant space you'd never reach by hand under a deadline, combined with the human judgment that keeps the suite honest.

Frequently Asked Questions

Is Medusa a drop-in replacement for Echidna? Not exactly, but close in spirit. The property- and assertion-testing model is the same, so migrating harnesses is usually straightforward. Medusa's edge is its Go codebase, parallel workers, and coverage HTML report; Echidna's is a longer audit track record. Many teams run both.

Do I need Foundry to use these tools? Medusa works well with a Foundry layout but is configured through its own medusa.json. Invgen specifically expects a Foundry project with a setUp function that deploys the contract-under-test, since that setup is what it analyzes.

Can I trust LLM-generated invariants? Treat them as drafts. Invgen surfaces relationships you'd miss, but it can also propose properties that aren't actually true for your contract. Review every candidate and validate it under a fuzzer before you rely on it.

How long should a fuzzing campaign run? Longer is generally better — coverage-guided fuzzers keep discovering deeper states over time. Run a short campaign locally to catch harness bugs, then a much longer one in CI or overnight with a high testLimit and a persisted corpus.

Conclusion

Medusa and Invgen attack two halves of the same problem. Medusa gives you a fast, parallel, coverage-guided engine — the direct descendant of Echidna, rebuilt in Go on go-ethereum — that turns your invariants into an aggressive search for counterexamples. Invgen tackles the human bottleneck by using an LLM to draft those invariants against a Foundry setup, so you spend your time reviewing guarantees instead of writing boilerplate. Neither replaces careful thinking about what your contract must never do, and in my experience the strongest results come from the loop: let the machine propose, apply your judgment, and let a coverage-guided fuzzer try its hardest to prove you wrong.

Get Free Crypto Wallets Network