Why we built Chimera: write once, fuzz everywhere
Why we built Chimera: write once, fuzz everywhere
You're about to add fuzzing to your protocol. You look at the options: Foundry's built-in invariant testing is fast but shallow. Echidna is battle-tested but uses its own test format. Medusa runs parallel campaigns but needs yet another config. Pick one, and you're locked in. Switch later, and you rewrite everything.
We built Chimera because this tradeoff shouldn't exist. You should write your properties once and run them with any fuzzer — today and two years from now when better tools ship.
Get a Chimera test suite built for your protocol
The problem: fuzzer lock-in
Every fuzzing tool has a different interface. Foundry expects invariant_ prefixed functions inside a Test contract. Echidna wants echidna_ prefixed booleans or assertion-mode properties in a standalone contract. Medusa needs its own config pointing to a different entry contract.
The properties themselves — "the vault must always be solvent," "share price must never decrease on deposit" — are identical across tools. But the test harness code that wraps those properties is completely different. In practice, teams end up in one of three situations:
- Pick one fuzzer and stick with it. You miss bugs the other tools would've caught. Echidna's coverage-guided exploration finds different sequences than Foundry's random approach, and Medusa's parallelism covers more state space per hour than either.
- Maintain separate test suites. Two or three copies of every property, every handler, every setup. They drift apart. The Echidna suite gets a new property, the Foundry suite doesn't. Now you have false confidence.
- Don't fuzz at all. The overhead of choosing and learning a tool is enough to delay fuzzing until "after launch." We've seen this more often than we'd like.
None of these are good outcomes. The tool should adapt to the properties, not the other way around.
What Chimera actually does
Chimera is a Solidity framework that provides a single inheritance chain for writing invariant tests. You write your setup, properties, and handler functions once, in plain Solidity, and Chimera provides the adapter layer that makes them work with Foundry, Echidna, and Medusa without changes.
It isn't a fuzzer. It doesn't generate inputs or guide coverage. It's the layer between your properties and whatever fuzzer runs them. Think of it as an interface adapter: your test code talks to Chimera, Chimera talks to the fuzzer.
The architecture is a five-contract inheritance chain:
Setup → BeforeAfter → Properties → TargetFunctions → CryticTester
Each contract has a specific job. The chain isn't arbitrary — it reflects how fuzzing campaigns actually work: deploy state, snapshot before/after, define correctness, define actions, and expose an entry point.
How the inheritance chain works
Most tutorials show you the chain and tell you to follow it. Here's WHY each layer exists and what breaks if you skip it.
Setup (inherits BaseSetup) — Deploys all contracts and sets initial state. This layer exists because every fuzzer needs a deterministic starting point, but they initialize it differently. Foundry calls setUp(). Echidna and Medusa call the constructor. BaseSetup abstracts this: you write setup() once, and the right lifecycle hook calls it.
contract Setup is BaseSetup {
SimpleVault vault;
MockERC20 token;
function setup() internal override {
token = new MockERC20("Test", "T", 18);
vault = new SimpleVault(address(token));
token.mint(address(this), 1_000_000e18);
token.approve(address(vault), type(uint256).max);
}
}
BeforeAfter (inherits Setup) — Captures state snapshots before and after each fuzzer action. This layer exists because transition properties ("share price must not decrease after deposit") need both the old and new values. Without it, you'd write snapshot logic inline in every property. BeforeAfter provides __before() and __after() hooks that run automatically.
contract BeforeAfter is Setup {
uint256 internal _sharePriceBefore;
uint256 internal _totalSupplyBefore;
function __before() internal {
_sharePriceBefore = vault.convertToAssets(1e18);
_totalSupplyBefore = vault.totalSupply();
}
function __after() internal {
// Values are now available for comparison in Properties
}
}
Properties (inherits BeforeAfter) — Defines what "correct" means. Pure invariant logic, no side effects. This layer is separate from target functions because properties should be readable on their own. When an auditor reviews your test suite, they read Properties.sol to understand your security model.
contract Properties is BeforeAfter {
function invariant_solvency() public view returns (bool) {
uint256 totalClaim = vault.convertToAssets(vault.totalSupply());
return token.balanceOf(address(vault)) >= totalClaim;
}
function invariant_sharePriceNonDecreasing() public view returns (bool) {
if (_totalSupplyBefore == 0) return true;
return vault.convertToAssets(1e18) >= _sharePriceBefore;
}
}
TargetFunctions (inherits Properties) — Defines what the fuzzer can do. Each handler_ function maps to one user action with bounded inputs. This layer inherits from Properties so it can call __before() and __after() around each action.
contract TargetFunctions is Properties {
function handler_deposit(uint256 amount) external {
amount = bound(amount, 1, token.balanceOf(address(this)));
__before();
vault.deposit(amount, address(this));
__after();
}
function handler_withdraw(uint256 shares) external {
uint256 maxShares = vault.balanceOf(address(this));
if (maxShares == 0) return;
shares = bound(shares, 1, maxShares);
__before();
vault.redeem(shares, address(this), address(this));
__after();
}
}
CryticTester (inherits TargetFunctions + CryticAsserts) — The entry point for Echidna and Medusa. It exists so that Crytic tools can find all handler functions and properties through a single contract. Foundry uses a separate FoundryTest wrapper that inherits the same chain.
contract CryticTester is TargetFunctions, CryticAsserts {
constructor() {
setup();
}
}
Same property, three fuzzers
Here's the payoff. That invariant_solvency property from above — let's run it with all three tools, zero code changes:
Foundry:
forge test --match-contract FoundryTest -vvv
Fast iteration, runs in seconds. Good for development and CI. Limited sequence depth.
Echidna:
echidna test/recon/CryticTester.sol --contract CryticTester --config echidna.yaml
Coverage-guided exploration, finds sequences Foundry misses. Runs for minutes to hours. Corpus-based, so each campaign builds on the last.
Medusa:
medusa fuzz
Parallel workers, higher throughput. Best for long campaigns where you want maximum state space coverage per hour.
The exact same Properties.sol, the exact same TargetFunctions.sol, the exact same Setup.sol. Three different exploration strategies, three different bug-finding strengths. You get the union of their results.
When to use Chimera vs writing directly
Chimera isn't always the right choice. Here's a decision table:
| Scenario | Recommendation |
|---|---|
| New protocol, planning to fuzz seriously | Chimera. You'll want to run multiple fuzzers as you iterate. |
| Existing Foundry tests, want to add Echidna | Chimera. Refactor into the chain once, then you have both. |
| Quick one-off fuzz test for a single function | Direct Foundry. Write a testFuzz_ function, run it, done. |
| You only ever use Foundry and won't switch | Either works. Chimera adds a small abstraction cost but keeps the door open. |
| Team wants to run Recon Pro cloud campaigns | Chimera. Recon Pro uses the Chimera structure to parallelize across fuzzers. |
| Learning to fuzz for the first time | Chimera. The structure teaches you the right patterns from the start. |
The short version: if you're writing more than a handful of properties, or if there's any chance you'll want a second fuzzer later, start with Chimera. The upfront cost is one afternoon. The switching cost without it is a full rewrite.
Scaffolding with create-chimera-app and the VS Code extension
You don't have to set up the inheritance chain manually. Two tools generate it for you:
create-chimera-app — A CLI that scaffolds a complete Chimera project inside any existing Foundry repo:
npx create-chimera-app
It creates the test/recon/ directory with all five contracts, wired up with your project's imports and a starter property. You fill in the deployment logic and start writing properties.
The Recon VS Code extension — If you prefer staying in the editor, install the extension and run "Recon: Initialize" from the command palette. Same output, plus it sets up fuzzer configs and gives you one-click execution for Echidna, Medusa, and Foundry right from the editor.
Both tools produce identical project structures that work with all three fuzzers out of the box.
Write properties, not glue code
The point of fuzzing is to find bugs. Every hour you spend adapting test code to a specific tool's interface is an hour you're not spending writing properties that catch real vulnerabilities.
Chimera exists so you can focus on the hard part — figuring out what "correct" means for your protocol — and let the framework handle the boring part of making it work across tools.
If you've been putting off fuzzing because the tooling fragmentation is annoying, start with the beginner's guide. If you already have tests and want to add a second fuzzer, the Chimera framework docs cover migration.
Need a complete Chimera test suite purpose-built for your protocol? Request an audit with Recon — we design properties based on your protocol's specific risk model and run campaigns at scale across all three fuzzers.
Related Posts
LLM-generated invariant properties: what works, what hallucinates, how we use them
We've tested LLM-generated properties across dozens of engagements. Some are surprisingly good. Some...
Chimera advanced patterns: multi-contract fuzzing with actors and ghosts
Take your Chimera skills to the next level. Multi-contract setups, actor patterns for simulating rea...