mcpField
L
launch-curve-audit

launch-curve-audit

github.com/setyamickalaセキュリティ59292 回インストール1 週間前に更新
リポジトリ

launch-curve-audit について

launch-curve-auditは、トークン全般ではなく、ローンチ前の販売メカニズムを調査するスキルです。オンチェーン証拠と攻撃的な呼び出し順序を使い、価格設定、参加条件、エスクロー、ローンチ移行、トークン発行を検証します。

主な機能

  1. 01カーブ価格と予算の分析
  2. 02参加条件とID制限の調査
  3. 03エスクローと返金の確認
  4. 04ローンチ移行とLP管理の確認
  5. 05フォーク上での不変条件テスト

活用シーン

  1. 01ローンチ前のトークン販売のレビュー
  2. 02購入者の退出や返金可否の確認
  3. 03カーブ価格と想定式の照合
  4. 04ローンチ移行条件とLP保管先の評価

インストール

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

$npx skills add https://github.com/setyamickala/bsc-contract-audit-skill --skill launch-curve-audit

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

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

roles.example.env
references/
attack-surface.md
economics.md
invariants.md
scripts/
curve_math.py
rpc.py
snapshot.sh

詳細

ローンチカーブ監査

カーブローンチでは、トークン監査では検出できない問題が発生します。価格推移に対して請求額が不足する、予算が順序どおりに消費されない、卒業時に誰も退出できないプールへミントされる、あるいは誰かが取引する前に開始価格が変更される、といった問題です。また、その資金はホルダーのものではなく購入者のものです。そして、移動手段は送金ではなく、セールコントラクトです。

ここで対象とするのは、セールメカニズムです。価格設定、参加許可、エスクロー、卒業、そしてその後にトークンを発行する仕組みとの連携を調べます。

BSCでのAgent-IDゲーティングを伴う線形のP→5Pカーブが、これを基に構築した参照モデルですが、この手法はその形状に限定されません。コントラクトからパラメーターを読み取り、エンコードされているカーブをモデル化してください。

対象範囲

対象:セールまたはカーブコントラクト、その価格設定ライブラリ、参加制御(許可リスト、レジストリ、Agent ID、ウォレットごと・IDごとの予算)、エスクローブックがあればその内容、卒業/ローンチ経路とLPの保管管理、ローンチ前のトークン送金ロックと税の配線、ローンチ後に引き継がれるトークン発行コントラクト。

対象外:信頼上の前提として扱うバックエンドの報酬ロジック、フロントエンド、デプロイスクリプト、AMM自体、上場後の市場リスクです。対象外としたものを明記してください。

事前情報

チェーンに触れる前に以下を収集し、不足があれば確認してください。

  1. チェーンとアドレス(トークン、セール、プール、ブック、オーナー)。SPAでは、アドレスはページではなくJSバンドル内にあります。0x[a-fA-F0-9]{40}をgrepし、周辺の設定キーを確認してください。
  2. カーブのパラメーター:開始価格、終了価格、供給上限、予算上限、IDごとの上限、期間。
  3. 参加モデル:オープン、許可リスト、レジストリ/Agent-ID、ステーキングゲート。誰が参加を許可され、誰が判断するのか。
  4. 卒業:上限到達時と期限到来時に何が起きるか、誰が実行できるか、LPの移転先。
  5. ローンチ後に誰がトークンを発行し、誰が呼び出せるか。
  6. 検証済みソース(まずSourcify、次にEtherscan v2のchainid=56)と監査対象のコミット。

パラメーターがなくてもアドレスは監査できます。コンストラクター引数とバイトコードから読み取ってください。ドキュメントだけを根拠にしてはいけません。

エビデンスのルール

上から順に優先し、上位の方法が使えない場合にのみ一段下げてください。

  1. 固定したブロック時点のバイトコードとオンチェーン状態。
  2. 検証済みソース。exact_matchのみ。
  3. 正確な呼び出し順序を再現したフォークシミュレーション。
  4. コンストラクター引数とデプロイトランザクション。
  5. ドキュメント、サイト、Mediumの投稿。差分を取るべき主張として扱います。

