第6話:誰がアクセスできる?関数の振る舞いを決める4つの「可視性」

2025年7月31日

第6話:誰がアクセスできる?関数の振る舞いを決める4つの「可視性」

概要

スマートコントラクトのセキュリティを左右する、4つの関数可視性(public, external, internal, private)の完全ガイドです。それぞれの違いと、「誰が」「どこから」呼び出せるのかを継承関係も踏まえて徹底解説。

「最小権限の原則」に基づき、不正アクセスを防ぎつつ、external活用によるガス代節約といったプロの設計思想を学び、安全で効率的なDApp開発の土台を固めます。

目次
Solidity学習講座:最終版 目次(全20話)
Solidity学習講座:最終版 目次(全20話)

基礎編 第1話:未来のインターネットへようこそ!Solidityとスマートコントラクトの全体像 第2話:準備は1分!ブラウザだけで開発できる「Remix IDE」の基本操作 第3話:記念すべき初コント ...

続きを見る

はじめに

第5話で、あなたは「関数」という心臓部をコントラクトに与え、publicprivateといったキーワードで、その機能へのアクセスを制御する方法を学びました。誰でも呼び出せる公開機能と、内部だけで使う非公開機能を区別できるようになったわけです。

しかし、実際の開発では、もっと繊細なアクセス制御が必要になる場面が数多くあります。

「特定の関係者(子コントラクト)だけが使えるようにしたい…」 「ユーザーからの呼び出し専用にして、少しでもガス代を節約したい…」

こうした要求に応えるのが、今回学ぶ**4つの「可視性(Visibility)」**の全貌です。publicprivateに加え、internalexternalをマスターすることで、あなたのアクセス制御は、まるで金庫のダイヤル錠のように、より精密で強固なものになります。

可視性の正しい理解と使い分けは、単なる作法ではありません。それは、あなたのコントラクトとユーザーの資産を潜在的な攻撃から守るための、最も重要なセキュリティ技術の一つなのです。

なぜ「可視性」がセキュリティの要なのか?

Web2.0の世界でバグが起きれば、それはサービス停止やデータ漏洩につながるかもしれません。しかし、ブロックチェーンの世界では、たった一つの関数の可視性の設定ミスが、ユーザー資産の即時かつ永久的な喪失に直結する可能性があります。

