Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Property-based testing and fuzzing using Echidna for smart contracts. Includes invariant definition, corpus management, coverage analysis, and CI/CD integration for comprehensive security testing.
.claude/skills/a5c-ai-echidna-fuzzer/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-01 | ✗→✓ | ▲ Improved | 59% | 0% |
| case-03 | ✗→✓ | ▲ Improved | 13% | 0% |
| case-05 | ✗→✓ | ▲ Improved | -28% | 0% |
| case-07 | ✗→✓ | ▲ Improved | 37% | 0% |
| case-15 | ✗→✓ | ▲ Improved | 99% | 0% |
Property-based testing and fuzzing for smart contracts using Echidna, the premier smart contract fuzzer from Trail of Bits.
bash# Install via docker (recommended) docker pull ghcr.io/crytic/echidna/echidna:latest # Or download binary curl -LO https://github.com/crytic/echidna/releases/latest/download/echidna-Linux chmod +x echidna-Linux mv echidna-Linux /usr/local/bin/echidna # Verify echidna --version
solidity// contracts/Token.sol contract Token { mapping(address => uint256) public balances; uint256 public totalSupply; function transfer(address to, uint256 amount) external { require(balances[msg.sender] >= amount); balances[msg.sender] -= amount; balances[to] += amount; } } // contracts/TokenTest.sol contract TokenTest is Token { constructor() { balances[msg.sender] = 10000; totalSupply = 10000; } // Echidna property: name starts with echidna_ function echidna_totalSupply_constant() public view returns (bool) { return totalSupply == 10000; } function echidna_balance_under_total() public view returns (bool) { return balances[msg.sender] <= totalSupply; } }
soliditycontract TokenAssertions is Token { function transfer(address to, uint256 amount) external override { uint256 balanceBefore = balances[msg.sender] + balances[to]; super.transfer(to, amount); uint256 balanceAfter = balances[msg.sender] + balances[to]; // Assertion: conservation of tokens assert(balanceBefore == balanceAfter); } }
yaml# Test configuration testMode: property # property, assertion, exploration, overflow testLimit: 50000 seqLen: 100 shrinkLimit: 5000 # Contract configuration deployer: "0x10000" sender: ["0x10000", "0x20000", "0x30000"] psender: "0x10000" # Corpus configuration corpusDir: "corpus" coverage: true coverageFormats: ["html", "lcov"] # Filtering filterBlacklist: true filterFunctions: ["excludedFunction"] # Advanced codeSize: 0xffff gasLimit: 10000000 prefix: "echidna_" # Assertion mode specific checkAsserts: true # Workers workers: 4
bash# Basic run echidna contracts/TokenTest.sol --contract TokenTest # With config echidna contracts/TokenTest.sol --contract TokenTest --config echidna.yaml # Assertion mode echidna contracts/TokenTest.sol --contract TokenTest --test-mode assertion # Multi-ABI mode (test multiple contracts) echidna . --contract TokenTest --crytic-args "--compile-all"
soliditycontract TimeBased { uint256 public startTime; uint256 public lockedUntil; constructor() { startTime = block.timestamp; lockedUntil = block.timestamp + 1 days; } function withdraw() external { require(block.timestamp >= lockedUntil); // withdraw logic } function echidna_locked_before_time() public view returns (bool) { // Echidna can manipulate block.timestamp return block.timestamp < lockedUntil || true; // simplified } }
soliditycontract EchidnaTest { Token token; Staking staking; constructor() { token = new Token(); staking = new Staking(address(token)); } function stake(uint256 amount) public { token.approve(address(staking), amount); staking.stake(amount); } function echidna_staking_invariant() public view returns (bool) { return staking.totalStaked() <= token.totalSupply(); } }
soliditycontract AMMTest is AMM { function echidna_constant_product() public view returns (bool) { // k = x * y should be constant (or increase) uint256 currentK = reserveX * reserveY; return currentK >= initialK; } function echidna_no_free_tokens() public view returns (bool) { // Total tokens in pool >= total LP tokens value return reserveX + reserveY >= totalLPTokens; } function echidna_price_bounds() public view returns (bool) { // Price should be within reasonable bounds uint256 price = (reserveX * 1e18) / reserveY; return price > 0 && price < type(uint256).max / 1e18; } }
yaml# echidna.yaml coverage: true coverageFormats: ["html", "lcov", "txt"] corpusDir: "corpus"
bash# Run with coverage echidna contracts/Test.sol --contract Test --config echidna.yaml # View HTML coverage open corpus/covered.html
corpus/
├── coverage/
│ ├── covered.txt
│ └── covered.html
├── reproducers/
│ └── failing_test.txt
└── corpus/
└── sequence_1234.txtbash# Replay a failing sequence echidna contracts/Test.sol --contract Test --corpus-dir corpus --replay
yamlname: Echidna Fuzzing on: [push, pull_request] jobs: echidna: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Install Foundry uses: foundry-rs/foundry-toolchain@v1 - name: Compile contracts run: forge build - name: Run Echidna uses: crytic/echidna-action@v2 with: files: contracts/Test.sol contract: Test config: echidna.yaml test-limit: 10000
yaml# echidna-extended.yaml testLimit: 1000000 timeout: 86400 # 24 hours workers: 8
bash# Run extended campaign echidna . --contract Test --config echidna-extended.yaml
| Process | Purpose | |---------|---------| | smart-contract-fuzzing.js | Primary fuzzing | | invariant-testing.js | Invariant verification | | smart-contract-security-audit.js | Security testing | | amm-pool-development.js | DeFi invariants | | lending-protocol.js | Protocol invariants |
yamlgasLimit: 100000000 # Increase limit
bash# Use crytic-compile directly echidna . --crytic-args "--foundry-compile-all"
yamlworkers: 8 # Increase parallelism shrinkLimit: 1000 # Reduce shrinking
skills/slither-analysis/SKILL.md - Static analysisskills/mythril-symbolic/SKILL.md - Symbolic executionskills/foundry-framework/SKILL.md - Forge invariant testingagents/solidity-auditor/AGENT.md - Security auditor| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | fail→pass | 17,339 | 10,798 | -38% | 1 | 1 | 0% | 3,022 | 4,792 | +59% | 0 | 0 | — |
case-02 | fail→fail | 12,725 | 13,601 | +7% | 1 | 1 | 0% | 2,465 | 4,870 | +98% | 0 | 0 | — |
case-03 | fail→pass | 18,132 | 8,191 | -55% | 1 | 1 | 0% | 3,310 | 3,724 | +13% | 0 | 0 | — |
case-04 | fail→fail | 5,600 | 5,097 | -9% | 1 | 1 | 0% | 1,156 | 3,279 | +184% | 0 | 0 | — |
case-05 | fail→pass | 20,605 | 2,883 | -86% | 1 | 1 | 0% | 3,736 | 2,700 | -28% | 0 | 0 | — |
case-06 | fail→fail | 15,144 | 2,671 | -82% | 1 | 1 | 0% | 1,359 | 2,671 | +97% | 0 | 0 | — |
case-07 | fail→pass | 11,748 | 4,354 | -63% | 1 | 1 | 0% | 2,082 | 2,853 | +37% | 0 | 0 | — |
case-08 | pass→pass | 16,386 | 10,001 | -39% | 1 | 1 | 0% | 2,588 | 4,159 | +61% | 0 | 0 | — |
case-09 | fail→fail | 3,436 | 2,383 | -31% | 1 | 1 | 0% | 639 | 2,627 | +311% | 0 | 0 | — |
case-10 | fail→fail | 4,078 | 3,415 | -16% | 1 | 1 | 0% | 667 | 2,768 | +315% | 0 | 0 | — |
case-11 | fail→fail | 5,863 | 3,878 | -34% | 1 | 1 | 0% | 909 | 2,668 | +194% | 0 | 0 | — |
case-12 | fail→fail | 12,971 | 5,499 | -58% | 1 | 1 | 0% | 2,424 | 3,242 | +34% | 0 | 0 | — |
case-13 | fail→fail | 7,430 | 6,835 | -8% | 1 | 1 | 0% | 1,386 | 3,457 | +149% | 0 | 0 | — |
case-14 | pass→pass | 7,649 | 1,474 | -81% | 1 | 1 | 0% | 1,158 | 2,403 | +108% | 0 | 0 | — |
case-15 | fail→pass | 11,747 | 10,630 | -10% | 1 | 1 | 0% | 2,289 | 4,565 | +99% | 0 | 0 | — |
case-16 | fail→pass | 10,629 | 6,030 | -43% | 1 | 1 | 0% | 2,044 | 3,247 | +59% | 0 | 0 | — |
case-17 | fail→fail | 5,309 | 2,368 | -55% | 1 | 1 | 0% | 1,093 | 2,601 | +138% | 0 | 0 | — |
case-18 | fail→fail | 3,342 | 1,698 | -49% | 1 | 1 | 0% | 637 | 2,465 | +287% | 0 | 0 | — |
case-19 | fail→fail | 4,619 | 1,695 | -63% | 1 | 1 | 0% | 977 | 2,437 | +149% | 0 | 0 | — |
case-20 | fail→fail | 4,808 | 4,935 | +3% | 1 | 1 | 0% | 1,045 | 3,046 | +191% | 0 | 0 | — |
case-21 | fail→fail | 4,621 | 2,170 | -53% | 1 | 1 | 0% | 940 | 2,528 | +169% | 0 | 0 | — |
case-22 | fail→fail | 6,113 | 5,419 | -11% | 1 | 1 | 0% | 1,318 | 3,290 | +150% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted. The headline lift of +27 percentage points is the difference between those two pass rates over the 22 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.