はじめに

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

今回は、EIP7702のコード委任を活用して、すべてのEVM互換チェーンに許可不要かつ決定的にCREATE2ファクトリーコントラクトをデプロイする仕組みを提案しているERC7955についてまとめていきます!

以下にまとめられているものを解説しながらまとめていきます。

他にも様々なEIP・BIP・SLIP・CAIP・ENSIP・RFC・ACPについてまとめています。

概要

ERC7955とは、EIP7702のコード委任メカニズムを利用して、すべてのEVM互換チェーンの同一アドレス(0xC0DEb853af168215879d284cc8B4d0A645fA9b0E)にCREATE2ファクトリーコントラクトをデプロイする方法を標準化する提案です。

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

CREATE2ファクトリーとは

CREATE2ファクトリーとは、CREATE2オペコードを使って他のコントラクトを決定的なアドレスにデプロイするためのコントラクトです。 同じ初期化コードとsalt(ランダムな値)を使えば、どのチェーンでも同じアドレスにコントラクトをデプロイできます。 マルチチェーン対応のプロトコルにとって、全チェーンで同じアドレスを使えることは非常に重要です。

しかし、CREATE2ファクトリー自体を全チェーンの同じアドレスにデプロイするにはどうすればいいかという「ブートストラップ問題」があります。 ERC7955はこの問題を、秘密鍵の管理不要、プリインストール不要、チェーン固有のインフラ不要で解決します。 誰でも、どのチェーンでも、許可なくCREATE2ファクトリーをデプロイできるようになります。

動機

コントラクトのマルチチェーンデプロイ問題

複数のチェーンで同じアドレスに同じコードのコントラクトをデプロイすることは難しい問題です。 DeFiプロトコルやウォレット、ブリッジなどのプロジェクトは、ユーザー体験の一貫性のために全チェーンで同一アドレスを使いたいと考えます。

これを実現するために、特定のアドレスに存在するCREATE2ファクトリーを経由してコントラクトをデプロイする方法が広く使われています。 しかし、そのCREATE2ファクトリー自体を全チェーンの同じアドレスにデプロイする方法が標準化されていません。

既存の3つの手法とその課題

現在、この問題に対処するための手法は3つあります。 それぞれに深刻な課題があります。

Nick's Method

EIP155のリプレイ保護を使わないトランザクションに対して、ランダムな署名を生成する方法です。 秘密鍵が存在しないアカウントからのデプロイとなるため、結果のアドレスは全チェーンで決定的になります。 Deterministic Deployment Proxy(0x4e59b44847b379578588920ca78fbf26c0b4956c)がこの方式を使用しています。

しかし、EIP155のリプレイ保護を必須とするチェーンでは動作しません。 さらに、デプロイトランザクションのガスパラメータが署名に封印されているため、ガスパラメータが変更されたチェーンではデプロイが失敗します。 加えて、代替ガススケジュール等によるリバートが発生した場合も、そのアドレスに二度とデプロイできなくなります。

秘密鍵方式

厳重に管理された秘密鍵を使ってデプロイトランザクションに署名する方法です。 Safe Singleton Factory(0x914d7Fec6aaC8cd542e72Bca78B30650d45643d7)やCreateX(0xba5Ed099633D3B313e4D5F7bdc1305d3c28ba5Ed)がこの方式を採用しています。 Nick's Methodと違い、デプロイ時のガスパラメータを柔軟に設定できます。

しかし、秘密鍵の保有者に許可を得なければ新しいチェーンにデプロイできません。 秘密鍵が漏洩・紛失した場合、新しいチェーンへのデプロイが保証されなくなります。 また、指定したnonceのトランザクションがCREATE2ファクトリーのデプロイでなかった場合(人的ミスなど)、正規のアドレスにデプロイできなくなります。

プリインストール

チェーンの起動時にCREATE2ファクトリーをあらかじめ組み込む方法です。 OP StackやZKsyncなどのL2がこの方式を採用しています。

