ブロックチェーン・仮想通貨・暗号資産の開発ブログ

Solidityの多重継承やダイヤモンド継承の挙動や優先順位について

Ethereum Advent Calendar 2018の11日目の記事です。

今回は、TrueUSDなど、Solidityの多重継承を活用して上手にモジュール化したプロジェクトを見ていて、Solidityでの多重継承の挙動がどのように行われるか不安になったので調べてみました。

本記事では、子コントラクトで親コントラクトのメソッドを実装しない場合どうなるのか、子コントラクトで親コントラクトのメソッドを実装しsuper.methodNameのようにして親コントラクトのメソッドを呼び出した場合どうなるのか、多重継承している場合はどうなるのか、ダイヤモンド継承をしている場合はどうなるかを解説します。

また、今回はSolidityの単純な継承の説明は省略しています。

実行環境は以下です。

  • Solidity 0.5.1+commit.c8a2cb62.Emscripten.clang
  • Remix
  • Rinkebyテストネットワーク

単純な多重継承

まず簡単のために、2つの親クラスを持つコントラクトで考えます。クラス図で表すと以下のような感じです。CはA, Bの順に継承します。

つまり、コントラクトCはコントラクトAとコントラクトBを多重継承しています。コードは以下のようになります。


コントラクトCはfooとbarをオーバーライドしており、bazは継承したメソッドをそのまま使うようにしています。さらに、fooはsuper.foo()を実行しています。

この実装でどのように結果が変わるかですが(すでに図に書いてますが)、以下のようになりました。

  • C.fooを実行すると、Log("C"), Log("B")が実行された
  • C.barを実行すると、Log("C")が実行された
  • C.bazを実行すると、Log("B")が実行された

ここから分かるのは(Solidityのドキュメントにも書いてありますが)、Solidityは右側から多重継承するということです。

つまり、contract C is A, B {}のように実装したとき、親クラスのメソッドを評価するときにはAよりも先にBを評価するということです。なので、C.bazを実行するとコントラクトBのbazが実行されました。

なお、Pythonの多重継承は左側から継承するため、Pythonに慣れた人は別の注意が必要です。

次にC.barですが、C.barは親クラスのメソッドをオーバーライドしており、親クラスのA.barやB.barは評価されません。これはJavaなど一般的な継承によるオーバーライドと同じ挙動です。

面白いのはC.fooの挙動で、まずオーバーライドされたC.fooが実行され、次にsuper.fooによってB.fooが実行されます。そして、Aは特に実行されずに関数の実行が終了されます。

どういうときにAの関数が実行されるかというと、Bで実装されていない関数があり、Cでもオーバーライドされていなければ実行されることになります。

ダイヤモンド継承(菱形継承)

次に、いわゆるダイヤモンド継承(菱形継承)の場合を考えてみます。クラス図は以下のようになります。CはA, Bの順に継承します。


CはA, Bを多重継承しており、AとBもそれぞれDを継承するというダイヤモンド継承(菱形継承)の形になっています。コードは以下のようになります。


このときCの各関数を実行すると以下のような結果が得られます。

  • C.fooを実行すると、Log("C"), Log("B"), Log("A"), Log("D")が実行された
  • C.barを実行すると、Log("C"), Log("B"), Log("A")が実行された
  • C.bazを実行すると、Log("C"), Log("B")が実行された
  • C→B→D→Aのように実行する経路は無い

コントラクトA, B, C全てでsuper.foo()を追加したfooメソッドを実行すると、全てのコントラクトのfooメソッドが実行されました。評価の順番はCが最初で、一つ上の階層をB, Aと右から評価し、最後に最上層のDが実行されました。

次に、barの前にbazを考えます。C.bazの次に右からB.bazが実行されます。B.bazにはsuper.bazが入っていないのでここで処理が終了します。

最後にC.barですが、C.barの次に右からB.barが実行され、B.barのsuper.barはA.barを実行し、A.barにはsuper.barが入っていないのでここで処理が終了します。

barは最も注目すべきメソッドで、ダイヤモンド継承(菱形継承)されたとき、barの挙動を見ると幅優先探索のように親クラスよりも先に兄弟クラスのメソッドを先に探索することがわかります。

まとめ

Solidityの多重継承やダイヤモンド継承(菱形継承)における関数実行の挙動をまとめると、

  • 関数実行しようとしているコントラクトにその関数が実装されていなければ、親コントラクトの関数を実行する
  • 複数の親コントラクトを多重継承している場合は右側から探索し、最初に見つかった関数を実行する
  • super.methodNameによる呼び出しの基本は、最後に継承した子コントラクトが走査する順に探索して実行するということ(多重継承していれば親を見ずすぐ左のコントラクトを探索する)
    • ある子コントラクトが多重継承しようとしている親コントラクトの一つからsuper.methodNameを呼び出す場合、さらなる親コントラクトがあったとしても、子コントラクトが走査する順に(右から順に)次のコントラクトを探索し、最初に見つかった関数を実行する

というようになります。

いかがでしょうか。TrueUSDのコントラクトはさらに複雑な継承関係になっていますが、このルールを意識して読むと理解が進むかと思います。

Shun Takagiwa / 高際 隼

Blockchain Engineer at LayerX inc.
コメント機能やTwitterなどでお気軽にコメントいただけると嬉しいです。

0 件のコメント :

コメントを投稿

Leave a Comment...