厳守事項:

  • すべての主張をBNBとトークンの単位で価格評価してください。数値のない「安い」は監査結果ではありません。
  • コメントを読むだけで証明されたカーブの性質は、未証明として指摘してください。
  • セールをホームページで評価してはいけません。ページとバイトコードが食い違うなら、その差自体が監査結果です。
  • ABIを確認するまでは、関数がない場合は「欠落」ではなく存在しないと報告してください。スキャンでセレクターが見つからなくても、プロキシやライブラリ経由で呼び出せる場合があります。
  • ブロックを固定してください。ブロックが記録されていないスナップショットは噂にすぎません。

ツール

export RPC_URL=https://bsc-rpc.publicnode.com
./scripts/snapshot.sh roles.env          # lifecycle, supply, privilege, balances
python3 scripts/curve_math.py --p <wei> --supply <units> --check 1e18,100e18 [--budget <wei>]

scripts/rpc.pyは単発の呼び出しに同梱されています。snapshot.shはオーナーがEOAかコントラクトかも出力します。これが判定を左右する最初の要素です。curve_math.pyは参照積分で、単調性、丸めの方向、およびステップごとのどの順序でも閉形式より少ない支払いにならないことを証明します。どちらも汎用的なもので、roles.example.envが入力テンプレートです。

流れ

まずカーブをモデル化し、その後にゲートを確認します。4つの赤いゲートは必ずBLOCK、オーナーによる保管は典型的なHOLDです。各ゲートは以下のワークフローステップに対応します。

flowchart TD
    A[Intake & model curve<br/>pin block · owner type] --> G1{Gate 1 · exit<br/>buyers can exit / refund?}
    G1 -->|no| B1[BLOCK · buyer funds trapped]
    G1 -->|yes| G2{Gate 2 · curve<br/>cost = integral, cap safe?}
    G2 -->|no| B2[BLOCK · undercharge / cap blowable]
    G2 -->|yes| G3{Gate 3 · opening price<br/>not settable?}
    G3 -->|no| B3[BLOCK · price settable: donate+sync]
    G3 -->|yes| G4{Gate 4 · graduation<br/>anyone triggers, LP burned/locked?}
    G4 -->|no| B4[BLOCK · LP recoverable]
    G4 -->|yes| G5{Gate 5 · custody<br/>owner Safe, setters bounded?}
    G5 -->|no| H1[HOLD · owner EOA / loose setters]
    G5 -->|yes| D[Deep review<br/>coupling · admission · invariants]
    D --> S[Verdict<br/>SHIP · HOLD · BLOCK]
    S --> V[+ DECISION<br/>GO / CAUTION / BLOCK]

ワークフロー

0. コードを読む前に経済性をモデル化する

パラメーターから、実装に頼らずに想定されるカーブを数式で記述してください。price(x)、cost(a,b) = ∫price、上限、参加ルール、卒業トリガーを定義します。

次に、実装とモデルを照合してください。食い違いがあれば、別の設計として文書化されているのか、それとも監査結果なのかを判断し、その旨を記載してください。

1. デプロイを固定する
./scripts/snapshot.sh roles.env
python3 scripts/curve_math.py --p ... --supply ... --check ...

ブロック、所有者の種類、上限、売却済み数量、調達額、期限、現在の状態(tradingOpen、launched)を記録します。 単独で判定を上限にする項目は3つです。所有者がEOAであること、ローンチしないまま期限を過ぎていること、上限に達してもグラデュエーションしていないことです。

2. 価格曲線を評価する