しかし、すべてのチェーンで標準化されていません。 チェーンの運営者が特定のCREATE2ファクトリーを組み込まないと選択することもあり、許可制です。 RIP-7740による標準化の試みも成功していません。

この提案による解決策

以下の図は、既存手法の課題とERC7955の解決策を比較したものです。

fig2-comparison.png

ERC7955は上記3つの手法の課題をすべて解消する第4の方法を提案しています。 この提案には以下の利点があります。

  • 誰でもどのチェーンでも実行でき、スマートコントラクトの決定的デプロイを保証する(汎用性)
  • out of gasやリバートなどのエラーに対して安全で、エラー後も再デプロイが可能(耐障害性)
  • CREATE2ファクトリーのデプロイに許可を必要としない(許可不要)
比較項目 Nick's Method 秘密鍵方式 プリインストール ERC7955
許可不要 ✅ ❌ ❌ ✅
ガス変更耐性 ❌ ✅ ✅ ✅
全チェーン対応 ❌ ❌ ❌ ✅
秘密鍵の管理不要 ✅ ❌ ✅ ✅
エラー耐性 ❌ ❌ ✅ ✅

仕様

用語定義

この提案で使用される主要な用語は以下の通りです。

用語 説明
デプロイヤー DEPLOYER_PRIVATE_KEYに対応するアカウント(DEPLOYER_ADDRESS)。公開された秘密鍵を持つ。
CREATE2ファクトリーコントラクト CREATE2オペコードを使って他のコントラクトを決定的アドレスにデプロイするコントラクト。
ブートストラップコントラクト デプロイヤーがEIP7702で委任する先のコントラクト。CREATE2ファクトリーのデプロイを実行する。
ブートストラッピングコード ブートストラップコントラクト内の、CREATE2デプロイを実行する核心的なコード部分。

パラメータ

ERC7955では以下のパラメータが定義されています。

パラメータ 値 説明
DEPLOYER_PRIVATE_KEY 0x942ba6... デプロイヤーの秘密鍵。公開されている。
CREATE2_FACTORY_INIT_CODE 0x7c6020... CREATE2ファクトリーの初期化コード。
CREATE2_FACTORY_SALT 0x0000...01bec5 CREATE2デプロイ時のsalt値。

これらのパラメータから以下の値が導出されます。

導出パラメータ 値 説明
DEPLOYER_ADDRESS 0x962560A0... デプロイヤーのアドレス。秘密鍵から導出。
CREATE2_FACTORY_ADDRESS 0xC0DEb853... CREATE2ファクトリーのデプロイ先アドレス。
CREATE2_FACTORY_RUNTIME_CODE 0x60203d3d... ファクトリーのランタイムコード(29バイト)。
CREATE2_FACTORY_CODE_HASH 0x2ad75e1e... ランタイムコードのkeccak256ハッシュ。

ブートストラップコントラクト

デプロイプロセスの鍵となるのがブートストラップコントラクトです。 これは、デプロイヤーアカウントがEIP7702で委任する先のコントラクトで、以下のブートストラッピングコードを実行します。

javascript
bytes memory initCode = CREATE2_FACTORY_INIT_CODE;
bytes32 salt = CREATE2_FACTORY_SALT;
assembly ("memory-safe") {
    create2(0, add(initCode, 32), mload(initCode), salt)
}

このコードはCREATE2オペコードを実行し、指定された初期化コードとsaltでCREATE2ファクトリーをデプロイします。 ブートストラップコントラクトには以下のような追加機能を実装できます。

  • CREATE2ファクトリーが既にデプロイ済みか、デプロイヤーの委任が正しく設定されているかを確認し、条件を満たさない場合は早期リターンする
  • デプロイが期待通りに成功したかの追加検証
  • ブートストラップコントラクトやCREATE2ファクトリーのデプロイを追跡するためのイベント発行

デプロイプロセス

以下の図はデプロイの全体フローです。

fig1-deploy-flow.png

