web3-audit について
Web3プロトコルを調査するセキュリティ研究者向けのスマートコントラクト監査スキルです。10種類のバグ分類を軸にコードレビュー項目をまとめ、事前の対象選定とFoundryによるPoC作成を案内します。
主な機能
- 01監査前の対象選定シグナル
- 02スマートコントラクトの10種類のバグ
- 03関連関数のアクセス制御確認
- 04Solidityの検索パターン
- 05FoundryのPoCテンプレート
- 06実際に報奨金が支払われた脆弱性事例
活用シーン
- 01コードレビュー前のプロトコル選定
- 02会計処理とアクセス制御の確認
- 03オラクルとERC4626ボールトのロジック確認
- 04Foundryでの概念実証の作成
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ elementalsouls/ claude-bughunter --skill web3-auditインストール後、Claude Code で/web3-auditを入力して読み込まれたか確認します。
詳細
Web3 スマートコントラクト監査
10 のバグクラス。着手前の中止シグナル。Foundry PoC テンプレート。実際の報奨金獲得事例。
着手前の中止シグナル(コードレビューの前に必ず確認)
ZKsync の教訓:TVL 3 億 2,200 万ドル + OZ 監査 + 75 万 LOC + 5 セッション = 発見ゼロ。大規模で十分に監査されたブリッジは、非常に攻略が困難です。
- TVL < 50 万ドル → 最大報奨金が、労力に見合わないほど低い
- トップクラスの監査が 2 件以上(Halborn、ToB、Cyfrin、OpenZeppelin)実施されているシンプルなプロトコル → バグはすでに発見済み
- プロトコルが 500 行未満で、単一の A→B→C フロー → 攻撃対象領域が最小限
- 計算式:
max_realistic_payout = min(10% × TVL, program_cap)— 1 万ドル未満なら見送る
ソフト中止条件: 現行バージョンに対する OZ/ToB/Cyfrin の監査があり、かつコードベースが 50 万 LOC を超える場合 → 1 件見つかるかどうかで 40 時間以上かかる見込みです。報奨金の下限が 5 万ドルを超え、かつそのプロトコル固有の専門知識がある場合に限り、進めてください。
ターゲットのスコアリング(6/10 以上なら着手):
- TVL > 1,000 万ドル:+2
- Critical の報奨金が 5 万ドル以上の Immunefi プログラム:+2
- 現行バージョンに対するトップクラスの監査がない:+2
- デプロイから 30 日未満:+1
- 以前に調査したことがあるプロトコル:+1
- ソースコードと natspec コメントがある:+1
- アップグレード可能なプロキシ:+1
唯一のルール
「すべての関連関数を読むこと。
vote()にモディファイアがあるなら、poke()、reset()、harvest()も確認する。関連関数にモディファイアがないこと、それ自体がバグです。」
このルールだけで、Critical の発見事例全体の 19% を説明できます。
1. 会計状態の不整合
Critical バグクラスの第 1 位 — Immunefi の Critical 全体の 28%。
内容
同期を保つべき 2 つの状態変数があります。あるコードパスで A を更新しても、B の更新が抜けています。その後、コードが両方を読み取り、古い B に基づいて判断します。
Real Value = A - B
If A updated but B isn't → Real Value appears larger → phantom value
根本原因のパターン
パターン 1:架空の利回り(Yeet プロトコル — 重複報告 35 件)
function startUnstake(uint256 amount) external {
totalSupply -= amount; // decremented BEFORE transfer
// aToken.balanceOf(this) still reflects old value
// yieldAmount = aToken.balanceOf - totalSupply = phantom yield
}
パターン 2:高速パスで状態更新がスキップされる(Alchemix V3)
function claimRedemption(uint256 tokenId) external {
if (transmuter.balance >= amount) {
transmuter.transfer(user, amount);
_burn(tokenId);
return; // EARLY RETURN — cumulativeEarmarked, _redemptionWeight, totalDebt never updated
}
// Slow path: updates all state vars correctly
alchemist.redeem(...);
}
パターン 3:更新順序が誤っている(Alchemix)
function deposit(uint256 amount) external {
_shares = (amount * totalShares) / totalAssets; // calculated BEFORE deposit
totalAssets += amount; // assets added AFTER shares calculated → wrong rate
}
grep パターン
# Find all accounting variables
grep -rn "totalSupply\|totalShares\|totalAssets\|totalDebt\|cumulativeReward\|rewardPerShare" contracts/
# Find all early returns in claim/redeem functions
grep -rn "\breturn\b" contracts/ -B3 | grep -B3 "if\b"
# For each early return: which state updates in normal path are skipped?
2. アクセス制御
Critical の第 2 位 — Critical 全体の 19%。2024 年だけで 9 億 5,300 万ドルが失われています。
パターン 1:関連関数にモディファイアがない
function vote(uint256 tokenId) external onlyNewEpoch(tokenId) { // guarded
function reset(uint256 tokenId) external onlyNewEpoch(tokenId) { // guarded
function poke(uint256 tokenId) external { // NO GUARD → infinite FLUX inflation
}
パターン 2:チェックの誤り(存在確認と所有権確認の取り違え)
function split(uint256 tokenId, uint256 amount) external {
_requireOwned(tokenId); // checks if token EXISTS, not if caller OWNS it
_burn(tokenId);
_mint(msg.sender, amount); // attacker steals tokens they don't own
}
パターン 3:作用しないモディファイア(if と require の違い)
// VULNERABLE — non-admin silently gets through:
modifier onlyAdmin() {
if (msg.sender == admin) {
_; // body only executes for admin, but non-admin doesn't revert
}
}
// CORRECT: require(msg.sender == admin, "Not admin"); _;
パターン 4:未初期化のプロキシ
function initialize(address _owner) public { // MISSING: initializer modifier
owner = _owner; // anyone can call → become owner
}
// Fix: constructor() { _disableInitializers(); }
grep パターン
# Find sibling function families — do ALL have the same modifier set?
grep -rn "function vote\|function poke\|function reset\|function update\|function claim\|function harvest" contracts/ -A2
# Ownership check: existence vs ownership?
grep -rn "_requireOwned\|ownerOf\|_isApprovedOrOwner\|_checkAuthorized" contracts/ -B5
# Silent modifiers
grep -rn "modifier\b" contracts/ -A8 | grep -B3 "if (" | grep -v "require\|revert"
# Uninitialized initializer
grep -rn "function initialize\b" contracts/ -A3
grep -rn "_disableInitializers()" contracts/
実際の報奨金獲得事例
| プロトコル | 報奨金 | バグ |
|---|---|---|
| Wormhole | 1,000 万ドル | UUPS プロキシが未初期化 → 誰でも initialize() を呼び出せる |
| ZeroLend | なし | split() が所有権確認ではなく、存在確認を使用 |
| Alchemix | なし | poke() に onlyNewEpoch がない → FLUX が無限に増加 |
| Parity | 1 億 5,000 万ドル相当が凍結 | ライブラリ内の initWallet() にアクセス制御がない |
3. 不完全なコードパス
Critical の第 3 位 — Critical 全体の 17%。
関数ファミリー比較テスト
1. List all state changes in function A (deposit/place/create)
2. List all state changes in function B (withdraw/update/cancel)
3. For each state change in A: does B have the corresponding reverse?
4. For each token transfer in A: does B have the corresponding refund?
If A does X but B doesn't do the reverse of X → BUG.
パターン 1:更新関数に返金処理がない(ThunderNFT)
function place_order(OrderInput calldata order) external {
token.safeTransferFrom(msg.sender, address(this), order.price); // takes tokens
orders[orderId] = order;
}
function update_order(OrderInput calldata updatedOrder) external {
// BUG: NO REFUND for sell orders when price decreases → tokens permanently stuck
orders[orderId] = updatedOrder;
}
パターン 2:一部約定したトークンが残留する(Plume)
function swapForETH(uint256 amountIn) external {
token.safeTransferFrom(msg.sender, address(this), amountIn);
uint256 filled = dex.swap(amountIn); // partial fill possible
_refundExcessEth(amountIn - filled); // BUG: refunds ETH only, not ERC20
}
パターン 3:mint() が deposit() にあるチェックを回避する(MetaPool)
function deposit(uint256 assets, address receiver) public override {
shares = _deposit(assets, receiver); // includes receipt validation
}
function mint(uint256 shares, address receiver) public override {
assets = convertToAssets(shares);
_mint(receiver, shares); // MISSING: _deposit() validation → mints without receiving assets
}
grep パターン
grep -rn "function place_\|function create_\|function add_\|function open_" contracts/ -A5
grep -rn "function update_\|function modify_\|function cancel_" contracts/ -A5
grep -rn "safeApprove\b" contracts/ # safeApprove without zero-reset before
grep -rn "delete\b" contracts/ -B5 -A5 # delete before operation completes
grep -rn "function deposit\|function mint\|function withdraw\|function redeem" contracts/ -A10
4. オフバイワンと境界条件
High の第 4 位 — High 全体の 22%。たった 1 文字の変更で、大きな影響が生じます。
根本原因
// VeChain Stargate — post-exit reward drain:
function _claimableDelegationPeriods(address delegator) internal view returns (uint256) {
if (endPeriod > nextClaimablePeriod) { // BUG: should be >=
return 0; // exited users get nothing
}
return nextClaimablePeriod - lastClaimedPeriod; // rewards for period AFTER exit
}
あらゆる比較で行う思考テスト
すべての
if (A > B)について、「A == B のとき何が起こるか?」それは正しい動作か?
確認すべき 6 つの境界箇所
- 期間/エポックの境界:期間終了時に
>と>=のどちらを使っているか - 時間ベースのロック:
block.timestamp == deadlineのとき、ロックされるか解除されるか - ループの終了条件:
breakで>と>=のどちらを使っているか - 配列インデックスの境界:
i <= array.length(i < array.lengthにすべき) - 数量/残高の境界:
>= amountで全額の引き出しが可能になるか - 丸め処理/精度:ゼロ以外になるべき出力が 0 になる入力はないか
grep パターン
# Boundaries in comparisons
grep -rn "Period\|Epoch\|Round\|Deadline\|period\|epoch\|deadline" contracts/ -A3 | grep "[<>][^=]"
# Loop breaks
grep -rn "\bbreak\b" contracts/ -B10
# Off-by-one in array access
grep -rn "\.length\s*-\s*1\|i\s*<=\s*.*\.length\b" contracts/
5. オラクル/価格操作
報告全体の 12%。個別の報奨金としては最大規模。Mango で 1 億 1,700 万ドル、Curve で 7,000 万ドル。
バグ A:鮮度チェックがない(最も一般的)
// VULNERABLE:
(, int256 price,,,) = priceFeed.latestRoundData();
return uint256(price); // If Chainlink node goes down, stale price returned indefinitely
// CORRECT:
(, int256 price,, uint256 updatedAt,) = priceFeed.latestRoundData();
require(block.timestamp - updatedAt <= MAX_PRICE_AGE, "Stale price");
require(price > 0, "Invalid price");
バグ B:信頼区間チェックがない(Pyth)
// VULNERABLE:
PythStructs.Price memory p = pyth.getPriceUnsafe(priceFeed);
return p.price; // ignores p.conf (confidence interval)
// CORRECT:
require(p.conf * 10 <= uint64(p.price), "Price too uncertain");
// conf > 10% of price = untrustworthy
バグ C:TWAP の期間が短すぎる(フラッシュローンで操作可能)
// VULNERABLE: 60-second TWAP
uint32[] memory secondsAgos = new uint32[](2);
secondsAgos[0] = 60; secondsAgos[1] = 0;
// Flash loan can shift price for entire 60s window
// CORRECT: 1800s minimum TWAP (30 min)
バグ D:単一ソースのオラクル
// VULNERABLE: only Uniswap spot price
uint price = getUniswapSpotPrice(token); // flash loan manipulatable
// CORRECT: Chainlink primary, Uniswap TWAP as fallback, require close agreement
grep パターン
# Missing staleness check
grep -rn "latestRoundData" contracts/ -A5 | grep -v "updatedAt\|timestamp"
# Pyth price usage — confidence interval checked?
grep -rn "getPriceUnsafe\|getPrice\b" contracts/ -A8 | grep -v "conf\|confidence"
# TWAP windows — short TWAP flag
grep -rn "secondsAgo\|TWAP\|cardinality" contracts/ -A5
6. ERC4626 VAULT 攻撃
為替レートの操作(残高がほぼゼロのボールト)
// VULNERABLE — first depositor attack:
// 1. Attacker deposits 1 wei → gets 1 share
// 2. Attacker donates large amount directly (transfer, not deposit)
// 3. Exchange rate: 1 share = (1 + donation) assets
// 4. Victim deposits → rounds down to 0 shares → free donation to attacker
// CORRECT: virtual shares (OpenZeppelin v4.9+)
function _decimalsOffset() internal view virtual override returns (uint8) {
return 9; // add 1e9 virtual shares + assets to prevent manipulation
}
ERC4626 の転送(シェアは移動するが、ステーク/ロックの記録は移動しない)
// VULNERABLE: shares transferred, but lock records stay with original owner
// → shares stuck, can't redeem → permanent freeze (Belong pattern)
function transfer(address to, uint256 amount) external override {
_transfer(msg.sender, to, amount); // moves shares
// MISSING: transfer lock record from msg.sender to `to`
}
Grep パターン
grep -rn "function transfer\|function transferFrom" contracts/ -A15
grep -rn "function deposit\|function mint\|function withdraw\|function redeem" contracts/ -A10
7. 再入攻撃
2016年〜現在。CEI パターンで防止できます。現在も DeFi で発見されています。
種類
- 単一関数:状態が更新される前に、攻撃者が同じ関数を再度呼び出します
- 関数間:状態が古いまま、関連する別の関数を再度呼び出します
- コントラクト間:別のプロトコルへのコールバックを介して再度呼び出します
- 読み取り専用:攻撃者が利用する古いデータを返す view 関数を再度呼び出します
根本原因のパターン
// VULNERABLE (effects after interaction):
function withdraw(uint256 amount) external {
require(balances[msg.sender] >= amount);
(bool success,) = msg.sender.call{value: amount}(""); // INTERACTION first
require(success);
balances[msg.sender] -= amount; // EFFECT after → reentrancy window
}
// CORRECT (CEI — Checks, Effects, Interactions):
function withdraw(uint256 amount) external {
require(balances[msg.sender] >= amount); // CHECK
balances[msg.sender] -= amount; // EFFECT
(bool success,) = msg.sender.call{value: amount}(""); // INTERACTION last
require(success);
}
Grep パターン
# External calls before state updates
grep -rn "\.call{value\|safeTransfer\|transfer(" contracts/ -B10 | grep -v "require\|revert"
# Missing nonReentrant modifier on critical functions
grep -rn "function withdraw\|function redeem\|function claim" contracts/ -A2 | grep -v "nonReentrant"
# Storage slot for reentrancy guard
grep -rn "nonReentrant\|ReentrancyGuard\|_notEntered" contracts/
8. フラッシュローン攻撃
フラッシュローンを介したオラクル操作
// Attack flow:
// 1. Borrow $100M from Aave flash loan
// 2. Dump token in Uniswap pool → crash spot price
// 3. Protocol reads Uniswap spot → undercollateralized loans accepted
// 4. Borrow max against cheap collateral
// 5. Repay flash loan, keep profits
価格オラクルの妥当性チェック(確認すべき点)
grep -rn "getReserves\|getAmountsOut\|slot0\b" contracts/ -A5
# spot price from reserves = manipulatable with flash loan
# slot0 = Uniswap V3 spot price = manipulatable
9. 署名のリプレイ
ノンスの欠如
// VULNERABLE:
function permit(address owner, address spender, uint256 value,
uint256 deadline, uint8 v, bytes32 r, bytes32 s) external {
bytes32 hash = keccak256(abi.encodePacked(owner, spender, value, deadline));
// MISSING: nonce not included → same signature usable multiple times
require(ecrecover(hash, v, r, s) == owner);
}
Chain ID の欠如
// VULNERABLE: signature valid on mainnet AND testnet AND all forks
bytes32 hash = keccak256(abi.encodePacked(params));
// MISSING: block.chainid not in hash → works on any chain
Grep パターン
grep -rn "ecrecover\|ECDSA\.recover" contracts/ -B20
# Check: does the signed hash include nonce + chainId + contract address?
grep -rn "nonce\|_nonces\|nonces\[" contracts/
10. プロキシ/アップグレードの問題
ストレージの衝突
// Implementation and proxy share storage layout
// Proxy slot 0: _owner
// Implementation slot 0: _initialized
// → writing to _initialized overwrites _owner
未初期化の実装
// If implementation can be initialized directly → anyone becomes owner of implementation
// Attack: call initialize() on implementation contract → call upgradeTo() → replace logic
ユーザー制御アドレスへの delegatecall
function execute(address target, bytes calldata data) external onlyOwner {
target.delegatecall(data); // target is validated, but what if owner is compromised?
}
Grep パターン
# UUPS initialization protection
grep -rn "function initialize\b\|_disableInitializers\|initializer" contracts/
# Delegate call
grep -rn "delegatecall\b" contracts/ -B3 -A5
# Storage layout — proxy uses EIP-1967 slots?
grep -rn "0x360894\|EIP1967\|_IMPLEMENTATION_SLOT" contracts/
FOUNDRY POC テンプレート
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "forge-std/Test.sol";
import "../src/VulnerableContract.sol";
contract ExploitTest is Test {
VulnerableContract target;
address attacker = makeAddr("attacker");
address victim = makeAddr("victim");
function setUp() public {
// Fork mainnet at specific block
vm.createSelectFork("mainnet", BLOCK_NUMBER);
// Deploy or load target
target = VulnerableContract(TARGET_ADDRESS);
// Fund accounts
deal(address(token), attacker, INITIAL_BALANCE);
deal(address(token), victim, VICTIM_BALANCE);
}
function test_exploit() public {
console.log("Attacker balance before:", token.balanceOf(attacker));
vm.startPrank(attacker);
// Step 1: Setup conditions
// Step 2: Execute exploit
// Step 3: Verify impact
vm.stopPrank();
console.log("Attacker balance after:", token.balanceOf(attacker));
assertGt(token.balanceOf(attacker), INITIAL_BALANCE, "Exploit failed");
}
}
主な Foundry チートコード
vm.prank(address) // next call from address
vm.startPrank(address) // all calls from address until stopPrank()
vm.deal(address, amount) // set ETH balance
deal(token, address, amount) // set ERC20 balance
vm.warp(timestamp) // set block.timestamp
vm.roll(blockNumber) // set block.number
vm.createSelectFork("mainnet", blockNumber) // fork mainnet
vm.expectRevert(bytes) // next call should revert
vm.label(address, "name") // label for trace output
vm.assume(condition) // fuzz: discard inputs where false
テストの実行
# Run specific test
forge test --match-test test_exploit -vvvv
# Run with fork
forge test --match-test test_exploit -vvvv --fork-url $MAINNET_RPC
# Gas report
forge test --gas-report
# Coverage
forge coverage --report summary
関連スキルとチェーン
meme-coin-audit— 対象が DeFi プロトコルではなく、ミームコイン/SPL トークンの場合に使用します。ワークフローの基本方針:事前調査での中止シグナルは異なります。このスキルの「TVL < $500K ならスキップ」という基準は、ミームコインには適用されません。ミームコインではラグチェック(ミント権限、凍結権限、LP ロック)が監査のすべてとなるため、代わりにmeme-coin-auditに振り分けます。triage-validation— コントラクトの指摘事項を Immunefi に提出できる状態になった場合に使用します。ワークフローの基本方針:Immunefi には独自のレポート形式がありますが、triage-validationの影響検証とチェーン全体にわたる検証の規律も引き続き適用されます。提出前に Foundry PoC に対して 7Q ゲートを実施してください。report-writing— Immunefi のレポート本文を作成する場合に使用します。ワークフローの基本方針:Foundry PoC、根本原因のコードスニペット、定量化された経済的影響を含むreport-writingの Immunefi テンプレートが、このスキルの指摘事項を反映する本文の骨格となります。offensive-osint— プロトコルのオフチェーン攻撃対象領域(フロントエンド、管理 API、RPC ゲートウェイ)を監査する場合に使用します。ワークフローの基本方針:オンチェーン監査はこのスキルの担当です。プロトコルの Web2 コンポーネント(Web フロントエンド、管理パネル、インデクサー API)は、偵察のためoffensive-osintに振り分けます。bb-methodology— そもそも調査に着手するかどうかを判断する場合に使用します。ワークフローの基本方針:bb-methodologyの PART 0 で、対象の案件が Web3 バグバウンティ/プライベート監査/スマートコントラクトレビューであることを確認します。このスキルの事前調査での中止シグナルは、その案件種別の標準スコアリング基準に代わるものです。
オペレーター向けメモ(Claude-BugHunter)
ベンダー提供の基盤に対する、案件由来および2026年固有の追加事項です。 実際の承認済み案件から得た知見と、このリポジトリの31以上のスキル領域にわたる ライブテストでのフェーズ2検証に基づいています。上流のコンテンツは「何」を扱い、 このレイヤーは「うまくいく場合と、うまくいかない場合」を扱います。
2026年も報奨金の対象となっているバグの種類
フラッシュローン攻撃は、Immunefiで依然として最も高額な報奨金の対象です(2024~2026年の報奨金額トップ5)。経済的な仕組み、つまり「5,000万ドルを借り入れ、価格オラクルを操作し、プールの資金を抜き取り、返済する」という攻撃が繰り返されるのは、新しいプロトコルが、フラッシュローンによる不均衡のもとでは成り立たないコンポーザビリティの前提を置いたまま、次々と公開されているためです。
リエントランシーは今も報奨金の対象です。新しいプロトコルでERC-777、フック、コールバックが使われ続けているためです。この種のバグはもうなくなったと思い込まないでください。2023~2025年の報奨金対象コーパスには、Checks-Effects-Interactionsパターンに従っているコードベースで発見されたリエントランシーのバグが40件以上含まれています(関数をまたぐリエントランシーや、状態更新の途中で呼び出されるview関数を介した読み取り専用リエントランシーなど)。
オラクル操作:依然として高額な報奨金の対象ですが、難度は上がっています。現在、ほとんどのプロジェクトがChainlink価格フィードを使っています。攻撃対象は、ほとんどのプロジェクトが併用する第2のオラクルです(流動性の低いUniswap V2ペアのTWAP、プロトコル独自の内部オラクル、古い値を使うフォールバック経路など)。プライマリフィードだけでなく、フェイルオーバーの経路を監査してください。
同梱コンテンツの作成後に登場したもの
- EIP-1153(一時ストレージ) — 2024年に導入されました。新たなリエントランシーの種類が生まれています。同一トランザクション内でキャッシュされた一時ストレージの読み取り値が永続ストレージの値とずれる可能性があります。
tload/tstoreの使用箇所について、外部呼び出し後の読み取りを監査してください。 - EIP-7702(2025年のPectraハードフォーク) — EOAからスマートアカウントへのアップグレードが追加されました。再委任によってATOに似た仕組みが可能になります。一度だけ署名したEOAが攻撃者の制御するコントラクトに委任し、その後、委任先をまたいで署名がリプレイされる可能性があります。
- アカウント抽象化(ERC-4337バンドラー) — ペイマスターによるスポンサーシップの悪用や、バンドラーへのグリーフィングが問題になります。送信者を厳格な許可リストで制限していないペイマスターコントラクトは、初回の呼び出しで資金を抜き取られる可能性があります。
- ZKロールアップブリッジのバグ — ロールアップをまたいだ証明のリプレイ、オフチェーンの証明生成者の侵害、シーケンサーによる検閲が強制取り込みのエッジケースにつながる問題などがあります。
- LST/LRTのデペッグ動態 — 損失が発生した際も1対1のペッグを前提とするリキッドステーキングおよびリキッドリステーキングトークンが問題になります。オラクルはペッグを前提とする一方で、市場ではデペッグが反映され、清算ロジックが破綻します。
2026年のツールスタック
テストフレームワークには、引き続きFoundryが使われています。不変条件テストにはforge test --gas-report --debug、プロパティベーステストにはforge fuzz、ストレージレイアウトの監査にはforge inspectを使います。静的解析とファジングにはSlitherとEchidnaを組み合わせます。小規模なコントラクトの記号実行にはMythrilを使います。メインネットの状態を対象に攻撃を再現する場合は、フォークとシミュレーションにtenderly.coを使うのが最も操作性に優れています。
Solanaでは、anchorフレームワーク、sealevel-attacksコーパス(anchorメンテナーが厳選したPoC)、soteria-sec/sec3スキャナーを使います。Move(Aptos、Sui)では、move-proverとaptos-cliのaptos move testを使います。
クロスチェーンでは、hyperlaneとLayerZeroがそれぞれ監査ツールのリポジトリを公開しています。ブリッジのバグを調べるには、片方のエンドポイントだけでなく、両方をシミュレートする必要があります。
事前調査で見極めるべき打ち切りのサイン
TVLが50万ドル未満なら、報奨金の最低額が高い場合を除き、監査に時間をかける価値はありません。すでに監査会社が監査している場合も、見落とされた点を見つけない限り、投資対効果は低くなります。監査レポートの対象外事項のセクションを確認し、明示的に監査対象から除外されたもの(オラクル、ガバナンス、オフチェーンのコンポーネント、フロントエンド、管理者の経路など)を調べてください。
マルチシグの署名者が5人を超え、タイムロックが48時間を超える場合、ラグプルのリスクは低くなります。発見事項がチームの悪意を前提とするなら影響度は低く、Immunefiの「centralization risk」の除外規定に基づいて、対象外となる可能性も高いです。プログラムの概要を確認してください。ほとんどのImmunefiプログラムでは、管理者の悪意を前提とする発見事項は、明示的に評価を下げるか、対象外としています。
報告に関する規律
ImmunefiではFoundry PoCが必須です。PoCのない提出は自動的に却下されます。手動セットアップ(「まずこれをデプロイしてから、あれを呼び出す」)が必要なPoCを添付した提出は、通常、重大度が引き下げられます。PoCは、影響を証明する単一の forge test 呼び出しである必要があり、流出した残高、ミントされたトークン、破損した状態に対する明示的な assertEq を含めてください。
重大度の主張には、Immunefiの重大度マトリックスをそのまま使用してください。独自の重大度を作らないでください。このマトリックスは、TVLに対する直接的な経済的損失の割合を基準としています。500Kドルのプロトコルに対するクリティカルと、500Mドルのプロトコルに対するクリティカルでは、報奨額が異なります。クリティカルと主張する前に、プログラム固有の重大度割り当てを確認してください。