OlympusV3 の契約を読んでいると、アーキテクチャの設計が非常に印象的であり、明確なアーキテクチャがあり、スマートコントラクト領域での高い凝集度と低い結合度の設計思想が実現されており、それにより、モジュールごとに深く独立して読むことに集中でき、心の負担が軽減されています。
OlympusV3 のアーキテクチャ図は以下の通りです:
後で、このフレームワークが Olympus 独自のものではないことを知りました。Olympus が契約アーキテクチャを紹介する記事で、Default フレームワークに言及しています。
スマートコントラクトアーキテクチャの複雑さ#
Default フレームワークを紹介する前に、著者は伝統的なプロトコルである AAVE MakerDAO のアーキテクチャ図を比較しました。
アーキテクチャ図だけでは、開発者は呼び出しのリンクを明確にすることは困難です。著者はこの設計を、プロセスを中心に取り組むアプローチとして説明しています。
Today, most projects take a process-centric approach towards protocol development: each smart contract is developed to manage a process along a chain of logic that is triggered when external interactions occur.
したがって、ビジネスのアップグレードや交代に伴い、論理チェーンの複雑さは指数関数的に増加します。これは、プロトコル全体がネットワークアーキテクチャを持っているためであり、おそらく 2 つのポイントごとに論理チェーンが存在するため、1 つのポイントを追加するたびに、複雑さが増加し、カバーするのが難しいです。
ソフトウェアエンジニアリングの設計パターンの進化プロセスと同様に、スマートコントラクトのエンジニアリングアーキテクチャも改善が続けられており、Default Framework はこの問題に取り組んでいます。
The Default Framework とは何ですか#
名前だけでは設計思想がわからず、Diamond Frameworkのように連想を起こさせることはありません。The Default Framework は特に革新的なものではなく、伝統的な開発領域の設計思想を借りて、コントラクト全体をレイヤー化しています。
- Modules:内部向けで、モジュールはマイクロサービスと考えることができ、各モジュールはデータの一部を管理し、依存関係は存在せず、高度に独立しており、モジュールへのアクセスはすべてアクセス制御によって制約されます。
- Policy:外部呼び出し向けで、内部で異なるモジュールを組織するためのもので、ビジネスロジックを実現します。
コアの管理ロジックはすべてKernal.solファイルに集約されており、グローバルな Policy と Modules を管理しています。
Modules#
各モジュールには固有の KEYCODE と VERSION が含まれており、モジュールを定義する際にこのメソッドを実装する必要があります。Kernal では、すべての keycode とモジュールのマッピングを維持します。
Kernal には以下のメソッドが含まれます。
- _installModule:モジュールのインストール
- _upgradeModule:モジュールのアップグレード
例:
/// @inheritdoc Module
function KEYCODE() public pure override returns (Keycode) {
return toKeycode("MINTR");
}
/// @inheritdoc Module
function VERSION() external pure override returns (uint8 major, uint8 minor) {
major = 1;
minor = 0;
}
Policy#
Policy は、伝統的なソフトウェア開発のサービス層と考えることができ、一連のモジュールを組織するために使用されます。以下のメソッドが含まれます:
- configureDependencies:どのモジュールをサポートするかを返します。
- requestPermissions:サポートされているモジュールの具体的な関数セレクタを返します。
Kernal には以下のメソッドが含まれます:
- _activatePolicy
- _deactivatePolicy
例:
/// @inheritdoc Policy
function configureDependencies() external override returns (Keycode[] memory dependencies) {
dependencies = new Keycode[](3);
dependencies[0] = toKeycode("MINTR");
dependencies[1] = toKeycode("TRSRY");
dependencies[2] = toKeycode("ROLES");
MINTR = MINTRv1(getModuleAddress(dependencies[0]));
TRSRY = TRSRYv1(getModuleAddress(dependencies[1]));
ROLES = ROLESv1(getModuleAddress(dependencies[2]));
(uint8 TRSRY_MAJOR, ) = TRSRY.VERSION();
(uint8 MINTR_MAJOR, ) = MINTR.VERSION();
(uint8 ROLES_MAJOR, ) = ROLES.VERSION();
// Ensure Modules are using the expected major version.
// Modules should be sorted in alphabetical order.
bytes memory expected = abi.encode([1, 1, 1]);
if (MINTR_MAJOR != 1 || ROLES_MAJOR != 1 || TRSRY_MAJOR != 1)
revert Policy_WrongModuleVersion(expected);
}
/// @inheritdoc Policy
function requestPermissions() external view override returns (Permissions[] memory requests) {
Keycode MINTR_KEYCODE = MINTR.KEYCODE();
requests = new Permissions[](4);
requests[0] = Permissions(MINTR_KEYCODE, MINTR.mintOhm.selector);
requests[1] = Permissions(MINTR_KEYCODE, MINTR.burnOhm.selector);
requests[2] = Permissions(MINTR_KEYCODE, MINTR.increaseMintApproval.selector);
requests[3] = Permissions(MINTR_KEYCODE, MINTR.decreaseMintApproval.selector);
}