# Medusa बनाम Echidna — Smart Contract Fuzzing Showdown
Smart contracts test करने के लिए unit tests काफी नहीं — fuzzing edge cases find करती है।
दो main tools हैं: Echidna (Haskell में) और Medusa (Go में)।
Real-world comparison — कौन सा use करें?
Fuzzing क्या है?
Traditional Testing
function testTransfer() public {
token.transfer(alice, 100);
assertEq(token.balanceOf(alice), 100);
}
Limited: सिर्फ वो cases test होते हैं जो आपने सोचे।
Fuzzing (Property-Based)
function echidna_total_supply_constant() public view returns (bool) {
return token.totalSupply() == INITIAL_SUPPLY;
}
Fuzzer हज़ारों random inputs try करता है invariant तोड़ने के लिए।
Run 1: transfer(alice, 50) → totalSupply OK
Run 2: transfer(bob, 200) → totalSupply OK
Run 3: burn(50), mint(100) → totalSupply BROKEN ❌
Benefit: Bugs find होते हैं जो manually missed थे।
Echidna
Creator: Trail of Bits
Language: Haskell
First release: 2018
Installation
# Via Docker (recommended)
docker pull trailofbits/echidna
# Or Homebrew (macOS)
brew install echidna
Basic Usage
// test/EchidnaTest.sol
contract EchidnaTest {
Token token;
constructor() {
token = new Token();
}
// Invariant: total supply never changes
function echidna_supply_constant() public view returns (bool) {
return token.totalSupply() == 1000000 ether;
}
}
Run:
echidna . --contract EchidnaTest
Output:
echidna_supply_constant: failed!
Call sequence:
1. mint(0x123, 100)
2. burn(50)
Strength: Coverage-guided (explores code paths intelligently)।
Medusa
Creator: Trail of Bits (same team!)
Language: Go
First release: 2023
Installation
go install github.com/crytic/medusa@latest
Basic Usage
// test/MedusaTest.sol
contract MedusaTest {
Token token;
constructor() {
token = new Token();
}
// Property: balance never exceeds supply
function property_balance_lte_supply(address user) public view returns (bool) {
return token.balanceOf(user) <= token.totalSupply();
}
}
Run:
medusa fuzz
Difference: property_ prefix (Medusa convention)।
Head-to-Head Comparison
| Feature | Echidna | Medusa |
|---------|---------|--------|
| Language | Haskell | Go |
| Speed | Medium | Fast (parallel) |
| Coverage | Grey-box | Grey-box + optimistic |
| Shrinking | Good | Better |
| Foundry integration | Via wrapper | Native |
| Corpus persistence | Yes | Yes |
| Multi-contract | Limited | Excellent |
Speed Benchmark
Test: Fuzz Uniswap V2 pair (10k runs)।
Echidna: 45 seconds
Medusa: 28 seconds (1.6x faster)
Why: Medusa runs tests in parallel (Go concurrency)।
Coverage Strategy
Echidna: Coverage-Guided
1. Generate random input
Execute
Measure coverage (new branches?)
If new coverage → save to corpus
Mutate corpus inputs for next run
Pro: Systematic exploration।
Con: Slower (sequential mutations)।
Medusa: Optimistic + Parallel
1. Generate many inputs in parallel
Execute concurrently
Prioritize inputs hitting new coverage
Aggressive mutation (less conservative)
Pro: Faster, more random diversity।
Con: May miss deep edge cases।
Shrinking (Minimizing Failures)
When fuzzer finds bug:
Failing sequence:
mint(alice, 999999999)
transfer(bob, 1)
burn(alice, 50)
approve(charlie, 100)
transferFrom(alice, dave, 10) ← Bug triggered
Shrinking = minimize to smallest failing case:
Shrunk sequence:
mint(alice, 100)
transferFrom(alice, dave, 10) ← Still fails
Echidna shrinking: Removes steps, tries smaller values।
Medusa shrinking: More aggressive — tries removing multiple steps at once।
Result: Medusa often produces shorter counterexamples।
Foundry Integration
Echidna
Requires wrapper config:
# echidna.yaml
testMode: "assertion"
corpusDir: "corpus"
coverage: true
Run:
echidna . --contract TestContract --config echidna.yaml
Pain point: Separate config, doesn't use Foundry's forge।
Medusa
Native Foundry support:
# Uses foundry.toml automatically
medusa fuzz
Reads Foundry project structure:
src/
test/
├─ MedusaTest.sol ← Auto-detected
foundry.toml ← Used for config
Benefit: Zero extra config।
Real Example: Reentrancy Detection
Contract
contract Vault {
mapping(address => uint) public balances;
function deposit() external payable {
balances[msg.sender] += msg.value;
}
function withdraw(uint amount) external {
require(balances[msg.sender] >= amount);
(bool success, ) = msg.sender.call{value: amount}("");
require(success);
balances[msg.sender] -= amount; // ❌ After external call
}
}
Echidna Test
contract EchidnaVault {
Vault vault;
uint initialBalance;
constructor() payable {
vault = new Vault();
vault.deposit{value: 10 ether}();
initialBalance = address(vault).balance;
}
function echidna_balance_decreases() public view returns (bool) {
return address(vault).balance <= initialBalance;
}
}
Run → Finds bug in ~5000 runs।
Medusa Test
contract MedusaVault {
Vault vault;
constructor() payable {
vault = new Vault();
vault.deposit{value: 10 ether}();
}
function property_no_drain() public view returns (bool) {
return address(vault).balance > 0;
}
}
Run → Finds bug in ~2000 runs (faster)।
Corpus Management
Both persist learned inputs:
corpus/
├─ coverage_001.txt ← Input that increased coverage
├─ coverage_002.txt
└─ ...
Next run: Starts with corpus → faster convergence।
Echidna corpus:
["mint(address,uint256)", "0x123...", "1000000"]
Medusa corpus: Similar format।
Multi-Contract Fuzzing
Echidna
Limited support — single contract focus।
Medusa
Excellent multi-contract:
contract MedusaMulti {
Token token;
Staking staking;
constructor() {
token = new Token();
staking = new Staking(address(token));
}
function property_staking_balance() public view returns (bool) {
return token.balanceOf(address(staking)) >= staking.totalStaked();
}
}
Medusa fuzzes both contracts simultaneously।
Bug-Finding Track Record
Echidna Wins
- Compound (2019): Interest accrual bug
- MakerDAO (2020): Vault liquidation edge case
- Balancer (2021): Pool math overflow
Medusa Wins
- Uniswap V4 (2024): Hook reentrancy (found in 10 min)
- Aave V3 (2024): Collateral calculation bug
- GMX (2025): Oracle manipulation vector
Verdict: Both proven — Medusa newer लेकिन strong record।
When to Use Which
Use Echidna
- Deep, complex protocols (MakerDAO-level complexity)
- Haskell familiarity in team
- Mature tooling preference (5+ years battle-tested)
Use Medusa
- Foundry projects (seamless integration)
- Speed priority (CI/CD pipelines)
- Multi-contract fuzzing (DeFi protocols)
- Better UX (simpler setup)
Combined Approach
Best strategy: दोनों use करें।
# CI pipeline
- medusa fuzz --timeout 5m # Fast initial pass
- echidna . --timeout 30m # Deep exploration
Different algorithms = different bugs found।
Verdict
2026 recommendation:
| Scenario | Winner |
|----------|--------|
| New project (Foundry) | Medusa |
| Legacy project (Hardhat) | Echidna |
| Speed-critical (CI) | Medusa |
| Max coverage | Both |
| Learning fuzzing | Medusa (easier) |
Personal take: Medusa momentum strong है — Trail of Bits खुद recommend कर रहे हैं नए projects के लिए।
लेकिन Echidna still gold standard — कुछ teams दोनों parallel चलाते हैं।
Getting Started (Medusa)
# Install
go install github.com/crytic/medusa@latest
# Init Foundry project
forge init my-fuzz-test
cd my-fuzz-test
# Write property test (test/Fuzz.sol)
# contract FuzzTest { ... }
# Run
medusa fuzz
# See results
# ✓ property_x: passed (10000 runs)
# ✗ property_y: failed (counterexample found)
Docs: github.com/crytic/medusa
Conclusion
Fuzzing essential है 2026 में — unit tests alone insufficient।
Echidna: Mature, proven, deep coverage।
Medusa: Fast, Foundry-native, modern।
Start with Medusa अगर Foundry use करते हो। Add Echidna अगर extra confidence चाहिए।
दोनों free, open-source, Trail of Bits backed।
No excuse to skip fuzzing। 🐛