可視性とは、関数への「ドアの鍵」を管理するようなものです。

  • 誰でも入れる正面玄関(public
  • 関係者専用の通用口(internal
  • 金庫室の扉(private
  • 外部からの荷物受け渡し口(external

もし、金庫室の扉に「正面玄関」の看板をかけてしまったら…? 考えるだに恐ろしいですね。だからこそ、私たちは**「最小権限の原則」**に従わなければなりません。つまり、すべての関数は、その役割を果たすために必要な、最小限の可視性を持つべきなのです。

それでは、4つの鍵を一つずつ詳しく見ていきましょう。

4つの可視性(Visibility)徹底解説

1. public:正面玄関(誰でもOK)

すでにお馴染みのpublicは、最もオープンな可視性です。

  • 誰が呼べる?:
    • ✅ ユーザー(外部アカウント)
    • ✅ 他のスマートコントラクト
    • ✅ 同じコントラクト内の他の関数
    • ✅ 子コントラクト(継承先のコントラクト)
  • いつ使う?: トークンのtransferやNFTのmintなど、コントラクトが外部に提供する主要な機能に使います。ユーザーが直接操作するボタンの裏側は、ほとんどがpublic関数です。

2. external:外部専用窓口(内部からはNG)

externalpublicによく似ていますが、一つだけ重要な違いがあります。

  • 誰が呼べる?:
    • ✅ ユーザー(外部アカウント)
    • ✅ 他のスマートコントラクト
  • 誰が呼べない?:
    • ❌ 同じコントラクト内の他の関数(this.myExternalFunction()のような呼び出しは不可)
  • いつ使う?: 関数が外部からのみ呼び出されると断言できる場合に使います。
  • なぜ使う?: ガス代の節約のためです。publicは内部・外部両方からの呼び出しを考慮する必要があるため、引数の扱いが少し複雑になり、ガス代が若干高くなることがあります。externalは外部からの呼び出しに特化しているため、より効率的です。APIとして公開する関数で、内部からは決して呼ばないものには、publicよりexternalを選ぶのがプロフェッショナルな選択です。

3. private:秘密の金庫室(この契約だけ)

privateは、最も閉鎖的で、最も厳しい可視性です。

  • 誰が呼べる?:
    • ✅ 同じコントラクト内の他の関数のみ
  • 誰が呼べない?:
    • ❌ ユーザー
    • ❌ 他のスマートコントラクト
    • 子コントラクトでさえも呼び出せない
  • いつ使う?: そのコントラクト固有のロジックで、外部に公開する必要がなく、かつ将来的に子コントラクトに挙動を変えられたくない(オーバーライドされたくない)重要な補助関数に使います。非常に厳格に隠したい情報や処理に用います。

4. internal:関係者通用口(仲間内OK)

internalは、privateの制限を少しだけ緩めた、非常に使い勝手の良い可視性です。

  • 誰が呼べる?:
    • ✅ 同じコントラクト内の他の関数
    • 子コントラクト
  • 誰が呼べない?:
    • ❌ ユーザー
    • ❌ 他のスマートコントラクト(継承関係にない限り)
  • いつ使う?: 内部的な補助関数にデフォルトで使うべき可視性です。外部から隠しつつ、コードの再利用性を高めるために子コントラクトに機能を継承させることができます。OpenZeppelinのような標準ライブラリでは、_transfer_mintのように、カスタマイズ可能なコアロジックの多くがinternalで定義されています。

一覧表と実践コード

可視性外部から (ユーザー/他契約)内部から (同じ契約内)子契約から (継承先)ガス代 (外部コール時)主な用途
public標準主要な公開機能
external効率的外部専用の公開機能
internal-継承可能な内部機能
private-継承させない内部機能

百聞は一見に如かず。Remixで以下のコードを試してみてください。

Solidity

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

// 親コントラクト
contract VisibilityParent {
    // 誰でもOK
    function publicFunc() public pure returns (string memory) {
        return "Called publicFunc";
    }

    // 外部からだけOK
    function externalFunc() external pure returns (string memory) {
        return "Called externalFunc";
    }

    // 仲間内(この契約と子契約)だけOK
    function internalFunc() internal pure returns (string memory) {
        return "Called internalFunc";
    }

    // この契約だけOK
    function privateFunc() private pure returns (string memory) {
        return "Called privateFunc";
    }

    // 内部から他の関数を呼ぶテスト
    function testInternalCalls() public pure returns (string memory) {
        // internalとprivateは内部から呼べる
        string memory result1 = internalFunc();
        string memory result2 = privateFunc();
        // externalは内部から呼べない! -> this.externalFunc() はコンパイルエラー
        return "Internal and Private calls work from inside.";
    }
}

// 子コントラクト
contract VisibilityChild is VisibilityParent {
    function testChildCalls() public pure returns (string memory) {
        // publicとinternalは子契約から呼べる
        string memory result1 = publicFunc();
        string memory result2 = internalFunc();
        // privateは子契約から呼べない! -> privateFunc() はコンパイルエラー
        return "Public and Internal calls work from child.";
    }
}

まとめ:セキュリティ意識の高い開発者へ

お疲れ様でした!これであなたは、Solidityにおけるアクセス制御の全てをマスターしました。

  • public: 公開APIの基本。
  • external: ガス代を意識した、外部専用の公開API。
  • internal: 継承を前提とした、再利用性の高い内部ロジックの推奨デフォルト
  • private: 継承すら許さない、厳格に隠蔽された内部ロジック。

常に「最小権限の原則」を心に刻み、すべての関数に最も適切な可視性を設定する癖をつけましょう。それが、あなたの書くスマートコントラクトを堅牢にし、あなたをワンランク上のWeb3開発者へと引き上げてくれます。

さて、これまでは主にコントラクトの「機能」に焦点を当ててきました。しかし、実際のアプリケーションでは、「ユーザーごと」にデータを管理する必要があります。例えば、Aさんの残高、Bさんの残高…といった具合です。

次回、**第7話「キーと値でデータを管理!超重要データ構造「マッピング (mapping)」を使いこなす」**では、この「ユーザーごとのデータ管理」を実現するための、極めて強力なデータ構造を学びます。

目次
Solidity学習講座:最終版 目次(全20話)
Solidity学習講座:最終版 目次(全20話)

基礎編 第1話:未来のインターネットへようこそ!Solidityとスマートコントラクトの全体像 第2話:準備は1分!ブラウザだけで開発できる「Remix IDE」の基本操作 第3話:記念すべき初コント ...

続きを見る

-Solidity