はじめに
『DApps開発入門』という本や色々記事を書いているかるでねです。
今回は、コントラクトの実行機能とデータ保存機能を分離して、柔軟で拡張可能な仕組みを提案しているERC725についてまとめていきます!
以下にまとめられているものを翻訳・要約・補足しながらまとめていきます。
他にも様々なEIPについてまとめています。
概要
ERC725は、スマートコントラクトにおける柔軟で長期的に活用できるデータ管理と実行の仕組みを提供するための標準提案です。
この標準は2つのサブ仕様から構成されており、それぞれ独立して使うことも、組み合わせて使うこともできます。
1つ目は、汎用的なキーと値のデータストレージ機能を提供するERC725Yです。
スマートコントラクト内部に自由な形式でデータを保存でき、例えばNFTのメタデータ拡張などにも応用できます。
2つ目は、任意のコールを実行可能にする汎用的な実行機能を提供するERC725Xです。
この仕様では、スマートコントラクト自身が外部のコントラクトを呼び出すなど、動的な処理が可能になります。
これら2つを組み合わせることで、スマートコントラクトアカウントの構築、アップグレード可能なメタデータの管理、柔軟なロジックの追加など、より多様なユースケースに対応できる拡張的なアカウントシステムが実現されます。
この組み合わせによって構築されたスマートコントラクトアカウントの標準は、LSP0-ERC725Accountとして定義されています。
動機
この標準が提案された背景には、将来的にも活用できる柔軟なスマートコントラクトアカウントシステムの必要性がありました。
Ethereumにおけるスマートコントラクトアカウントは、多くのユースケースに使われていますが、柔軟性や拡張性に欠けることがあります。
例えば、ある程度まで機能を実装したアカウントでも、後から新しい仕様に対応したり、メタデータを追加したりすることが難しいことがあります。
ERC725では、そのような課題を解決するために、汎用的なデータストレージと実行機能を別々の仕様として定義しました。
これにより、必要に応じて一方だけを利用することも、両方を組み合わせてアカウントを構築することもできるようになります。
また、ERC725はアカウント用途以外でも利用できます。
例えば、ERC725YはNFTやトークンのメタデータ管理、オンチェーンプロファイルの拡張などにも応用でき、ERC725Xは柔軟なオペレーション処理を実現する仕組みとして使用可能です。
このように、ERC725の2つの仕様は、再利用性の高いビルディングブロックとしてさまざまなスマートコントラクトの実装基盤となることを目的としています。
仕様
概要
ERC725XとERC725Yは、スマートコントラクトに柔軟性を持たせるための2つの標準仕様です。
それぞれ単体でも使えますが、組み合わせることで汎用的なアカウントコントラクトを構築する基盤となります。
- ERC725Xは、コントラクトから他のコントラクトへの呼び出し、ネイティブトークンの送付、新しいコントラクトのデプロイなどを行う「汎用的な実行機能」を提供します。
- ERC725Yは、キーと値の形式で任意のデータを格納・取得する「汎用的なストレージ機能」を提供します。
これらを組み合わせて設計されたアカウントコントラクトは、LSP0-ERC725Accountとして標準化されています。
インターフェース
// SPDX-License-Identifier: CC0-1.0
pragma solidity >=0.5.0 <0.7.0;
// ERC165 identifier: `0x7545acac`
interface IERC725X /* is ERC165, ERC173 */ {
event Executed(uint256 indexed operationType, address indexed target, uint256 indexed value, bytes4 data);
event ContractCreated(uint256 indexed operationType, address indexed contractAddress, uint256 indexed value, bytes32 salt);
function execute(uint256 operationType, address target, uint256 value, bytes memory data) external payable returns(bytes memory);
function executeBatch(uint256[] memory operationsType, address[] memory targets, uint256[] memory values, bytes memory datas) external payable returns(bytes[] memory);
}
// ERC165 identifier: `0x629aa694`
interface IERC725Y /* is ERC165, ERC173 */ {
event DataChanged(bytes32 indexed dataKey, bytes dataValue);
function getData(bytes32 dataKey) external view returns(bytes memory);
function getDataBatch(bytes32[] memory dataKeys) external view returns(bytes[] memory);
function setData(bytes32 dataKey, bytes memory dataValue) external;
function setDataBatch(bytes32[] memory dataKeys, bytes[] memory dataValues) external;
}
interface IERC725 /* is IERC725X, IERC725Y */ {
}
所有権管理(ERC173)
ERC725X/Yを実装するコントラクトは、所有者(オーナー)によって制御されます。
この所有者は、外部アカウントでもスマートコントラクトでも構いません。
ERC173については以下の記事を参考にしてください。
owner
function owner() external view returns (address);
コントラクトの現在の所有者アドレスを返す関数。
戻り値
-
address- 現在のコントラクト所有者のアドレス。
transferOwnership
function transferOwnership(address newOwner) external;
所有権を新しいアドレスに移転する関数。
コントラクトの所有者のみが実行でき、新たな所有者アドレスへ所有権を移転します。
引数
-
newOwner- 新しく所有者となるアドレス。
OwnershipTransferred
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
所有者が変更されたときに発行されるイベント。
所有者が変更されたタイミングで発行され、以前と新しい所有者アドレスを示します。
パラメータ
-
previousOwner- 所有者であったアドレス。
-
newOwner- 新しい所有者となったアドレス。
ERC725X(実行機能)
インターフェースID
ERC165準拠で、supportsInterfaceにより識別されます。
-
ERC725X のinterfaceId:
0x7545acac
ERC165については以下の記事を参考にしてください。
execute
function execute(
uint256 operationType,
address target,
uint256 value,
bytes memory data
) external payable returns(bytes memory);
他のコントラクトを呼び出す、ネイティブトークンを送付する、またはコントラクトをデプロイする関数。
呼び出し元が所有者であることが前提です。
呼び出す操作の種類(CALL, CREATE, STATICCALLなど)を指定し、目的のアクションを実行します。
CREATE2によるデプロイ時はsaltも含めて構築されます。
引数
-
operationType- 実行する操作のタイプ(0: CALL, 1: CREATE, 2: CREATE2, 3: STATICCALL, 4: DELEGATECALL)。
-
target- 呼び出し先のアドレス(CREATE/CREATE2では未使用)。
-
value- 送付するネイティブトークンの量(Wei単位)。
-
data- 呼び出すデータまたはデプロイコード+コンストラクタ引数(CREATE2ではさらにsaltを含む)。
戻り値
-
bytes- 呼び出した関数の戻り値、またはデプロイされたコントラクトのアドレス。
executeBatch
function executeBatch(
uint256[] memory operationsType,
address[] memory targets,
uint256[] memory values,
bytes[] memory datas
) external payable returns(bytes[] memory);
複数の操作を一括で実行する関数。
一連のコール、デプロイ、送付などをまとめて処理します。
すべての配列は同じ長さである必要があり、実行者は所有者でなければなりません。
引数
-
operationsType- 各操作の種類。
-
targets- 各呼び出し先のアドレス。
-
values- 各呼び出しで送付するETH量。
-
datas- 各呼び出しのデータ、またはデプロイコード。
戻り値
-
bytes[]- 各呼び出しの戻り値またはデプロイ先のアドレス。
Executed
event Executed(uint256 indexed operationType, address indexed target, uint256 indexed value, bytes4 data);
execute によりコールが発生した時に発行されるイベント。
CREATE、STATICCALL、DELEGATECALL 以外の実行時に記録されます。
パラメータ
-
operationType- 実行された操作の種類。
-
target- 呼び出されたアドレス。
-
value- 送付されたネイティブトークンの量。
-
data- 呼び出しに使われた関数セレクター(先頭4バイト)。
ContractCreated
event ContractCreated(uint256 indexed operationType, address indexed contractAddress, uint256 indexed value, bytes32 salt);
CREATEまたはCREATE2でコントラクトが作成された時に発行されるイベント。
新しいコントラクトのデプロイ時に、その操作種別とデプロイ先アドレス、投入されたETH、CREATE2の場合はsaltを記録します。
パラメータ
-
operationType- CREATE または CREATE2 のいずれか。
-
contractAddress- デプロイされたコントラクトのアドレス。
-
value- 送付されたネイティブトークンの量。
-
salt- CREATE2で使用されたソルト(CREATEでは空)。
ERC725Y(データストア機能)
インターフェースID
ERC165準拠で、supportsInterfaceにより識別されます。
-
ERC725Y のinterfaceId:
0x629aa694
getData
function getData(bytes32 dataKey) external view returns(bytes memory);
指定されたキーに紐づくデータを取得する関数。
任意のバイト列で保存されたデータにアクセスする手段を提供します。
引数
-
dataKey- 取得したいデータのキー。
戻り値
-
bytes- 保存されているデータの内容。
getDataBatch
function getDataBatch(bytes32[] memory dataKeys) external view returns(bytes[] memory);
複数のキーに対応するデータを一括で取得する関数。
getDataを複数回実行する代わりに、一度で複数の値をまとめて取得できます。
引数
-
dataKeys- データを取得したいキーの配列。
戻り値
-
bytes[]- 各キーに対応するデータ値の配列。
setData
function setData(bytes32 dataKey, bytes memory dataValue) external;
単一のキーに対してデータを書き込む関数。
所有者のみがデータを書き込めます。
設定後は DataChanged イベントが発行されます。
引数
-
dataKey- 記録したいデータのキー。
-
dataValue- 記録するバイト列データ。
setDataBatch
function setDataBatch(bytes32[] memory dataKeys, bytes[] memory dataValues) external;
複数のキーに対して一括でデータを書き込む関数。
全てのキーとデータのペアを同時に保存します。
所有者のみが使用できます。
引数
-
dataKeys- 記録するキーの配列。
-
dataValues- 各キーに対応するバイト列データの配列。
DataChanged
event DataChanged(bytes32 indexed dataKey, bytes dataValue);
データが正常に保存されたときに発行されるイベント。
新しいデータが書き込まれた際、書き込まれたキーと値を記録します。
パラメータ
-
dataKey- 書き換えられたデータのキー。
-
dataValue- 新しく保存されたバイト列の値。
データキー仕様について補足
ERC725Yでは、データを保存・取得するためのキー(bytes32型)は任意に設計できますが、標準的には keccak256('ラベル名') によって決定することが推奨されます。
例えば、keccak256('ERCXXXMyNewKeyType') は以下のようなデータキーを生成します:
0x6935a24ea384927f250ee0b954ed498cd9203fc5d2bf95c735e52e6ca675e047
このキーにより、構造化されたデータスキーマを扱うことが可能になり、拡張性の高いデータ定義が実現されます。
関連標準として、LSP2-ERC725JSONSchemaがあります。
これはデータの型、構造、エンコーディング方式を明確に定義するものです。
補足
柔軟なデータ管理のための設計
ERC725Yでは、キーと値の形式による汎用的なデータ保存方法を採用しています。
このアプローチは、スマートコントラクトのアップグレード性を高め、将来的な変更や機能拡張にも柔軟に対応できるようにするために考案されました。
保存されたデータは、後から書き換えることができるため、例えばアカウントプロフィールやオンチェーンの設定情報など、動的に変化する情報を安全かつ標準化された方法で扱うことが可能になります。
また、保存されたデータはオフチェーンからの読み取りだけでなく、他のスマートコントラクトからもアクセスすることができます。
これにより、外部のプロトコルやDAppが、ERC725準拠のコントラクトに保存された情報を元に、動的に動作を変えることができます。
関数の最適化と言語非依存性への対応
元々の設計では、Solidityの関数オーバーロード(同じ関数名で引数の異なるものを定義する機能)を活用し、getData や setData の単体版・複数版の両方を同一名で実装していました。
これにより、ガスコストを最小限に抑えつつ使い分けができる利点がありました。
しかしながら、Solidity以外の多くのブロックチェーン言語ではオーバーロードに対応していないケースが多く、言語間の互換性が課題となっていました。
このため、2023年以降のバージョンでは、関数のオーバーロードを廃止し、代わりに配列を受け取る関数には明示的に「Batch」という接尾語を付ける方針に変更されました。
例えば getDataBatch や setDataBatch がこれに該当します。
これにより、標準自体がより言語非依存となり、多様な実装環境での互換性が確保されました。
互換性
ERC725は、2018年〜2019年頃に提案されたERC725v2以来、継続的に改良されてきましたが、基本的な設計思想は変わっていません。
そのため、過去のERC725v2を実装したコントラクトも、多くは現在の標準と互換性を持っています。
主な変更点は以下の通りです。
-
getDataやsetDataに、複数キーに対応したgetDataBatch/setDataBatchが追加された。 - イベントの構造や
interfaceIdが一部変更された。 - 実行関数
executeBatchが新たに追加され、複数のコールを効率的に処理できるようになった。 - オーバーロードが廃止され、言語非依存な形式に統一された。
このように、ERC725Y は長期的な互換性と将来的な拡張性の両立を図りながら、進化し続けている仕様です。
これにより、開発者は安全かつ効率的にオンチェーンデータの設計・操作を行うことができます。
セキュリティ
汎用実行機能がもたらすリスク
ERC725Xは非常に柔軟な実行機構を提供する一方で、その特性ゆえに重大なセキュリティリスクも併せ持っています。
この標準では、外部コントラクトへの任意のコールや、新たなコントラクトのデプロイ、ネイティブトークンの送付など、幅広い操作を1つの関数で実行できます。
この自由度の高さが魅力である反面、設計や利用方法によっては意図しない攻撃を受ける可能性があります。
最も注意すべきは「再入可能性(re-entrancy)攻撃」です。
これは、一度の外部コールの中で再び当該コントラクトが呼び出されることにより、状態管理が破綻し、不正な資金移動やデータ改ざんが引き起こされる脆弱性です。
特に、execute 関数で他のアドレスに向けた CALL を行う場合、その呼び出し先が悪意を持って fallback 関数などを通じて再度 execute を呼び出すようなコードを含んでいる可能性があるため、細心の注意が必要です。
delegatecallの危険性
operationType に 4 を指定することで実行される delegatecall は、最も危険な操作のひとつです。
これは、呼び出し先コントラクトのコードを「呼び出し元コントラクトの文脈で実行する」ものです。
つまり、ストレージの操作や所有者の書き換え、ERC725Yで保存している任意のデータの変更が可能になってしまいます。
さらに、delegatecall によって selfdestruct(コントラクトの破壊)を呼び出される可能性もあります。
これにより、コントラクトが完全に削除され、元に戻すことができなくなる危険性があります。
悪意あるコードがdelegatecall経由で実行されれば、コントラクトのロジックや資産はすべて破壊される恐れがあります。
安全に使用するための対策
ERC725Xを実装・活用する際には、以下のような防御策を徹底する必要があります。
- 所有者チェックを厳格に行う。
-
delegatecallを使用する場合は信頼されたコードに限定する。 - 外部コール前にすべての状態更新を完了させる(チェック-エフェクト-インタラクションパターン)。
-
reentrancyGuardなどの再入防止メカニズムを導入する。 - ストレージ保護のためのスロット制御やデータバリデーションを適切に行う。
ERC725Xの持つ柔軟性は強力ですが、それと同時に慎重な設計と徹底したセキュリティ対策が求められます。
開発者は、特に delegatecall のような操作に関してはできるからといって安易に実装せず、設計意図を明確にして脆弱性のリスクを把握した上で利用することが重要です。
引用
Fabian Vogelsteller (@frozeman), Tyler Yasaka (@tyleryasaka), "ERC-725: General data key/value store and execution [DRAFT]," Ethereum Improvement Proposals, no. 725, October 2017. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-725.
最後に
今回は「コントラクトの実行機能とデータ保存機能を分離して、柔軟で拡張可能な仕組みを提案しているERC725」についてまとめてきました!
いかがだったでしょうか?
質問などがある方は以下のTwitterのDMなどからお気軽に質問してください!
他の媒体でも情報発信しているのでぜひ他も見ていってください!