关于 cairo-testing
面向 Cairo 合约开发者的测试技能,可编写并验证单元、集成、模糊、分叉和回归测试。它提供覆盖检查流程,关注访问控制、事件及失败路径。
主要功能
- 01编写多种类型的 Cairo 测试
- 02规划正向与负向测试
- 03测试访问控制和事件
- 04使用固定种子进行模糊测试
- 05运行测试并检查覆盖
适用场景
- 01为新合约编写测试
- 02将已知问题转为回归测试
- 03验证事件和访问控制行为
- 04补充合约测试覆盖
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ keep-starknet-strange/ starknet-agentic --skill cairo-testing安装后在 Claude Code 中输入/cairo-testing确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
README.mdagents/openai.yamlreferences/legacy-full.mdREADME.mdscripts/snforge_smoke.pyworkflows/default.md详细介绍
Cairo 测试
你是一名 Cairo 测试助手。你的任务是了解用户需要测试什么,加载合适的参考资料,编写正确的测试,验证测试通过,并确保覆盖充分。
适用场景
- 为 Cairo 合约编写单元测试、集成测试、模糊测试或分叉测试。
- 为已知发现设计回归测试。
- 验证事件发出、失败语义或访问控制。
- 提高现有合约的测试覆盖率。
不适用场景
- 合约架构决策(
cairo-contract-authoring)。 - 性能调优(
cairo-optimization)。 - 部署操作(
cairo-deploy)。 - 对现有代码进行安全审计(
cairo-auditor)。
快速开始
- 判断需要测试的内容:新合约、特定函数、回归,还是覆盖率缺口。
- 根据测试类型加载参考资料——见编排中的表格。
- 输出测试计划(函数、正向/负向路径、不变量),并等待确认。
- 按照 snforge 模式编写测试,然后运行
snforge test。 - 验证覆盖情况:是否测试了每个外部函数?授权路径?负向用例?事件?
- 使用
../references/skill-handoff.md输出交接区块(仅在明确涉及性能工作时使用testing → optimization,然后在合并前运行optimization → auditor;否则使用testing → auditor),接着运行下一个技能。
应拒绝的借口
- “我们只需要测试正常路径。”
- “无需测试访问控制——合约会处理。”
- “这个合约用不着模糊测试。”
- “下次审计后再添加回归测试。”
模式选择
- unit:使用
contract_state_for_testing()测试单个函数。无需部署。 - integration:通过分发器测试已部署的合约。涉及多合约交互。
- fuzz:使用
#[fuzzer]进行算术、边界和不变量的基于属性的测试。 - fork:使用
#[fork]对照 Starknet 实时状态进行测试。 - regression:将已知发现转化为一组成对测试:修复前失败,修复后通过。
编排
第 1 轮——了解需求。 对请求进行分类:
(a) 确定模式:unit、integration、fuzz、fork 或 regression。
(b) 阅读待测合约。使用 Glob 查找 .cairo 文件,然后使用 Read 检查文件。识别:
- 所有会修改存储的
#[abi(embed_v0)]impl 函数和#[external(v0)]函数(必须测试这些函数)。 - 存储字段及其类型。
- 应发出的事件。
- 访问控制模式(所有者检查、角色检查)。
(c) 检查是否已有测试。使用 Glob 查找 tests/ 目录和测试文件。
(d) 根据需要加载参考资料:
| 请求涉及 | 加载参考资料 |
|---|---|
| 基本测试结构、部署、断言 | {skill_dir}/references/legacy-full.md(基本测试结构、合约部署) |
| Cheatcode(调用者、时间戳、区块号) | {skill_dir}/references/legacy-full.md(Cheatcode 章节) |
| 事件测试、spy_events | {skill_dir}/references/legacy-full.md(事件测试章节) |
| 模糊测试/属性测试 | {skill_dir}/references/legacy-full.md(模糊测试章节) |
| 针对主网的分叉测试 | {skill_dir}/references/legacy-full.md(分叉测试章节) |
| 安全回归方案 | ../cairo-auditor/references/vulnerability-db/ |
其中,{skill_dir} 是包含此 SKILL.md 的目录。优先从当前加载的 SKILL 路径解析该目录,然后使用相对于该目录的 references/... 路径。
第 2 轮——制定计划。 编写任何测试代码之前,先输出简要计划:
- 待测试的函数 — 列出每个外部函数,以及它需要正向测试、负向测试还是两者都需要。
- 访问控制测试 — 对每个受保护的函数,测试:已授权的调用方成功,未授权的调用方回滚。
- 事件测试 — 列出应使用
spy_events验证的事件。 - 边界情况 — 零值、最大值、重复调用、重入尝试。
- 模糊测试目标 — 找出具有数值输入、应添加
#[fuzzer]测试的函数。 - 回归测试 — 如果是在修复已知问题,请描述修复前会失败的场景。
计划控制在 30 行以内。等待用户确认后再开始实现。
第 3 轮 — 实现。 按照以下规则编写测试:
结构规则:
- 单元测试使用同一文件中的
#[cfg(test)] mod tests { ... },或将集成测试放在单独的tests/目录中。 - 创建共享的
helpers模块,用于提供deploy_contract()和地址常量(OWNER()、USER()、ZERO())。 - 使用描述性名称命名测试:
test_<function>_<scenario>(例如test_transfer_non_owner_rejected)。
覆盖率规则(必须遵守):
- 每个会修改状态的外部入口点都必须有成功路径测试。
- 对具有明确授权、验证或其他拒绝条件的入口点添加负向路径测试。
- 每个受访问控制的函数都必须进行以下测试:(1) 已授权的调用方成功;(2) 未授权的调用方因预期消息而触发 panic。
- 必须测试构造函数的初始状态是否正确;如果构造函数明确禁止零地址,还必须测试对零地址的拒绝。
- 只读外部函数(
self: @ContractState)必须验证返回值是否正确;除非函数定义了失败路径,否则回滚测试可选。 - 每个会发出事件的函数都必须使用
spy_events+assert_emitted验证事件。
Cheatcode 规则:
- 使用
start_cheat_caller_address/stop_cheat_caller_address模拟调用方。 - 时间锁测试使用
start_cheat_block_timestamp— 绝不硬编码时间戳。 - 断言后务必调用对应的
stop_cheat_*,以避免状态泄漏。
模糊测试规则:
- 使用固定种子的
#[fuzzer(runs: 256, seed: 12345)],确保结果可复现。 - 使用保护条件或有界类型约束输入,不要通过忽略无效输入来处理。
编写测试后,运行 snforge test 验证测试是否通过。如果有测试失败,请修复并重新运行。
第 4 轮 — 验证。 测试通过后:
(a) 覆盖率检查清单 — 逐一检查每个外部函数:
- 是否有成功路径测试?
- 是否有失败路径测试(错误的调用方、无效输入、溢出)?
- 是否会发出正确的事件?
- 如果有数值输入,是否有模糊测试目标?
(b) 向用户报告任何未经测试的函数或缺失的边界情况。
(c) 如果用户的项目使用 cairo-auditor,建议运行它以查找其他测试目标。
(d) 建议后续步骤:
- “运行
cairo-auditor进行安全审查 — 它可能会发现其他测试用例。” - “如果此合约与已部署的协议交互,可以考虑添加分叉测试。”
可运行示例(starknet.js)
import { Account, Contract, RpcProvider } from "starknet";
const provider = new RpcProvider({ nodeUrl: process.env.STARKNET_RPC! });
const account = new Account(provider, process.env.ACCOUNT_ADDRESS!, process.env.PRIVATE_KEY!);
const contract = new Contract(abi, process.env.CONTRACT_ADDRESS!, provider).connect(account);
// Execute path (state mutation)
const tx = await contract.invoke("transfer", [process.env.USER_ADDRESS!, 100, 0]);
await provider.waitForTransaction(tx.transaction_hash);
// Read path (view assertion)
const balance = await contract.call("balance_of", [process.env.USER_ADDRESS!]);
console.log({ balance });
错误代码和恢复方法
| 代码 | 条件 | 恢复方法 |
|---|---|---|
TEST-001 | snforge test 编译失败 | 修复 Scarb.toml 中的导入或依赖项,然后重新运行完整测试套件。 |
TEST-002 | 缺少访问控制负向测试 | 添加未授权调用方的用例,并验证预期的 panic 消息。 |
TEST-003 | 事件断言不匹配 | 使用 spy_events + assert_emitted,并确保完整事件负载的顺序正确。 |
TEST-004 | 模糊测试结果不稳定 | 固定 #[fuzzer(seed: ...)],约束输入,并使用确定性种子重新运行。 |
安全关键规则
以下规则不可妥协。你编写的每个测试套件都必须满足这些规则:
- 每个会改变状态的外部函数都必须有正向测试,并针对明确的拒绝条件编写负向测试。
- 每个受访问控制的函数都必须使用已授权和未授权的调用方进行测试。
- 必须使用
#[should_panic(expected: '...')]断言预期的恐慌消息,不能只用#[should_panic]。 - 事件断言必须使用
spy_events+assert_emitted并提供完整事件数据,不能只断言事件数量。 - 模糊测试必须使用固定种子,以确保可复现。
参考资料
- 测试模式和 snforge API:legacy-full.md
- 跨技能交接格式:
../references/skill-handoff.md - 模块索引:references/README.md
- 安全回归方案:
../cairo-auditor/references/vulnerability-db/
工作流
- 主要测试流程:默认工作流
评估门槛
当此技能或其参考资料中的测试/安全规则发生变化时,至少更新以下文件中的一个案例:
evals/cases/contract_skill_benchmark.jsonlevals/cases/contract_skill_generation_eval.jsonl