Medusa
A parallelized smart contract fuzzer based on Echidna's approach, offering faster execution through concurrent testing across multiple workers.
Medusa is a smart contract fuzzer developed by Trail of Bits that builds on Echidna's proven approach to property-based testing while adding parallelization for significantly faster execution. Written in Go, Medusa leverages multiple CPU cores to run concurrent fuzzing workers, making it particularly effective for large test suites or time-constrained security testing. It maintains compatibility with Echidna's property conventions while offering additional features and improved performance.
Medusa vs Echidna
Medusa was created to address Echidna's single-threaded limitation:
| Feature | Echidna | Medusa |
|---|---|---|
| Language | Haskell | Go |
| Parallelization | Single-threaded | Multi-worker |
| Speed | Baseline | 2-10x faster |
| Property syntax | echidna_* | echidna_* (compatible) |
| Corpus sharing | Per-run | Shared across workers |
| Shrinking | Advanced | Good |
| Maturity | Established | Newer |
For most use cases, Medusa offers the same testing methodology with better performance. Echidna remains valuable for its more sophisticated shrinking and edge case handling.
Installation and Usage
1# Install via Go2go install github.com/crytic/medusa@latest34# Or download binary from releases5# https://github.com/crytic/medusa/releases67# Initialize config in your project8medusa init910# Run fuzzing11medusa fuzz
Medusa generates a medusa.json config file with sensible defaults.
Configuration
Basic medusa.json:
1{2 "fuzzing": {3 "workers": 4,4 "testLimit": 100000,5 "callSequenceLength": 100,6 "corpusDirectory": "corpus",7 "coverageEnabled": true8 },9 "compilation": {10 "platform": "crytic-compile",11 "platformConfig": {12 "target": ".",13 "solcVersion": "0.8.19"14 }15 },16 "testing": {17 "propertyTesting": {18 "enabled": true19 },20 "assertionTesting": {21 "enabled": true22 }23 }24}
Key settings:
- workers: Number of parallel fuzzing workers (set to CPU cores)
- testLimit: Total transactions to execute across all workers
- callSequenceLength: Maximum transaction sequence length
- corpusDirectory: Where to save interesting inputs
Writing Medusa Tests
Medusa uses the same conventions as Echidna:
1contract MedusaTest {2 Token token;34 constructor() {5 token = new Token();6 token.mint(address(this), 1000000);7 }89 // Property: must always return true10 function echidna_supply_matches_minted() public view returns (bool) {11 return token.totalSupply() == 1000000;12 }1314 // Functions Medusa will call with random inputs15 function transfer(address to, uint256 amount) public {16 token.transfer(to, amount);17 }1819 function burn(uint256 amount) public {20 token.burn(amount);21 }22}
The echidna_ prefix works with Medusa, making migration between tools seamless.
Parallel Fuzzing Architecture
Medusa's parallelization works through:
- Multiple workers: Each worker executes independent transaction sequences
- Shared corpus: Interesting inputs discovered by one worker benefit all workers
- Coordinated coverage: Workers collectively maximize code coverage
- Lock-free design: Minimal synchronization overhead
1┌─────────────────────────────────────────┐2│ Medusa Controller │3├─────────────────────────────────────────┤4│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │5│ │ Worker 1│ │ Worker 2│ │ Worker 3│ │6│ │ EVM │ │ EVM │ │ EVM │ │7│ └────┬────┘ └────┬────┘ └────┬────┘ │8│ │ │ │ │9│ └───────────┴───────────┘ │10│ Shared Corpus │11└─────────────────────────────────────────┘
Test Modes
Property Testing
The default mode—tests that invariants hold:
1function echidna_invariant() public view returns (bool) {2 return someCondition;3}
Assertion Testing
Tests that assertions don't fail:
1function test_operation(uint256 x) public {2 uint256 result = contract.operation(x);3 assert(result > 0); // Medusa checks this doesn't fail4}
Optimization Testing
Finds inputs that maximize/minimize values:
1function optimize_maximize_value() public view returns (int256) {2 return int256(contract.someValue());3}
Coverage-Guided Fuzzing
Medusa prioritizes inputs that explore new code paths:
1{2 "fuzzing": {3 "coverageEnabled": true,4 "corpusDirectory": "corpus"5 }6}
Coverage data guides mutation:
- Execute transaction sequence
- Record which code paths were hit
- If new paths discovered, save inputs to corpus
- Mutate corpus entries to find more new paths
Real-World Example: Testing a Vault
1contract VaultTest {2 Vault vault;3 ERC20 token;45 address[] users = [address(0x1), address(0x2), address(0x3)];67 constructor() {8 token = new ERC20("Test", "TST");9 vault = new Vault(address(token));1011 // Setup initial state12 for (uint i = 0; i < users.length; i++) {13 token.mint(users[i], 10000 ether);14 }15 }1617 // Actions18 function deposit(uint8 userIdx, uint256 amount) public {19 address user = users[userIdx % users.length];20 vm.prank(user);21 token.approve(address(vault), amount);22 vm.prank(user);23 vault.deposit(amount);24 }2526 function withdraw(uint8 userIdx, uint256 shares) public {27 address user = users[userIdx % users.length];28 vm.prank(user);29 vault.withdraw(shares);30 }3132 // Invariants33 function echidna_vault_solvent() public view returns (bool) {34 return token.balanceOf(address(vault)) >= vault.totalAssets();35 }3637 function echidna_shares_backed() public view returns (bool) {38 if (vault.totalSupply() == 0) return true;39 return vault.totalAssets() > 0;40 }41}
Run with multiple workers:
1medusa fuzz --workers 8
Integration with Foundry
Medusa works alongside Foundry in the same project:
1project/2├── src/3│ └── Vault.sol4├── test/5│ ├── Vault.t.sol # Foundry tests6│ └── VaultMedusa.sol # Medusa properties7├── foundry.toml8└── medusa.json
Use Foundry for unit tests and quick fuzzing, Medusa for deep invariant testing with parallelization.
CI/CD Integration
1# GitHub Actions2- name: Run Medusa3 run: |4 medusa fuzz --workers 4 --test-limit 100000
For large projects, run Medusa in CI with higher test limits during nightly builds.
Best Practices
- Maximize workers: Set workers equal to available CPU cores
- Persist corpus: Keep the corpus directory in version control or CI cache
- Combine with Echidna: Use both—Echidna's shrinking can provide better counterexamples
- Start with short sequences: Begin with
callSequenceLength: 50, increase if needed - Bound inputs realistically: Use
require()to constrain inputs to valid ranges
Limitations
Memory usage: Multiple workers multiply memory consumption.
Counterexample quality: Shrinking is less sophisticated than Echidna's.
Debugging: Parallel execution can make reproducing issues trickier.
Medusa represents the evolution of smart contract fuzzing—same battle-tested methodology as Echidna, but leveraging modern hardware through parallelization. For security-critical code, running both Echidna and Medusa provides complementary coverage and counterexample quality.
Articles Using This Term
Learn more about Medusa in these articles:
Related Terms
Fuzzing
Automated testing technique using randomly generated inputs to discover edge cases and vulnerabilities in smart contracts.
Echidna
A property-based fuzzer for Ethereum smart contracts that uses grammar-based input generation to find violations of user-defined invariants.
Invariant Testing
Property-based testing approach verifying that critical protocol conditions remain true across all possible execution paths.
Foundry
Fast, portable Ethereum development framework written in Rust, featuring advanced testing and debugging capabilities.
Need expert guidance on Medusa?
Our team at Zealynx has deep expertise in blockchain security and DeFi protocols. Whether you need an audit or consultation, we're here to help.
Get a Quote

