はじめに

『DApps開発入門』という本や色々記事を書いているかるでねです。

今回は、Ethereum上で型定義とデータ保存を分離し、データの共有を行う仕組みを提案しているERC2157についてまとめていきます!

以下にまとめられているものを翻訳・要約・補足しながらまとめていきます。

他にも様々なEIPについてまとめています。

概要

**ERC2157*は、分散型型システムである「dType」のためのストレージ拡張を提案しています。 ERC1900を拡張する形で、型インスタンス(type instance)を保持するストレージコントラクトに対して、共通のABI(Application Binary Interface)を定義します。 これにより、すべてのdTypeストレージコントラクトに対して同じ手順でデータを取得できるようになり、オンチェーンおよびオフチェーンにおける型データの活用がより容易になります。

ERC1900については以下の記事を参考にしてください。

この標準化によって、開発者は特定の型に基づいたグローバルな公開データに依存するアプリケーション(例:分散型マーケットプレイス)を簡単に構築できるようになります。

動機

現在のEthereumでは、オンチェーンのデータに対して決定論的なアドレス指定(addressability)を行う標準が存在しません。 これは、privateや限定的な利用を前提としたデータには問題になりませんが、publicに公開されるべきデータにとっては課題です。

ERC1900はデータ型の標準化を通じてプロジェクト間の相互運用性を高めていますが、それだけではグローバルなデータエコシステムを構築するには不十分です。 データの格納場所とその取得方法が予測可能(=決定論的)であることで、オンチェーン・オフチェーンを問わず、誰でも同じデータセットにアクセスして新しいサービスを構築することが可能になります。

例えば、マーケットプレイスの型に基づいて情報を格納する場合、データの保存場所が標準化されていれば、検索や商品情報の取得が自動的に行えます。 これは中央集権的なキャッシュに依存せず、より分散化されたデータ利用を可能にします。

また、コントラクトのインターフェースが標準化されていれば、ABIの推定も可能になり、dTypeレジストリと組み合わせることで必要な情報を自動で再構築できます。

将来的には、この仕組みにアクセス制御や変更権限の管理機能を追加することで、公開型システムにおける一貫した権限管理も実現できます。 さらに、Ethereumのシャード間や他チェーンとのデータブリッジにも活用でき、チェーン間で同じ型定義とストレージ形式を用いることで、データの移行も容易になります。

このように、型の定義とストレージを分離するアプローチにより、Solidityのような非関数型言語でも関数型プログラミングに近い設計が可能になります。

仕様

TypeRootContract

ERC1900では、型のメタデータに含まれるcontractAddressフィールドは、その型定義が存在するEthereum型ライブラリのアドレスを示していました。 しかし、ERC2157ではそのフィールドにTypeRootContractのアドレスを格納します。

TypeRootContractは、対象となる型の定義ライブラリ(libraryAddress)と、その型インスタンスを保持するストレージコントラクト(storageAddress)の両方を記録します。 これにより、型の定義とインスタンスデータの保存先を明確にリンクできます。

TypeRootContract.sol
contract TypeRootContract {
  address public libraryAddress;
  address public storageAddress;

  constructor(address _library, address _storage) public {
    libraryAddress = _library;
    storageAddress = _storage;
  }
}

型の定義ライブラリと、型データの格納先ストレージコントラクトを紐付けるコントラクト。 このコントラクトは、型の定義(library)とそのデータの保管先(storage)を関連付け、データアクセスを容易にする。

  • libraryAddress

    • ERC1900で定義された型ライブラリのEthereumアドレス。
  • storageAddress

    • 型のインスタンスデータを保持するTypeStorageContractのEthereumアドレス。

TypeStorageContract

TypeAStorage.sol
import './TypeALib.sol';

contract TypeAStorage {
    using TypeALib for TypeALib.TypeA;

    bytes32[] public typeIndex;
    mapping(bytes32 => Type) public typeStruct;

    struct Type {
        TypeALib.TypeA data;
        uint256 index;
    }

    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(TypeALib.TypeA memory data) public returns (bytes32 identifier);

    function insertBytes(bytes memory data) public returns (bytes32 identifier);

    function remove(bytes32 identifier) public returns(uint256 index);

    function update(bytes32 identifier, TypeALib.TypeA memory data) public returns(bytes32 identifier)

    function isStored(bytes32 identifier) public view returns(bool stored);

    function getByHash(bytes32 identifier) public view returns(TypeALib.TypeA memory data);

    function getByIndex(uint256 index) public view returns(TypeALib.TypeA memory data);

    function count() public view returns(uint256 counter);
}

TypeStorageContractは、特定の型に基づいたインスタンスデータを保持し、標準化されたインターフェースでアクセス可能にするコントラクトです。 各データは主識別子(identifier)でアドレス可能となり、その識別子は、型ライブラリ内のgetIdentifier関数によって算出されます。

ERC2157では、Solidityにおける典型的なCRUD(Create, Read, Update, Delete)パターンを用いて、データの挿入・更新・削除・取得を提供します。 各レコードには一意の識別子と、追加順を示すインデックスが付きます。

イベントと構造体

Type
javascript
struct Type {
    TypeALib.TypeA data;
    uint256 index;
}

型インスタンスのデータとその登録順を表す構造体。 TypeAに準拠したデータを保持し、追加された順序を示すインデックスを含む。

パラメータ

  • data
    • TypeALibによって定義された型インスタンス。
  • index
    • 追加順を表す連番(インデックス)。
LogNew
javascript
event LogNew(bytes32 indexed identifier, uint256 indexed index);

