はじめに
『DApps開発入門』という本や色々記事を書いているかるでねです。
今回は、Ethereum上に分散型の型レジストリ(dType)を導入することで、型定義やABI情報の標準化と再利用を可能にする仕組みを提案しているERC1900についてまとめていきます!
以下にまとめられているものを翻訳・要約・補足しながらまとめていきます。
他にも様々なEIPについてまとめています。
概要
Ethereum Virtual Machine(EVM)とSolidityなどの関連言語を「単一のOS(世界のコンピュータ)」へと進化させるには、拡張可能な型システムに対するコンセンサスが必要です。
ERC1900では、Ethereum上における分散型の型システムを構築するため、オンチェーンの型レジストリ(dType)と、構造体(struct)を基盤とした型の作成インターフェースを定義します。
これによりABI(Application Binary Interface)の一貫性が保たれます。
動機
Ethereum上で相互運用可能なプロトコルを構築するには、データ標準の整備が不可欠です。
データが標準化されれば、オンチェーン上の情報のやり取りが円滑になり、オフチェーンにおいてもブロックチェーンエクスプローラーの分析精度が向上し、開発者が既存の型を新しいスマートコントラクトで再利用しやすくなります。
ERC1900は、単なる型レジストリにとどまりません。
将来的には、以下のような拡張を予定しています。
- データストレージを持つ分散型型システム(ERC2158)
- データの操作・更新方法を知っている純粋関数ライブラリ(dType Functions Extension)
これにより、Ethereum上に汎用的な関数型プログラミング環境が構築され、開発者は再利用可能なビルディングブロックを使って効率的に開発ができるようになります。
以下はERC1900の提案の要点です。
- Ethereum全体でのデータとその関係性の統合手段を提供。
- 各データ型に対する「ふるまい」もアドレスできる仕組み。
-
map
,reduce
,filter
などの関数を型ライブラリで実装可能。 - Solidityの開発ツール(例:Remix)にシームレスに統合可能。
- 将来的にはEVMに対するプリコンパイル型のサポートも視野に入れている。
- 他言語の型定義との互換性も持たせる(型定義はSwarmに格納されたソースコードに含まれる)。
- dTypeデータベースはEthereumの「システムレジストリ」の一部として機能することを目指す。
仕様
ガバナンスの方針
型レジストリ(dType)は、CRUD(作成・読み取り・更新・削除)操作に対してガバナンスプロトコルを備えることが可能です。
ただし、ERC1900ではガバナンスやアクセス制御に関する仕様は扱いません。
型定義とメタデータ
dTypeレジストリは、Solidityにおける基本型および複合型の登録をサポートし、さらにコントラクトイベントの定義にも対応します。
ERC1900では、Solidityで定義されたユーザー定義型に必要な最小限のオンチェーン型定義とメタデータに焦点を当てます。
型定義:TypeLibrary
型定義は、「型ライブラリ」と呼ばれる構造体ベースの定義に加え、以下のような関数を含みます。
-
isInstanceOf
- ある変数が定義された型に準拠しているかを判定。
- 高階関数(HOF)
-
map
,filter
,reduce
など。
-
-
structureBytes
/destructureBytes
- 構造化・非構造化操作を行い、低レベルの処理やアセンブリ呼び出しの際に便利です。
pragma solidity ^0.5.0;
pragma experimental ABIEncoderV2;
library myBalanceLib {
struct myBalance {
string accountName;
uint256 amount;
}
function structureBytes(bytes memory data) pure public returns(myBalance memory balance)
function destructureBytes(myBalance memory balance) pure public returns(bytes memory data)
function isInstanceOf(myBalance memory balance) pure public returns(bool isInstance)
function map(
address callbackAddr,
bytes4 callbackSig,
myBalance[] memory balanceArr
)
view
internal
returns (myBalance[] memory result)
}
型は他の型を含むことができ、その場合は有向非巡回グラフ(DAG)となります。
型メタデータ:dType Registry
型メタデータはdTypeレジストリコントラクト上に保存され、以下を含みます。
-
name
- 型名(Solidityの型名に準ずる)。
-
typeChoice
- 型の種類を示す列挙型(例:基本型、関数、イベントなど)。
-
contractAddress
- 型ルートコントラクトのアドレス。
-
source
- Swarmに格納された型定義のソースコードのハッシュ。
-
types
- サブタイプ情報。各フィールドは名前、ラベル、配列の次元情報を持ちます。
型メタデータの例
基本型の例
{
"contractAddress": "0x0000000000000000000000000000000000000000",
"typeChoice": 0,
"source": "0x...",
"name": "uint256",
"types": []
}
複合型の例
{
"contractAddress": "0x1056...c268",
"typeChoice": 0,
"source": "0x...",
"name": "myBalance",
"types": [
{"name": "string", "label": "accountName", "dimensions": []},
{"name": "uint256", "label": "amount", "dimensions": []}
]
}
dTypeレジストリのデータ構造
enum TypeChoices {
BaseType,
PayableFunction,
StateFunction,
ViewFunction,
PureFunction,
Event
}
struct dTypes {
string name;
string label;
string[] dimensions;
}
struct dType {
TypeChoices typeChoice;
address contractAddress;
bytes32 source;
string name;
dTypes[] types;
}
struct Type {
dType data;
uint256 index;
}
mapping(bytes32 => Type) public typeStruct;
bytes32[] public typeIndex;
識別子は keccak256(abi.encodePacked(name))
によって生成され、将来的には言語名を含めた形(keccak256(abi.encodePacked(language, name))
)に拡張可能です。
dTypeレジストリのインターフェース
import './dTypeLib.sol';
interface dType {
event LogNew(bytes32 indexed identifier, uint256 indexed index);
event LogUpdate(bytes32 indexed identifier, uint256 indexed index);
event LogRemove(bytes32 indexed identifier, uint256 indexed index);
function insert(dTypeLib.dType calldata data) external returns (bytes32 identifier);
function remove(bytes32 identifier) external returns(uint256 index);
function count() external view returns(uint256 counter);
function getTypeIdentifier(string memory name) pure external returns (bytes32 identifier);
function getByIdentifier(bytes32 identifier) view external returns(dTypeLib.dType memory dtype);
function get(string memory name) view external returns(dTypeLib.dType memory dtype);
function isRegistered(bytes32 identifier) view external returns(bool registered);
}
イベント・構造体・変数
LogNew
event LogNew(bytes32 indexed identifier, uint256 indexed index);
新しい型が登録された時に発行されるイベント。
登録された型の識別子とインデックスを記録します。
パラメータ
-
identifier
- 型の識別子(keccak256で生成された一意の値)。
-
index
-
typeIndex
配列内のインデックス。
-
LogUpdate
event LogUpdate(bytes32 indexed identifier, uint256 indexed index);
型情報が更新された時に発行されるイベント。
指定された識別子の型に対する更新操作を示します。
パラメータ
-
identifier
- 更新対象の型の識別子。
-
index
-
typeIndex
配列内の位置。
-
LogRemove
event LogRemove(bytes32 indexed identifier, uint256 indexed index);
型が削除された時に発行されるイベント。
対象となる型の削除時に発行されます。
パラメータ
-
identifier
- 削除された型の識別子。
-
index
- 削除対象のインデックス。
関数
insert
function insert(dTypeLib.dType calldata data) external returns (bytes32 identifier);
新しい型を登録する関数。
dTypeの定義をレジストリに保存し、一意の識別子を返します。
引数
-
data
- 登録する型の情報。
戻り値
-
identifier
- 生成された型の識別子。
remove
function remove(bytes32 identifier) external returns(uint256 index);
指定した型をレジストリから削除する関数。
与えられた識別子の型データを削除し、そのインデックスを返します。
引数
-
identifier
- 削除対象の型の識別子。
戻り値
-
index
- 削除された型のインデックス。
count
function count() external view returns(uint256 counter);
登録されている型の総数を取得する関数。
typeIndex
配列の長さを返します。
戻り値
-
counter
- 型の登録数。
getTypeIdentifier
function getTypeIdentifier(string memory name) pure external returns (bytes32 identifier);
型名から識別子を生成する関数。
型名を入力として、keccak256で識別子を生成します。
引数
-
name
- 型の名前。
戻り値
-
identifier
- 生成された型の識別子。
getByIdentifier
function getByIdentifier(bytes32 identifier) view external returns(dTypeLib.dType memory dtype);
識別子から型情報を取得する関数。
指定された識別子に対応するdType構造体を返します。
引数
-
identifier
- 取得したい型の識別子。
戻り値
-
dtype
- 対応する型情報。
get
function get(string memory name) view external returns(dTypeLib.dType memory dtype);
型名から型情報を取得する関数。
名前から識別子を生成し、その型データを取得して返します。
引数
-
name
- 取得したい型の名前。
戻り値
-
dtype
- 対応する型情報。
isRegistered
function isRegistered(bytes32 identifier) view external returns(bool registered);
指定した識別子が登録済みかどうかを確認する関数。
指定された型識別子がすでにレジストリに存在するかを真偽値で返します。
引数
-
identifier
- 確認対象の型の識別子。
戻り値
-
registered
- 登録済みであれば
true
。未登録であればfalse
。
- 登録済みであれば
補足
dType型レジストリは、型のABI定義を再構築するために必要最小限の情報のみを保存します。
これにより、以下のようなメリットがあります。
- オンチェーンでの相互運用性を実現できる。
- ブロックチェーン上の副作用(イベントやストレージ変更など)をオフチェーンでデコードできるため、ブロックエクスプローラーなどの解析ツールに役立つ。
- 型付きスマートコントラクトの開発支援ツール(例:エディタ用プラグイン)が、型情報をキャッシュ・検索できるようになる。
Ethereumのようなグローバルなオペレーティングシステムの登場により、グローバルな型システムの整備が現実的になりました。
個別プロジェクトごとに独自の型を定義するのではなく、共通の型レジストリに準拠することで、エコシステム全体の連携が促進されます。
新たな型の追加や未使用型の削除に関しては、dTypeに対するガバナンスシステムが担います。
この基本仕様を整えた上で、将来的にはレジストリに保存された定義とルールに基づいたコンパイル時の静的型チェックシステムの構築を目指すことが可能です。
なお、Type Libraryはあくまで定義された型に固有の「ふるまい」のみを表現すべきです。
個別プロジェクトのビジネスロジックなどに必要な追加の処理は、別のライブラリとして後から定義するのが望ましく、それらも将来的にはdTypeに登録できる予定です。
このように、定義・データ・ふるまいを明確に分離する設計とすることで、安全かつ柔軟な部分的アップグレードが可能になります。
互換性
EIP1900は、既存のEthereum標準やスマートコントラクト実装との互換性を損なうものではありません。
型レジストリは、Solidityの実験的機能である ABIEncoderV2
を使用していますが、既存の機能や動作に影響を与えない設計となっています。
引用
Loredana Cirstea (@loredanacirstea), Christian Tzurcanu (@ctzurcanu), "ERC-1900: dType - Decentralized Type System for EVM [DRAFT]," Ethereum Improvement Proposals, no. 1900, March 2019. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-1900.
最後に
今回は「Ethereum上に分散型の型レジストリ(dType)を導入することで、型定義やABI情報の標準化と再利用を可能にする仕組みを提案しているERC1900」についてまとめてきました!
いかがだったでしょうか?
質問などがある方は以下のTwitterのDMなどからお気軽に質問してください!
他の媒体でも情報発信しているのでぜひ他も見ていってください!