WTF Solidity 超シンプル入門:抽象コントラクト(abstract)とインターフェース(interface)を実例でマスターする
【免费下载链接】WTF-SolidityWTF Solidity 极简入门教程,供小白们使用。Now supports English! 官网: https://wtf.academy项目地址: https://gitcode.com/GitHub_Trending/wt/WTF-Solidity
本稿は WTF-Solidity 入門コースの第14講を基に、Solidity における**抽象コントラクト(abstract)とインターフェース(interface)**の設計思想・文法規則・実践用法を解説する技術ガイドです。ERC721 トークン標準のIERC721インターフェースと、実際の NFT プロジェクト「Bored Ape Yacht Club(BAYC)」との対話例を題材に、コントラクトの「雛形(テンプレート)」を書くことでコードの冗長性を減らし、他コントラクトと安全に連携する方法を学びます。読了後は、抽象コントラクトの宣言規則、インターフェースの5大ルール、インターフェース経由で任意の実装コントラクトを呼び出すスキルを身につけられます。
抽象コントラクト(abstract contract)
スマートコントラクト内に未実装の関数(関数本体{}の中身が空)が1つ以上存在する場合、そのコントラクトは必ずabstractと宣言しなければなりません。宣言しないままコンパイルするとエラーになります。さらに、未実装の関数にはvirtualキーワードを付与し、子コントラクトがオーバーライドできるようにしておく必要があります。
例として、本コースの挿入ソートコントラクトを考えます。挿入ソートの具体的な実装方法がまだ決まっていない段階でも、以下のように契約をabstractと宣言しておけば、将来ほかの開発者に実装を委ねることができます。
abstract contract InsertionSort{ function insertionSort(uint[] memory a) public pure virtual returns(uint[] memory); }抽象コントラクトの実例(リポジトリのソースから)
リポジトリ内の AbstractDemo.sol には、抽象コントラクトを継承して実装する最小限のデモが収録されています。
// SPDX-License-Identifier: MIT pragma solidity ^0.8.34; abstract contract Base{ string public name = "Base"; function getAlias() public pure virtual returns(string memory); } contract BaseImpl is Base{ function getAlias() public pure override returns(string memory){ return "BaseImpl"; } }ポイントは次の通りです。
- 抽象コントラクト
Baseは状態変数nameを持つことができます(インターフェースとの大きな違い)。 - 未実装関数
getAlias()はvirtual宣言され、本体を持ちません。 - 継承するコントラクト
BaseImplはoverrideキーワードでgetAlias()を実装しています。 - このデモは Solidity
^0.8.34を前提としており、uint[]など旧来の書き方もそのまま利用できます。
インターフェース(interface)
interfaceは抽象コントラクトと似ていますが、関数を一切実装しないことが求められます。インターフェースを宣言する際のルールは次の5つです。
- 状態変数を含むことができない
- コンストラクタを含むことができない
- インターフェース以外のコントラクトを継承することができない(
interface IERC721 is IERC165のように、インターフェース同士の継承は可能) - すべての関数は
externalでなければならず、関数本体({ })を持ってはならない - インターフェースを継承する非抽象コントラクトは、定義されたすべての関数を実装しなければならない
インターフェースが提供する2つの重要な情報
インターフェースは実際の機能を実装しませんが、スマートコントラクトの「骨格(スケルトン)」として極めて重要です。あるコントラクトがインターフェース(ERC20やERC721など)を実装していれば、他の Dapps やスマートコントラクトは、その詳細な内部実装を知らなくても対話方法を理解できます。その理由は、インターフェースが次の2つの情報を提供するためです。
- 関数セレクターと関数シグネチャ:コントラクト内の各関数について、
関数名(各パラメータの型)という関数シグネチャと、それをハッシュ化して先頭4バイトを取り出したbytes4セレクター。 - インターフェースID(interface id):ERC165 仕様(EIP-165)に基づく識別子。詳細はリポジトリ内の IERC165.sol を参照してください。
IERC165はsupportsInterface(bytes4 interfaceId)という関数を宣言しており、コントラクトがどのインターフェースを実装しているかを他のコントラクトが問い合わせられるようにします。
インターフェースと ABI の等価性
インターフェースはコントラクトのABI(Application Binary Interface)と等価であり、相互に変換できます。インターフェースコントラクトをコンパイルするとその ABI が得られ、逆に ABI JSON ファイルからインターフェースの Solidity ファイルを復元することも可能です。
IERC721:ERC721 トークン標準のインターフェース
具体例として、ERC721トークン標準を定義するIERC721インターフェースを見てみましょう。これは3つのイベントと9つの関数を持ち、すべてのERC721標準 NFT が実装しなければならない内容です。通常のコントラクトとの違いは、各関数の宣言が関数本体{ }ではなく;で終わる点です。また、インターフェース内の関数はすべてデフォルトでvirtualとみなされるため、明示的にvirtualを付ける必要はありません。
interface IERC721 is IERC165 { event Transfer(address indexed from, address indexed to, uint256 indexed tokenId); event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId); event ApprovalForAll(address indexed owner, address indexed operator, bool approved); function balanceOf(address owner) external view returns (uint256 balance); function ownerOf(uint256 tokenId) external view returns (address owner); function safeTransferFrom(address from, address to, uint256 tokenId) external; function transferFrom(address from, address to, uint256 tokenId) external; function approve(address to, uint256 tokenId) external; function getApproved(uint256 tokenId) external view returns (address operator); function setApprovalForAll(address operator, bool _approved) external; function isApprovedForAll(address owner, address operator) external view returns (bool); function safeTransferFrom( address from, address to, uint256 tokenId, bytes calldata data) external; }この完全版の実装は 34_ERC721/IERC721.sol に収録されています。同ディレクトリの IERC721Receiver.sol には、安全転送時に受信側コントラクトが実装すべきonERC721Received()が宣言されています。
IERC721 の3つのイベント
Transferイベント:転送の際に発火します。送信元アドレスfrom、受信アドレスto、tokenIdを記録します。Approvalイベント:単体承認の際に発火します。トークン所有者owner、承認先アドレスapproved、tokenIdを記録します。ApprovalForAllイベント:バッチ承認の際に発火します。バッチ承認を発行した所有者owner、承認先operator、承認が有効か否かを示すapprovedを記録します。
IERC721 の9つの関数
balanceOf:引数のアドレスが保有する NFT の数量balanceを返します。ownerOf:tokenIdに対応する NFT の所有者ownerを返します。transferFrom:tokenIdの所有権をfromからtoへ移す通常の転送です。safeTransferFrom:安全転送です。受信先がコントラクトアドレスの場合、IERC721Receiverインターフェースを実装していることを確認します(受け取り関数の戻り値がonERC721Received.selectorと一致しない場合、リポジトリ実装では ERC721.sol の_checkOnERC721ReceivedがERC721InvalidReceiverエラーでリバートします)。approve:他のアドレスに特定 NFT の管理権限を付与・解除します。getApproved:単一のtokenIdに対して承認されているアドレスを照会します。setApprovalForAll:このコントラクト内の全 NFT を管理する権限を第三者operatorに対して一括で有効化・無効化します。isApprovedForAll:あるアドレスが別のアドレスの承認済みオペレーター(バッチ承認先)かどうかを照会します。safeTransferFrom(オーバーロード版):パラメータにdataを含む安全転送用の重載関数です。
なお、これらの関数がどのように実装されるかは、本コースのERC721 完全実装(34_ERC721/ERC721.sol)で確認できます。balanceOfは_balancesマッピング、ownerOfは_ownersマッピング、approveは_tokenApprovalsマッピングを参照するなど、インターフェース宣言が内部の状態変数とどう対応するかをソースレベルで追跡できます。
インターフェースはいつ使うのか:BAYC との対話
あるコントラクトがIERC721インターフェースを実装していると分かっていれば、その具体的なコード実装を知らなくても対話できます。
「Bored Ape Yacht Club(BAYC)」はERC721規格の NFT であり、IERC721インターフェースのすべての関数を実装しています。BAYC のソースコードを知らなくても、コントラクトアドレスとIERC721インターフェースさえ分かれば、balanceOf()でアドレスの BAYC 保有数を照会したり、safeTransferFrom()で BAYC を転送したりできます。
contract interactBAYC { // BAYCアドレスを使ってインターフェース型のコントラクト変数を作成(ETHメインネット) IERC721 BAYC = IERC721(0xBC4CA0EdA7647A8aB7C2061c2E118A18a936f13D); // インターフェース経由で BAYC の balanceOf() を呼び出し、保有数を照会 function balanceOfBAYC(address owner) external view returns (uint256 balance){ return BAYC.balanceOf(owner); } // インターフェース経由で BAYC の safeTransferFrom() を呼び出し、安全に転送 function safeTransferFromBAYC(address from, address to, uint256 tokenId) external{ BAYC.safeTransferFrom(from, to, tokenId); } }このデモコードは Languages/ja/14_Interface_ja/Interface.sol に収録されています(同ファイル冒頭には抽象コントラクトのInsertionSort例も含まれています)。インターフェースを「型」として使うことで、実装の詳細に依存しない疎結合な設計が可能になることが分かります。
インターフェースと実装の対応例
リポジトリの InterfaceDemo.sol には、インターフェースを実装する最小デモもあります。
// SPDX-License-Identifier: MIT pragma solidity ^0.8.34; interface Base { function getFirstName() external pure returns(string memory); function getLastName() external pure returns(string memory); } contract BaseImpl is Base{ function getFirstName() external pure override returns(string memory){ return "Amazing"; } function getLastName() external pure override returns(string memory){ return "Ang"; } }インターフェースに宣言されたすべての関数を、継承側でoverride付きで実装している点がルール5(非抽象コントラクトは全関数の実装が必須)の具体例です。
Remix での検証方法
本講のコードは Remix IDE 上ですぐに検証できます。
- 抽象コントラクトの例:
Base(抽象)とBaseImpl(実装)をコンパイル・デプロイし、BaseImpl.getAlias()が"BaseImpl"を返すことを確認します。下図の通り、未実装関数を含むコントラクトはabstract宣言によりコンパイルが通ります。
- インターフェースの例:
IERC721インターフェースをコンパイルし、interactBAYCコントラクトをデプロイしてbalanceOfBAYC()やsafeTransferFromBAYC()を呼び出します。インターフェースの関数が;で終わる構文と、インターフェース経由の外部コールが動作する様子を確認できます。
まとめ
この講では、Solidity の**抽象コントラクト(abstract)とインターフェース(interface)**を紹介しました。いずれも「コントラクトの雛形」を書き、ソースコードの冗長性を低減するために使われます。
- 未実装関数を含むコントラクトは
abstract宣言が必須で、未実装関数にはvirtualを付けます。 interfaceは状態変数・コンストラクタを持てず、全関数がexternalかつ本体なし、という5つのルールに従います。ERC721標準のインターフェースIERC721(3イベント・9関数)は NFT コントラクト相互運用の共通言語です。- インターフェースを型として利用すれば、BAYC のような既存プロジェクトの詳細実装を知らなくても安全に対話できます。
さらに深く学びたい方は、本リポジトリの第14講の readme(日本語版)、インターフェース関連のソース一式、そして実際の ERC721 コントラクト実装(34_ERC721/ERC721.sol) をあわせて読むことで、宣言と実装の対応関係をより確実に理解できます。
【免费下载链接】WTF-SolidityWTF Solidity 极简入门教程,供小白们使用。Now supports English! 官网: https://wtf.academy项目地址: https://gitcode.com/GitHub_Trending/wt/WTF-Solidity
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考