- 应用安全
- 区块链
【免费下载链接】slither
Static Analyzer for Solidity and Vyper
slither-check-erc是 Slither 项目中内置的 ERC 符合性(ERC conformance)检测工具,它通过静态分析自动核对智能合约是否遵循 ERC20、ERC721、ERC1155、ERC4626 等主流代币标准。本文以 ERC-Conformance.md 为骨架,结合仓库源码深入讲解工具的使用方法、检测维度、输出格式与实现原理,帮助开发者在合约发布前自动发现缺失函数、错误返回类型、缺失事件与索引错误等标准背离问题。
工具定位与核心能力
slither-check-erc基于 Slither 的静态分析能力,读取目标合约的 AST 与中间表示(SlithIR),对照标准定义逐项检查,不需要部署或运行合约即可完成验证。根据官方文档,它会检查以下维度:
- 标准要求的所有函数是否全部存在;
- 标准要求的所有事件是否全部存在;
- 函数的返回类型是否正确;
- 必须声明为
view的函数是否为view; - 事件的参数是否正确 indexed;
- 函数是否发出标准要求的事件;
- 派生合约(derived contracts)不破坏标准的符合性。
从源码结构看,工具主体位于 slither/tools/erc_conformance/main.py,标准定义(每个 ERC 的函数签名、返回类型、事件与索引要求)统一维护在 slither/utils/erc.py 中,而通用检查逻辑实现在 slither/tools/erc_conformance/erc/ercs.py。
检测维度的实现对应
| 文档声称的检测能力 | 源码实现位置 | 说明 |
|---|---|---|
| 函数是否存在 | _check_signature | 先按签名查找函数,若不存在则尝试把同名 public/external 状态变量视为 getter(ercs.py#L15-L61) |
| 返回类型正确 | _check_signature | 对比实际返回类型与标准返回类型(ercs.py#L66-L100) |
| 必须为 view | _check_signature | 标准声明view=True时校验函数view属性(ercs.py#L102-L112) |
| 函数是否发出事件 | _check_signature | 遍历函数全部 SlithIR 操作,检查是否存在EventCall(ercs.py#L114-L147) |
| 事件参数索引 | _check_events | 按标准要求逐参数核对indexed(ercs.py#L150-L181) |
| 派生合约检查 | generic_erc_checks | 对contract.derived_contracts递归执行同一套检查(ercs.py#L208-L209) |
支持的 ERC 标准
文档列出的支持范围与 slither/utils/erc.py 中ERCS字典的注册条目完全一致,共 11 个标准:
| 标准 | 状态/来源(源码注释) | 要点 |
|---|---|---|
| ERC20 | Final | Token 标准,含 6 个必需函数 + name/symbol/decimals 可选 |
| ERC223 | Draft | Token 标准,含带bytes、bytes,string的重载transfer |
| ERC165 | Final | 标准接口检测,supportsInterface(bytes4) |
| ERC721 | Final | 非同质化代币,隐含 ERC165 要求 |
| ERC1820 | Final | 伪内省注册表,canImplementInterfaceForAddress |
| ERC777 | Last Call | Token 标准,含Sent/Minted/Burned等事件 |
| ERC1155 | Final | 多代币标准,隐含 ERC165 要求 |
| ERC2612 | Review | ERC20 的 Permit 扩展,叠加 ERC20 全部要求 |
| ERC1363 | Review | Payable Token,叠加 ERC20 + ERC165 |
| ERC4524 | Review | Safer ERC-20,叠加 ERC20 + ERC165 |
| ERC4626 | Final | Tokenized Vaults,叠加 ERC20 |
注意复合标准的定义方式:例如ERC2612、ERC1363、ERC4524、ERC4626都是通过 Python 列表拼接直接叠加底层标准(如ERC1363 = [...] + ERC20 + ERC165,见 erc.py),因此选中这些标准检查时,底层标准的全部函数与事件要求也会一并核对。
快速上手:命令行用法
基本用法(对应 ERC-Conformance.md 中的示例):
slither-check-erc contract.sol ContractName其中:
contract.sol:待分析的合约源码或编译项目路径(由 crytic-compile 支持,可指向 Hardhat/Truffle 等项目目录);ContractName:第一个符合该标准的合约名,工具会从它开始,沿继承关系向下检查所有派生合约。
工具已在 pyproject.toml 中注册为命令行入口(slither-check-erc = "slither.tools.erc_conformance.__main__:main"),安装 Slither 后即可直接调用。
常用选项
根据main.py 的参数解析实现,除位置参数外支持以下选项:
| 选项 | 作用 | 默认值 |
|---|---|---|
--erc ERC | 指定要检查的标准,可用值即上表 11 个标准(如--erc ERC721) | erc20 |
--json FILE | 将结果导出为 JSON 文件;传--json -输出到标准输出 | 关闭 |
| crytic-compile 默认参数 | 例如--solc-version、--compile-force-framework等编译控制参数 | 由 crytic-compile 决定 |
指定其他标准示例:
slither-check-erc contract.sol MyToken --erc ERC721 slither-check-erc contract.sol MyVault --erc ERC4626--erc参数在入口处对ERCS字典做大小写不敏感匹配(args.erc.upper()),若传入未注册的标准会报错Incorrect ERC selected ...并退出(见main.py)。
输出解读:一次完整的 ERC20 检查
文档给出了一个最小化合约示例,这里完整复现其输出格式与语义:
contract ERC20{ event Transfer(address indexed,address,uint256); function transfer(address, uint256) public{ emit Transfer(msg.sender,msg.sender,0); } }运行slither-check-erc erc20.sol ERC20后,工具报告:
# Check ERC20 ## Check functions [ ] totalSupply() is missing [ ] balanceOf(address) is missing [✓] transfer(address,uint256) is present [ ] transfer(address,uint256) -> () should return bool [✓] Transfer(address,address,uint256) is emitted [ ] transferFrom(address,address,uint256) is missing [ ] approve(address,uint256) is missing [ ] allowance(address,address) is missing [ ] name() is missing (optional) [ ] symbol() is missing (optional) [ ] decimals() is missing (optional) ## Check events [✓] Transfer(address,address,uint256) is present [✓] parameter 0 is indexed [ ] parameter 1 should be indexed [ ] Approval(address,address,uint256) is missing输出格式约定
[✓]/[ ]:符合 / 不符合;- 函数检查区块以
## Check functions为标题,事件检查区块以## Check events为标题; (optional)后缀表示该函数在标准中是可选的(如 ERC20 的name/symbol/decimals),缺失不会导致违规;无后缀的为必需项;- 缩进的子行给出具体违规细节:返回类型错误、非 view、未发出事件、事件参数未索引等;
- 每个被检查合约都会输出独立的
# Check <ContractName>区块,派生合约依此继续追加。
输出语义详解
transfer(address,uint256) -> () should return bool:函数签名存在,但声明了空返回,而标准要求返回bool。这来自_check_signature中返回值比对逻辑(ercs.py#L85-L100);Transfer(address,address,uint256) is emitted:工具遍历transfer函数的 SlithIR 操作,发现参数类型为(address,address,uint256)的EventCall(ercs.py#L128-L147);parameter 1 should be indexed:ERC20 标准要求Transfer事件的前两个参数 indexed(定义见 erc.py 的indexes=[True, True, False]),合约中第二个参数未加indexed,因此被标记(ercs.py#L170-L181)。
检查引擎如何工作:源码级剖析
入口流程
main.py 的main()执行以下步骤:
- 调用
Slither(args.project, **vars(args))对目标项目做完整静态分析,生成合约抽象语法信息; - 用
slither.get_contract_from_name(args.contract_name)定位合约,若找不到或匹配不唯一则报错退出; - 从
ERCS字典取出该标准的(函数列表, 事件列表),调用generic_erc_checks执行通用检查; - 对 ERC20、ERC1155 额外执行标准专属检查(
ADDITIONAL_CHECKS映射); - 若指定
--json,将ret结果通过output_to_json序列化导出。
通用检查:函数与事件的核对
generic_erc_checks对传入合约执行两轮核对(ercs.py):
- 函数核对:对标准函数列表逐一调用
_check_signature。查找函数时,若按签名找不到,还会检查是否存在同名public/external状态变量——这是为了兼容 Solidity 编译器为 public 状态变量自动生成 getter 的情况,此时直接把 getter 视为函数参与后续检查(ercs.py#L27-L57); - 事件核对:对标准事件列表逐一调用
_check_events,核对事件是否存在及参数索引是否符合要求。
随后递归遍历contract.derived_contracts,对每个派生合约重复执行,从而验证"派生合约不破坏符合性"这一条能力。
事件发射检测的原理
函数是否发射事件,是通过 SlithIR 中间表示判断的:遍历函数的all_slithir_operations(),筛出EventCall类型的操作,比对事件名与实参类型列表是否与标准定义一致(ercs.py#L128-L147)。这意味着检测不依赖执行路径穷举,只要求函数体内存在符合签名的emit语句即可判定通过。
标准专属检查
除通用检查外,两个标准还注册了专属检查(main.py):
- ERC20:
check_erc20检查合约是否提供increaseAllowance(address,uint256)或safeIncreaseAllowance(address,uint256),若无则报告not protected for the ERC20 approval race condition(erc20.py),即经典的 approve/transferFrom 竞争条件防护缺失; - ERC1155:
check_erc1155检查safeBatchTransferFrom是否发射了TransferSingle或TransferBatch事件(erc1155.py)。
这也解释了为何测试输出末尾会出现[ ] ERC20 is not protected for the ERC20 approval race condition这样不属于函数/事件区块的额外条目。
实战:用真实测试用例验证输出
仓库内置了完整的 CI 测试用例,可直接复现工具行为。测试脚本 scripts/ci_test_erc.sh 使用solc-select use 0.5.1固定编译器版本后运行:
slither-check-erc "tests/tools/check_erc/erc20.sol" ERC20被测合约 tests/tools/check_erc/erc20.sol 只声明了一个public状态变量:
contract ERC20{ uint public totalSupply; }期望输出 tests/tools/check_erc/test_1.txt 展示了几个值得注意的行为:
totalSupply()被判定[✓] present且返回类型(uint256)正确——即使合约没有显式函数,public 状态变量生成的 getter 也被认可;- 其余函数全部
missing,事件全部missing,末尾追加ERC20 is not protected for the ERC20 approval race condition专属检查结果。
该用例同时验证了两个实现细节:状态变量 getter 兼容逻辑与标准专属检查的输出格式。运行该脚本会将实际输出与test_1.txt做diff对比,任何输出变化都会导致 CI 失败。
JSON 导出与结果结构化
当需要把检测结果接入自动化流程(如 CI 门禁、报告生成)时,使用--json选项:
slither-check-erc contract.sol ERC20 --erc ERC20 --json result.json slither-check-erc contract.sol ERC20 --json - # 输出到 stdout从main.py 看,JSON 顶层结构为{"erc-conformance": { ... }},其内部键由各检查逻辑写入ret字典的类别组成,主要包括:
missing_function:缺失的函数;incorrect_return_type:返回类型错误;should_be_view:应为 view 而非 view 的函数;missing_event/missing_event_index:缺失的事件 / 事件参数索引缺失;missing_event_emmited:函数未发射标准要求的事件;lack_of_erc20_race_condition_protection:ERC20 竞争条件防护缺失(仅 ERC20);erroneous_erc1155_safeBatchTransferFrom_event:ERC1155 批量转账事件缺失(仅 ERC1155)。
每条结果由 slither/utils/output.py 中的Output对象承载,附带标准名、预期/实际类型、函数定位等附加字段,便于程序化解析与定位到具体合约函数。
使用建议与限制
- 固定编译环境:ERC 标准校验对编译器版本敏感(如事件 indexed 语义、getter 生成规则),建议参照 scripts/ci_test_erc.sh 的做法,用 solc-select 固定 solc 版本后运行,保证结果可复现;
- 以首个符合标准的合约为入口:
contract_name应传继承链中最早实现标准的合约,派生合约会被自动递归检查; - 复合标准会叠加底层要求:检查 ERC4626 时会同时要求 ERC20 的全部函数与事件,合约若只实现 ERC4626 新增接口,会在底层 ERC20 项上大量报缺,属预期行为;
- 事件发射检测是静态的:只要函数体内存在签名匹配的
emit即判定通过,工具不做路径敏感或执行条件分析,这是静态工具的固有边界; - 它是检查器而非修复器:
slither-check-erc只报告不符合项,不会自动改写合约代码。
总结
slither-check-erc把 ERC 标准条文落成可重复执行的静态检查:函数与事件的存在性、签名与返回类型、view 约束、事件索引、事件发射以及派生合约传播,配合 ERC20/ERC1155 的专属安全检查,能在不部署合约的情况下快速定位标准背离。其标准定义集中维护在 slither/utils/erc.py,通用检查引擎在 slither/tools/erc_conformance/erc/ercs.py,入口与参数解析在 slither/tools/erc_conformance/main.py,无论是审计自有代币合约,还是在 CI 流水线中为多标准代币项目加一道符合性门禁,它都是一个开箱即用的基础工具。
- 应用安全
- 区块链
【免费下载链接】slither
Static Analyzer for Solidity and Vyper
相关推荐
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考