全項目についてはreferences/economics.mdを参照してください。特に重要なのは次の4つです。

  • 請求不足。 コストの丸め下げや購入の分割によって、積分値より支払額が少なくなります。cost(n)をcost(n/2) * 2および閉形式と比較します。
  • オーバーフロー。 2^96に近い供給量に対し、512ビットのmulDivを使わず、除算より先に乗算しています。
  • 上限での購入許可。 上限以上の購入は、ルールに従ってリバートするか、数量を切り詰める必要があります。また、最後の1単位も予算内に収めなければならず、暗黙に超過してはなりません。
  • 予算の迂回。 IDごとの上限がウォレットごとにリセットされる、または送金後に記録されます。

cost()/quote()のゲッターがないセールは数多くあります。その場合、価格は支払い可能な購入関数の中で直接計算されています。状態差分(実行前後のraised、sold)で価格を評価するか、valueを指定した購入関数のフォーク上でのeth_callを使い、使用した方法を記録します。

3. 不変条件を実行する

references/invariants.mdには一般的な一覧があります。手順0を基に、プロジェクト固有の不変条件を記述します。フォーク上で、敵対的な呼び出し順序、資金を持つアクター、同一IDの繰り返しを用いて証明します。正常系でしか成り立たない性質は、不変条件とはいえません。

4. 攻撃者の視点で試す

references/attack-surface.mdでは各フェーズを順に説明しています。一般的な監査では見落とされる、ローンチ固有のポイントは次のとおりです。

  • 寄付してからsync()を呼び、開始時の価格を動かす、またはスキムを回避する。
  • 最初の1単位を購入し、その後、税スワップまたは手数料の換金をギャップに向けて発生させる。
  • セールが自身の残高を使って実行するスワップで、amountOutMin = 0を指定する。
  • エスクロー台帳における返金のリエントランシーと、片側しか減算されない返金会計。
  • 上限を先回りする。最終ブロックでのスナイピングにより、チームが継続を望んでいたセールを終了させる。
  • エントリー数に応じてガス使用量が増え、報酬を受け取れなくなる請求経路。
5. グラデュエーションとカストディ

上限到達時と期限到来時に、誰でもトリガーできる、定義済みの処理をコードで1つ実行し、LPをバーンアドレスまたは検証済みロッカーに送る必要があります。確認事項:ローンチが所有者、ポーズ状態、または外部レジストリーの読み取りを条件としていませんか?空のセールでもグラデュエーションしますか?LPは中間アドレスで保管されていませんか?同じトランザクション内でトークンの取引ロックは解除されますか?

6. 連携

セールはシステムの半分にすぎません。ローンチ後に別のコントラクトがミントする場合は、次を確認します。

  • ローンチ後のミントは予算に拘束されていますか、それともmaxSupplyだけに制限されていますか?
  • 予算は単調に引き継がれますか、それとも期間ごとにリセットされますか?
  • ミンター一覧をロックできますか?所有者は一覧から除外されていますか?
  • 上限に達したとき、請求経路は数量を切り詰めますか、それともすべてをリバートしますか?
  • セールの所有者は、その後にトークンまたは排出コントラクトを差し替えられますか?
7. 参加許可とID

許可リストやレジストリーによるゲートには、インサイダーへの割り当てが隠れていることがあります。IDの数、各IDへの割り当て量、公開時期、追加権限を持つ者を測定します。供給量1,000単位に対して500ウォレットの許可リストがある場合、インサイダーが買い占めています。tx.originによるゲートはスマートウォレットを使えなくします。プロキシまたはマルチシグの参加が想定されている場合を除き、バグではなく挙動として記録します。

8. 管理者とパラメーター

経路上にあるすべてのセッターを列挙し、値がrequireで制限されているかを確認します。制限のない項目:送信手数料、税率、ウォレットごとの上限、曲線の終点、ペアアドレス、手数料受取先。許容されるパラメーター:不変のトークン、一度だけ設定できるプール、ロック済みのミンター一覧。次にカストディを確認します。タイムロック付きSafeですか、それとも単一のEOAですか?稼働中のセールでEOAが所有者であることは、合格ではなく上限要因です。