CREATE2ファクトリーのデプロイは以下の3ステップで行われます。

  1. ブートストラップコントラクトのデプロイ 前述のブートストラッピングコードを含むコントラクトをデプロイします。
  2. EIP7702認可の署名 DEPLOYER_PRIVATE_KEYを使って、ブートストラップコントラクトへの委任を認可するEIP7702署名を生成します。
  3. EIP7702トランザクションの実行 ステップ2の認可を含む0x04タイプのトランザクションを実行します。 このトランザクションはDEPLOYER_ADDRESSを直接または間接的に呼び出し、ブートストラップコントラクトに委任された状態でCREATE2を実行します。 参考実装ではブートストラップコントラクトをエントリーポイントとして間接的に呼び出す方式を採用しており、事前チェックによるガスグリーフィングの軽減が可能です。
sequenceDiagram
    participant Anyone as 誰でも
    participant Bootstrap as ブートストラップ
    participant Deployer as デプロイヤー<br/>(0x9625...)
    participant Factory as CREATE2ファクトリー<br/>(0xC0DE...)

    Anyone->>Bootstrap: 1. ブートストラップをデプロイ
    Anyone->>Deployer: 2. EIP-7702 認可署名を生成
    Anyone->>Bootstrap: 3. 0x04 tx実行(認可含む)
    Bootstrap->>Deployer: deploy()を呼び出し
    Deployer->>Factory: CREATE2で生成
    Note over Factory: 固定アドレスに<br/>ファクトリーが誕生

ブートストラッピングコードが正常に実行されると、CREATE2_FACTORY_ADDRESSにCREATE2ファクトリーがデプロイされます。

CREATE2ファクトリーの動作

デプロイされたCREATE2ファクトリーは、salt || init_code(32バイトのsalt値と初期化コードを連結したもの)をcalldataとして受け取ります。 指定されたsaltと初期化コードを使いCREATE2を実行し、keccak256(0xff || CREATE2_FACTORY_ADDRESS || salt || keccak256(init_code))[12:]のアドレスにコントラクトをデプロイします。

成功時はデプロイされたコントラクトのアドレスを32バイトにパディングして返します。 初期化コードの実行が失敗した場合は、revertデータがそのまま呼び出し元に伝播されます。

CREATE2ファクトリーのランタイムコードでは、互換性向上のためにPUSH0(0x5f)オペコードの代わりにRETURNDATASIZE(0x3d)を使用してスタックに0をプッシュしています。 これはEIP7702をサポートしているがEIP3855(PUSH0を導入したEIP)をサポートしていないチェーンとの互換性を確保するためです。

補足

なぜ公開秘密鍵を使うのか

ERC7955のデプロイメカニズムでは、DEPLOYER_PRIVATE_KEYが公開されています。 これは意図的な設計で、誰でもこの秘密鍵を使ってデプロイヤーアカウントのEIP7702認可署名を生成し、任意のチェーンでCREATE2ファクトリーをデプロイできるようにするためです。

Nick's Methodでは秘密鍵が存在しないため署名の偽造は不可能ですが、keccak(MAGIC || rlp([chain_id, address, nonce]))のEIP7702認可メッセージにおいてaddressを固定する必要があります。 しかし、addressを固定するには特定のコードを持つコントラクトが全チェーンで同じアドレスに存在する必要があり、これはまさにCREATE2ファクトリーが解決しようとしている「ブートストラップ問題」そのものです。 つまり「鶏と卵」の問題が発生するため、Nick's MethodはEIP7702の認可署名生成には使えません。

フロントランニングの考慮

DEPLOYER_PRIVATE_KEYが公開されているため、悪意のある第三者が別の委任や別のトランザクションに署名してフロントランニングし、正当なCREATE2ファクトリーのデプロイを妨害する可能性があります。 フロントランニングによりデプロイヤーアカウントのnonceが増加し、EIP7702の認可が無効になります。

