solidity-coding について
Solidityコントラクトを開発する人向けのコーディング規約スキルで、初期化、安全性、イベント、プロジェクト構成を扱います。コントラクトの条件に応じた初期化方式とOpenZeppelinコンポーネントの選択を促します。
主な機能
- 01Solidityのバージョンと記述規則
- 02初期化方式と安全ルール
- 03イベント設計と命名規則
- 04コントラクト構成と設定管理
- 05OpenZeppelinコンポーネントの選択
活用シーン
- 01Solidityのコーディング規約の策定
- 02コントラクトの初期化方式の選択
- 03イベント、エラー、アクセス制御の設計
- 04Foundryプロジェクトファイルの整理
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ 0xlayerghost/ solidity-agent-kit --skill solidity-codingインストール後、Claude Code で/solidity-codingを入力して読み込まれたか確認します。
同梱ファイルreferences/complex-function-refactoring.mdreferences/shared-contract-behaviors.md
詳細
Solidity コーディング標準
言語ルール
- 常にユーザーが使用している言語で応答してください。 ユーザーが中国語で質問した場合は中国語で、英語で質問した場合は英語で応答してください。
コーディング原則
- プラグマ:
pragma solidity ^0.8.19;を使用し、プロジェクト内のすべてのファイルで統一してください - 依存関係:OpenZeppelin Contracts 4.9.x を使用し、
remappings.txtでインポートを管理してください - エラー処理:
requireの文字列よりカスタムエラーを優先してください。ガスを節約でき、より明確に表現できます- 定義:
error InsufficientBalance(uint256 available, uint256 required); - 使用:
if (balance < amount) revert InsufficientBalance(balance, amount);
- 定義:
- ドキュメント:すべての
public/external関数に NatSpec(@notice、@param、@return)を記述してください - イベントのインデックス化:
indexedはaddress型のパラメーターにのみ追加してください。それ以外の型をインデックス化する場合はコメントを追加してください - 特殊キーワード:
immutable/constant/unchecked/assemblyには、使用理由を説明するインラインコメントを付けてください
初期化戦略
コントラクトで状態の初期化(外部アドレス、ビジネスパラメーター、ロールの割り当て)が必要な場合、単一の方式をデフォルトとして選ばないでください。次のワークフローに従ってください。
ワークフロー
- 評価する:コントラクトはアップグレード可能ですか? デプロイ時にすべての依存関係が判明していますか? コントラクト間のデプロイ順序に制約はありますか?
- 選択肢を提示する:以下の3つの初期化方式と、それぞれのトレードオフを開発者に示してください
- 開発者に選んでもらう:方式を決めつけないでください。プロジェクトやコントラクトによって必要な方式は異なります
- 実装する:選択した方式を、以下の安全ルールに従って適用してください
方式の比較
| 方式 A:コンストラクターのみ | 方式 B:イニシャライザーのみ | 方式 C:ハイブリッド | |
|---|---|---|---|
| アプローチ | すべての初期化をコンストラクターパラメーターで行う | 空の状態でデプロイし、後から initialize() を呼び出す | コンストラクターで判明済みのパラメーターを処理し、initialize() でデプロイ後の依存関係を処理する |
| アトミック性 | デプロイに失敗すると何も作成されず、危険な未初期化状態にならない | 「デプロイ済みだが未初期化」の期間が生じるリスクがある | 部分的にアトミック。コンストラクターの部分は安全だが、イニシャライザーの部分にはその期間が生じる |
| フロントランニングのリスク | なし。コンストラクターはフロントランニングできない | 保護されていない場合、initialize() がフロントランニングされる可能性がある | イニシャライザー部分にのみリスクがある |
| ガス | デプロイと初期化を1回のトランザクションで行う | デプロイのトランザクション+N回の初期化トランザクション | デプロイのトランザクション+1回の初期化トランザクション |
| 柔軟性 | 低い。デプロイ時にすべてのパラメーターが判明している必要がある | 高い。他のコントラクトのデプロイ後に依存関係を設定できる | 中程度。デプロイ時とデプロイ後で分ける |
| アップグレード可能性 | プロキシパターンと互換性がない | UUPS/Transparent Proxy で必須 | 部分的。コンストラクターでプロキシに安全なパラメーターを設定し、残りはイニシャライザーで設定する |
各方式を提案する場面
| シナリオ | 推奨方式 |
|---|---|
| アップグレード不可で、すべての依存関係がデプロイ時に判明している | A |
| アップグレード可能なコントラクト(UUPS/Transparent Proxy) | B |
| アップグレード不可だが、一部の依存関係が後からデプロイされる(例:トークンのデプロイ後にLPペアを作成する) | C |
| 相互参照のある複数コントラクトのシステム | C |
| シンプルなユーティリティ/ライブラリ形式のコントラクト | A |
| デプロイ後にマルチシグ/DAO による設定が必要なコントラクト | B または C |
安全ルール(方式にかかわらず適用)
- 一度限りのロック:一度しか呼び出してはならないセッターでは、
require(currentValue == address(0))またはbool initializedフラグを使って再初期化を防止します - アクセス制御のない初期化関数は禁止:
initialize()関数には、アクセス制御(onlyOwner、initializer修飾子、またはデプロイ者のみのチェック)を設ける必要があります - 不変値を優先:デプロイ時に値が確定している場合は、通常のストレージではなく
immutableを使用します。読み取りのたびに発生するガスを節約できます - コンストラクター内で複雑な外部呼び出しをしない:コンストラクターから、状態変更を伴う操作(swap、addLiquidity)のために外部コントラクトを呼び出してはなりません。コントラクトの構築がまだ完了していないためです
- デプロイスクリプトの責任:方式 C を使用する場合、デプロイスクリプトは同じトランザクションバッチ内、または直後に
initialize()を呼び出す必要があります。必要な呼び出し順序を文書化してください
イベント設計パターン
理由を注記するイベント
イベントが条件分岐の結果を表す場合(同じアクションが異なる理由で発生する場合)、オンチェーンデータだけで内容がわかるように string reason パラメーターを追加します:
// GOOD — on-chain data is self-explanatory
event WithdrawalFailed(address indexed user, uint256 amount, string reason);
emit WithdrawalFailed(user, amount, "insufficient balance");
emit WithdrawalFailed(user, amount, "cooldown not expired");
event ProposalRejected(uint256 indexed proposalId, string reason);
emit ProposalRejected(id, "quorum not reached");
emit ProposalRejected(id, "voting period ended");
// BAD — must read contract source to understand why
event WithdrawalFailed(address indexed user, uint256 amount);
適用する場面:
- 資金の行き先が分岐する場合:同じ資金が異なる条件に応じて異なるアドレスに送られる
- 操作が拒否/縮小される場合:ユーザーのリクエストが完全には満たされず、その理由がある
- 状態遷移:同じ状態変更が異なるトリガー条件によって起こる可能性がある
- 管理者の操作:理由を記録する場合(例:
"security incident"、"parameter tuning")
適用しない場面:
- イベントのトリガー経路が1つだけの場合(分岐がない)→ reason は不要です
- 高頻度イベント(各 swap/transfer)→ string は追加のガスを消費するため、代わりに
uint8 reasonCodeを使用します
Reason の実装方式の選択
| 場面 | 方式 | ガスコスト |
|---|---|---|
| 理由の種類が少なく固定されている(≤5種) | string reason のリテラル | 低(コンパイラーが短い文字列を最適化) |
| 理由の種類が多い、または動的である | uint8 reasonCode + フロントエンドのマッピングテーブル | 最低 |
| 動的なデータを含める必要がある | string reason の連結 | 高め。使用は慎重に |
// 方式 A:string literal(推荐,可读性最好)
emit WithdrawalFailed(user, amount, "cooldown not expired");
// 方式 B:uint8 code(高频场景省 gas)
event SwapRejected(address indexed user, uint256 amount, uint8 reasonCode);
// 0 = slippage, 1 = insufficient liquidity, 2 = paused
emit SwapRejected(user, amount, 0);
イベント設計の一般ルール
- 状態を変更する
external/public関数は、それぞれ少なくとも1つのイベントを emit しなければなりません - イベントパラメーターには、フロントエンド/indexer が追加の RPC 呼び出しなしで完全なコンテキストを再構築できるだけの情報を含める必要があります
- NatSpec の
@paramには、reason パラメーターで使用可能な値をすべて列挙する必要があります
命名規則
| 要素 | 規則 | 例 |
|---|---|---|
| コントラクト/ライブラリ | PascalCase | MyToken、StakingPool |
| インターフェース | I + PascalCase | IMyToken、IStakingPool |
| 状態変数/関数 | lowerCamelCase | totalSupply、claimDividend |
| 定数/不変変数 | UPPER_SNAKE_CASE | MAX_SUPPLY、ROUTER_ADDRESS |
| イベント | PascalCase(過去形) | TokenTransferred、PoolCreated |
| カスタムエラー | PascalCase | InsufficientBalance、Unauthorized |
| 関数パラメーター | セッターでは接頭辞 _ を使用 | function setFee(uint256 _fee) |
- 禁止事項:ピンイン名、単一文字の変数名(ループ内の
i/j/kを除く)、過度な略語 - 戻り値の命名:戻り値の変数名が状態変数名と競合する場合、末尾に
_を付けるのではなく、意味を表す接頭辞(is/can/has/current/total)を使用します。フロントエンド利用者向けの ABI をすっきり保てます
コード構成ルール
| 状況 | ルール |
|---|---|
| コントラクト間で共有する定数 | src/common/Const.sol に配置します |
| インターフェース定義 | 実装とは分けて src/interfaces/I<Name>.sol に配置します |
| 共有する状態を持つ振る舞い | src/abstract/<Name>.sol に配置し、実装をコピーするのではなく継承します |
| 単純なオンチェーン照会 | Foundry cast CLI(call/send)を使用します |
| 複雑な複数ステップの操作 | Foundry スクリプト(*.s.sol)を使用します |
| インポート形式 | 名前付きインポートを使用します:import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; |
共有管理機能
複数のコントラクトで同じ管理操作が必要な場合は、関数名、パラメーターの順序、イベント、エラー、NatSpec、実装を正規のものに統一してください。新しいバリエーションを追加する前に、リポジトリを検索してください。
状態を伴う動作やモディファイアには共有の抽象コントラクトを、外部の利用者に安定した ABI が必要な場合はインターフェースを、状態を伴わないロジックにはライブラリを使用してください。業務コントラクトは、共有実装をコピーせず、必ず継承してください。
プロジェクトで正規とされる、オーナーが制御する資産回収関数と継承ルールについては、references/shared-contract-behaviors.md を参照してください。プロジェクトのポリシーでは、意図的にオーナーを最上位の権限として扱い、コントラクトが保有するすべての ERC20 トークンとネイティブ通貨の回収を許可しています。
複雑な関数のリファクタリング
ある関数が複数の業務段階を調整する場合、検証と複数の状態変更または外部呼び出しが混在する場合、あるいはコンパイラーのスタック上限に近づいている場合は、変更する前に references/complex-function-refactoring.md を参照してください。
関数の数を増やすだけの目的で、小さな関数を分割しないでください。ユーザーが動作の変更を明示的に求めていない限り、外部 ABI、実行順序、リバート動作、状態遷移の順序、発行されるイベント、資金フローの不変条件を維持してください。
プロジェクトのディレクトリ構成
src/ — Contract source code
interfaces/ — Interface definitions (I*.sol)
abstract/ — Shared stateful base contracts (AssetRescue.sol, etc.)
common/ — Shared constants, types, errors (Const.sol, Types.sol)
test/ — Test files (*.t.sol)
script/ — Deployment & interaction scripts (*.s.sol)
config/ — Network config, parameters (*.json)
deployments/ — Deployment records (latest.env)
docs/ — Documentation, changelogs
lib/ — Dependencies (managed by Foundry)
設定管理
config/*.json— ネットワーク RPC URL、コントラクトアドレス、業務パラメーターdeployments/latest.env— 最新のデプロイ済みコントラクトアドレス。デプロイのたびに更新する必要がありますfoundry.toml— コンパイラーバージョン、オプティマイザー設定、リマッピング- 重要な設定変更は PR の説明に記載する必要があります
OpenZeppelin ライブラリの選定基準
Solidity コントラクトを記述する際は、独自実装よりも実績のある OpenZeppelin ライブラリを優先して使用してください。状況に応じて、適切なライブラリを選択してください。
アクセス制御
| 状況 | ライブラリ | インポートパス |
|---|---|---|
| 単一オーナーによる管理 | Ownable | @openzeppelin/contracts/access/Ownable.sol |
| オーナーの移転に安全性が必要 | Ownable2Step | @openzeppelin/contracts/access/Ownable2Step.sol |
| 複数のロールによる権限管理(admin/operator/minter) | AccessControl | @openzeppelin/contracts/access/AccessControl.sol |
| ロールのメンバーを列挙する必要がある | AccessControlEnumerable | @openzeppelin/contracts/access/AccessControlEnumerable.sol |
| タイムロック遅延を伴うガバナンス | TimelockController | @openzeppelin/contracts/governance/TimelockController.sol |
オーナーシップモデルの選択
単一オーナーのコントラクトでは、開発者はコード生成の前にオーナーシップモデルを選択する必要があります。
| 選択肢 | 動作 | 使用する状況 |
|---|---|---|
Ownable | 1 回のトランザクションで所有権の移転が完了します | シンプルさと即時の移転が明示的に優先される場合 |
Ownable2Step | 新しいオーナーが acceptOwnership() を呼び出す必要があります | 誤ったアドレスへの所有権移転を防ぐことが優先される場合 |
Ownable2Step を暗黙に強制しないでください。プロジェクトで選択が記録されていない場合は、両方の選択肢を提示して開発者に選んでもらってください。選択した後は、共有ベースコントラクト、デプロイスクリプト、テスト、所有権移転コマンドを含め、プロジェクト全体で一貫してそのモデルを使用してください。最初から選択した継承とインポートを生成してください。Ownable2Step を生成し、開発者に手動で置き換えさせないでください。
ルール:ロールが 1 つの場合 → 開発者が Ownable または Ownable2Step を選択。ロールが 2 つ以上の場合 → AccessControl。ガバナンス/DAO → TimelockController
セキュリティ保護
| シナリオ | ライブラリ | 使用方法 |
|---|---|---|
| 外部呼び出し/トークンの送金 | ReentrancyGuard | nonReentrant 修飾子を追加します |
| 緊急時に一時停止が必要 | Pausable | ユーザー向け関数に whenNotPaused を追加し、管理者関数は一時停止の対象外にします |
| ERC20 トークンとのやり取り | SafeERC20 | 生の呼び出しではなく、safeTransfer / safeTransferFrom / safeApprove を使用します |
ルール:トークンまたは ETH を送金するコントラクトでは、必ず ReentrancyGuard と SafeERC20 を使用してください。
トークン規格
| シナリオ | ライブラリ | 備考 |
|---|---|---|
| 代替可能トークン | ERC20 | 基本規格 |
| バーン機能付きトークン | ERC20Burnable | burn() と burnFrom() を追加します |
| 最大供給量の上限付きトークン | ERC20Capped | totalSupply <= cap を強制します |
| ガス代不要の承認(EIP-2612) | ERC20Permit | ユーザーの approve トランザクションのガス代を節約します |
| ガバナンス投票トークン | ERC20Votes | スナップショットベースの投票権 |
| NFT | ERC721 | 基本的な NFT 規格 |
| 列挙機能付き NFT | ERC721Enumerable | tokenOfOwnerByIndex による照会をサポートします |
| マルチトークン(FT と NFT の混在) | ERC1155 | ゲームアイテム、一括操作 |
ユーティリティライブラリ
| シナリオ | ライブラリ | 使用方法 |
|---|---|---|
| ホワイトリスト/エアドロップの検証 | MerkleProof | ガス効率の高いマークルツリー検証 |
| 署名の検証 | ECDSA + EIP712 | オフチェーンで署名し、オンチェーンで検証します |
| 自動インクリメント ID | Counters | トークン ID、注文 ID の生成 |
| 関数呼び出しの一括実行 | Multicall | 1 つのトランザクションで複数の操作を実行します |
| アドレスセット/uint セット | EnumerableSet | O(1) で追加/削除/存在確認ができる反復可能なセット |
| 収益分配 | PaymentSplitter | ETH/トークンの支払いを持分に応じて分割します |
| 標準化された利回りボールト | ERC4626 | DeFi ボールト規格 |
コントラクトのアップグレード
| シナリオ | ライブラリ | 備考 |
|---|---|---|
| アップグレード可能なコントラクト(ガス効率重視) | UUPSUpgradeable | 実装コントラクトにアップグレードロジックを配置します |
| アップグレード可能なコントラクト(管理者を分離) | TransparentUpgradeableProxy | プロキシにアップグレードロジックを配置し、ガス代は高くなります |
| イニシャライザー(コンストラクターの代替) | Initializable | コンストラクターの代わりに initializer 修飾子を使用します |
ルール:新しいプロジェクトでは UUPSUpgradeable を優先し、アップグレード可能なコントラクトでは必ず Initializable を使用してください。
オラクルとオフチェーンサービス
| シナリオ | ライブラリ | 備考 |
|---|---|---|
| トークン価格データ | AggregatorV3Interface | オラクルのデータフィードがサポートされているトークンにのみ使用します |
| 検証可能な乱数(宝くじ/NFT) | VRFConsumerBaseV2 | オンチェーンで公平性を証明できる乱数 |
| 自動実行(cron ジョブ) | AutomationCompatible | 中央集権型のキーパーを置き換えます |
| クロスチェーンメッセージング | CCIP | クロスチェーンでのトークン/メッセージ転送 |
ライブラリ選択の判断フロー
Does contract handle user funds/tokens?
├── YES → Add ReentrancyGuard + SafeERC20
│ Does it need emergency stop?
│ ├── YES → Add Pausable
│ └── NO → Skip
└── NO → Skip
How many admin roles needed?
├── 1 role → Ask developer to choose Ownable or Ownable2Step
├── 2+ roles → AccessControl
└── DAO/governance → TimelockController
Does contract need price data?
├── Token has oracle feed → AggregatorV3Interface
├── No oracle feed → Custom TWAP with min-liquidity check
└── No price needed → Skip
Will contract need upgrades?
├── YES → UUPSUpgradeable + Initializable
└── NO → Standard deployment (immutable)
アンチパターン(禁止事項)
- 禁止:独自の
transferラッパーを書かないでください。SafeERC20を使用してください。 - 禁止:独自のアクセス制御修飾子を書かないでください。
Ownable/AccessControlを使用してください。 - 禁止:独自の一時停止ロジックを書かないでください。
Pausableを使用してください。 - 禁止:Solidity >= 0.8.0 で
SafeMathを使用しないでください。オーバーフローチェックは組み込みです。 - 禁止:
require(token.transfer(...))を使用しないでください。SafeERC20を介してtoken.safeTransfer(...)を使用してください。 - 禁止:認証に
tx.originを使用しないでください。Ownable/AccessControlとともにmsg.senderを使用してください。
MCP を利用したコントラクト生成(利用可能な場合)
OpenZeppelinContracts MCP が設定されている場合は、ゼロから作成するのではなく、ベースコントラクトの生成に使用してください。
| コントラクトの種類 | MCP ツール | 使用するタイミング |
|---|---|---|
| 代替可能トークン | solidity-erc20 | 新しい ERC20 トークンコントラクトを作成する場合 |
| NFT | solidity-erc721 | 新しい NFT コントラクトを作成する場合 |
| マルチトークン | solidity-erc1155 | ゲームアイテム、一括操作 |
| ステーブルコイン | solidity-stablecoin | ERC20 に準拠したステーブルコイン |
| 実世界資産 | solidity-rwa | 資産のトークン化 |
| スマートアカウント | solidity-account | ERC-4337 のアカウント抽象化 |
| ガバナンス | solidity-governor | DAO の投票と提案 |
| カスタム | solidity-custom | OZ パターンを使った標準外のコントラクト |
ワークフロー:MCP でベースを生成 → このスキルの命名規則と構造ルールを適用 → ビジネスロジックをカスタマイズ → /solidity-security のルールを適用
手動作成より MCP を使う理由:MCP の出力は OZ Contracts Wizard と同じルールセットに照らして検証されます。インポート、修飾子、セキュリティチェックが確実に正しく設定されます。手動でコーディングすると、インポートを忘れたり、誤った OZ バージョンを使用したりするリスクがあります。
MCP を使用しない場合:標準外のパターンを使う高度にカスタマイズされたコントラクト、OZ のテンプレートに当てはまらないコントラクト、または最初の行から細かく制御する必要がある場合です。
フォールバック:MCP が設定されていない場合は、上記の Library Selection Standards に従い、このスキルのすべてのルールに沿って手動でコントラクトを作成してください。
Foundry クイックリファレンス
| 操作 | コマンド |
|---|---|
| 新しいプロジェクトを作成 | forge init <project-name> |
| 依存関係をインストール | forge install openzeppelin-contracts |
| コントラクトをビルド | forge build |
| コードをフォーマット | forge fmt |
| remappings を更新 | forge remappings |