はじめに

初めまして。 『DApps開発入門』という本や色々記事を書いているかるでねです。

以下でも情報発信しているので、興味ある記事があればぜひ読んでみてください!

今回は「SolidityスマートコントラクトをOpenAPI/Swagger形式で仕様書化する方法」についてまとめていきます。

Solidityスマートコントラクトの開発において、ABI(Application Binary Interface)だけでは関数の目的やエラーの発生条件、イベントの意味を十分に伝えることが難しい場面があります。 特に、チーム開発やクライアントへの説明、フロントエンド開発者との連携において、より詳細で構造化されたドキュメントが求められます。

この記事では、SolidityスマートコントラクトをOpenAPI 3.0(YAML)形式で仕様書化する方法を提案しています。

OpenAPI/Swagger形式で仕様書化するメリット

OpenAPI/Swagger形式で仕様書化することで、以下のメリットがあります。

  • 標準化されたフォーマット APIドキュメントの業界標準であるOpenAPI/Swagger形式を使用することで、多くの開発者にとって馴染みのある形式で仕様を共有できます。
  • インタラクティブなドキュメント Swagger UI Reactコンポーネントを使って、視覚的で操作可能なドキュメントサイトを構築可能です。
  • 型情報の明確化 Solidityの型(address、uint256、bytes32など)をパターン付きで表現し、バリデーションルールを明示できます。
  • エラーハンドリングの可視化 カスタムエラーやrevertの条件を構造化して記述できます。
  • イベント・構造体の文書化 コントラクトの全要素を網羅的にドキュメント化可能です。

設計方針

目的と制約

この仕様書はHTTP API設計ではなく、Solidityコントラクトのインターフェイス定義をOpenAPI/Swagger形式で記述するためのものです。 HTTPのベストプラクティスに合わせるのではなく、Solidityの挙動を正確かつコンパクトに表現することを最優先にしています。

実際にHTTPで呼び出すことを前提としていません。 Swagger/OpenAPIの制約上、便宜的にパスとHTTPメソッドを割り当てています。

基本ルール