しかし、以下の理由から深刻な問題とは考えられていません。

  • 攻撃者はデプロイを遅延させることはできますが、永久に阻止することはできません。 持続的な攻撃にはガスコストがかかります。
  • ダメージはデプロイを試みるアカウントへのガス消費(ガスグリーフィング)に限られます。 参考実装ではガスグリーフィングの影響を最小化するよう設計されています。
  • 執拗な攻撃者に対しては、プライベートトランザクションの利用やブロックビルダーとの直接連携で回避できます。

saltの選定理由

CREATE2_FACTORY_SALTは、0から始めて最初にCREATE2ファクトリーのERC55チェックサムアドレスが0xC0DE...で始まる値として選ばれました。 このvanityアドレスのマイニング方法を検証可能にすることで、ERC著者がCREATE2ファクトリーのアドレスに対するハッシュ衝突を悪用する可能性を排除しています。

デプロイヤーの秘密鍵の選定

DEPLOYER_PRIVATE_KEYは、ニーモニックmake code code code code code code code code code code coconutの導出パスm/44'/60'/0'/0/0として選ばれています。

なぜ複数トランザクションが必要か

EIP7702のタイプ0x04トランザクションはtoフィールドがnullであることを許可しません。 そのため、ブートストラップコントラクトのデプロイとそれへの委任を1つのトランザクションで同時に行うことはできません。 ブートストラップコントラクトを先にデプロイし、その後でEIP7702トランザクションを送信する2段階の手順が必要になります。

なぜCREATE2ファクトリーを中間に置くのか

デプロイヤーアカウントは任意のCREATE2デプロイを実行できるため、中間のCREATE2ファクトリーを介さずに直接コントラクトをデプロイすることも可能です。 しかし、直接デプロイには以下の問題があります。

  • すべてのデプロイがフロントランニングのリスクにさらされる
  • EIP7702認可がnonceを増加させるため、同時に複数のデプロイを送信するとnonce競合が発生する

CREATE2ファクトリーを中間に置くことで、ファクトリーのデプロイ時のみフロントランニングリスクを負い、その後のコントラクトデプロイはファクトリー経由で安全に行えます。

互換性

この提案にはいくつかの互換性に関する考慮事項があります。

  • EIP7702が有効なチェーンでのみ使用可能です。 EIP7702をサポートしていないチェーンではこのデプロイ方法は使えません。
  • 既存のCREATE2ファクトリーとは別のコントラクトがデプロイされるため、ツーリングの対応が必要です。
  • 既存のCREATE2ファクトリーの一部は、作成されたコントラクトのアドレスを20バイトのままパディングなしで返しますが、この提案のファクトリーは32バイトにパディングして返します。
  • 既存のCREATE2ファクトリーの一部は、初期化コードの実行失敗時に空のデータでrevertしますが、この提案のファクトリーはrevertデータをそのまま伝播します。

参考実装

以下はブートストラップコントラクトの参考実装です。 deploy()関数を呼び出すことで、EIP7702の認可とともにCREATE2ファクトリーをデプロイします。

javascript
contract Bootstrap {
    address private constant _DEPLOYER_ADDRESS = 0x962560A0333190D57009A0aAAB7Bfa088f58461C;
    address private constant _CREATE2_FACTORY_ADDRESS = 0xC0DEb853af168215879d284cc8B4d0A645fA9b0E;
    bytes32 private constant _CREATE2_FACTORY_CODE_HASH =
        hex"2ad75e1e9642e6fce7d293d52fa5a8f62a79a2079abb7402256add02d6e8bc30";
    bytes private constant _CREATE2_FACTORY_INIT_CODE =
        hex"7c60203d3d3582360380843d373d34f5806019573d813d933efd5b3d52f33d52601d6003f3";
    bytes32 private constant _CREATE2_FACTORY_SALT =
        hex"000000000000000000000000000000000000000000000000000000000001bec5";

    error InvalidDelegation();
    error CreationFailed();

    function deploy() external {
        if (_CREATE2_FACTORY_ADDRESS.codehash == _CREATE2_FACTORY_CODE_HASH) {
            return;
        }

        bytes32 delegation = keccak256(abi.encodePacked(hex"ef0100", this));
        require(_DEPLOYER_ADDRESS.codehash == delegation, InvalidDelegation());

        Bootstrap(_DEPLOYER_ADDRESS).bootstrap();
    }

    function bootstrap() external {
        bytes memory initCode = _CREATE2_FACTORY_INIT_CODE;
        bytes32 salt = _CREATE2_FACTORY_SALT;

        address factory;
        assembly ("memory-safe") {
            factory := create2(0, add(initCode, 32), mload(initCode), salt)
        }

        require(factory == _CREATE2_FACTORY_ADDRESS, CreationFailed());
    }
}

