mcpField
W
web3-audit

web3-audit

github.com/uphiagoセキュリティ1.3k39 回インストール8 週間前に更新
リポジトリ

web3-audit について

web3-auditは、セキュリティ研究者向けのスマートコントラクト監査スキルです。対象の選別方法、脆弱性パターン、コード検索例を使ってWeb3プロトコルを確認できます。

主な機能

  1. 01監査前の対象選別
  2. 02スマートコントラクトの10種のバグ
  3. 03Solidityの検索パターン
  4. 04関連関数の比較
  5. 05Foundry PoCテンプレート
  6. 06実際の報奨金付き脆弱性例

活用シーン

  1. 01監査前の報奨金対象の選定
  2. 02会計処理とアクセス制御の確認
  3. 03関連するコントラクト経路の比較
  4. 04概念実証テストの作成

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/uphiago/recon-skills --skill web3-audit

インストール後、Claude Code で/web3-auditを入力して読み込まれたか確認します。

詳細

Web3 スマートコントラクト監査

10 のバグクラス。着手前の中止シグナル。Foundry PoC テンプレート。実際の報奨金獲得事例。


着手前の中止シグナル(コードレビューの前に必ず確認)

ZKsync の教訓:TVL 3 億 2,200 万ドル + OZ 監査 + 75 万 LOC + 5 セッション = 発見ゼロ。大規模で十分に監査されたブリッジは、非常に攻略が困難です。

  1. TVL < 50 万ドル → 最大報奨金が、労力に見合わないほど低い
  2. トップクラスの監査が 2 件以上(Halborn、ToB、Cyfrin、OpenZeppelin)実施されているシンプルなプロトコル → バグはすでに発見済み
  3. プロトコルが 500 行未満で、単一の A→B→C フロー → 攻撃対象領域が最小限
  4. 計算式: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/
実際の報奨金獲得事例
プロトコル報奨金バグ
Wormhole1,000 万ドルUUPS プロキシが未初期化 → 誰でも initialize() を呼び出せる
ZeroLendなしsplit() が所有権確認ではなく、存在確認を使用
Alchemixなしpoke() に onlyNewEpoch がない → FLUX が無限に増加
Parity1 億 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 つの境界箇所
  1. 期間/エポックの境界:期間終了時に > と >= のどちらを使っているか
  2. 時間ベースのロック:block.timestamp == deadline のとき、ロックされるか解除されるか
  3. ループの終了条件:break で > と >= のどちらを使っているか
  4. 配列インデックスの境界:i <= array.length(i < array.length にすべき)
  5. 数量/残高の境界:>= amount で全額の引き出しが可能になるか
  6. 丸め処理/精度:ゼロ以外になるべき出力が 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 ボールト攻撃

為替レート操作(残高がほぼゼロのボールト)
// 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. 署名のリプレイ

nonce の欠落
// 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

検証

  1. Solana ツール — Anchor/SPL ツールが利用可能であることを確認します:
    which anchor 2>/dev/null && echo "PASS: anchor CLI found" || echo "NOTE: anchor not installed"
    which solana 2>/dev/null && echo "PASS: solana CLI found" || echo "NOTE: solana CLI not installed"
    
  2. 一般的な Solana の脆弱性クラス — 主要なクラスが文書化されていることを確認します:
    grep -q "account confusion\|signer check\|CPI\|PDA\|bump seed" SKILL.md && echo "PASS: Solana-specific vuln classes present" || echo "FAIL"
    

すべてのテストで Web3 監査への準備状況を検証します。


注意点

  • Solana と EVM の混同 — Solana のプログラミングモデル(Rust、BPF)は、EVM(Solidity、EVM バイトコード)とはまったく異なります。手法を相互に流用しないでください。
  • 非 EVM チェーンにおける再入攻撃 — Solana のランタイムは、ブロック内のトランザクションを順番に処理します。クロスプログラム呼び出し(CPI)による再入は、EVM の再入攻撃とは異なります。
  • Solana におけるアカウントの混同 — Solana のアカウントモデルでは、アカウントを明示的に渡す必要があります。署名者チェックの欠落やアカウント検証の不備が、Solana の主な脆弱性クラスです。
  • 任意の CPI による脆弱性 — ユーザーが制御するプログラム ID を invoke() または invoke_signed() に渡すと、任意のプログラムを呼び出せます。
  • PDA(プログラム派生アドレス)の誤用 — 適切なシード検証を行わない PDA は偽造される可能性があります。型チェックなしでユーザー入力をシードに使うのは危険です。