9. 判定
判定条件
BLOCK保有者/購入者が資金を引き出せない、上限を超過できる、開始価格を設定できる、またはLPを回収できる
HOLD購入者は退出でき、カーブも健全だが、管理者が操作できる経路が残っている、または不変条件が証明されていない
SHIPカーブ、不変条件、卒業、カストディがすべて証明済みで、残る項目はInfoのみ

件数にかかわらず判定を引き下げる上限事項:ownerがEOAである(HOLD)、期限またはローンチがownerの承認を必要とする(HOLD)、供給量の半分を超える分がインサイダーに割り当てられている(HOLD)、ドキュメントで約束されている返金経路が欠けている(BLOCK)。

統合とゲートの判定

このスキルはローンチ前の判断レイヤーです。bsc-contract-audit および Binance のエージェント型ウォレットスキルと組み合わせて使用します。

  • アセットを引き継ぐ。 販売が卒業したら、ローンチ済みトークン/NFT(TOKEN/MINT トラック)と、デポジット返金用エスクロー(LOCK トラック)に対して bsc-contract-audit を実行します。このスキルが対象とするのは 販売メカニズム で、もう一方が対象とするのは アセットとロック です。
  • ウォレットをゲートする。 ローンチへの購入前に呼び出された場合は、ウォレットが読み取るものと同じ機械可読形式の行で締めくくります。
DECISION: GO | CAUTION | BLOCK — <one-line reason>

判定に応じてマッピングします。SHIP → GO、HOLD → CAUTION(未解決の上限事項をユーザーが了承した場合にのみ続行)、BLOCK → BLOCK(拒否)。この監査結果とウォレット独自の query-token-audit の結果が異なる場合は、より厳しい結果を採用します。

レポート形式

# Launch audit <SYMBOL> — curve launch, BSC 56 <ADDR>
Block <n> · source exact_match · owner EOA|Safe · cap <n> BNB · sold <n>/<cap>

Verdict: HOLD (caps: owner EOA, setAmmPair live)
DECISION: CAUTION — curve sound and buyer exits work, but owner is an EOA with a live setAmmPair

## Economics
price 0% <wei> -> 100% <wei> · integral vs stepped: <delta> · max purchase inside budget: yes/no

## Findings
| ID | Sev | Phase | Function | Trigger | Invariant |
|---|---|---|---|---|---|
| F-01 | High | curve | cost | step-split pays 0.4% less | INV-3 |

## Invariants
INV-1 ok · INV-3 FAIL · ...

## Unproven
## Recommendations
## Evidence log
E1 <command> -> <trimmed output>
## Snapshots
block, owner type, balances

各指摘には、attack-surface.md のフェーズ、関数、および違反する不変条件を記載します。 重大度:Critical=購入者が安全に資金を引き出せない、または価格を設定できる。High=上限または予算を超過できる、LPを回収できる。Medium=現実的なトリガーのもとでの価値の抽出、ローンチの活性。Low=誤った動作、不正確なコメント。Info=衛生面。

必ず Reference only, not investment advice. で締めくくります。

よくある誤解

  • 完売=健全。 最後の購入分がチーム自身のウォレットによるものか、カーブが実際に終点へ達したのか、それとも単に予算を使い切っただけなのかを確認します。
  • ownerの放棄=安全。 未ロックの発行コントラクトで、minterリストがロックされていても意味はありません。
  • 「税はわずか1%」。 何に対する割合で、いつ、誰が課すのか――_transfer を確認します。
  • 寄付では何も動かない。 開始価格は変動しますが、寄付者の損失は指摘事項ではありません。
  • getterがない=機能がない。 buyTax() がリバートする場合、通常はgetterの名前が異なるだけです。
  • テスト合格=カーブが健全。 フォークテストで証明されるのは、テストが検証する内容だけです。算術は自分で確認してください。
  • 卒業は自動に見える。 ローンチに onlyOwner が設定されている場合、販売が永久に開いたままになる可能性があります。
  • デプロイ後にスナップショットを再利用する。 もう一度読み込み、ブロックを記録します。

コメント