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(Basic Test Structure、Contract Deployment) |
| チートコード(呼び出し元、タイムスタンプ、ブロック番号) | {skill_dir}/references/legacy-full.md(Cheatcodes セクション) |
| イベントのテスト、spy_events | {skill_dir}/references/legacy-full.md(Event Testing セクション) |
| ファジング/プロパティテスト | {skill_dir}/references/legacy-full.md(Fuzzing セクション) |
| mainnet に対するフォークテスト | {skill_dir}/references/legacy-full.md(Fork Testing セクション) |
| セキュリティ回帰テストのレシピ | ../cairo-auditor/references/vulnerability-db/ |
{skill_dir} はこの SKILL.md が格納されているディレクトリです。現在読み込まれている SKILL のパスから解決することを優先し、その後、そのディレクトリからの相対パス references/... を使用します。
ターン 2 — 計画。 テストコードを書く前に、簡潔な計画を出力します。
- テスト対象の関数 — 各外部関数を列挙し、正常系テスト、異常系テスト、またはその両方を実施するかを示します。
- アクセス制御テスト — アクセス制御された各関数について、認可された呼び出し元では成功し、認可されていない呼び出し元ではリバートすることをテストします。
- イベントテスト —
spy_eventsで検証するイベントを列挙します。 - エッジケース — ゼロ値、最大値、重複呼び出し、リエントランシーの試行。
- ファズテスト対象 —
#[fuzzer]テストを追加すべき数値入力を持つ関数を特定します。 - 回帰テスト — 既知の指摘事項を修正する場合は、修正前に失敗するシナリオを説明します。
計画は30行未満にしてください。実装を始める前に、ユーザーの確認を待ってください。
ターン 3 — 実装。 以下のルールに従ってテストを記述します。
構成ルール:
- 同じファイル内の単体テストには
#[cfg(test)] mod tests { ... }を使用するか、統合テストには別のtests/ディレクトリを使用します。 deploy_contract()とアドレス定数(OWNER()、USER()、ZERO())を含む共有helpersモジュールを作成します。- テストには説明的な名前を付けます:
test_<function>_<scenario>(例:test_transfer_non_owner_rejected)。
カバレッジルール(必須):
- 状態を変更するすべての外部エントリポイントに、成功パスのテストを用意します。
- 明示的な認可、検証、その他の拒否条件があるエントリポイントには、異常パスのテストを追加します。
- アクセス制御されたすべての関数について、(1)認可された呼び出し元で成功すること、(2)認可されていない呼び出し元では期待されるメッセージとともにパニックすることをテストします。
- コンストラクターでは正しい初期状態をテストします。また、コンストラクターでゼロアドレスが明示的に禁止されている場合は、ゼロアドレス拒否もテストします。
- 読み取り専用の外部関数(
self: @ContractState)では戻り値の正しさを検証します。関数に失敗パスが定義されていない場合、リバートのテストは任意です。 - イベントを発行するすべての関数について、
spy_eventsとassert_emittedを使ってイベントを検証します。
チートコードのルール:
- 呼び出し元になりすますには
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 | アクセス制御の異常系テストが不足している | 期待されるパニックメッセージを指定して、認可されていない呼び出し元のケースを追加します。 |
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/
ワークフロー
- 主なテストフロー:デフォルトのワークフロー
評価ゲート
このスキルまたはその参考資料におけるテスト/セキュリティルールを変更した場合、以下のうち少なくとも1つのケースを更新します。
evals/cases/contract_skill_benchmark.jsonlevals/cases/contract_skill_generation_eval.jsonl