新しいデータが追加された時に発行されるイベント。 識別子とその追加順(インデックス)を示す。

パラメータ

  • identifier
    • データの一意な識別子。
  • index
    • データが追加された順序を示すインデックス。
LogUpdate
javascript
event LogUpdate(bytes32 indexed identifier, uint256 indexed index);

既存のデータが更新された時に発行されるイベント。 更新されたデータの識別子とインデックスを示す。

パラメータ

  • identifier
    • 更新対象の識別子。
  • index
    • 更新されたデータのインデックス。
LogRemove
javascript
event LogRemove(bytes32 indexed identifier, uint256 indexed index);

データが削除された時に発行されるイベント。 削除されたデータの識別子とインデックスを示す。

パラメータ

  • identifier
    • 削除対象の識別子。
  • index
    • 削除されたデータのインデックス。

関数

insert
javascript
function insert(TypeALib.TypeA memory data) public returns (bytes32 identifier);

型データを挿入する関数。 指定されたTypeAデータをストレージに追加し、識別子を返す。

引数

  • data
    • 挿入するTypeA構造体データ。

戻り値

  • identifier
    • 登録されたデータの識別子。
insertBytes
javascript
function insertBytes(bytes memory data) public returns (bytes32 identifier);

バイト形式の型データを挿入する関数。 バイト列からTypeAデータを復元し、ストレージに保存する。

引数

  • data
    • バイト列で渡されたTypeAデータ。

戻り値

  • identifier
    • 登録されたデータの識別子。
remove
javascript
function remove(bytes32 identifier) public returns(uint256 index);

指定されたデータを削除する関数。 識別子に対応するデータを削除し、そのインデックスを返す。

引数

  • identifier
    • 削除対象のデータ識別子。

戻り値

  • index
    • 削除されたデータのインデックス。
update
javascript
function update(bytes32 identifier, TypeALib.TypeA memory data) public returns(bytes32 identifier);

既存のデータを更新する関数。 識別子に対応するTypeAデータを新しい内容で上書きする。

引数

  • identifier
    • 更新対象の識別子。
  • data
    • 新しいTypeAデータ。

戻り値

  • identifier
    • 更新後の識別子(基本的に同一)。
isStored
javascript
function isStored(bytes32 identifier) public view returns(bool stored);

指定された識別子のデータが存在するかを確認する関数。 データストレージ内に識別子が存在するかを真偽値で返す。

引数

  • identifier
    • 確認対象の識別子。

戻り値

  • stored
    • データが存在するかどうか。
getByHash
javascript
function getByHash(bytes32 identifier) public view returns(TypeALib.TypeA memory data);

識別子を用いてデータを取得する関数。 指定した識別子に対応するTypeAデータを返す。

引数

  • identifier
    • 取得対象の識別子。

戻り値

  • data
    • 対応するTypeAデータ。
getByIndex
javascript
function getByIndex(uint256 index) public view returns(TypeALib.TypeA memory data);

インデックスを用いてデータを取得する関数。 追加順に基づくインデックスからTypeAデータを取得する。

引数

  • index
    • 取得対象のインデックス。

戻り値

  • data
    • 対応するTypeAデータ。
count
javascript
function count() public view returns(uint256 counter);

格納されているデータの総数を返す関数。 ストレージ内に存在するTypeAデータの数を返す。

戻り値

  • counter
    • 登録されているTypeAデータの総数。

補足

現在のEthereumスマートコントラクト設計は、内部的にしか理解できない状態変更関数をもつ「カプセル化されたオブジェクト」として扱われがちです。 これはオブジェクト指向プログラミング(OOP)に近い設計ですが、グローバルなEthereumオペレーティングシステム(OS)を目指す上では、相互運用性やスケーラビリティの面で課題となります。

そのため、ERC2157では「データ」、「ビジネスロジック」、「データ構造定義」を分離するアーキテクチャを採用します。 特に公開データを型ごとに分類し集約することで、誰でもそのデータを基にしたツールやサービスを構築できるようになります。 これはWeb2で一般的だった閉じたデータ構造や分散的な情報配置とは異なる、Web3ならではの革新的なアプローチです。

また、TypeStorageコントラクトをdTypeレジストリに拡張フィールドとして埋め込むのではなく、独立したTypeRootContractとして定義することで、将来的なインターフェースの変更や拡張を容易にします。 より柔軟で保守性の高い設計といえます。

さらに、dType自体とすべてのTypeStorageコントラクトに対して共通のストレージパターンを採用することで、開発・テスト・監査のコスト削減が期待されます。

この設計パターンによって、以下の3つが保証されます。

  • 主識別子による型インスタンスのアドレス可能性
  • 全レコードの取得方法の標準化
  • 登録レコード数の取得機能

互換性

本提案は既存のEthereum標準や実装に影響を与えません。現在のSolidityにおける実験的なABIエンコーダ(ABIEncoderV2)を活用していますが、これもSolidityにおいては既に一般的に使用されているため、後方互換性を損なうことはありません。

参考実装

引用

Loredana Cirstea (@loredanacirstea), Christian Tzurcanu (@ctzurcanu), "ERC-2157: dType Storage Extension - Decentralized Type System for EVM [DRAFT]," Ethereum Improvement Proposals, no. 2157, June 2019. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-2157.

最後に

今回は「Ethereum上で型定義とデータ保存を分離し、データの共有を行う仕組みを提案しているERC2157」についてまとめてきました! いかがだったでしょうか?

質問などがある方は以下のTwitterのDMなどからお気軽に質問してください!

他の媒体でも情報発信しているのでぜひ他も見ていってください!