deploy()関数はまずCREATE2ファクトリーが既にデプロイされているかを確認し、デプロイ済みであれば早期リターンします。 次にデプロイヤーアカウントがブートストラップコントラクトに正しく委任されているかを検証します。 検証が通るとbootstrap()を呼び出し、CREATE2でファクトリーをデプロイします。

この事前チェックにより、ファクトリーが既にデプロイされている場合やデプロイヤーの委任が正しくない場合のガス消費を最小限に抑え、フロントランニングによるガスグリーフィングの影響を軽減しています。

最小構成のブートストラップ

事前チェックを省略した最小構成のブートストラップコントラクトも実装可能です。 この場合はDEPLOYER_ADDRESSを直接呼び出す方式になりますが、ガスグリーフィングのリスクが高くなります。

javascript
contract MiniBootstrap {
    bytes private constant _CREATE2_FACTORY_INIT_CODE =
        hex"7c60203d3d3582360380843d373d34f5806019573d813d933efd5b3d52f33d52601d6003f3";
    bytes32 private constant _CREATE2_FACTORY_SALT =
        hex"000000000000000000000000000000000000000000000000000000000001bec5";

    fallback() external {
        bytes memory initCode = _CREATE2_FACTORY_INIT_CODE;
        assembly ("memory-safe") {
            pop(create2(0, add(initCode, 32), mload(initCode), _CREATE2_FACTORY_SALT))
        }
    }
}

fallback()関数が直接CREATE2を実行するシンプルな構成です。 フル実装版のBootstrapコントラクトと比較すると、デプロイ済みチェックや委任検証がないため、不要な場合でもガスが消費されます。

セキュリティ

フロントランニングによる妨害

DEPLOYER_PRIVATE_KEYが公開されているため、攻撃者はデプロイヤーのEIP7702委任を無効化するトランザクションをフロントランニングで送信し、デプロイを失敗させることができます。 しかし、攻撃者が負担するガスコストに対して得られる利益はデプロイの遅延のみであり、攻撃のインセンティブは限定的です。

執拗な攻撃に対しては、プライベートトランザクションキューの利用やブロックビルダーとの直接連携により、EIP7702ブートストラッピングトランザクションがフロントランニングされないようにすることで回避できます。

将来のネットワークアップグレードへの影響

将来のネットワークアップグレードで、アカウントのコードを永続的に設定する新しいオペコードやトランザクションタイプが導入された場合、この方法が機能しなくなる可能性があります。 DEPLOYER_PRIVATE_KEYが公開されているため、悪意のある第三者がデプロイヤーアカウントのコードをブートストラッピングコードを含まないものに永続的に設定できてしまいます。

しかし、ERC7955が十分に普及していればEthereumメインネット上のデプロイは既に完了しているため、秘密鍵の価値はなくなります。 他のチェーンについては、RIPを採用してCREATE2_FACTORY_ADDRESSに正しいコードがプリインストールされるよう保証できます。

引用

最後に

今回は「ERC7955による許可不要のCREATE2ファクトリーデプロイ」についてまとめてきました。 いかがだったでしょうか?

マルチチェーン時代において、全チェーンで同じアドレスにコントラクトをデプロイしたいというニーズはますます高まっています。 ERC7955は公開秘密鍵とEIP7702を組み合わせることで、既存手法の課題をすべて解消し、誰でもどのチェーンでもCREATE2ファクトリーをデプロイできる仕組みを実現しています。

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

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