名称: 刺猬模糊测试器 描述: 使用Echidna进行智能合约的基于属性的测试和模糊测试。包括不变式定义、语料库管理、覆盖率分析以及用于全面安全测试的CI/CD集成。 允许的工具: Read, Grep, Write, Bash, Edit, Glob, WebFetch
Echidna模糊测试技能
使用Trail of Bits开发的顶级智能合约模糊测试器Echidna,进行基于属性的测试和模糊测试。
能力
- 属性测试: 编写Echidna兼容的属性测试
- 配置: 自定义模糊测试参数
- 不变式测试: 定义和验证合约不变式
- 覆盖率分析: 分析模糊测试覆盖率
- 语料库管理: 处理和最小化测试用例
- 扩展测试活动: 运行长时间的模糊测试活动
- CI集成: 在流水线中自动化模糊测试
安装
# 通过docker安装(推荐)
docker pull ghcr.io/crytic/echidna/echidna:latest
# 或下载二进制文件
curl -LO https://github.com/crytic/echidna/releases/latest/download/echidna-Linux
chmod +x echidna-Linux
mv echidna-Linux /usr/local/bin/echidna
# 验证
echidna --version
属性测试
基本属性
// 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属性: 名称以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;
}
}
断言模式
contract 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];
// 断言: 代币守恒
assert(balanceBefore == balanceAfter);
}
}
配置
echidna.yaml
# 测试配置
testMode: property # property, assertion, exploration, overflow
testLimit: 50000
seqLen: 100
shrinkLimit: 5000
# 合约配置
deployer: "0x10000"
sender: ["0x10000", "0x20000", "0x30000"]
psender: "0x10000"
# 语料库配置
corpusDir: "corpus"
coverage: true
coverageFormats: ["html", "lcov"]
# 过滤
filterBlacklist: true
filterFunctions: ["excludedFunction"]
# 高级
codeSize: 0xffff
gasLimit: 10000000
prefix: "echidna_"
# 断言模式特定
checkAsserts: true
# 工作进程
workers: 4
运行命令
# 基本运行
echidna contracts/TokenTest.sol --contract TokenTest
# 使用配置
echidna contracts/TokenTest.sol --contract TokenTest --config echidna.yaml
# 断言模式
echidna contracts/TokenTest.sol --contract TokenTest --test-mode assertion
# 多ABI模式(测试多个合约)
echidna . --contract TokenTest --crytic-args "--compile-all"
高级模式
基于时间的属性
contract TimeBased {
uint256 public startTime;
uint256 public lockedUntil;
constructor() {
startTime = block.timestamp;
lockedUntil = block.timestamp + 1 days;
}
function withdraw() external {
require(block.timestamp >= lockedUntil);
// 提现逻辑
}
function echidna_locked_before_time() public view returns (bool) {
// Echidna可以操纵block.timestamp
return block.timestamp < lockedUntil || true; // 简化版
}
}
多合约测试
contract 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();
}
}
DeFi不变式
contract AMMTest is AMM {
function echidna_constant_product() public view returns (bool) {
// k = x * y 应该恒定(或增加)
uint256 currentK = reserveX * reserveY;
return currentK >= initialK;
}
function echidna_no_free_tokens() public view returns (bool) {
// 池中总代币 >= LP代币总价值
return reserveX + reserveY >= totalLPTokens;
}
function echidna_price_bounds() public view returns (bool) {
// 价格应在合理范围内
uint256 price = (reserveX * 1e18) / reserveY;
return price > 0 && price < type(uint256).max / 1e18;
}
}
覆盖率分析
生成覆盖率
# echidna.yaml
coverage: true
coverageFormats: ["html", "lcov", "txt"]
corpusDir: "corpus"
# 运行覆盖率测试
echidna contracts/Test.sol --contract Test --config echidna.yaml
# 查看HTML覆盖率
open corpus/covered.html
解读覆盖率
- 绿色: 完全覆盖的行
- 黄色: 部分覆盖(某些分支)
- 红色: 未覆盖的代码
- 灰色: 不可执行的
语料库管理
语料库结构
corpus/
├── coverage/
│ ├── covered.txt
│ └── covered.html
├── reproducers/
│ └── failing_test.txt
└── corpus/
└── sequence_1234.txt
重放语料库
# 重放失败的序列
echidna contracts/Test.sol --contract Test --corpus-dir corpus --replay
CI/CD集成
GitHub Actions
name: Echidna模糊测试
on: [push, pull_request]
jobs:
echidna:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: 安装Foundry
uses: foundry-rs/foundry-toolchain@v1
- name: 编译合约
run: forge build
- name: 运行Echidna
uses: crytic/echidna-action@v2
with:
files: contracts/Test.sol
contract: Test
config: echidna.yaml
test-limit: 10000
扩展模糊测试
# echidna-extended.yaml
testLimit: 1000000
timeout: 86400 # 24小时
workers: 8
# 运行扩展测试活动
echidna . --contract Test --config echidna-extended.yaml
流程集成
| 流程 | 目的 |
|---|---|
smart-contract-fuzzing.js |
主要模糊测试 |
invariant-testing.js |
不变式验证 |
smart-contract-security-audit.js |
安全测试 |
amm-pool-development.js |
DeFi不变式 |
lending-protocol.js |
协议不变式 |
最佳实践
- 从简单属性开始
- 对内部检查使用断言模式
- 测试永不破坏的不变式
- 部署前运行扩展测试活动
- 在运行之间维护语料库
- 审查覆盖率以改进测试
- 与Slither和Mythril结合使用
故障排除
燃气不足
gasLimit: 100000000 # 增加限制
编译问题
# 直接使用crytic-compile
echidna . --crytic-args "--foundry-compile-all"
模糊测试缓慢
workers: 8 # 增加并行度
shrinkLimit: 1000 # 减少缩减
另请参阅
skills/slither-analysis/SKILL.md- 静态分析skills/mythril-symbolic/SKILL.md- 符号执行skills/foundry-framework/SKILL.md- Forge不变式测试agents/solidity-auditor/AGENT.md- 安全审计员- Echidna文档