項目 ルール
成功レスポンス 常にHTTP 200を使用
エラーレスポンス 常にHTTP 500を使用(4xx系や他の5xx系は使わない)
説明文 すべて日本語で記述
識別子の表記 変数名、関数名、エラー名などはバッククォート(`)で囲む
extensionフィールド x- で始まるフィールドは使用しない

パス設計ルール

パスには関数名やイベント名をそのまま使用し、分かりやすい構造を維持します。

要素 パス形式 HTTPメソッド
view/pure関数 /{functionName} GET
nonpayable/payable関数 /{functionName} POST
変数/定数/Mapping /{variableName} GET
イベント /events/{EventName} GET
エラー /errors/{ErrorName} GET
構造体 /structs/{StructName} GET
Modifier /modifiers/{ModifierName} GET

パラメータの配置ルール

パラメータは常に query または path で定義し、requestBody は使用しません。

yaml
# 正しい例
parameters:
  - name: owner
    in: query
    required: true

# 間違った例(requestBodyは使用しない)
requestBody:
  content:
    application/json:
      schema:
        properties:
          owner:
            type: string

継承関係の表現

コントラクトの継承関係は info.description に記載します。 継承しているコントラクト名を箇条書きで列挙します。

yaml
info:
  title: "MyToken Solidity Interface"
  version: "1.0.0"
  description: |
    MyToken コントラクトの Solidity インターフェイスを OpenAPI 形式で表現した仕様です。
    このコントラクトは以下のコントラクトを継承しています。

    ・ERC20
    ・Ownable
    ・Pausable

継承元コントラクトの関数やイベント、エラーも仕様書に含めることで、コントラクトの全体像を把握できるようにします。

コントラクト仕様書の構造

スマートコントラクトをOpenAPI/Swagger形式で表現する際の基本構造を説明します。

全体構成

yaml
openapi: 3.0.3
info:
  title: "コントラクト名 Solidity Interface"
  version: "1.0.0"
  description: |
    コントラクトの説明。
    継承関係やネットワーク情報などをここに記載。

tags:
  - name: Read Functions
    description: Functions that retrieve information from the contract without modifying state.
  - name: Write Functions
    description: Functions that modify state.
  - name: Variables
  - name: Constants
  - name: Mapping
  - name: Events
  - name: Errors
  - name: Structs
  - name: Modifier

x-tagGroups:
  - name: Functions
    tags:
      - Read Functions
      - Write Functions
  - name: Storage
    tags:
      - Variables
      - Constants
      - Mapping
  - name: Others
    tags:
      - Events
      - Errors
      - Structs
      - Modifier

paths:
  # 関数、イベント、エラー、構造体のパス定義

components:
  schemas:
    # ErrorResponse、イベントスキーマ、構造体スキーマ

タグによるカテゴリ分類

仕様書では以下の9つのタグでコントラクトの要素を分類します。

タグ 説明
Read Functions view/pure関数。ステートを変更せず情報を取得
Write Functions nonpayable/payable関数。ステートを変更
Variables publicなstate variable
Constants constant/immutableな値
Mapping publicなmapping
Events コントラクトで定義されたevent
Errors カスタムエラー
Structs struct定義
Modifier modifier定義

タグ名は英語で統一されています。 Read Functions と Write Functions のみ description が設定され、その他のタグは名前のみです。

各要素の表現方法

スマートコントラクトには、関数、イベント、エラー、変数など、さまざまな要素が含まれています。 これらをOpenAPI/Swagger形式で正確に表現することで、開発者がコントラクトの仕様を理解しやすくなり、フロントエンドやバックエンドとの連携もスムーズになります。

このセクションでは、Solidityの主要な要素それぞれについて、なぜその表現方法を採用するのか、どのように記述するのかを具体例とともに解説していきます。

表現する要素一覧

  • 関数(読み取り関数・書き込み関数)
  • イベント
  • エラー
  • 変数・定数
  • Mapping
  • 構造体
  • Modifier
  • Solidity型のマッピング

各要素には、その性質に合ったHTTPメソッド(GET、POST)とパス構造を割り当てます。 例えば、ステートを変更しない読み取り関数は GET メソッドで、ステートを変更する書き込み関数は POST メソッドで表現します。 これは、HTTPの設計原則(RESTful API)に従った設計であり、OpenAPI/Swaggerを使い慣れたWeb開発者にとっても理解しやすい形式です。

関数の表現

関数はスマートコントラクトの中核となる要素です。 Solidityの関数には、ステートを変更しない読み取り専用関数(view/pure)と、ステートを変更する書き込み関数(nonpayable/payable)があります。 OpenAPI/Swagger形式では、これらをHTTPメソッドで区別して表現します。

読み取り関数

読み取り関数は、ブロックチェーンのステートを読み取るだけで変更を加えない関数です。 view 修飾子が付いた関数はステート変数の読み取りが可能で、pure 修飾子が付いた関数はステート変数にすらアクセスせず、引数のみから結果を計算します。

これらの関数は、HTTP GET メソッドで表現します。 なぜなら、GETメソッドはデータの取得のみを目的とし、サーバー(この場合はブロックチェーン)のステートを変更しないという性質が、読み取り関数の特性と一致するためです。

引数は parameters フィールドで定義し、in: query または in: path で指定します。

  • 基本的な例(引数なし)
yaml
/totalSupply:
  get:
    summary: "totalSupply"
    description: |
      トークンの総供給量を返します。

      この関数はERC20標準で定義されており、現在発行されているトークンの総量を確認できます。
      ガス代は発生しません。
    operationId: totalSupply
    tags:
      - Read Functions
    responses:
      "200":
        description: "正常終了時のレスポンスです。"
        content:
          application/json:
            schema:
              type: object
              properties:
                result:
                  type: string
                  description: "トークンの総供給量(wei単位)。"
                  example: "1000000000000000000000000"
  • 引数ありの例
yaml
/balanceOf:
  get:
    summary: "balanceOf"
    description: |
      指定されたアカウントのトークン残高を取得します。

      ERC20標準関数です。この関数を呼び出すことで、任意のアドレスが保有しているトークンの量を確認できます。
      ブロックチェーンのステートを変更しないため、ガス代は発生しません。
    operationId: balanceOf
    tags:
      - Read Functions
    parameters:
      - name: owner
        in: query
        required: true
        schema:
          type: string
          pattern: "^0x[0-9a-fA-F]{40}$"
        description: "所有者のアドレスを指定します。40桁の16進数文字列である必要があります。"
        example: "0x1234567890123456789012345678901234567890"
    responses:
      "200":
        description: "正常終了時のレスポンスです。"
        content:
          application/json:
            schema:
              type: object
              properties:
                result:
                  type: string
                  description: "指定されたアドレスのトークン残高(wei単位)を返します。"
                  example: "1000000000000000000"
  • 複数の引数を持つ例
yaml
/allowance:
  get:
    summary: "allowance"
    description: |
      `spender` が `owner` から使用を許可されているトークン量を取得します。

      ERC20標準関数です。この関数により、あるアドレス(`owner`)が別のアドレス(`spender`)に対して、
      どれだけのトークンを使用することを許可しているかを確認できます。
      `approve` 関数で設定された許可量が返されます。
    operationId: allowance
    tags:
      - Read Functions
    parameters:
      - name: owner
        in: query
        required: true
        schema:
          type: string
          pattern: "^0x[0-9a-fA-F]{40}$"
        description: "トークンを所有しているアドレスを指定します。"
        example: "0x1234567890123456789012345678901234567890"
      - name: spender
        in: query
        required: true
        schema:
          type: string
          pattern: "^0x[0-9a-fA-F]{40}$"
        description: "トークンの使用を許可されたアドレスを指定します。"
        example: "0x0987654321098765432109876543210987654321"
    responses:
      "200":
        description: "正常終了時のレスポンスです。"
        content:
          application/json:
            schema:
              type: object
              properties:
                result:
                  type: string
                  description: "`spender` が使用できる残りのトークン量(wei単位)を返します。"
                  example: "500000000000000000"

各フィールドの意味は以下です。

フィールド 説明 注意点
summary 関数名を指定。Swagger UIのサイドバーに表示される 冗長な表現(「を取得」など)は避け、関数名のみを記載
description 関数の詳細な説明。マルチライン文字列(|)で複数行記述可能 関数の目的、使用場面、注意事項を記載。識別子は必ずバッククォートで囲む
operationId 一意の識別子。通常は関数名をそのまま使用 他の操作と重複しないように注意
tags 関数の分類。Swagger UIでグループ化される 読み取り関数は Read Functions タグを使用
parameters 関数の引数。in: query でクエリパラメータとして表現 各引数に適切な型とバリデーションルールを設定
responses 戻り値とエラー。成功時は 200、エラー時は 500 読み取り関数は通常200レスポンスのみ
  • 型の表現方法
Solidity型 OpenAPI表現 理由
address type: string, pattern: "^0x[0-9a-fA-F]{40}$" 40桁の16進数文字列であることをバリデーション
uint256 type: string JavaScriptの安全な整数範囲(2^53-1)を超える可能性があるため
uint8 type: integer, minimum: 0, maximum: 255 安全な範囲内なので数値型として表現可能
bool type: boolean 真偽値はそのまま表現
string type: string 文字列型はそのまま表現

書き込み関数

書き込み関数は、ブロックチェーンのステートを変更する関数です。 nonpayable 修飾子が付いた関数はEtherを受け取らず、payable 修飾子が付いた関数はEtherを受け取ることができます。

これらの関数は、HTTP POST メソッドで表現します。 POSTメソッドはサーバーのステートを変更する操作を表すため、書き込み関数の特性と一致します。

書き込み関数では、エラーハンドリングが重要になります。 500レスポンスで発生しうるエラーを明示的に記載します。

  • 基本的な例
yaml
/transfer:
  post:
    summary: "transfer"
    description: |
      指定されたアドレスにトークンを転送します。

      ERC20標準関数です。この関数を呼び出すと、呼び出し元のアカウントから `to` で指定したアドレスに、
      `amount` で指定した量のトークンが転送されます。

      トランザクションが成功すると、`Transfer` イベントが発行されます。
      残高が不足している場合や、送金先が無効なアドレスの場合はエラーが発生します。
    operationId: transfer
    tags:
      - Write Functions
    parameters:
      - name: to
        in: query
        required: true
        schema:
          type: string
          pattern: "^0x[0-9a-fA-F]{40}$"
        description: "送金先のアドレスを指定します。ゼロアドレス(`0x0000000000000000000000000000000000000000`)は指定できません。"
        example: "0x1234567890123456789012345678901234567890"
      - name: amount
        in: query
        required: true
        schema:
          type: string
        description: "トークン量を指定します(wei単位)。呼び出し元の残高以下である必要があります。"
        example: "1000000000000000000"
    responses:
      "200":
        description: "正常終了時のレスポンスです。"
        content:
          application/json:
            schema:
              type: object
              properties:
                result:
                  type: boolean
                  description: "送金が成功した場合 `true` を返します。"
                  example: true
      "500":
        description: "この関数で発生しうるエラーの一覧です。\n\n・InsufficientBalance\n\n・InvalidAddress"
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ErrorResponse'
            examples:
              InsufficientBalance:
                summary: "残高不足"
                value:
                  message: "InsufficientBalance"
                  data: {}
              InvalidAddress:
                summary: "無効なアドレス"
                value:
                  message: "InvalidAddress"
                  data: {}
  • 複雑な例(複数のエラーパターンを持つ関数)
yaml
/transferFrom:
  post:
    summary: "transferFrom"
    description: |
      第三者が所有者の代わりにトークンを転送します。

      ERC20標準関数です。この関数は、`approve` 関数で事前に許可された量の範囲内で、
      `from` アドレスから `to` アドレスにトークンを転送します。

      呼び出し元は、`from` アドレスから十分な許可量(allowance)を得ている必要があります。
      転送が成功すると、`Transfer` イベントが発行され、許可量が減少します。
    operationId: transferFrom
    tags:
      - Write Functions
    parameters:
      - name: from
        in: query
        required: true
        schema:
          type: string
          pattern: "^0x[0-9a-fA-F]{40}$"
        description: "トークンの転送元アドレスを指定します。このアドレスから `amount` 分のトークンが引き出されます。"
        example: "0x1234567890123456789012345678901234567890"
      - name: to
        in: query
        required: true
        schema:
          type: string
          pattern: "^0x[0-9a-fA-F]{40}$"
        description: "トークンの転送先アドレスを指定します。ゼロアドレスは指定できません。"
        example: "0x0987654321098765432109876543210987654321"
      - name: amount
        in: query
        required: true
        schema:
          type: string
        description: "転送するトークン量を指定します(wei単位)。呼び出し元の許可量以下である必要があります。"
        example: "1000000000000000000"
    responses:
      "200":
        description: "正常終了時のレスポンスです。"
        content:
          application/json:
            schema:
              type: object
              properties:
                result:
                  type: boolean
                  description: "転送が成功した場合 `true` を返します。"
                  example: true
      "500":
        description: "この関数で発生しうるエラーの一覧です。\n\n・InsufficientBalance\n\n・InsufficientAllowance\n\n・InvalidSender\n\n・InvalidReceiver"
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ErrorResponse'
            examples:
              InsufficientBalance:
                summary: "残高不足"
                value:
                  message: "InsufficientBalance"
                  data:
                    account: "0x1234567890123456789012345678901234567890"
                    balance: "500000000000000000"
                    required: "1000000000000000000"
              InsufficientAllowance:
                summary: "許可量不足"
                value:
                  message: "InsufficientAllowance"
                  data:
                    spender: "0xabcdefabcdefabcdefabcdefabcdefabcdefabcd"
                    allowance: "500000000000000000"
                    required: "1000000000000000000"
              InvalidSender:
                summary: "無効な送信者アドレス"
                value:
                  message: "InvalidSender"
                  data:
                    sender: "0x0000000000000000000000000000000000000000"
              InvalidReceiver:
                summary: "無効な受信者アドレス"
                value:
                  message: "InvalidReceiver"
                  data:
                    receiver: "0x0000000000000000000000000000000000000000"

書き込み関数特有のフィールドは以下です。

フィールド 説明 注意点
responses["500"] エラーレスポンス。description にエラー一覧を箇条書きで記載 各エラーの間には \n\n で空行を入れる
examples 各エラーの具体例。エラー名をキーに summary と value を指定 エラーに引数がある場合は data フィールドに含める
$ref 共通スキーマの参照。#/components/schemas/ErrorResponse で形式を統一 すべてのエラーで同じスキーマを使用

エラーはすべてHTTP 500で表現し、200と500以外のステータスコードは使用しません。 これは、Solidityの revert やカスタムエラーを統一的に表現するための設計方針です。 HTTPの4xx系エラー(クライアントエラー)と5xx系エラー(サーバーエラー)の区別は、スマートコントラクトの文脈では意味を持たないため、500に統一しています。

エラーレスポンスの詳細

エラーレスポンスは、書き込み関数において特に重要な要素です。 Solidityでは、revert、require、カスタムエラーなど複数のエラーハンドリング方法があり、これらを統一的に表現する必要があります。

ErrorResponseスキーマ

すべてのエラーは共通の ErrorResponse スキーマを使用します。 このスキーマは、エラーの種類に関わらず一貫した形式でエラー情報を提供するために設計されています。

yaml
components:
  schemas:
    ErrorResponse:
      type: object
      description: "コントラクト呼び出し時のエラー情報を表現する共通スキーマです。"
      properties:
        message:
          type: string
          description: "カスタムエラー名またはrevert/requireのメッセージ文字列。"
        data:
          type: object
          description: "追加情報(任意)。カスタムエラーが引数を持つ場合、その値をここに格納します。"
  • スキーマの構成要素
フィールド 型 説明 例
message string エラー名またはメッセージ "InsufficientBalance", "Amount must be greater than zero"
data object エラーの詳細情報(オプション) {"balance": "100", "required": "1000"}

message フィールドには、Solidityのカスタムエラー名(例: InsufficientBalance)または、revert()/require() で指定した文字列メッセージが入ります。

data フィールドには、カスタムエラーが引数を持つ場合、その引数の値を格納します。 例えば、以下のようなSolidityエラーの場合には、

solidity
error InsufficientBalance(address account, uint256 balance, uint256 required);

data フィールドは以下のようになります:

json
{
  "account": "0x1234567890123456789012345678901234567890",
  "balance": "100",
  "required": "1000"
}

500レスポンスのdescription書き方

500レスポンスの description には、その関数で発生する可能性のあるすべてのエラーを箇条書きで記載します。 これにより、関数を呼び出す際に想定すべきエラーパターンを一目で把握できます。

  • 基本的な書き方
yaml
"500":
  description: "この関数で発生しうるエラーの一覧です。\n\n・InsufficientBalance\n\n・InvalidAddress\n\n・AmountZero"
  • 複数のエラーがある場合の例
yaml
"500":
  description: |
    この関数で発生しうるエラーの一覧です。

    ・InsufficientBalance
    ・InvalidAddress
    ・AmountZero
    ・Unauthorized
    ・ContractPaused

examplesの書き方

各エラーの具体例は examples フィールドで定義します。 examples を使うことで、Swagger UIでエラーごとの詳細な情報を確認できるようになります。

  • 基本的な書き方
yaml
examples:
  InsufficientBalance:
    summary: "残高不足"
    value:
      message: "InsufficientBalance"
      data: {}
  InvalidAddress:
    summary: "無効なアドレス"
    value:
      message: "InvalidAddress"
      data:
        address: "0x0000000000000000000000000000000000000000"
  • フィールドの説明
フィールド 説明 注意点
キー(例: InsufficientBalance) エラー名。Swagger UIのドロップダウンに表示される Solidityのエラー名と一致させる
summary エラーの簡潔な日本語説明 ユーザーフレンドリーな表現を使う
value.message エラー名またはメッセージ文字列 Solidityのエラー名をそのまま記載
value.data エラーの詳細情報 カスタムエラーが引数を持つ場合のみ記載
  • 引数を持たないエラーの例
yaml
examples:
  Unauthorized:
    summary: "権限がありません"
    value:
      message: "Unauthorized"
      data: {}
  • 引数を持つエラーの例
yaml
examples:
  InsufficientBalance:
    summary: "残高不足"
    value:
      message: "InsufficientBalance"
      data:
        account: "0x1234567890123456789012345678901234567890"
        balance: "500000000000000000"
        required: "1000000000000000000"
  InsufficientAllowance:
    summary: "許可量不足"
    value:
      message: "InsufficientAllowance"
      data:
        spender: "0xabcdefabcdefabcdefabcdefabcdefabcdefabcd"
        allowance: "100000000000000000"
        required: "500000000000000000"
  • require文のエラーの例

Solidityの require() や revert() を使ったエラーの場合、message にはエラーメッセージ文字列が入ります。

yaml
examples:
  RequireError:
    summary: "require文によるエラー"
    value:
      message: "Amount must be greater than zero"
      data: {}

エラーに引数がある場合は、必ず data フィールドにその値を含めます。 これにより、エラーが発生した際の具体的な状況を把握しやすくなります。

イベントの表現

イベント(Event)は、スマートコントラクトのステート変化を外部に通知するための重要な仕組みです。 トランザクションのログに記録され、フロントエンドアプリケーションやインデックスサービスがこれを監視してリアルタイムにステート変化を追跡できます。

イベントは /events/{EventName} パスで表現し、HTTP GET メソッドを使用します。 イベントは読み取り専用の情報であり、発行されたイベントを照会する操作として扱うため、GETメソッドが適切です。

基本的なイベントの例

yaml
/events/Transfer:
  get:
    summary: "Transfer"
    description: |
      トークンが転送された時に発行されるイベントです。

      ERC20標準イベントです。このイベントは、`transfer` 関数や `transferFrom` 関数が実行されたときに発行されます。
      トークンの移動を追跡するために使用されます。

      **パラメータ:**
      - `from` (address) - indexed
      - `to` (address) - indexed
      - `value` (uint256)
    operationId: event_Transfer
    tags:
      - Events
    responses:
      "200":
        description: "トークン転送イベント"
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/TransferEvent'

イベントスキーマの定義

イベントのスキーマは components/schemas に定義します。 各パラメータの型、説明、サンプル値を明確に記載します。

yaml
components:
  schemas:
    TransferEvent:
      type: object
      description: "Transfer イベント。トークンが転送されたときに発行されます。"
      properties:
        from:
          type: string
          pattern: "^0x[0-9a-fA-F]{40}$"
          description: "indexed: true。送金元のアドレス。トピックとして検索可能です。"
          example: "0x1234567890123456789012345678901234567890"
        to:
          type: string
          pattern: "^0x[0-9a-fA-F]{40}$"
          description: "indexed: true。送金先のアドレス。トピックとして検索可能です。"
          example: "0x0987654321098765432109876543210987654321"
        value:
          type: string
          description: "indexed: false。転送されたトークン量(wei単位)。"
          example: "1000000000000000000"

複雑なイベントの例(複数のindexedパラメータ)

yaml
/events/Approval:
  get:
    summary: "Approval"
    description: |
      トークンの使用許可が設定された時に発行されるイベントです。

      ERC20標準イベントです。このイベントは、`approve` 関数が実行されたときに発行されます。
      `owner` が `spender` に対してトークンの使用を許可した量を記録します。

      **パラメータ:**
      - `owner` (address) - indexed
      - `spender` (address) - indexed
      - `value` (uint256)
    operationId: event_Approval
    tags:
      - Events
    responses:
      "200":
        description: "承認イベント"
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ApprovalEvent'
  • スキーマ定義
yaml
components:
  schemas:
    ApprovalEvent:
      type: object
      description: "Approval イベント。トークンの使用許可が設定されたときに発行されます。"
      properties:
        owner:
          type: string
          pattern: "^0x[0-9a-fA-F]{40}$"
          description: "indexed: true。トークンの所有者アドレス。トピックとして検索可能です。"
          example: "0x1234567890123456789012345678901234567890"
        spender:
          type: string
          pattern: "^0x[0-9a-fA-F]{40}$"
          description: "indexed: true。トークンの使用を許可されたアドレス。トピックとして検索可能です。"
          example: "0x0987654321098765432109876543210987654321"
        value:
          type: string
          description: "indexed: false。許可されたトークン量(wei単位)。"
          example: "1000000000000000000"

カスタムイベントの例

yaml
/events/RoleGranted:
  get:
    summary: "RoleGranted"
    description: |
      ロールが付与された時に発行されるイベントです。

      AccessControl で使用されるイベントです。管理者が特定のアカウントに新しいロールを付与したときに発行されます。
      このイベントを監視することで、権限の変更履歴を追跡できます。

      **パラメータ:**
      - `role` (bytes32) - indexed
      - `account` (address) - indexed
      - `sender` (address) - indexed
    operationId: event_RoleGranted
    tags:
      - Events
    responses:
      "200":
        description: "ロール付与イベント"
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RoleGrantedEvent'
  • スキーマ定義
yaml
components:
  schemas:
    RoleGrantedEvent:
      type: object
      description: "RoleGranted イベント。ロールが付与されたときに発行されます。"
      properties:
        role:
          type: string
          pattern: "^0x[0-9a-fA-F]{64}$"
          description: "indexed: true。付与されたロールのID(bytes32形式)。"
          example: "0x0000000000000000000000000000000000000000000000000000000000000000"
        account:
          type: string
          pattern: "^0x[0-9a-fA-F]{40}$"
          description: "indexed: true。ロールを付与されたアカウントのアドレス。"
          example: "0x1234567890123456789012345678901234567890"
        sender:
          type: string
          pattern: "^0x[0-9a-fA-F]{40}$"
          description: "indexed: true。ロールを付与した管理者のアドレス。"
          example: "0xabcdefabcdefabcdefabcdefabcdefabcdefabcd"

イベントスキーマのフィールドは以下です。

フィールド 説明 注意点
type スキーマの型。イベントは object を使用 常に object
description イベントの説明。どのような状況で発行されるかを記述 発行条件、用途、関連する関数を明記
properties イベントの各パラメータ。indexed はdescriptionに明記 各パラメータに indexed: true/false を明記

indexedパラメータの重要性

Solidityの indexed パラメータは、イベントログのトピック(topics)として記録されます。 トピックとして記録されたパラメータは、効率的に検索・フィルタリングが可能です。

  • indexed パラメータ descriptionに「indexed: true」と明記し、「トピックとして検索可能です」と追記。
  • 非indexed パラメータ descriptionに「indexed: false」と明記。

例えば、特定のアドレス(from)から送信されたすべての Transfer イベントを検索したい場合、from が indexed であれば効率的に検索できます。

yaml
description: "indexed: true。送金元のアドレス。トピックとして検索可能です。"

一方、indexed でないパラメータは、イベントのデータ部分に記録され、検索には使用できませんが、より多くの情報を含めることができます。

yaml
description: "indexed: false。転送されたトークン量(wei単位)。"

エラーの表現

カスタムエラーは、Solidity 0.8.4以降で導入された効率的なエラーハンドリング機能です。 従来の revert() や require() よりもガス代が安く、型安全性が高いという特徴があります。

カスタムエラーは /errors/{ErrorName} パスで表現し、HTTP GET メソッドを使用します。 エラーの定義を参照する操作として扱うため、GETメソッドが適切です。

基本的なカスタムエラーの例(引数なし)

yaml
/errors/Unauthorized:
  get:
    summary: "Unauthorized"
    description: |
      権限がないアカウントが操作を実行しようとした時に返されるエラーです。

      このエラーは、アクセス制御で保護された関数を、権限を持たないアカウントが呼び出した場合に発生します。
      適切な権限(ロール)を付与してから、再度実行してください。
    operationId: error_Unauthorized
    tags:
      - Errors
    responses:
      "200":
        description: "権限エラー"
        content:
          application/json:
            schema:
              type: object
              properties:
                name:
                  type: string
                  example: "Unauthorized"

引数を持つカスタムエラーの例

カスタムエラーに引数がある場合、エラーが発生した際の具体的な状況を伝えることができます。

yaml
/errors/InsufficientBalance:
  get:
    summary: "InsufficientBalance"
    description: |
      残高が不足している時に返されるエラーです。

      このエラーは、トークン転送や支払いの際に、実行者の残高が必要な量に満たない場合に発生します。
      `balance` フィールドで現在の残高を、`required` フィールドで必要な残高を確認できます。

      **エラー引数:**
      - `account` (address) - 残高が不足しているアカウントのアドレス
      - `balance` (uint256) - 現在の残高
      - `required` (uint256) - 必要な残高
    operationId: error_InsufficientBalance
    tags:
      - Errors
    responses:
      "200":
        description: "残高不足エラー"
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/InsufficientBalanceError'
  • エラースキーマの定義
yaml
components:
  schemas:
    InsufficientBalanceError:
      type: object
      description: "InsufficientBalance エラー。残高が不足している場合に返されます。"
      properties:
        account:
          type: string
          pattern: "^0x[0-9a-fA-F]{40}$"
          description: "残高が不足しているアカウントのアドレス。"
          example: "0x1234567890123456789012345678901234567890"
        balance:
          type: string
          description: "現在の残高(wei単位)。"
          example: "500000000000000000"
        required:
          type: string
          description: "必要な残高(wei単位)。"
          example: "1000000000000000000"

複雑なカスタムエラーの例

yaml
/errors/InvalidAddress:
  get:
    summary: "InvalidAddress"
    description: |
      無効なアドレスが指定された時に返されるエラーです。

      このエラーは、ゼロアドレス(`0x0000000000000000000000000000000000000000`)が指定された場合や、
      コントラクトのロジック上許可されないアドレスが指定された場合に発生します。

      **エラー引数:**
      - `addr` (address) - 無効と判定されたアドレス
    operationId: error_InvalidAddress
    tags:
      - Errors
    responses:
      "200":
        description: "無効なアドレスエラー"
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/InvalidAddressError'
  • エラースキーマの定義
yaml
components:
  schemas:
    InvalidAddressError:
      type: object
      description: "InvalidAddress エラー。無効なアドレスが指定された場合に返されます。"
      properties:
        addr:
          type: string
          pattern: "^0x[0-9a-fA-F]{40}$"
          description: "無効と判定されたアドレス。"
          example: "0x0000000000000000000000000000000000000000"

エラー定義のフィールドは以下です。

フィールド 説明 注意点
summary エラー名をそのまま記載 Solidityのエラー名と一致させる
description エラーの詳細な説明。発生条件、対処方法を記述 引数がある場合は「エラー引数:」として列挙
operationId error_{ErrorName} の形式 他の操作と重複しないよう接頭辞を付ける
tags Errors タグを使用 エラーは単一のタグで分類
responses["200"] エラースキーマへの参照 引数がある場合は $ref でスキーマを参照

カスタムエラーを使うことで、従来の文字列ベースのエラーメッセージよりも構造化された詳細な情報を提供でき、デバッグやエラーハンドリングが容易になります。

変数・定数の表現

Solidityの public なステート変数や定数は、コンパイラによって自動的にgetter関数が生成されます。 これらの自動生成されたgetterをOpenAPI/Swagger形式で表現する際には、HTTP GET メソッドを使用します。

重要なポイントとして、description には必ず変数宣言そのものをコードブロックで含めます。 これにより、型、可視性、初期値などの重要な情報を一目で確認できます。

基本的な変数の例

yaml
/totalSupply:
  get:
    summary: "totalSupply"
    description: |
      ```solidity
      uint256 public totalSupply;
      ```

      トークンの総供給量を取得します。

      ERC20標準のステート変数です。この値は、トークンの発行(mint)や焼却(burn)によって変動します。
      現在発行されているトークンの総量を確認できます。
    operationId: totalSupply
    tags:
      - Variables
    responses:
      "200":
        description: "正常終了時のレスポンスです。"
        content:
          application/json:
            schema:
              type: object
              properties:
                result:
                  type: string
                  description: "トークンの総供給量(wei単位)を返します。"
                  example: "1000000000000000000000000"

定数(constant)の例

定数は、コンパイル時に値が確定し、デプロイ後は変更できません。 constant キーワードを使って定義します。

yaml
/DECIMALS:
  get:
    summary: "DECIMALS"
    description: |
      ```solidity
      uint8 public constant DECIMALS = 18;
      ```

      トークンの小数点以下の桁数を表す定数です。

      この値は、トークンの表示や計算において重要な役割を果たします。
      例えば、`DECIMALS` が18の場合、1トークンは `1 * 10^18` wei として扱われます。
      ERC20標準では、通常18が使用されます(Etherと同じ)。

      この値はコンパイル時に確定し、変更できません。
    operationId: DECIMALS
    tags:
      - Constants
    responses:
      "200":
        description: "正常終了時のレスポンスです。"
        content:
          application/json:
            schema:
              type: object
              properties:
                result:
                  type: integer
                  minimum: 0
                  maximum: 255
                  description: "小数点以下の桁数を返します。"
                  example: 18

イミュータブル変数(immutable)の例

イミュータブル変数は、コンストラクタ内で一度だけ値を設定でき、その後は変更できません。 デプロイ時に値が確定します。

yaml
/owner:
  get:
    summary: "owner"
    description: |
      ```solidity
      address public immutable owner;
      ```

      コントラクトのオーナーアドレスを取得します。

      この値は、コントラクトのデプロイ時にコンストラクタで設定されます。
      デプロイ後は変更できません。
      オーナーは、コントラクトの管理機能(ミント、バーン、権限設定など)にアクセスできます。
    operationId: owner
    tags:
      - Variables
    responses:
      "200":
        description: "正常終了時のレスポンスです。"
        content:
          application/json:
            schema:
              type: object
              properties:
                result:
                  type: string
                  pattern: "^0x[0-9a-fA-F]{40}$"
                  description: "コントラクトのオーナーアドレスを返します。"
                  example: "0x1234567890123456789012345678901234567890"

bytes32型の定数の例

ロールベースのアクセス制御でよく使われる bytes32 型の定数の例です。

yaml
/ADMIN_ROLE:
  get:
    summary: "ADMIN_ROLE"
    description: |
      ```solidity
      bytes32 public constant ADMIN_ROLE = keccak256("ADMIN_ROLE");
      ```

      管理者ロールを表す定数です。

      AccessControl で使用されるロール識別子です。
      このロールを持つアカウントは、コントラクトの管理機能にアクセスできます。
      `hasRole(ADMIN_ROLE, account)` で、特定のアカウントがこのロールを持っているかを確認できます。
    operationId: ADMIN_ROLE
    tags:
      - Constants
    responses:
      "200":
        description: "正常終了時のレスポンスです。"
        content:
          application/json:
            schema:
              type: object
              properties:
                result:
                  type: string
                  pattern: "^0x[0-9a-fA-F]{64}$"
                  description: "ロールIDを表すbytes32値を返します。"
                  example: "0xdf8b4c520ffe197c5343c6f5aec59570151ef9a492f2c624fd45ddde6135ec42"

変数・定数の表現において重要なポイントは以下です。

ポイント 説明 例
変数宣言の記載 description の最初にSolidityの変数宣言をコードブロックで記載 solidity uint256 public totalSupply;
型の明確化 変数宣言により、型、可視性、修飾子が一目で分かる uint256 public, uint8 public constant, address public immutable
変更可能性の説明 constant や immutable の場合、変更できないことを明記 「この値はコンパイル時に確定し、変更できません」
使用目的の説明 変数が何のために使われるか、どのように使われるかを説明 「トークンの発行や焼却によって変動します」

変数宣言を含めることで、Swagger UIで仕様書を見る人が、実際のSolidityコードの構造を理解しやすくなります。

Mappingの表現

Mappingは、Solidityでキーと値の対応関係を管理するデータ構造です。 public な Mapping は、コンパイラによって自動的にgetter関数が生成されます。

Mappingも変数と同様に、description には型定義をコードブロックで含めます。 特に、ネストしたMapping(二重Mapping)の場合は、型定義を明記することで構造が理解しやすくなります。

単一Mappingの例

yaml
/balanceOf:
  get:
    summary: "balanceOf"
    description: |
      ```solidity
      mapping(address account => uint256) public balanceOf;
      ```

      各アカウントのトークン残高を管理するMappingです。

      ERC20標準のMappingです。アカウントアドレスをキーとして、そのアカウントが保有するトークン量を取得できます。
      トークンの転送や発行、焼却によって残高が変動します。
    operationId: balanceOf
    tags:
      - Mapping
    parameters:
      - name: account
        in: query
        required: true
        schema:
          type: string
          pattern: "^0x[0-9a-fA-F]{40}$"
        description: "残高を照会するアカウントのアドレスを指定します。"
        example: "0x1234567890123456789012345678901234567890"
    responses:
      "200":
        description: "正常終了時のレスポンスです。"
        content:
          application/json:
            schema:
              type: object
              properties:
                result:
                  type: string
                  description: "指定されたアカウントのトークン残高(wei単位)を返します。"
                  example: "1000000000000000000"

ネストしたMapping(二重Mapping)の例

ERC20の allowance のような二重Mappingの例です。

yaml
/allowance:
  get:
    summary: "allowance"
    description: |
      ```solidity
      mapping(address owner => mapping(address spender => uint256)) public allowance;
      ```

      `spender` が `owner` から使用を許可されているトークン量を管理するMappingです。

      ERC20標準のMappingです。このMappingは、トークンの所有者(`owner`)が、
      別のアカウント(`spender`)に対して使用を許可したトークン量を記録します。

      `approve` 関数で許可量を設定し、`transferFrom` 関数で実際に使用されます。
      `transferFrom` が実行されると、この許可量が減少します。
    operationId: allowance
    tags:
      - Mapping
    parameters:
      - name: owner
        in: query
        required: true
        schema:
          type: string
          pattern: "^0x[0-9a-fA-F]{40}$"
        description: "トークンの所有者アドレスを指定します。"
        example: "0x1234567890123456789012345678901234567890"
      - name: spender
        in: query
        required: true
        schema:
          type: string
          pattern: "^0x[0-9a-fA-F]{40}$"
        description: "トークンの使用を許可されたアドレスを指定します。"
        example: "0x0987654321098765432109876543210987654321"
    responses:
      "200":
        description: "正常終了時のレスポンスです。"
        content:
          application/json:
            schema:
              type: object
              properties:
                result:
                  type: string
                  description: "`spender` が使用できる残りのトークン量(wei単位)を返します。"
                  example: "500000000000000000"

アクセス制御に使われるMappingの例

yaml
/hasRole:
  get:
    summary: "hasRole"
    description: |
      ```solidity
      mapping(bytes32 role => mapping(address account => bool)) public hasRole;
      ```

      特定のアカウントが特定のロールを持っているかを確認するMappingです。

      AccessControl で使用されるMappingです。ロールID(`role`)とアカウントアドレス(`account`)の
      組み合わせに対して、そのアカウントがそのロールを持っているかどうかをブール値で管理します。

      `grantRole` 関数でロールが付与され、`revokeRole` 関数で剥奪されます。
    operationId: hasRole
    tags:
      - Mapping
    parameters:
      - name: role
        in: query
        required: true
        schema:
          type: string
          pattern: "^0x[0-9a-fA-F]{64}$"
        description: "確認するロールのID(bytes32形式)を指定します。"
        example: "0xdf8b4c520ffe197c5343c6f5aec59570151ef9a492f2c624fd45ddde6135ec42"
      - name: account
        in: query
        required: true
        schema:
          type: string
          pattern: "^0x[0-9a-fA-F]{40}$"
        description: "確認するアカウントのアドレスを指定します。"
        example: "0x1234567890123456789012345678901234567890"
    responses:
      "200":
        description: "正常終了時のレスポンスです。"
        content:
          application/json:
            schema:
              type: object
              properties:
                result:
                  type: boolean
                  description: "アカウントがロールを持っている場合 `true`、持っていない場合 `false` を返します。"
                  example: true

Mappingの表現において重要なポイントは以下です。

ポイント 説明 例
型定義の記載 Mappingの完全な型定義をコードブロックで記載 mapping(address => uint256) public balanceOf
ネスト構造の明示 二重Mappingの場合、キーの階層構造を明確に説明 「owner → spender → amount」
parametersの順序 Mappingのキーの順序に従ってparametersを定義 外側のキー(owner)を先に、内側のキー(spender)を後に
使用場面の説明 どの関数でこのMappingが使われるかを明記 「approve で設定、transferFrom で使用」

構造体の表現

構造体(struct)は、複数のデータをまとめて管理するための複合型です。 関連するデータをグループ化することで、コードの可読性と保守性を向上させます。

構造体は /structs/{StructName} パスで表現し、HTTP GET メソッドを使用します。

基本的な構造体の例

yaml
/structs/TokenInfo:
  get:
    summary: "TokenInfo"
    description: |
      トークンの基本情報を保持する構造体です。

      この構造体は、トークンのメタデータ(名前、シンボル、decimals)を一つのデータ構造にまとめています。
      コントラクトの初期化時や設定変更時に使用されます。

      **フィールド:**
      - `name` (string) - トークンの名前
      - `symbol` (string) - トークンのシンボル
      - `decimals` (uint8) - トークンの小数点以下の桁数
    operationId: struct_TokenInfo
    tags:
      - Structs
    responses:
      "200":
        description: "TokenInfo構造体"
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/TokenInfoStruct'

構造体のスキーマは components/schemas に定義します。

yaml
components:
  schemas:
    TokenInfoStruct:
      type: object
      description: "TokenInfo 構造体。トークンの基本情報を保持します。"
      properties:
        name:
          type: string
          description: "トークンの名前。例: 'MyToken'。"
          example: "Example Token"
        symbol:
          type: string
          description: "トークンのシンボル。例: 'MTK'。通常は3-5文字の大文字。"
          example: "EXT"
        decimals:
          type: integer
          minimum: 0
          maximum: 255
          description: "トークンの小数点以下の桁数。通常は18。"
          example: 18

複雑な構造体の例

yaml
/structs/VestingSchedule:
  get:
    summary: "VestingSchedule"
    description: |
      トークンのベスティング(段階的権利確定)スケジュールを管理する構造体です。

      この構造体は、トークンの配布スケジュールを定義します。
      ベスティング期間中、受益者は時間の経過とともに徐々にトークンを受け取る権利を獲得します。

      **フィールド:**
      - `beneficiary` (address) - 受益者のアドレス
      - `start` (uint256) - ベスティング開始時刻(UNIXタイムスタンプ)
      - `cliff` (uint256) - クリフ期間(秒)。この期間中はトークンを受け取れない
      - `duration` (uint256) - ベスティング期間(秒)
      - `totalAmount` (uint256) - ベスティング対象のトークン総量
      - `releasedAmount` (uint256) - すでに引き出されたトークン量
    operationId: struct_VestingSchedule
    tags:
      - Structs
    responses:
      "200":
        description: "VestingSchedule構造体"
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/VestingScheduleStruct'
  • スキーマ定義
yaml
components:
  schemas:
    VestingScheduleStruct:
      type: object
      description: "VestingSchedule 構造体。トークンのベスティングスケジュールを定義します。"
      properties:
        beneficiary:
          type: string
          pattern: "^0x[0-9a-fA-F]{40}$"
          description: "受益者のアドレス。このアドレスがベスティングされたトークンを受け取ります。"
          example: "0x1234567890123456789012345678901234567890"
        start:
          type: string
          description: "ベスティング開始時刻(UNIXタイムスタンプ、秒単位)。"
          example: "1704067200"
        cliff:
          type: string
          description: "クリフ期間(秒)。開始からこの期間中はトークンを引き出せません。"
          example: "2592000"
        duration:
          type: string
          description: "ベスティング期間(秒)。この期間をかけてトークンが徐々に権利確定します。"
          example: "31536000"
        totalAmount:
          type: string
          description: "ベスティング対象のトークン総量(wei単位)。"
          example: "1000000000000000000000"
        releasedAmount:
          type: string
          description: "すでに引き出されたトークン量(wei単位)。"
          example: "500000000000000000000"

ネストした構造体の例

yaml
/structs/Proposal:
  get:
    summary: "Proposal"
    description: |
      ガバナンス提案を管理する構造体です。

      この構造体は、DAOの投票提案に関する情報を保持します。
      提案の内容、投票状況、実行ステートなどを一元管理します。

      **フィールド:**
      - `id` (uint256) - 提案のID
      - `proposer` (address) - 提案者のアドレス
      - `targets` (address[]) - 実行対象のコントラクトアドレスのリスト
      - `values` (uint256[]) - 各呼び出しで送信するEther量のリスト
      - `signatures` (string[]) - 各呼び出しの関数シグネチャのリスト
      - `calldatas` (bytes[]) - 各呼び出しのコールデータのリスト
      - `startBlock` (uint256) - 投票開始ブロック番号
      - `endBlock` (uint256) - 投票終了ブロック番号
      - `forVotes` (uint256) - 賛成票の総数
      - `againstVotes` (uint256) - 反対票の総数
      - `abstainVotes` (uint256) - 棄権票の総数
      - `executed` (bool) - 提案が実行済みかどうか
      - `canceled` (bool) - 提案がキャンセル済みかどうか
    operationId: struct_Proposal
    tags:
      - Structs
    responses:
      "200":
        description: "Proposal構造体"
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ProposalStruct'

構造体スキーマのフィールドは以下です。

フィールド 説明 注意点
type スキーマの型。構造体は object を使用 常に object
description 構造体の用途や役割の説明 どのような場面で使われるかを明記
properties 構造体の各フィールド。Solidityの変数名をキー名として使用 フィールドごとに詳細な説明を記載

構造体を使うことで、関連するデータをまとめて扱えるため、関数の引数や戻り値をシンプルに保つことができます。

Modifierの表現

Modifier(修飾子)は、関数の実行前後に特定の条件をチェックしたり、処理を追加したりする仕組みです。 アクセス制御、ステートチェック、再入攻撃対策などに広く使われます。

Modifierは /modifiers/{ModifierName} パスで定義し、HTTP GET メソッドを使用します。

基本的なModifierの例

yaml
/modifiers/onlyOwner:
  get:
    summary: "onlyOwner"
    description: |
      ```solidity
      modifier onlyOwner() {
          require(msg.sender == owner, "Not owner");
          _;
      }
      ```

      このmodifierは、関数の実行者がコントラクトのオーナーであることを確認します。

      関数の実行前に `msg.sender` がオーナーアドレスと一致するかをチェックします。
      オーナー以外が実行した場合は、「Not owner」というメッセージとともにrevertします。

      **使用例:**
      - `mint` 関数: トークンの発行
      - `burn` 関数: トークンの焼却
      - `transferOwnership` 関数: オーナー権限の移譲
    operationId: modifier_onlyOwner
    tags:
      - Modifier
    responses:
      "200":
        description: "onlyOwner modifier"
        content:
          application/json:
            schema:
              type: object
              properties:
                name:
                  type: string
                  example: "onlyOwner"

ロールベースのModifierの例

yaml
/modifiers/onlyRole:
  get:
    summary: "onlyRole"
    description: |
      ```solidity
      modifier onlyRole(bytes32 role) {
          require(hasRole(role, msg.sender), "AccessControl: account is missing role");
          _;
      }
      ```

      このmodifierは、関数の実行者が指定されたロールを持っているかを確認します。

      AccessControl で使用されるmodifierです。関数の実行前に、呼び出し元のアカウントが
      指定されたロール(`role`)を持っているかをチェックします。
      ロールを持っていない場合は、エラーメッセージとともにrevertします。

      **引数:**
      - `role` (bytes32) - 必要なロールのID

      **使用例:**
      - `grantRole` 関数: ロールの付与(`ADMIN_ROLE` が必要)
      - `revokeRole` 関数: ロールの剥奪(`ADMIN_ROLE` が必要)
    operationId: modifier_onlyRole
    tags:
      - Modifier
    responses:
      "200":
        description: "onlyRole modifier"
        content:
          application/json:
            schema:
              type: object
              properties:
                name:
                  type: string
                  example: "onlyRole"

ステートチェックのModifierの例

yaml
/modifiers/whenNotPaused:
  get:
    summary: "whenNotPaused"
    description: |
      ```solidity
      modifier whenNotPaused() {
          require(!paused, "Pausable: paused");
          _;
      }
      ```

      このmodifierは、コントラクトが一時停止ステートでないことを確認します。

      Pausable パターンで使用されるmodifierです。関数の実行前に、コントラクトの `paused` フラグを
      チェックします。コントラクトが一時停止中(`paused == true`)の場合は、関数の実行を拒否します。

      **使用例:**
      - `transfer` 関数: 一時停止中は転送を禁止
      - `mint` 関数: 一時停止中は発行を禁止
      - `burn` 関数: 一時停止中は焼却を禁止

      **関連するModifier:**
      - `whenPaused`: 一時停止中のみ実行可能(一時停止解除のため)
    operationId: modifier_whenNotPaused
    tags:
      - Modifier
    responses:
      "200":
        description: "whenNotPaused modifier"
        content:
          application/json:
            schema:
              type: object
              properties:
                name:
                  type: string
                  example: "whenNotPaused"

再入攻撃対策のModifierの例

yaml
/modifiers/nonReentrant:
  get:
    summary: "nonReentrant"
    description: |
      ```solidity
      modifier nonReentrant() {
          require(_status != _ENTERED, "ReentrancyGuard: reentrant call");
          _status = _ENTERED;
          _;
          _status = _NOT_ENTERED;
      }
      ```

      このmodifierは、再入攻撃を防ぐためのガードです。

      ReentrancyGuard パターンで使用されるmodifierです。関数の実行中に、同じ関数が再度呼び出されることを防ぎます。
      外部コントラクトを呼び出す関数や、Etherを送信する関数に適用することで、再入攻撃のリスクを軽減できます。

      **仕組み:**
      1. 関数の実行前に、`_status` が `_NOT_ENTERED` であることを確認
      2. `_status` を `_ENTERED` に設定
      3. 関数本体を実行
      4. 関数の実行後、`_status` を `_NOT_ENTERED` に戻す

      **使用例:**
      - `withdraw` 関数: Etherの引き出し
      - `claim` 関数: 報酬の受け取り
    operationId: modifier_nonReentrant
    tags:
      - Modifier
    responses:
      "200":
        description: "nonReentrant modifier"
        content:
          application/json:
            schema:
              type: object
              properties:
                name:
                  type: string
                  example: "nonReentrant"

Modifierが付与された関数のdescription

関数にmodifierが付与されている場合、その効果を関数の description に追記します。 これにより、関数を呼び出す際に必要な権限や条件を明確にできます。

  • 単一のModifierの場合
yaml
/mint:
  post:
    summary: "mint"
    description: |
      新しいトークンを発行します。

      指定されたアドレス(`to`)に、指定された量(`amount`)のトークンを新規に発行します。
      総供給量(`totalSupply`)が増加し、`Transfer` イベントが発行されます。

      :::message
      **Modifier**: この関数は `onlyOwner` modifierにより保護されています。
      コントラクトのオーナーのみが実行可能です。
      :::
    operationId: mint
    tags:
      - Write Functions
  • 複数のModifierの場合
yaml
/withdraw:
  post:
    summary: "withdraw"
    description: |
      コントラクトからEtherを引き出します。

      呼び出し元のアカウントが預けているEtherの全額を引き出します。
      残高が不足している場合はエラーが発生します。

      :::message
      **Modifiers**:
      - `whenNotPaused`: コントラクトが一時停止中でないことを確認
      - `nonReentrant`: 再入攻撃を防止
      :::
    operationId: withdraw
    tags:
      - Write Functions

複数のmodifierが付与されている場合は、すべてのmodifierの効果を箇条書きで記載します。 これにより、関数を安全に呼び出すために必要な条件を包括的に理解できます。

Solidityの型マッピング

Solidityの型をOpenAPI/Swagger形式に正確にマッピングすることは、仕様書の品質を左右する重要なポイントです。 以下では、よく使われるSolidity型とそのOpenAPI表現を詳しく説明します。

基本型のマッピング表

Solidity型 OpenAPI表現 理由・注意点
address type: string, pattern: "^0x[0-9a-fA-F]{40}$" 40桁の16進数文字列。パターンでバリデーション
uint256 / int256 type: string JavaScriptの安全な整数範囲(2^53-1)を超えるため文字列化
uint8 / uint16 / uint32 type: integer, minimum: 0, maximum: {max} 安全な範囲内なので数値型で表現可能
int8 / int16 / int32 type: integer, minimum: {min}, maximum: {max} 符号付き整数。範囲を明示
bytes32 type: string, pattern: "^0x[0-9a-fA-F]{64}$" 64桁の16進数文字列(32バイト)
bytes4 type: string, pattern: "^0x[0-9a-fA-F]{8}$" 8桁の16進数文字列(4バイト、関数セレクタなど)
bytes type: string 可変長バイト列。16進数文字列として表現
bool type: boolean 真偽値。そのまま表現
string type: string 文字列。そのまま表現
address[] type: array, items: {type: string, pattern: ...} アドレスの配列
uint256[] type: array, items: {type: string} uint256の配列

各型の詳細な説明と例

address型

yaml
schema:
  type: string
  pattern: "^0x[0-9a-fA-F]{40}$"
  description: "Ethereumアドレス(40桁の16進数文字列)"
  example: "0x1234567890123456789012345678901234567890"
  • 常に 0x プレフィックスを含む
  • 40桁の16進数(20バイト)
  • 大文字・小文字を区別しない(チェックサムアドレスには大文字小文字の区別あり)

uint256型

yaml
schema:
  type: string
  description: "符号なし256ビット整数(wei単位)。JavaScriptの安全な整数範囲を超えるため文字列として扱います。"
  example: "1000000000000000000"
  • JavaScriptの Number.MAX_SAFE_INTEGER は 2^53-1(約9京)
  • uint256 は 2^256-1(約10の77乗)まで扱える
  • 文字列化しないと精度が失われる

uint8型(小さい整数)

yaml
schema:
  type: integer
  minimum: 0
  maximum: 255
  description: "符号なし8ビット整数(0-255の範囲)"
  example: 18
  • 使用例

decimals(小数点以下の桁数)。

bytes32型

yaml
schema:
  type: string
  pattern: "^0x[0-9a-fA-F]{64}$"
  description: "32バイトの固定長バイト列(64桁の16進数文字列)"
  example: "0xdf8b4c520ffe197c5343c6f5aec59570151ef9a492f2c624fd45ddde6135ec42"
  • 使用例

ロールID、ハッシュ値、一意識別子。

bool型

yaml
schema:
  type: boolean
  description: "真偽値"
  example: true

ー 使用例

フラグ、権限の有無。

string型

yaml
schema:
  type: string
  description: "文字列"
  example: "Example Token"
  • 使用例

トークン名、シンボル、メッセージ。

配列型(address[])

yaml
schema:
  type: array
  items:
    type: string
    pattern: "^0x[0-9a-fA-F]{40}$"
  description: "アドレスの配列"
  example:
    - "0x1234567890123456789012345678901234567890"
    - "0x0987654321098765432109876543210987654321"

配列型(uint256[])

yaml
schema:
  type: array
  items:
    type: string
  description: "uint256の配列(各要素は文字列として表現)"
  example:
    - "1000000000000000000"
    - "2000000000000000000"

型マッピングの実例

  • 複数の型を持つ関数の例
yaml
/complexFunction:
  post:
    summary: "complexFunction"
    description: "複数の型を扱う関数の例です。"
    parameters:
      - name: account
        in: query
        required: true
        schema:
          type: string
          pattern: "^0x[0-9a-fA-F]{40}$"
        description: "アドレス型のパラメータ"
      - name: amount
        in: query
        required: true
        schema:
          type: string
        description: "uint256型のパラメータ(文字列として扱う)"
      - name: decimals
        in: query
        required: true
        schema:
          type: integer
          minimum: 0
          maximum: 255
        description: "uint8型のパラメータ(数値として扱える)"
      - name: isActive
        in: query
        required: true
        schema:
          type: boolean
        description: "bool型のパラメータ"
      - name: roleId
        in: query
        required: true
        schema:
          type: string
          pattern: "^0x[0-9a-fA-F]{64}$"
        description: "bytes32型のパラメータ"

このように、各型の特性を理解し、適切なOpenAPI表現を選択することで、正確で使いやすい仕様書を作成できます。

サンプル仕様書(ERC20)

以下は、ERC20トークンコントラクトの完全なOpenAPI仕様書サンプルです。

最後に

今回は「SolidityスマートコントラクトをOpenAPI/Swagger形式で仕様書化する方法」についてまとめてきました。