スマートコントラクトプログラミング言語であるSolidityのコントラクトの継承について記載します。
Solidityコントラクトの継承について
オブジェクト指向プログラミング言語の最も重要な機能の1つである概念に継承という処理があります。
継承は、プログラムの機能を拡張する方法であり、コードを分離し、依存関係を減らし、既存のコードの再利用性を高めるために使用されています。
Solidityではスマートコントラクト間の継承をサポートしています。複数のコントラクトを1つのコントラクトに継承することができるのです。
他のコントラクトが機能を継承するコントラクトをベースコントラクトと呼び、機能を継承するコントラクトを派生コントラクトと呼びます。
Solidityではこれらのコントラクトのことを、親子コントラクトとも呼んでいます。
Solidity における継承の範囲は、public および internal modifiers にのみ限定されています。
Solidityでの継承機能について、簡単にまとめておきます。
- 派生コントラクト(子コントラクト)は、状態変数(ステート変数)や内部メソッドなど、プライベートでないすべてのメンバにアクセスすることができる。しかし、これを使うことは許されない。
- 関数のオーバーライドは、関数のシグネチャが同じであれば可能。出力パラメータが異なる場合、コンパイルに失敗する。
- スーパーコントラクトの関数を呼び出すには、super キーワードを使用するか、スーパーコントラクト名を使用する。
Solidity は、下記のようなさまざまなタイプの継承をサポートしています。
- 単一継承
- マルチレベル継承
- 階層的継承
- 多重継承
- 抽象的な継承(Abstract Contract)
単一継承
単一継承とは、ベースとなるコントラクト(親コントラクト)の関数や変数が、1つの派生コントラクト(子コントラクト)にのみ継承されることを言います。
以下は、親コントラクトが子コントラクトに継承され、単一継承をしている例です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
// SPDX-License-Identifier: MIT pragma solidity >=0.4.16 <0.9.0; // 継承元のコントラクトA作成 contract A{ uint internal sum; // 内部状態変数sumの値を設定する外部関数の定義 function setValue() external { uint a = 20; uint b = 30; sum = a + b; } } // 継承先のコントラクトB作成 contract B is A{ // 内部状態変数sumの値を返す外部関数の定義 function getValue() external view returns(uint) { return sum; } } // コーリング用 contract caller { // 子契約オブジェクトの作成 B b = new B(); // setValue関数とgetValue関数を呼び出す関数の定義 function InheritanceCheck() public returns (uint) { b.setValue(); return b.getValue(); } } |
上記コードをコンパイルし、callerコントラクトをDeploy,InheritanceCheck関数を動かすと下記結果が表示されます。
2. マルチレベル継承
マルチレベル継承は、単一継承とよく似ていますが、親と子の関係にレベルがある点が異なります。
親から派生した子のコントラクトは、そこから派生したコントラクト(孫コントラクト)に対しても親の機能を継承します。
以下は、ContractのAがBに継承され、コントラクトのBがCに継承されたマルチレベル継承のソースコードです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
// SPDX-License-Identifier: MIT pragma solidity >=0.4.16 <0.9.0; // 親契約の定義A contract A { // 状態変数の宣言 string internal x; string a = "0x" ; string b = "Brokers"; // Defining external function to return concatenated string function getfromA() external{ x = string(abi.encodePacked(a, b)); } } // 連結された文字列を返す外部関数の定義 contract B is A { // 子契約Bの状態変数の宣言 string public y; string c = "by 0xMasa"; // 連結された文字列を返す外部関数の定義 function getfromB() external payable returns(string memory){ y = string(abi.encodePacked(x, c)); } } // 親契約Aを継承した子契約Cの定義 contract C is B { // 子契約Bで生成された連結文字列を返す外部関数を定義する。 function getfromC() external view returns(string memory){ return y; } } // 通話契約の定義 contract caller { // 子 C のオブジェクトを作成する C cc = new C(); // 最終的に連結された文字列を返すパブリック関数を定義する function testInheritance() public returns (string memory) { cc.getfromA(); cc.getfromB(); return cc.getfromC(); } } |
上記コードをコンパイルし、callerコントラクトをDeploy,testInheritance関数を動かすと下記結果が表示されます。
3. 階層的継承
階層型継承とは、親コントラクトが複数の子コントラクトを持つことを指します。
これは主に、共通の機能を異なる場所で使用する場合に使用されています。
親のコントラクトが携帯電話、親のコントラクトを継承した子供の契約がiPhoneとAndroidで枝分かれすると考えるとわかりやすいのではないでしょうか。
以下は、コントラクトAがBに継承され、コントラクトAがCにも別に継承されており、BとCでそれぞれ階層的な継承が行われているソースコードです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
// SPDX-License-Identifier: MIT pragma solidity >=0.4.16 <0.9.0; // 親コントラクトAの定義 contract A { string internal x; // 内部状態変数に値を設定する外部関数の定義 function getA() external { x = "0xBrokers"; } // 内部ステート変数 uint internal sum; // 内部状態変数sumの値を設定する外部関数を定義 function setA() external { uint a = 20; uint b = 30; sum = a + b; } } // 親コントラクトAを継承した子契約コントラクトBの定義 contract B is A { // 状態変数xを返す外部関数を定義 function getAstr() external view returns(string memory){ return x; } } // 親コントラクトAを継承した子契約コントラクトCの定義 contract C is A { // 状態変数の和を返す外部関数を定義 function getAValue() external view returns(uint){ return sum; } } // コントラクト呼び出しContractの定義 contract caller { // コントラクトBのオブジェクトを作成 B contractB = new B(); // コントラクトCのオブジェクトを作成 C contractC = new C(); // ステート変数xとsumの値を返すパブリック関数の定義 function InheritanceCheck() public returns (string memory, uint) { return (contractB.getAstr(), contractC.getAValue()); } } |
上記コードをコンパイルし、callerコントラクトをDeploy,InheritanceCheck関数を動かすと下記結果が表示されます。
4.多重継承
多重継承とは、1つのコントラクトを多くのコントラクトから継承することができことをさします。
親コントラクトは複数の子コントラクトを持つことができ、子コントラクトは複数の親コントラクトを持つことができます。
以下は、コントラクトAがBに継承され、コントラクトCがA、Bを継承している多重継承の例を示したスマートコントラクトコード例です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
// SPDX-License-Identifier: MIT pragma solidity >=0.4.16 <0.9.0; // 契約Aを定義する contract A { // 内部ステート変数の宣言 string internal x; // 内部状態変数xの値を設定する外部関数の定義 function set_x() external { x = "0xBrokers"; } } // 契約Bの定義 contract B { // 内部状態変数の宣言 uint internal y; // 内部状態変数yの値を設定する外部関数を定義する。 function set_y() external { uint a = 2; uint b = 20; y = a ** b; } } // コントラクトA、Bを継承した子契約Cの定義 contract C is A, B { // ステート変数xを返す外部関数を定義する function get_x() external returns(string memory) { return x; } // ステート変数yを返す外部関数の定義 function get_y() external returns(uint) { return y; } } // Contract呼び出しCallerコントラクトの定義 contract caller { // コントラクトCのオブジェクトを作成 C contractC = new C(); // 関数 get_x と get_y から値を返す publicファンクションの定義 function testInheritance() public returns(string memory, uint) { contractC.set_x(); contractC.set_y(); return (contractC.get_x(), contractC.get_y());} } |
上記コードをコンパイルし、callerコントラクトをDeploy,testInheritance関数を動かすと下記結果が表示されます。
5.抽象的な契約(Abstract Contract)
抽象的な契約とは少なくとも一つの関数を含むが、実装を含まないコントラクトのことを指します。
抽象コントラクトは、子コントラクトの基礎となり、子コントラクトがその機能を継承して利用できるようにします。
Solidity には、抽象コントラクトを確立するための設定はありません。以前までのVersionではcontractの前にabstractをつけることによって明示的に表現していましたが、現在では撤廃されています。
コントラクトに未実装の関数が含まれている場合、それは抽象的なContractとEVMが判断します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
// SPDX-License-Identifier: MIT pragma solidity >=0.4.16 <0.9.0; contract AbstractSample { function get() public view returns(uint); } contract x is AbstractSample { function get() public view returns(uint) { uint a = 10; uint b = 20; uint sum = a + b; return sum; } } |
このソースコードでは下記の実装をしています
- 抽象的な契約Abstract SampleのContractを定義
- x コントラクトは、Abstract Sampleを継承。
- 子コントラクトに実装されたすべての抽象関数は、子コントラクトオブジェクトを使用して呼び出されます。
このソースコードをコンパイルしてみましょう。コンパイルは何も問題なく可能です。
しかし、抽象的な契約(Abstract Contract)であるAbstract Sampleをイーサリアムブロックチェーン(EVM)へDeployしようとすると下記の警告が出てDeployすることが出来ない形になります。
EVMデプロイ時のAlert
抽象的なContractであるAbstractSampleを継承したxであれば問題なく、Deploy出来ます。
上記コードをコンパイルし、xコントラクトをDeploy,get関数を動かすと下記結果が表示されます。
最後に
今回紹介したSolidityの仕様やコードをRemix IDEやHardhatで実際に実行やテストをして確認してみましょう。
実際に動かしたり、テストすることで理解が深まるかと思います。
hardhatのインストール手順
HardhatでスマートコントラクトのDeploy手順