第二课:审计环境搭建——Solidity编译器、Foundry框架、Slither静态分析工具、Echidna模糊测试工具
做DeFi安全审计,第一步不是看代码,而是搭环境。这听起来有点枯燥,但我见过太多审计新手,代码还没看明白,先被环境配置折腾得想放弃。今天我就把这套流程拆开揉碎了讲给你听。
2.1 Solidity编译器:你的代码翻译官
Solidity编译器(solc)说白了就是把人类写的智能合约代码,翻译成EVM能读懂的字节码。我个人习惯用solc-select来管理多个编译器版本——毕竟不同项目用的Solidity版本可能差好几个大版本。
安装solc-select:
# 安装solc-select
pip3 install solc-select
# 查看可用版本
solc-select versions
# 安装指定版本
solc-select install 0.8.20
# 切换版本
solc-select use 0.8.20
嗯,这里要注意:千万别直接全局装solc。我在项目中遇到过,一个项目用0.8.17,另一个用0.8.20,全局装一个版本根本不够用。solc-select就是干这个的——版本切换,随用随切。
避坑指南:我曾经因为编译器版本不对,导致合约编译出来的字节码和线上部署的不一致,查了整整两天才发现是版本问题。从那以后,我每次审计第一件事就是确认合约的pragma版本,然后用solc-select切到对应版本。
2.2 Foundry框架:审计师的瑞士军刀
Foundry是我目前最推荐的Solidity开发框架。为什么?因为它快,而且原生支持Solidity测试。你想想看,用JavaScript写测试,然后调Solidity合约,中间隔了一层,调试起来多麻烦。Foundry直接让你用Solidity写测试,爽不爽?
安装Foundry很简单:
# 安装foundryup
curl -L https://foundry.toolchain.dev | bash
# 安装foundry工具链
foundryup
# 验证安装
forge --version
cast --version
anvil --version
Foundry包含三个核心工具:
- forge:编译、测试、部署一条龙
- cast:和链上交互的命令行工具
- anvil:本地测试节点,比Ganache快多了
我个人习惯用forge init创建新项目,然后直接写Solidity测试。举个例子:
// 一个简单的测试文件
// SPDX-License-Identifier: MIT
pragma solidity 0.8.20;
import "forge-std/Test.sol";
import "../src/MyToken.sol";
contract MyTokenTest is Test {
MyToken public token;
function setUp() public {
token = new MyToken();
}
function testMint() public {
token.mint(address(this), 100);
assertEq(token.balanceOf(address(this)), 100);
}
}
运行测试就一行命令:forge test。它会自动编译、部署、跑测试,一气呵成。
注意:Foundry默认使用solc 0.8.20+,如果你的合约版本低于这个,记得在foundry.toml里指定solc版本。我曾经帮一个朋友排查问题,他死活编译不过,最后发现是foundry.toml里没配solc版本。
2.3 Slither静态分析工具:代码的X光机
Slither是Trail of Bits出品的静态分析工具。静态分析是什么意思?就是不运行代码,直接扫描源码找漏洞。这就像医生不让你动,直接拍X光片——能发现很多肉眼看不到的问题。
安装Slither:
# 通过pip安装
pip3 install slither-analyzer
# 验证安装
slither --version
使用Slither扫描合约:
# 扫描单个文件
slither contracts/MyToken.sol
# 扫描整个项目
slither .
# 输出JSON格式结果
slither contracts/MyToken.sol --json results.json
Slither能检测什么?我列几个常见的:
- 重入攻击(Reentrancy)
- 未检查的外部调用
- 整数溢出
- 权限控制问题
- 未初始化的存储变量
实战经验:我在审计一个借贷协议时,Slither直接报了一个“unchecked low-level call”的警告。我顺着这个警告往下查,发现合约在调用外部合约时没有检查返回值,如果外部调用失败,整个交易状态不会回滚——这会导致严重的资金损失风险。
不过要提醒你,Slither不是万能的。它会产生一些误报(false positive),也会漏掉一些逻辑漏洞。所以我的习惯是:先用Slither扫一遍,把明显的漏洞修掉,然后再做人工审计。
2.4 Echidna模糊测试工具:暴力破解漏洞
模糊测试(Fuzzing)是什么?说白了就是给合约喂各种奇奇怪怪的输入,看它会不会崩溃。Echidna就是干这个的,它也是Trail of Bits的作品。
安装Echidna:
# 通过cargo安装
cargo install echidna
# 或者用预编译的二进制文件
# 去GitHub releases下载对应平台的版本
使用Echidna需要写测试合约,继承Echidna的测试基类:
// SPDX-License-Identifier: MIT
pragma solidity 0.8.20;
import "./MyToken.sol";
contract MyTokenFuzz is MyToken {
// 模糊测试属性:永远不能mint超过总量
function echidna_total_supply_never_exceeds_max() public view returns (bool) {
return totalSupply() <= MAX_SUPPLY;
}
// 模糊测试属性:任何人不能mint给自己以外的地址
function echidna_mint_only_to_caller() public view returns (bool) {
// 这个属性需要更复杂的实现
return true;
}
}
运行Echidna:
echidna-test contracts/MyTokenFuzz.sol --contract MyTokenFuzz
Echidna会生成成千上万个随机交易序列,尝试触发你定义的“不变量”(invariants)。如果某个不变量被违反了,Echidna会输出导致问题的交易序列,你可以直接复现。
我的经验:Echidna最适合找那种“边界条件”漏洞。比如一个合约允许用户提取存款,但有个隐藏条件——如果提取金额刚好等于某个阈值,会触发一个bug。这种漏洞人工审计很难发现,但Echidna跑个几万次交易,大概率能撞上。
2.5 工具链整合:我的日常工作流
说了这么多,你可能觉得工具太多记不住。别急,我总结一下我的日常工作流:
- 环境准备:用solc-select切到合约对应的Solidity版本
- 编译验证:用forge build编译合约,确保没有编译错误
- 静态扫描:用slither .扫描整个项目,修复明显漏洞
- 模糊测试:写Echidna测试合约,跑几小时模糊测试
- 人工审计:结合工具结果,逐行审查代码逻辑
这套流程我用了两年多,审计过上百个合约,效果还不错。当然,工具只是辅助,真正的审计能力还是靠经验积累。
核心要点:
- solc-select管理编译器版本,避免版本冲突
- Foundry用Solidity写测试,效率比JavaScript高很多
- Slither做静态扫描,快速发现常见漏洞
- Echidna做模糊测试,暴力破解边界条件漏洞
- 工具只是辅助,最终还是要靠人工审计
好了,环境搭建这部分就讲到这里。下一章我们会深入讲解如何用Foundry写高效的测试用例——这可是审计的核心技能之一。