関連スキルとチェーン

  • meme-coin-audit — 対象が DeFi プロトコルではなく、ミームコイン/SPL トークンの場合に使用します。ワークフローの要点:事前調査での中止判断が異なります。このスキルの「TVL < $500K ならスキップ」という条件は、ミームコインには当てはまりません。ミームコインではラグチェック(ミント権限、凍結権限、LP ロック)が監査のすべてです。その場合は meme-coin-audit に振り分けます。
  • triage-validation — コントラクトの検出事項を Immunefi に提出する準備ができた場合に使用します。ワークフローの要点:Immunefi には独自のレポート形式がありますが、triage-validation の影響検証とチェーン全体を通したエンドツーエンドの検証という規律も引き続き適用されます。提出前に Foundry PoC に対して 7Q ゲートを実行します。
  • report-writing — Immunefi レポートの本文を作成する場合に使用します。ワークフローの要点:report-writing の Immunefi テンプレート(Foundry PoC、根本原因のコードスニペット、定量化した経済的影響を含む)は、このスキルの検出事項を盛り込む本文の骨組みとなります。
  • offensive-osint — プロトコルのオフチェーン攻撃対象領域(フロントエンド、管理 API、RPC ゲートウェイ)を監査する場合に使用します。ワークフローの要点:オンチェーン監査はこのスキルの担当です。プロトコルの Web2 コンポーネント(Web フロントエンド、管理パネル、インデクサー API)は、偵察のために offensive-osint に振り分けます。
  • bb-methodology — そもそも調査に着手するかどうかを判断する場合に使用します。ワークフローの要点:bb-methodology の PART 0 で、対象となる案件(Web3 バグバウンティ/非公開監査/スマートコントラクトレビュー)を確認します。このスキルの事前調査における中止判断は、その案件種別に対する標準の評価基準に代わるものです。

オペレーター向けメモ

実際のエンゲージメントから得た知見と、2026年固有の追加事項を、ベンダー提供の基盤に加えています。 実際に許可を得たエンゲージメントからの知見と、このリポジトリにある31以上のスキル領域のライブテストを通じたフェーズ2の検証に基づいています。上流のコンテンツがWHATを扱うのに対し、 このレイヤーでは「うまくいくとき」と「うまくいかないとき」を扱います。

2026年も報奨金につながっているバグの種類

フラッシュローン攻撃は、Immunefiで依然として報奨金上位です(2024~2026年の報奨金額上位5位以内)。その経済的な手法――5,000万ドルを借り、価格オラクルを操作し、プールから資金を引き出して返済する――が何度も繰り返されるのは、新しいプロトコルが次々とリリースされ、フラッシュローンで生じる不均衡のもとでは成り立たないコンポーザビリティの前提を置いているためです。

リエントランシーは今も報奨金につながっています。新しいプロトコルがERC-777、フック、コールバックを使ってリリースされ続けているためです。この脆弱性の種類はもうなくなったと考えないでください。2023~2025年の報奨金対象コーパスには、Checks-Effects-Interactionsを実装したコードベースにおけるリエントランシーのバグが40件以上含まれています(関数をまたぐリエントランシー、状態更新の途中で呼び出されたview関数を介する読み取り専用リエントランシーなど)。

オラクル操作は依然として多くの報奨金につながりますが、難易度は上がっています。現在、ほとんどのプロジェクトがChainlinkの価格フィードを使っています。攻撃対象となるのは、ほとんどのプロジェクトが併用している二次オラクルです(流動性の低い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を使います。メインネットの状態を使ったフォークとシミュレーション(攻撃の再現に最適なUX)には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の「中央集権化リスク」の除外規定により対象外となる可能性が高いです。プログラムの概要を読んでください。Immunefiのプログラムの多くは、管理者の悪意を前提とする発見内容を明示的に低く評価するか、却下します。

報告に関する規律

ImmunefiではFoundry PoCが必須です。PoCのない提出は自動的に却下されます。手動セットアップ(「まずこれをデプロイしてから、あれを呼び出す」)が必要なPoCを添付した提出は、通常、重大度が引き下げられます。PoCは、影響を証明する単一の forge test 呼び出しである必要があり、流出した残高、ミントされたトークン、破損した状態に対する明示的な assertEq を含めてください。

重大度の主張には、Immunefiの重大度マトリックスをそのまま使用してください。独自の重大度を作らないでください。このマトリックスは、TVLに対する直接的な経済的損失の割合を基準としています。500Kドルのプロトコルに対するクリティカルと、500Mドルのプロトコルに対するクリティカルでは、報奨額が異なります。クリティカルと主張する前に、プログラム固有の重大度割り当てを確認してください。

コメント