はじめに
『DApps開発入門』という本や色々記事を書いているかるでねです。
今回は、オフチェーンで承認された実行提案をオンチェーンでスケジュール・実行・チャレンジ・解決する仕組みを提案しているERC3000についてまとめていきます!
以下にまとめられているものを翻訳・要約・補足しながらまとめていきます。
他にも様々なEIPについてまとめています。
概要
ERC3000は、オフチェーンで承認された実行内容をオンチェーンでスケジューリング・実行・チャレンジできる仕組みを提供しています。
この仕組みはオプティミスティックなガバナンス決定に基づいて動作し、基本的には承認された実行が問題ないと仮定して処理されます。
ただし、必要に応じて誰でも異議(チャレンジ)を申し立てることができ、それを検証する仕組みも含まれています。
この仕様は、コントラクトがサポートすべき6つのエントリポイント関数を明確に定義しています。
しかし、チャレンジやレスポンスといった検証方法については柔軟性を持たせており、開発者が任意のメカニズムを採用できるようになっています。
動機
ガバナンスの多くはオフチェーン(例:投票、合意形成など)で行われますが、その結果をオンチェーンで実行する時には、透明性とセキュリティの確保が必要です。
ERC3000はこの課題に対して、「オフチェーンで合意された処理をオンチェーンで安全に実行する方法」を提供することを目的としています。
ERC3000は、オプティミスティックロールアップのような「チャレンジ可能な実行モデル」を参考にしつつ、特定のチャレンジ解決方法を前提にしながらも、他の解決手法(例:決定論的な検証)に切り替え可能な構造を取っています。
さらに、稼働中のコントラクトでも**ホットスワップ(動的差し替え)**が可能になるよう設計されています。
これにより、将来的な技術進化や運用方針の変更に対応しやすく、柔軟性のあるガバナンス実行基盤を提供することが狙いです。
仕様
データ構造
library ERC3000Data {
struct Container {
Payload payload;
Config config;
}
struct Payload {
uint256 nonce;
uint256 executionTime;
address submitter;
IERC3000Executor executor;
Action[] actions;
bytes proof;
}
struct Action {
address to;
uint256 value;
bytes data;
}
struct Config {
uint256 executionDelay;
Collateral scheduleDeposit;
Collateral challengeDeposit;
Collateral vetoDeposit;
address resolver;
bytes rules;
}
struct Collateral {
address token;
uint256 amount;
}
}
Container
struct Container {
Payload payload;
Config config;
}
Payload
と Config
の2つの主要構造体を1つにまとめたコンテナ構造体。
オフチェーンで承認された実行要求をオンチェーンでスケジューリング・実行・チャレンジする際に、その内容と設定情報を一体化して取り扱うための基本単位。
これにより、単一のデータ構造としてガバナンス実行提案を管理できる。
パラメータ
-
payload
- 実行に関する内容本体。
-
config
- 実行条件に関する設定情報。
Payload
struct Payload {
uint256 nonce;
uint256 executionTime;
address submitter;
IERC3000Executor executor;
Action[] actions;
bytes proof;
}
ガバナンス実行要求の中核的な内容を表す構造体。
ガバナンス提案の実行に必要なすべての情報を保持。
提案の一意性を保つための nonce
や、誰が提案したか、いつ実行予定か、どのようなアクションを実行するかといった要素を含む。
proof
は外部検証用データ(例:Merkle proofなど)を想定。
パラメータ
-
nonce
- 提案の一意性を保つための番号。
-
executionTime
- 実行が可能になるブロックタイムスタンプ。
-
submitter
- この提案を提出したアドレス。
-
executor
- 実行処理を担当するコントラクト。
-
actions
- 実行すべきアクションの配列。
-
proof
- 提案の正当性を裏付ける暗号学的証明データ。
Action
struct Action {
address to;
uint256 value;
bytes data;
}
提案の一部として実行される具体的なアクション。
送金や関数呼び出しといったオンチェーンアクションを表す。
複数の Action
を含めることで、複雑な提案内容を1つの Payload
にまとめて記述可能。
パラメータ
-
to
- アクションの対象となるアドレス(コントラクトやEOA)。
-
value
- アクション実行時に送信するETHの量。
-
data
- 関数呼び出し等に使用されるバイナリデータ。
Config
struct Config {
uint256 executionDelay;
Collateral scheduleDeposit;
Collateral challengeDeposit;
Collateral vetoDeposit;
address resolver;
bytes rules;
}
ガバナンス提案の実行条件・制御ロジックを設定するための構造体。
実行までの遅延時間、チャレンジや拒否のために必要な担保、チャレンジ結果を判断するための resolver
アドレス、運用ルールの記述などを含む。
これにより、柔軟なガバナンスフローの制御が可能になる。
パラメータ
-
executionDelay
- スケジューリングから実行までに必要な最小時間(秒)。
-
scheduleDeposit
- 提案スケジュール時に必要な担保情報。
-
challengeDeposit
- チャレンジ時に必要な担保情報。
-
vetoDeposit
- 拒否(veto)時に必要な担保情報。
-
resolver
- チャレンジを裁定するためのコントラクトアドレス。
-
rules
- ガバナンスルールや検証条件を定義するバイナリデータ。
Collateral
struct Collateral {
address token;
uint256 amount;
}
ガバナンス提案の各フェーズで必要となる担保を表す構造体。
担保は、提案やチャレンジのスパムを防止するために必要なステーク資産を表す。
トークンアドレスと量から構成され、ERC20ベースの担保を想定している。
パラメータ
-
token
- 使用する担保トークンのアドレス。
-
amount
- 必要なトークンの数量。
インターフェース
abstract contract IERC3000 {
/**
* @notice Schedules an action for execution, allowing for challenges and vetos on a defined time window
* @param container A Container struct holding both the paylaod being scheduled for execution and
the current configuration of the system
*/
function schedule(ERC3000Data.Container memory container) virtual public returns (bytes32 containerHash);
event Scheduled(bytes32 indexed containerHash, ERC3000Data.Payload payload, ERC3000Data.Collateral collateral);
/**
* @notice Executes an action after its execution delayed has passed and its state hasn't been altered by a challenge or veto
* @param container A ERC3000Data.Container struct holding both the paylaod being scheduled for execution and
the current configuration of the system
* should be a MUST payload.executor.exec(payload.actions)
*/
function execute(ERC3000Data.Container memory container) virtual public returns (bytes[] memory execResults);
event Executed(bytes32 indexed containerHash, address indexed actor, bytes[] execResults);
/**
* @notice Challenge a container in case its scheduling is illegal as per Config.rules. Pulls collateral and dispute fees from sender into contract
* @param container A ERC3000Data.Container struct holding both the paylaod being scheduled for execution and
the current configuration of the system
* @param reason Hint for case reviewers as to why the scheduled container is illegal
*/
function challenge(ERC3000Data.Container memory container, bytes memory reason) virtual public returns (uint256 resolverId);
event Challenged(bytes32 indexed containerHash, address indexed actor, bytes reason, uint256 resolverId, ERC3000Data.Collateral collateral);
/**
* @notice Apply arbitrator's ruling over a challenge once it has come to a final ruling
* @param container A ERC3000Data.Container struct holding both the paylaod being scheduled for execution and
the current configuration of the system
* @param resolverId disputeId in the arbitrator in which the dispute over the container was created
*/
function resolve(ERC3000Data.Container memory container, uint256 resolverId) virtual public returns (bytes[] memory execResults);
event Resolved(bytes32 indexed containerHash, address indexed actor, bool approved);
/**
* @dev OPTIONAL
* @notice Apply arbitrator's ruling over a challenge once it has come to a final ruling
* @param payloadHash Hash of the payload being vetoed
* @param config A ERC3000Data.Config struct holding the config attached to the payload being vetoed
*/
function veto(bytes32 payloadHash, ERC3000Data.Config memory config, bytes memory reason) virtual public;
event Vetoed(bytes32 indexed containerHash, address indexed actor, bytes reason, ERC3000Data.Collateral collateral);
/**
* @dev OPTIONAL: implementer might choose not to implement (initial Configured event MUST be emitted)
* @notice Apply a new configuration for all *new* containers to be scheduled
* @param config A ERC3000Data.Config struct holding all the new params that will control the queue
*/
function configure(ERC3000Data.Config memory config) virtual public returns (bytes32 configHash);
event Configured(bytes32 indexed containerHash, address indexed actor, ERC3000Data.Config config);
}
schedule
function schedule(ERC3000Data.Container memory container) virtual public returns (bytes32 containerHash);
ガバナンス提案をオンチェーンでスケジュールする関数。
提案には一定の実行遅延が設定されており、その間にチャレンジまたは拒否(veto)が可能です。
引数
-
container
- 実行対象の
Payload
とその設定を含んだContainer
構造体。
- 実行対象の
戻り値
-
containerHash
- 提案コンテナのハッシュ値。
- イベントや後続処理の識別子として利用されます。
Scheduled
event Scheduled(bytes32 indexed containerHash, ERC3000Data.Payload payload, ERC3000Data.Collateral collateral);
スケジュールされた時に発行されるイベント。
schedule
が成功した際に発行され、提案の詳細と必要な担保情報を通知します。
パラメータ
-
containerHash
- 提案コンテナのハッシュ。
-
payload
- 実行対象の内容。
-
collateral
- スケジュール時に必要な担保。
execute
function execute(ERC3000Data.Container memory container) virtual public returns (bytes[] memory execResults);
スケジュールされた提案を実行する関数。
指定された executionTime
を経過しており、かつチャレンジや拒否されていないことが条件です。
実行処理は payload.executor.exec(payload.actions)
に委譲されます。
引数
-
container
- 実行対象の提案と設定を含んだ構造体。
戻り値
-
execResults
- アクションごとの実行結果を含むバイト列配列。
Executed
event Executed(bytes32 indexed containerHash, address indexed actor, bytes[] execResults);
提案が実行された時に発行されるイベント。
execute
によってガバナンス提案が実行されたことを通知するイベントです。
パラメータ
-
containerHash
- 提案のハッシュ。
-
actor
- 実行を行ったアクターのアドレス。
-
execResults
- 実行結果のバイト列配列。
challenge
function challenge(ERC3000Data.Container memory container, bytes memory reason) virtual public returns (uint256 resolverId);
提案が不正である可能性がある場合にチャレンジ(異議申し立て)を行う関数。
担保を支払い、後に紛争解決者が判定を下します。
引数
-
container
- チャレンジ対象の提案構造体。
-
reason
- 不正と考える理由を記述したヒント。
戻り値
-
resolverId
- 紛争解決者における紛争ID。
Challenged
event Challenged(bytes32 indexed containerHash, address indexed actor, bytes reason, uint256 resolverId, ERC3000Data.Collateral collateral);
提案に対するチャレンジが発生した時に発行されるイベント。
ユーザーが提案に異議申し立てを行った際に発行され、チャレンジ理由や担保情報などを通知します。
パラメータ
-
containerHash
- 対象となる提案のハッシュ。
-
actor
- チャレンジを行ったアドレス。
-
reason
- チャレンジの理由(ヒント)。
-
resolverId
- 紛争のID。
-
collateral
- チャレンジに使用された担保。
resolve
function resolve(ERC3000Data.Container memory container, uint256 resolverId) virtual public returns (bytes[] memory execResults);
チャレンジされた提案に対し、紛争解決者の最終判断を適用する関数。
提案が有効と判定された場合は、アクションを実行します。
引数
-
container
- 判定対象の提案構造体。
-
resolverId
- 紛争解決者によって割り当てられたID。
戻り値
-
execResults
- 実行結果(有効と判定された場合のみ)。
Resolved
event Resolved(bytes32 indexed containerHash, address indexed actor, bool approved);
チャレンジに対する最終判定が適用された時に発行されるイベント。
resolve
による紛争解決結果を通知します。
提案が承認されたか否かを approved
で示します。
パラメータ
-
containerHash
- 判定対象の提案ハッシュ。
-
actor
- 判定を適用したアクターのアドレス。
-
approved
- 判定結果(承認されたかどうか)。
veto
function veto(bytes32 payloadHash, ERC3000Data.Config memory config, bytes memory reason) virtual public;
主にガーディアンなどが、チャレンジを経ずに提案を拒否する(veto)機能を提供するオプション関数。
payloadHash
と Config
を使って対象を特定します。
引数
-
payloadHash
- 拒否対象の
Payload
のハッシュ。
- 拒否対象の
-
config
- 対象となる
Payload
に紐づく設定。
- 対象となる
-
reason
- 拒否理由。
Vetoed
event Vetoed(bytes32 indexed containerHash, address indexed actor, bytes reason, ERC3000Data.Collateral collateral);
提案が拒否された時に発行されるイベント。
veto
によって提案が拒否された際に発行され、誰がどの理由でどの担保を使って拒否したかを明示します。
パラメータ
-
containerHash
- 拒否された提案のハッシュ。
-
actor
- 拒否を実行したアクター。
-
reason
- 拒否理由。
-
collateral
- 使用された担保。
configure
function configure(ERC3000Data.Config memory config) virtual public returns (bytes32 configHash);
新しい Config
を適用し、以降の提案に対して利用されるパラメータセットを更新する関数。
既存の提案には影響しません。
引数
-
config
- 新しく適用する設定値。
戻り値
-
configHash
- 新しい設定のハッシュ。
Configured
event Configured(bytes32 indexed containerHash, address indexed actor, ERC3000Data.Config config);
設定が更新された時に発行されるイベント。
configure
の実行により設定が変更されたことを通知するイベントです。
パラメータ
-
containerHash
- この設定が初めて適用される提案のハッシュ。
-
actor
- 設定を適用したアドレス。
-
config
- 適用された新しい設定。
補足
ERC3000の設計において、最も重要な考え方の1つは「チャレンジ解決手段(resolver)の実装に制約を設けない」という柔軟性です。
ERC3000は、さまざまな種類のチャレンジ解決機構を将来的に導入できるよう、関数名や変数名をあえて汎用的・抽象的に設計しています。
その結果、プロトコル全体を改変せずとも、新たな resolver の追加や切り替えが可能になります。
ERC3000自体は単なる実装仕様ではなく、「公共財」として捉えるべきものです。
つまり、特定の企業やプロジェクトの利益のためではなく、オープンで透明なガバナンス基盤を構築するための共通基盤として設計されており、その上に様々なパブリックインフラが構築されていくことが期待されています。
セキュリティ
ERC3000の特徴の1つは、「各提案(Payload)ごとに異なる resolver を設定できる」という柔軟性です。
これにより、ユースケースごとに最適なセキュリティ・確定時間・実装の複雑さ・外部依存性のバランスを取ることができます。
主観的オラクル(Subjective Oracle)を resolver に使う場合
- メリット
- 社会的合意や評判システムをベースに、柔軟かつ迅速に裁定を下せる。
- デメリット
- そのセキュリティは暗号経済的な設計(例:Aragon Court など)に大きく依存し、不正や共謀のリスクがゼロではない。
決定論的 resolver(Deterministic Resolver)を使う場合
- メリット
- 判定基準が一貫しており、裁定内容を誰でも再現できる。
- デメリット
- 設計が非常に複雑になりがちで、バグの混入リスクが高まる。
- また、仕様変更の多いオフチェーンプロトコルに依存する場合、更新の追従が困難になる。
そのため、どの resolver を選択するかは、セキュリティ強度・最終確定までのスピード・実装保守性などの観点から慎重に検討する必要があります。
ERC3000はこの選択を制限せず、各実装者がプロジェクトの要件に応じて最適な方式を選べるようになっています。
参考実装
- https://github.com/aragon/govern/tree/master/packages/erc3k
- https://github.com/aragon/govern/tree/master/packages/govern-core
引用
Jorge Izquierdo (@izqui), Fabien Marino (@bonustrack), "ERC-3000: Optimistic enactment governance standard [DRAFT]," Ethereum Improvement Proposals, no. 3000, September 2020. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-3000.
最後に
今回は「オフチェーンで承認された実行提案をオンチェーンでスケジュール・実行・チャレンジ・解決する仕組みを提案しているERC3000」についてまとめてきました!
いかがだったでしょうか?
質問などがある方は以下のTwitterのDMなどからお気軽に質問してください!
他の媒体でも情報発信しているのでぜひ他も見ていってください!