Solidityについて継続して学習しています
以前、Solidityの基礎やCryptoZombieで学んだ内容などを記事にしました。その後、内容を見直して調べてみたり、実際のスマートコントラクトのプロジェクトのソースを読んでみたりして理解が不十分だった部分が分かってきたので、Solidityの基本的なアーキテクチャの確認も含めて説明したいと思います。
Solidityのプログラムアーキテクチャの概要を理解したい方、オブジェクト指向との違いなどを理解したい方はご参考にしていただければと思います。Solidityのことをあまり知らない方にも分かるように説明していきたいと思います。
Solidityプログラムの構成要素を確認します
まずは、Solidityのプログラムを構成する基本要素を確認していきたいと思います。
Solidityのプログラムは大きくコントラクト(contract)、状態変数(state variables)、関数(function)の3つの要素から構成されます。modifierやrequireといったものも特徴的な部分ではあるのですが、今回はプログラムの構造に焦点をあてるため割愛いたします。
オブジェクト指向のプログラムの書き方を比較すると、contractがclass、state varuablesがproperty、functionがmethodと対比できますが、根本的な考え方で異なる部分があるので注意が必要です。
コントラクト(contract)
コントラクト(contract)はsolidityのプログラムをカプセル化する基本ブロックの単位です。オブジェクト指向のプログラムでいうとclassに該当するものになりますが、プログラム上での扱い方はオブジェクト指向の場合とは異なることに注意が必要です。コンストラクタも同様に存在します。
オブジェクト指向だと、クラスの中にコンストラクタがあり、プログラム上からコンストラクタに値を渡すと、その値で初期化されたインスタンス(オブジェクトの実体)がメモリ上に記憶されプログラム上で扱えるようになります。
Solidityで書いたプログラムを利用できるようにするためには、イーサリアム上にデプロイすることが必要ですが、コンストラクタはデプロイの際にコントラクトに初期値を与えるために使われます。コンストラクタは一度デプロイされてしまえば、その後は使われることはありません。また、コントラクトがデプロイされるとブロックチェーン上に記録されるため、メモリ上に記憶されるだけのインスタンスとは大きく扱い方が異なります。
オブジェクト指向だとクラスからインスタンスを生成するのは、別のクラス(もしくはそのクラス内部)のプログラムから行う実装が一般的です。しかし、スマートコントラクトではそのような実装はコントラクトをデプロイすることになってしまい、メモリ上の処理ではなくブロックチェーン上に記録されることになってしまいます。その場合、ガス代もかなり必要になってしまうので、通常はそのような別のコントラクトからコントラクトを生成することは行いません。
そこを理解しておくことが、スマートコントラクトのプログラム構造を勉強する上で大きなポイントになります。
状態変数(state variables)
状態変数(state variables)はオブジェクト指向のプログラム構造でいうとpropertyに該当するものになりますが、実際のアプリケーション上での扱いは大きく異なります。コントラクトはイーサリアム上にデプロイすることで、利用できるようになりますが、その際にコントラクトの状態変数はイーサリアムのブロックチェーン上に記録されるデータという位置づけになります。
つまり、アプリケーションとしてはデータベースに該当する部分になります。
また、ブロックチェーン上にデータを書き込むには、改ざんが出来ないような仕組みを担保するために、複数のコンピュータが検証を行います。その検証のための報酬として、書き込むデータ量に応じてイーサ(暗号通貨)を支払う必要があります。それがブロックチェーンにデータを書き込むためのコストと位置づけられており、俗にガス代と言われています。
従来のデータベースにデータを記録するのと、ブロックチェーン上にデータを記録することのコストの違いは、木に文字を刻むことと、ダイヤモンドに文字を刻むことの違いをイメージしてもらうとよいです。
状態変数を更新するためには、そのガス代がかかってしまうため、メモリ上で扱われるオブジェクト指向のプロパティと比較して、取扱いを効率的に考える必要があります。なので、状態変数の定義の仕方や更新のタイミングなどをガス代を極力下げるように考慮をして設計する必要があります。
関数(function)
functionは関数です。関数についての役割はメソッドと大きく変わらないですが、状態変数(ブロックチェーン上に記録されている値)の利用方法に応じて、適切な修飾詞(参照だけならview、全く利用していない場合はpure)をつけることが求められます。関数内部ではメモリ上で使用されるローカル変数を定義することもできます。
Solidityプログラムのアーキテクチャを確認します
それでは、Solidityのプログラムのアーキテクチャを確認していきたいと思います。CryptoZombiesのソースを題材に確認していきます。
ソースリポジトリ:https://github.com/CryptozombiesHQ/cryptozombie-lessons
CryptoZombiesのプログラム構造を図で表現したものが以下のものです。
図では左2列のグレーの部分がコントラクトを表わしており、矢印方向(左側と下側)にあるコントラクトを継承(inherit)しています。例えば、ZombieOwnershipはERC721とZombiAttackを継承しており、ZombiAttackはZombieHelperを継承しているといった形です。
右から2列目の黄色の部分が状態変数、その右側が関数で、両方ともその左側のコントラクト内に実装されていることを表現しています。例えば、ZombieOwnershipの中にはzombieApprrovalsという状態変数やbalanceOfという関数を実装しているといった形です。関数に関しては、太字のものはpublicまたはexternalのもので外部から呼ぶことができるようになっています。
Solidityでは機能を分割して実装する際には、このようにコントラクトを分けて継承して実装します。理由は先ほどの、「コントラクト生成することは、イーサリアム上にデプロイすることになってしまい、ガス代がかかるため」です。オブジェクト指向プログラムのようにクラスの実体をプログラム内で自由に生成して他のプログラムに渡すということはガス代のハードルが高いので、コントラクトは機能単位に分割して継承することで実装しています。
また一番左の列のコントラクトはOpenZeppleinのコントラクトです。OpenZeppleinは安全なスマートコントラクトを実装するためのライブラリを提供しています。Ownableはコントラクトの管理者権限を実装するときの規格で、このコントラクトを継承して実装することで、管理者のみ実行できる機能を実装することができます。ERC721はNFTを実装するときの規格で、このコントラクトを継承してインターフェースで定義されている関数を実装することでOpenSeaなどの取引所で扱えるようになります。
また、OwnableとERC721では、それぞれでownerという単語が使われていますが、それぞれ、コントラクトの管理者を表現するownerと、NFT(ゾンビ)の所有者を表現するownerとで全く意味合いが異なってきますので、どちらのownerを指しているのかは、しっかり区別して確認する必要があります。
コントラクトのアーキテクチャとしては、基底コントラクトとしてゾンビトークンの生成する機能(ZombieFactory)をシステム管理者制御機能(Ownable)を継承して実装し、捕食機能(ZombieFeeding)とゾンビ管理機能(ZombieHelper)とバトル機能(ZombieAttack)といったゲーム性機能を実装、最下層でNFT機能(ERC721)を実装しているといった構造になっています。
基底コントラクト以外の4つのコントラクトについては、機能自体をこの順番で継承しなければいけない理由はないように思います。ZombieAttackではZombieFeedingのfeedAndMultiplyと_triggerCooldownを呼んでいる部分はありますが、リファクタリングによって構造を変える余地はあると思いますが、CryptoZombieのチュートリアルのカリキュラム上このような階層構造になっているのだと思います。
CryptoZombieのプログラムを改めて確認してみてください
今回はSolidityプログラムに関してオブジェクト指向との違いや、それによるアーキテクチャの違いをCryptoZombieのソースを例に確認しました。
本記事でSolidityに興味を持った方で、CryptoZombieをやったことない方は是非チュートリアルをやってみてください。すでにやったことある方でも、新たな発見があった方は、CryptoZombieのチュートリアルの内容を見直してもらえればと思います。
コメント