foundry-poc-mainnet-fork
このスキルは、ユーザーがメインネットフォーク上で実際にデプロイされたプロトコルに対するスマートコントラクトの脆弱性を再現するFoundry PoC(概念実証)テストを作成したい場合に使用します。「PoC を書く」「このバグを Foundry で再現する」「メインネットをフォークして悪用する」「この発見をオンチェーンで検証する」といったフレーズや、脆弱性レポートとデプロイ済みコントラクトアドレスが提供されている場合がトリガーとなります。このスキルは、EVM チェーン上のメインネットフォーク・実コントラクト・エンドツーエンドの再現に限定されます。Hardhat テスト、ローカルステート PoC、モックに対するファズ・インバリアントハーネス、または非 EVM チェーン(Solana、Cosmos、Move)には使用しないでください。
description の原文を見る
Use this skill when the user wants to write a Foundry Proof of Concept (PoC) test that reproduces a smart contract vulnerability against a real deployed protocol on a mainnet fork. Triggers include phrases like "write a PoC", "reproduce this bug with Foundry", "fork mainnet and exploit", "validate this finding on-chain", or when the user provides a vulnerability report alongside deployed contract addresses. This skill is strictly for mainnet-forked, real-contract, end-to-end reproductions on EVM chains. Do NOT use for Hardhat tests, local-state PoCs, fuzz or invariant harnesses against mocks, or non-EVM chains (Solana, Cosmos, Move).
SKILL.md 本文
メインネットフォーク上のFoundry PoC
目的
フォークされたEVMネットワーク上の実際にデプロイされたコントラクトに対する実際のスマートコントラクト脆弱性を再現する1つのFoundryテストファイルを作成します。テストは合格する必要があり、その合格が脆弱性をエンドツーエンドで証明する必要があります。脆弱性状態をトリガーする最初のアクションから、その間のすべてのオンチェーンステップを経由して、最終的に実現される影響まで。
読む順序 (必須)
リポジトリ内の他のものを読む前に、必ずfindingの説明を完全に読んでください。findingが完全に読まれた後でのみ、他のファイルを参照してください。特に以下の点に注意してください:
- findingが分類され、因果関係が書き出されるまで、ターゲットPoC ディレクトリ (通常
test/poc/) のファイルを読まないでください (手順を参照)。 - 計画された出力名を持つファイルがリポジトリに既に存在する場合、スキル独自の分類が完了するまで信頼できないものとして扱ってください。古いまたは前のセッションのPoC ファイルは、スキルのルールに違反する因果関係の選択をエンコードしている可能性があります。それに拘らないでください。
- 参照テストはスタイルマッチング (インポート、継承、ヘルパー) のために手順のステップ 6 で読まれます。それより前ではありません。その場合でも、その表面的なスタイルのみを模倣してください。独立した検証なしに、フォークブロックの選択、開始アクター、または因果関係の構造を模倣しないでください。
ファイルを読む順序はClaudeの推論に影響を与えます。findingを分類する前に既存のPoC を読むことは、出力をそのPoC の構造に固定し、実際にはスキルのルールをオーバーライドします。findingを最初に読むことはオプションではありません。
必要なインプット
コードを書く前に、ユーザーが以下を提供していることを確認してください:
- 脆弱性の説明: 根本原因、攻撃パス、影響。説明が短いか要約レベルの場合、完全なfindingを求めてください。
- チェーン: ethereum、arbitrum、base、optimism、polygon、bnb、avalanche、またはチェーンID。
- フォークターゲット:
latest、特定のブロック番号、またはタイムスタンプ (タイムスタンプの場合、ユーザーにブロック番号への変換を求めます。推測しないでください)。 - 攻撃パス内のすべてのコントラクトの実際にデプロイされたアドレス。役割によってラベル付けされたもの。
オプションですが、出力品質を向上させます:
- RPC URL (そうでない場合は公開デフォルトを使用して、どれが使用されたかを述べます)。固定されたブロック番号の場合、RPC はそのブロックのリアルタイム状態を持つ必要があります。publicnode などのパブリックRPC は多くの場合アーカイブデータを欠いています。drpc.org、mevblocker.io、eth-pokt.nodies.app はより広い履歴を保持しています。選択されたRPC が「historical state not available」を返す場合、ユーザーに archive 対応RPC を求める前に別のパブリックエンドポイントを試してください。
- スタイルマッチングのためのリポジトリの既存テストファイルへのパス。
- 重大度 (高、中程度、重大度については進めます; 低またはInfo の場合、PoC が必要でない可能性があることをユーザーに警告し、進める前に確認します)。
1、2、3、または 4 のいずれかが欠落している場合は、停止して質問してください。アドレスを推測しないでください。プロトコルが別のチェーン上にある可能性がある場合、「mainnet」は必ずしもEthereumを意味しないと仮定しないでください。フォークブロックを捏造しないでください。
厳格なルール
ルール 1: Foundry のみ
Foundryが唯一受け入れられるフレームワークです。ターゲットリポジトリがHardhat、Truffle、またはその他を使用している場合でも、PoC はスタンドアロンファイルとしてFoundry で書かれます。
ルール 2: 実コントラクトのみ
テストが対話するすべてのコントラクトは、ファイルの上部に constant としてバインドされた実際にデプロイされたアドレスである必要があります。具体的には:
- モックコントラクトはない。
- プロトコルコントラクトの最小限の再実装はない。
- デプロイされたバイトコードと異なるスタブインターフェースはない。
- テストファイルで書かれたインターフェースは、実際に呼び出される関数シグネチャのみを含む必要があり、これらのシグネチャはデプロイされたコントラクトと正確に一致する必要があります。
攻撃パスに必要なコントラクトがターゲットチェーン上にデプロイされていない場合は、停止してください。代替手段を捏造しないでください。ブロッカーをユーザーに述べてください。
テスト専用アクター (攻撃者EOA、受信者) のために makeAddr 経由で作成されたアドレスは受け入れられ、予想されます。プロトコルコントラクト、トークン、オラクル、ガバナンス、管理者のアドレスは実際のものである必要があります。
ルール 3: フォークは必須
setUp() 内の最初のステートメントは vm.createSelectFork(...) である必要があります。フォークは正しいチェーンのRPCを使用し、ユーザー指定のブロックまたは latest を使用します。状態はローカルで構築されるのではなく、フォークから読み込まれます。
ルール 4: エンドツーエンドの実行
テストは、脆弱性状態をトリガーする最初のアクションから最終的に実現される影響まで、脆弱性パス全体を実行する必要があります。これはメインネットのフォークと同じくらい重要です。
分類してから書いてください。 すべてのfindingは3つのカテゴリのいずれかに分類され、カテゴリはテストがどこから始まるかを決定します:
- (a) 凍結されたhistorical影響: 既にオンチェーンに存在する1回限りの悪い状態。再実行できない過去のアクションによって到達された (ブロック番号の進行、過去のガバナンス投票、既に完了したマイグレーション)。重大度は既存の凍結状態から完全に来ており、さらなる損害は蓄積していません。脆弱性後の状態でフォークすることが正しい開始点です。
- (b) 前向きなリスク: 脆弱性状態は、通常のプロトコル操作が実行される際に新しいインスタンスを生み出し続けるパターンです。重大度は、まだ影響を受けていないアクターに何が起こるかから来ます。PoC は脆弱性状態の新しいインスタンスを生み出すチェーンを再現する必要があり、現在安全な状態にあるアクターから開始します。過去のインスタンスを再生することは不十分です。
- (a+b) 両方: いくつかの値は既に影響を受けており、通常の操作が続く際にさらに影響を受けます。プライマリテストは前向きチェーン (b) を再現します。セカンダリアサーションは、既存の影響を受けた値 (a) を追加証拠として参照できますが、PoC の大部分は (b) チェーンです。
コードを書く前に、分類を声に出してユーザーに述べてください: 「このfindingはカテゴリ (a)/(b)/(a+b) です。理由は [シグナル]。テストを [アクターX] から [フォークブロック Y] で開始します。X はまだ安全な状態にあり、[ステップ 1、ステップ 2、...] を実行します。」
このステートメントは必須です。分類を最初に述べずに書かれたPoC は、結果として得られるコードがそれ以外の点で正しい場合でも、ルール違反です。
シグナル (b): 「currently at risk」「will freeze」「any X that does Y」「N actors vulnerable」などのフレーズ、影響を受けたものと影響を受けていないものの数が記載されたライブプロトコルエンティティのテーブル、プロトコルスケジュール上で繰り返されるパターンへの参照、「future」または「new」アクターに関する言語。これらのいずれかが表示される場合、findingが明示的にパターンが修正されたと述べない限り、(b) または (a+b) にデフォルトします。
最初にトリガーするアクションを特定します。 最初のアクションは常に攻撃者呼び出しではありません。可能性があります:
- 悪意あるアクターのオンチェーン呼び出し。
- 通常のユーザーアクション (deposit、withdraw、redeem、rebalance) は、たまたまプロトコルを脆弱性状態に置きます。
- プロトコル操作 (keeper bot、liquidation、debt reporting、oracle update) はスケジュールで実行されます。
- ガバナンスアクションまたは特権役割呼び出し。
順番に各ステップを実行します。 因果関係が A → B → C → 影響の場合、テストは A を実行し、次に B、次に C、次に影響を示す必要があります。各ステップは、オンチェーンでそれを実行する実際のコントラクト経由で、オンチェーンでそれを実行するコーラーを使用して行われます。ステップが「明らかに機能する」という理由でスキップされることはありません。
オフチェーン署名されたメッセージは正当なテスト設定です。 多くのプロトコルは、特権署名者 (KYC検証者、管理者、オラクルオペレータ) からの署名またはユーザー署名リクエスト (EIP-712撤回、メタトランザクション) の背後にある関数をゲートします。テスト秘密鍵を持つ vm.sign を使用してこれらの署名を生成することは許可され、予想されます。重要な条件: その秘密鍵のテストホルダーは、テストの前半の実際の特権呼び出し経由で対応するオンチェーン役割を付与されている必要があります (例えば、プロトコル管理者が実際のオンチェーン権限更新関数経由で役割を付与する)。役割をプランクすることではなく。
実現された影響で終了します。 バグがファンドの盗難に関するものである場合、テストはファンドが攻撃者のアドレスに到着したことを示す必要があります。コントラクトをブリックするためのものである場合、テストは次の正当な呼び出しがrevert することを示す必要があります。権限昇格に関するものである場合、テストは昇格された権限が以前は不可能だった何かを行うために行使されることを示す必要があります。ファンドをフリーズすることに関するものである場合、テストはフルの因果関係の実行後、抽出パスが機能しないことを示す必要があります。理想的には、可能であるべき時点での復旧を試みて、そうではないことを示します。
影響タイプ別の証明形状:
- 盗難: 攻撃者は正当に取得しなかったトークンで終わります。証拠は
assertGt(attackerBalAfter, attackerBalBefore)です。ここでattackerBalBeforeは悪用パスの前に記録されるか、攻撃者が最初に何かをデポジットした場合はassertGt(stolen, depositedOrPrincipal)です。シェア残高または所有権パーセンテージに関する中間アサーションは役に立ちますが、十分ではありません。最終的なアサーションは実現されたトークン転送をエンコードします。 - プール排出: 上記の攻撃者獲得アサーションと、プールが盗まれたトークンの残高がゼロ近くに低下したことを示すプール状態アサーションを組み合わせます。
assertLe(poolBalAfter, poolBalBefore / 50)または同様。 - フリーズ: 成功するべき時点で成功するべきアクションを試みて、revert または誤った値を返すことを証明します。ログまたはフリーズされた値を定量化するアサーションと組み合わせます。
- DoS: 次の正当な呼び出しは、成功するべき時に revert または zero を返します。呼び出しを試みて revert を示すことで証明します。
- アクセス制御/権限昇格: 許可されていないコーラーが以前はゲートされたアクションを実行します。証拠は、残高デルタではなく、成功した呼び出しによって生成された状態変更または放出されたイベントです。
盗難形状またはドレーン形状のfindingのコア証明に vm.expectRevert はありません。成功した実行がバグです。
残高デルタと最終状態アサーション。 チェーン中の中間アサーションは問題なく、多くの場合役に立ちますが、最終的なアサーションは中間点ではなく、実現された最終状態をエンコードする必要があります。
ルール 5: プロトコルパイプラインのショートカットなし
脆弱性がファンドをプロトコルパイプライン (liquidation、yield distribution、rebalancing、swap routing、oracle updates、cross-module calls) を通して移動することを含む場合、テストは最後のステップにファンドを注入するのではなく、実際のパイプラインを経由してファンドをルーティングする必要があります。
許可:
vm.deal(attacker, 1 ether)で攻撃者EOA にガスの資金を提供。deal(token, attacker, 100e18)で攻撃者に、メインネット経由のスワップで正当に取得できるトークンの開始資本を提供。vm.prank(user)で、そのユーザーが実行できる単一の正当なアクションについてリアルホルダーとして行動。
許可されていない:
deal(token, protocolContract, amount)の後に、その後のプロトコルコントラクトの代わりに内部またはホワイトリスト専用関数を呼び出すプランク。これは通常そのコントラクトにそれらのトークンを生成するパイプラインをバイパスします。- 特権役割 (keeper、liquidator、admin) をプランクして関数を呼び出す場合は、バグがその関数の下流で何が起こるかについてである場合、役割自体が finding の対象ではなく、上流パスを再現することが本当に実行不可能でない限り。その場合、プランクの上の1つのコメントで制限を明示的に文書化します。
vm.storeを使用してプロトコルを mid-chain 状態に配置。
実際のパイプラインが再現するには重すぎる場合 (ライブスワップルート、オラクルアップデート、クロスチェーンメッセージ、タイムロック投票が必要)、ショートカットの上の1つのコメントで制限を明示的に述べ、何がシミュレートされているか、そしてなぜそれを完全に再現することが実行不可能かを命名します。目標は、E2E の作業をスキップするための正当化ではなく、ギャップについての透明性です。
ルール 6: Pragma とインポートはターゲットに一致
pragma はターゲットコントラクトのコンパイラバージョンと一致します。インポートは、テストパターンが提供されている場合はターゲットリポジトリに存在するパスを使用し、そうでない場合は標準 Foundry および OpenZeppelin パスを使用します。
スタイルルール
- エムダッシュはありません。「—」はありません。「 – 」はありません。コンマまたはピリオドを使用します。
- 回避: 「crucial」「essentially」「delve」「seamless」「robust」「leverage」 (動詞として)、「it's important to note」「in summary」「in conclusion」「navigate」 (メタファーとして)。
- フィラーセクションコメントはありません:
// setup、// exploit、// assert、// attack begins here。 - テスト関数の上のサマリーコメントはありません。テスト名がそれを説明します。
- インラインコメントのみ、コードが自分で表現できないものを参照する場合: 特定のストレージスロット、ブロック固有の条件、既知のデプロイメントの癖、呼び出しが特定の方法で順序付けられない明白でない理由、またはシミュレートされた実行に対するもの。
ログルール
console2.logは、影響を定量化または状態変更を示すの値を出力するためにのみ使用されます。すべてのログには少なくとも2つの引数があります。ラベルと値。- 禁止: 「Exploit successful」「Vulnerability confirmed」「Attack starting」「Step 1 complete」などのステータス文字列、および同等のもの。
- 同じ行の別のログまたはアサーションが既に証明するものを複製するログ。
- バグが印刷する数値の影響がない場合 (revert を証明する純粋なロジックバグ)、ゼロログが正しい数です。
- 異なるフレーミングで同じことを2回証明する冗長なログはありません。
アサーションルール
- すべてのアサーションは脆弱性不変量をエンコードします。
vm.prankを再述べたり、フォークブロック番号を確認したりするアサーションはノイズです。 vm.prankまたは絶対残高ではなく、前/後の残高デルタで盗難または損失を証明します。- 中間のものではなく、最終的な最終状態で影響を証明します。
- 予想されるrevert の場合は型付きセレクタを使用します:
vm.expectRevert(Contract.ErrorName.selector)またはvm.expectRevert(abi.encodeWithSignature("ErrorName()")). ターゲットコントラクトが文字列でrevert する場合のみ文字列マッチングを使用します。 - アサーションメッセージはCI失敗ラベルであり、説明ではありません。 良い: 「timer not reset」「totalSupply nonzero」「no frozen balance」。悪い: 「timer must advance past first reset」「unlock window must be pushed out」。メッセージが完全な文として読まれるか、「must」を含む場合、長すぎます。失敗した内容を命名する2〜5語を目指します。説明より省略を優先します。
- 不変量ごとに1つの実証。 2つのイテレーションが同じパターンを証明する場合、2つで十分です。3番目は冗長です。繰り返されるイテレーションはそれぞれ異なる不変量を証明する必要があります。
- setUp sanity チェックは許可されています。 フォークが予想されるメインネット状態 (トークンの小数点以下、ホワイトリストステータス、プールの価値トークン) を返したことを検証するため。短いラベル付きで
assertEq/assertFalseを使用します。これらは脆弱性の証明ではありません。フォークドリフトから保護します。
ファイルテンプレート
// SPDX-License-Identifier: UNLICENSED
pragma solidity <version matching target>;
import { Test, console2 } from "forge-std/Test.sol";
import { IERC20 } from "openzeppelin-contracts/token/ERC20/IERC20.sol";
// additional imports from the target repo if patterns were provided
interface I<Target> {
// only functions the PoC calls, signatures matching deployed bytecode
}
contract POC_<FindingID> is Test {
// Protocol addresses, grouped and labeled by role
address constant <ROLE_NAME> = 0x...;
I<Target> target;
address attacker;
function setUp() public {
vm.createSelectFork(vm.envString("<CHAIN>_RPC_URL"), <BLOCK_OR_LATEST>);
target = I<Target>(<ADDRESS_CONSTANT>);
attacker = makeAddr("attacker");
// Minimal state prep the actor could legitimately reach
}
function test_<ShortVulnName>() public {
// Step A: first-triggering action
// Step B: intermediate real contract calls
// Step C: final action that realizes impact
// Assertions encoding the realized end-state
}
}
上記の番号付きコメントは4つのE2Eフェーズを示すプレースホルダーであり、出力にコピーするパターンではありません。実際の出力にはフィラーコメントはありません。
手順
- findingの説明を完全に読みます。これが完了するまで、他のファイルを開かないでください。
- 必要なインプットを確認します (必要なインプット を参照)。何かが欠落していたり曖昧な場合は、書く前に質問してください。
- findingを分類し、分類を声に出してユーザーに述べてください: 「(a)/(b)/(a+b) シグナル: [シグナル]。開始アクター: [X]。フォークブロック: [Y]。因果関係: [ステップ 1 → ステップ 2 → ... → 影響]。」このステートメントは、ファイル読み込みまたはコード作成の前に表示される必要があります。それをスキップしないでください。
- 提案されたフォークブロックが選択されたアクターを脆弱性前または脆弱性後の状態に置くかどうかを確認します。(a) の場合、脆弱性後が正しいです。(b) と (a+b) の場合、選択されたアクターはフォーク時点でまだ安全な状態にある必要があります。
- チェーンに必要なすべてのプロトコルアドレスが提供されていることを確認します。findingが「N個の脆弱なアクター」または「N個の単一-Xボルト」を言及し、これらの特定のアクターのアドレスが提供されていない場合、ユーザーにどちらから開始するか尋ねてアドレスを取得します。
- 次に、それ以外の場合はリポジトリ内のスタイルマッチング参照テストファイルを読みます (インポート、継承、ヘルパー使用、命名)。計画された出力名を持つファイルが既に存在する場合、名前を変更するかスキップします。因果関係構造のテンプレートとして使用しないでください。
- ターゲットコントラクトから pragma を決定します。
- すべてのプロトコルアドレスを名前付き
constantとしてバインドします。 - 必要な最小限のインターフェースを書きます。シグネチャはデプロイされたABI と一致する必要があります。
setUpを書きます: フォーク呼び出しが最初、コントラクトバインディング、次にテストされている因果関係の外にあり、アクターが正当に実行できる最小限の状態準備のみ。- テスト関数を書きます: ステップ 3 のチェーンのすべてのステップ、順番に、実際のコントラクト経由、各ステップの実際のコーラーを使用。実現された最終状態をエンコードするアサーションで終了します。
-vvvvを含む正確なforge testコマンドを作成して、完全なトレース可視性を実現します。- 出力を返す前に Self-Review Checklist を実行します。
Self-Review Checklist
- Finding はリポジトリ内の他のファイルを読む前に完全に読まれました。
- 分類 ((a)/(b)/(a+b)) はコーディング前にユーザーに声に出して述べられました。
- (b) または (a+b) findings の場合、テストは既に影響を受けたアクターではな
ライセンス: MIT(寛容ライセンスのため全文を引用しています) · 原本リポジトリ
詳細情報
- 作者
- cholakovvv
- ライセンス
- MIT
- 最終更新
- 2026/4/23
Source: https://github.com/cholakovvv/foundry-poc-mainnet-fork / ライセンス: MIT