はじめに
初めまして。
CryptoGamesというブロックチェーンゲーム企業でエンジニアをしている cardene(かるでね) です!
スマートコントラクトを書いたり、フロントエンド・バックエンド・インフラと幅広く触れています。
代表的なゲームはクリプトスペルズというブロックチェーンゲームです。
今回は、プロトコルの変更を回避して、アカウント抽象化を実装する提案であるERC4337についてまとめていきます!
以下にまとめられているものを翻訳・要約・補足しながらまとめていきます。
また、AA(AccountAbstraction)やERC4337についてのわかりやすい記事は、この記事の後半でまとめています。
この記事はあくまでERC4337の提案をまとめたものと認識してください。
ERC4337は現在Draft段階です。
そのため今後内容が変更される可能性があるのでご注意ください。
ERC4337について以下のスライドで簡単にまとめました。
概要
この提案は、新しいコンセンサスプロトコルを導入せずに、アカウント抽象化を実現する方法です。
これは、ボトムレイヤーのトランザクションタイプやプロトコルの変更を行わずに、アカウント抽象化を達成するための方法です。
代わりに、ユーザーオペレーションと呼ばれる高レイヤーのトランザクションオブジェクトを使います。
ユーザーは、これらのユーザーオペレーションを作成し、専用のメンプールに送信します。
ざっくりとEthereumのレイヤー構造について紹介します。
Ethereumのネットワークレイヤー
これは、Ethereumの基盤となるネットワークです。
ノードが情報をやり取りし、トランザクションをブロックにまとめて確定させる層です。
このレイヤーは提案に変更を加えることなく維持されます。
コンセンサスレイヤー
ここでは、ネットワーク全体での合意が形成されます。
提案されているアカウント抽象化は、このレイヤーに変更を加えずに実現されます。
新しいプロトコル変更やトランザクションタイプの導入はありません。
アプリケーションレイヤー
ここに提案されているアカウント抽象化のアイデアが詳細に位置付けられます。
ユーザーオペレーションという新しい概念が導入され、これによってスマートコントラクトウォレット内で実行される操作が表現されます。
ユーザーはこれらの操作を作成し、専用のmempoolに送信します。
そして、バンドラという特別な役割を持つアクターが登場します。
これらのバンドラは、ユーザーオペレーションをまとめて1つのトランザクションとして扱う作業を行います。
具体的には、あるコントラクトに対してhandleOpsという操作を呼び出しまとめられたトランザクションが新しいブロックに含まれます。
この提案は、コンセンサス層のプロトコルを変更せずに、アカウント抽象化を実現するための方法です。
ユーザーはEOA(外部所有者アカウント)を持つ必要なく、スマートコントラクトウォレットを利用できるようになります。
また、バンドラの参加も可能で、分散化が促進されます。
さらに、ユーザーは特定のアクターのアドレスを知る必要がないため、プライバシーも確保されます。
この提案は、Ethereumのコンセンサス層への変更を回避し、迅速な採用を目指しています。
そのため、多くのユースケースに対応できる柔軟さも持っています。
アクター
アクターは、システム内で特定の役割や機能を果たすエンティティです。
提案されているアカウント抽象化の文脈では、特に「バンドラ」というアクターが重要です。
バンドラは、ユーザーオペレーションと呼ばれるトランザクションをまとめてブロックに含める役割を担っています。
バンドラは、ユーザーが送信したユーザーオペレーションを収集し、これらを1つのトランザクションとしてまとめてブロックに含めます。
動機
詳細は以下のリンクを参照してください。
提案されている方法は、アカウント抽象化を実現する際に、新しいアプローチを採用しています。
具体的な目標とその方法について詳しく説明します。
アカウント抽象化の目標
この提案は、アカウント抽象化の主要な目標を達成しようとしています。
具体的には、ユーザーがスマートコントラクトウォレットを通じて任意の検証ロジックを持つアカウントを主要なアカウントとして使用できるようにすることを目指しています。
これにより、ユーザーはEOA(外部所有者アカウント)を持つ必要がなくなり、スマートコントラクトウォレットを使用して複雑な操作を実行できるようになります。
分散化
この提案では、バンドラと呼ばれるアクターが登場します。
バンドラはブロックを構築する役割を果たします。
重要なのは、誰でもバンドラとして参加し、アカウント抽象化されたユーザーオペレーションを含むプロセスに参加できることです。
これにより、ネットワークの分散化が促進されます。
プライバシーの確保
ユーザーは特定のアクターのアドレスを知る必要がないため、プライバシーが確保されます。
ユーザーオペレーションは専用のmempoolに送信され、バンドラがそれを収集して処理します。
ユーザーはバンドラのアドレスを直接知る必要はありません。
これにより、取引の際のプライバシーが向上します。
Mempool
EthereumのMempool(メンプール)は、新しいトランザクションがネットワークに送信され、ブロックに含まれる前に一時的に保持される場所です。
Mempoolは、ネットワーク内の未確定のトランザクションが一時的にキューイングされているようなものと考えることができます。
具体的に説明すると、Mempoolは以下のような役割を果たします。
-
トランザクションの保持
ユーザーが新しいトランザクションをネットワークに送信すると、それらのトランザクションはMempoolに追加されます。
ここで、トランザクションは未確定のまま保持されます。 -
トランザクションの順序付け
Mempoolは、トランザクションが送信された順序通りに保持されます。
これによって、トランザクションがブロックに含まれる順序が確定されます。 -
トランザクションの選択
ブロックが採掘される際、マイナーはMempoolからトランザクションを選択し、ブロックに含めることを決定します。
通常、トランザクションの手数料や優先度などが選択の基準となります。 -
未確定のトランザクションの公開
ネットワーク内で未確定のトランザクションがどれくらい多くキューイングされているかを示す情報として、Mempoolの状態が公開されることがあります。
Mempoolは一時的なものであり、トランザクションが採掘されてブロックに含まれると、それらはMempoolから削除されます。
このプロセスによって、ネットワーク内でのトランザクションの処理が管理され、ブロックに含めるトランザクションの選択が行われます。
コンセンサス変更の回避
この提案は、Ethereumのコンセンサス層への変更を必要としません。
既存のプロトコルに新たな概念を組み込むだけで目標を達成しようとしています。
これにより、迅速な採用を促進し、Ethereumのコンセンサス層の変更に関する課題を回避します。
他のユースケースのサポート
この提案は、アカウント抽象化に加えて、プライバシー保護アプリケーションやアトミックマルチオペレーションなど、さまざまなユースケースもサポートしようとしています。
これによって、より幅広いアプリケーションとニーズに応える柔軟なプラットフォームを提供することを目指しています。
まとめると、既存のコンセンサス層の変更を回避しながら、アカウント抽象化を実現し、分散化とプライバシーの確保を促進し、多様なユースケースをサポートする方法を提案しています。
仕様
定義
UserOperation(ユーザーオペレーション)
ユーザーオペレーションは、ユーザーの代理で送信されるトランザクションを記述する構造体です。
トランザクションと同様に、以下のフィールドが含まれます。
-
sender
- 送信者アドレス。
-
to
- 宛先アドレス。
-
calldata
- 送信者アドレスに渡すデータ。
-
maxFeePerGas
- 最大ガスあたりの手数料。
-
maxPriorityFee
- 最大優先ガスあたりの手数料。
-
signature
- 署名。
-
nonce
- 再入攻撃防止パラメータ。
ただし、トランザクションとは異なり、以下で説明する他のいくつかのフィールドも含まれています。
また、「signature
」フィールドの使用法はプロトコルによって定義されず、アカウントの実装によって異なります。
Sender(送信者)
Sender
は、ユーザーオペレーションを送信するアカウントコントラクトを指します。
ユーザーオペレーションが実行される際に、特定のアカウントコントラクトが送信者として指定され、そのアカウントの持つ情報やロジックがオペレーションの実行に関与します。
送信者のアカウントコントラクトは、ユーザーオペレーションがブロックに含まれる際に役割を果たします。
EntryPoint(エントリーポイント)
EntryPoint
は、ユーザーオペレーションのバンドルを実行するためのシングルトンコントラクトです。
このエントリーポイントは、バンドラやクライアントによってホワイトリストに登録されます。
バンドラは、このエントリーポイントを呼び出してユーザーオペレーションをバンドルとして処理し、それをトランザクションとしてブロックに含めます。
Bundler(バンドラ)
Bundler
は、ユーザーオペレーションを処理できるノード、つまりブロックビルダーを指します。
バンドラは、受信したユーザーオペレーションをまとめて有効なEntryPoint.handleOps()
トランザクションを作成し、それをブロックに含める役割を担います。
バンドラは、ブロックの生成プロセスに関与することで、ユーザーオペレーションを実行可能な形でブロックに組み込む役割を果たします。
Aggregator(アグリゲータ)
Aggregator
は、アカウントが信頼するヘルパーコントラクトで、集約署名を検証する役割を担います。
バンドラやクライアントは、ホワイトリストに登録されたサポートされるアグリゲータを利用します。
アグリゲータは、ユーザーオペレーションの検証において重要な役割を果たし、信頼性の確保に寄与します。
提案されている方法は、新しいトランザクションタイプを作成せずにアカウント抽象化を実現します。
ユーザーはユーザーオペレーションという構造体にアクションをパッケージ化して送信し、それがアカウントの実行をトリガーします。
各フィールドは、操作の内容やガスの割り当て、署名情報などを定義します。
このような方法によって、アカウント抽象化が効果的に実現され、さまざまな操作が柔軟に行えるようになります。
以下はUserOperation
構造体の各フィールドの表です。
フィールド | 型 | 説明 |
---|---|---|
sender | address | オペレーションを行うアカウントのアドレス。 |
nonce | uint256 | リプレイ攻撃対策のパラメータ。 |
initCode | bytes | アカウントがまだチェーン上に存在せず、作成する必要がある場合に必要なinitCode 。 |
callData | bytes | メイン実行コール中に送信者に渡すデータ。 |
callGasLimit | uint256 | メイン実行コールに割り当てるガスの量。 |
verificationGasLimit | uint256 | 検証ステップに割り当てるガスの量。 |
preVerificationGas | uint256 | バンドラの事前検証の実行とコールデータの補償に支払うガスの量。 |
maxFeePerGas | uint256 | ガスあたりの最大手数料(EIP1559のmax_fee_per_gas と類似)。 |
maxPriorityFeePerGas | uint256 | ガスあたりの最大優先手数料(EIP1559のmax_priority_fee_per_gas と類似)。 |
paymasterAndData | bytes | トランザクションのガス代を支払うアドレスと、そのアドレスに送信する追加データ(自身のアドレスの場合は空)。 |
signature | bytes | アカウントに渡されるデータ。検証ステップ中のnonce とともに渡される。 |
ユーザーオペレーションの送信とバンドラの役割:
ユーザーは、UserOperation
オブジェクトを専用のユーザーオペレーションMempoolに送信します。
このMempoolは、ユーザーオペレーションを一時的に保持し、処理待ちの状態を管理します。
バンドラと呼ばれる特別な役割のアクターは、このユーザーオペレーションMempoolを監視し、バンドルトランザクションを作成します。
バンドラは、ユーザーオペレーションをまとめて1つのhandleOps
呼び出しにパッケージ化し、事前に公開されたグローバルエントリーポイントコントラクトに送信します。
リプレイ攻撃の防止:
リプレイ攻撃(クロスチェーンおよび複数のEntryPoint実装の両方)を防ぐために、署名はchainid
とEntryPoint
アドレスに依存する必要があります。
これにより、署名にチェーンの識別情報とエントリーポイントのアドレスが含まれ、異なるチェーンや異なるEntryPoint
実装間でのリプレイ攻撃を防止します。
リプレイ攻撃については以下を参照してください。
エントリーポイントコントラクトの主要インターフェース:
このコントラクトは、バンドルトランザクションの受け入れと処理を行います。
エントリーポイントコントラクトの主要なインターフェースは次の通りです。
function handleOps(UserOperation[] calldata ops, address payable beneficiary);
function handleAggregatedOps(
UserOpsPerAggregator[] calldata opsPerAggregator,
address payable beneficiary
);
struct UserOpsPerAggregator {
UserOperation[] userOps;
IAggregator aggregator;
bytes signature;
}
function simulateValidation(UserOperation calldata userOp);
error ValidationResult(ReturnInfo returnInfo,
StakeInfo senderInfo, StakeInfo factoryInfo, StakeInfo paymasterInfo);
error ValidationResultWithAggregation(ReturnInfo returnInfo,
StakeInfo senderInfo, StakeInfo factoryInfo, StakeInfo paymasterInfo,
AggregatorStakeInfo aggregatorInfo);
struct ReturnInfo {
uint256 preOpGas;
uint256 prefund;
bool sigFailed;
uint48 validAfter;
uint48 validUntil;
bytes paymasterContext;
}
struct StakeInfo {
uint256 stake;
uint256 unstakeDelaySec;
}
struct AggregatorStakeInfo {
address actualAggregator;
StakeInfo stakeInfo;
}
このインターフェースは、ユーザーオペレーションの処理とバンドルトランザクションの作成において重要な役割を果たします。
バンドラはエントリーポイントコントラクトを通じてユーザーオペレーションを処理し、バンドルトランザクションを生成してブロックに組み込むことができます。
もちろんです。以下に上記のコントラクトの各要素を日本語でわかりやすく説明します。
handleOps
概要:
ユーザーオペレーションの配列を受け取り、それらを処理する関数。
詳細:
複数のユーザーオペレーションをまとめて処理します。
処理された結果は、指定された受取人(beneficiary
)に送金されます。
引数:
-
ops
- ユーザーオペレーションの配列。
- 処理するオペレーションが含まれています。
-
beneficiary
- 処理結果の送金先アドレス。
戻り値:
なし。
handleAggregatedOps
概要:
アグリゲータごとにまとめられたユーザーオペレーションの配列を受け取り、それらをまとめて処理する関数。
詳細:
アグリゲータごとにまとめられたユーザーオペレーションを受け取り、それらをまとめて処理します。
処理された結果は、指定された受取人(beneficiary
)に送金されます。
引数:
-
opsPerAggregator
- アグリゲータごとにまとめられたユーザーオペレーションと関連情報を含む配列。
-
beneficiary
- 処理結果の送金先アドレス。
戻り値:
なし。
UserOpsPerAggregator
概要:
アグリゲータごとにまとめられたユーザーオペレーションと関連情報を保持する構造体。
パラメータ
-
userOps
- アグリゲータごとにまとめられたユーザーオペレーションの配列。
-
aggregator
- アグリゲータのアドレス。
-
signature
- 署名情報。
simulateValidation
概要:
ユーザーオペレーションの検証手順をシミュレートする関数。
詳細:
指定されたユーザーオペレーションの検証手順をシミュレートします。
ただし、実際の検証は行われません。
引数:
-
userOp
- シミュレート対象のユーザーオペレーション。
戻り値:
なし。
ValidationResult
概要:
ユーザーオペレーションの検証結果のエラー。
詳細:
このエラーは、ユーザーオペレーションの検証が失敗した場合に発生します。
検証結果に関する情報(ガス、署名の成功/失敗など)が含まれています。
引数:
-
returnInfo
- 検証結果に関する情報を持つ構造体。
-
senderInfo
- 送信者のステーク情報を持つ構造体。
-
factoryInfo
- ファクトリーのステーク情報を持つ構造体。
-
paymasterInfo
- ペイマスターのステーク情報を持つ構造体。
senderInfo(送信者のステーク情報)
トランザクションの送信者(ユーザーアカウントなど)に関するステーク(賭け金)に関する情報を持つ構造体です。
ステークは、一種の保証金のようなもので、特定の操作やトランザクションに対して一定の金額を預けることで、その操作やトランザクションが正当であることを示すものです。
factoryInfo(ファクトリーのステーク情報)
トランザクションを作成する際のファクトリー(工場)に関するステーク情報を持つ構造体です。
ファクトリーは、スマートコントラクトのデプロイやトランザクションの生成を行う際に使用されるコントラクトです。その際にもステークが関連している場合があります。
paymasterInfo(ペイマスターのステーク情報)
トランザクションの手数料を支払うためにスポンサーとして機能するペイマスターに関するステーク情報を持つ構造体です。
ペイマスターは、他のユーザーやコントラクトのトランザクション手数料を支払うために、一定の資金を保有している必要があります。
ValidationResultWithAggregation
概要:
アグリゲータ情報を含むユーザーオペレーションの検証結果のエラー。
詳細:
このエラーは、アグリゲータ情報を含むユーザーオペレーションの検証が失敗した場合に発生します。
検証結果に関する情報とアグリゲータのステーク情報が含まれています。
引数:
-
returnInfo
- 検証結果に関する情報を持つ構造体。
-
senderInfo
- 送信者のステーク情報を持つ構造体。
-
factoryInfo
- ファクトリーのステーク情報を持つ構造体。
-
paymasterInfo
- ペイマスターのステーク情報を持つ構造体。
-
aggregatorInfo
- アグリゲータの情報を持つ構造体。
ReturnInfo
概要:
ユーザーオペレーションの検証結果や関連情報の構造体。
詳細:
この構造体は、検証結果に関する情報(ガス、署名の成功/失敗など)、プリオペレーションガス、プリファンド、有効期間などを持ちます。
パラメータ:
-
preOpGas
- プリオペレーションガスの量。
-
prefund
- プリファンドの量。
-
sigFailed
- 署名が失敗したかどうかを示すブール値。
-
validAfter
- 有効開始時間を示す整数。
-
validUntil
- 有効期限を示す整数。
-
paymasterContext
- ペイマスターコンテキストのバイトデータ。
preOpGas
トランザクションの実行前に予め割り当てられるガスの量を示します。
トランザクションの実行にはガスが必要であり、そのガスは各操作や計算に対するコストをカバーするために使用されます。
preOpGas
は、トランザクションの実行前に事前に確保されるガスであり、実際のトランザクションの実行中に追加のガスが必要になるかもしれません。
prefund
トランザクションの実行前に事前に保証されるトークンや通貨の量を示します。
トランザクションの実行には手数料(ガス料)が必要であり、それを支払うためのトークンや通貨が必要です。
prefund
は、トランザクション実行前に、トランザクションの手数料を支払うために必要なトークンや通貨を保証するためのものです。
StakeInfo
概要:
アカウントのステーク情報の構造体。
詳細:
この構造体は、アカウントのステーク量とアンステーク遅延時間を保持します。
パラメータ:
-
stake
- ステーク量を示す整数。
-
unstakeDelaySec
- アンステーク遅延時間を示す整数。
AggregatorStakeInfo
概要:
アグリゲータのステーク情報の構造体。
詳細:
この構造体は、実際のアグリゲータのアドレスとステーク情報を保持します。
パラメータ:
-
actualAggregator
- 実際のアグリゲータのアドレス。
-
stakeInfo
- アグリゲータのステーク情報を持つ
StakeInfo
構造体。
- アグリゲータのステーク情報を持つ
戻り値:
なし。
上記のコントラクトおよび関数は、ユーザーオペレーションの処理と検証に関連する重要な機能を提供します。
バンドラはこれらの機能を活用して、ユーザーオペレーションを効果的に処理し、ブロックに組み込むことができます。
ペイマスター
ペイマスターとは、トランザクションの手数料や処理コストを支払うためのアカウントやコントラクトのことを指します。
ペイマスターは、通常は特定のコントラクトとして実装されることが多いです。
ユーザーがトランザクションを送信する際、そのトランザクションには手数料が必要ですが、ペイマスターが手数料を肩代わりすることで、ユーザーが手数料を直接支払う必要がなくなります。
代わりに、ペイマスターが手数料を支払い、トランザクションが処理されることを保証します。
また、ペイマスターはトランザクションの実行前後に追加の処理やデータを提供することもあります。
例えば、特定の条件が満たされた場合にトランザクションを承認するような仕組みを持つこともあります。
利点:
- ユーザーは手数料を直接支払う必要がなくなり、スムーズなトランザクションの送信が可能となります。
- 特定の条件を満たすことで、ペイマスターがトランザクションの手数料を肩代わりすることで手数料を削減または無償でトランザクションを送信できる場合があります。
注意:
ペイマスターの存在や動作は、プロトコルやアプリケーションごとに異なる場合があります。
特定のコンセンサス層の変更やスマートコントラクトの設計に依存して機能します。
簡潔にまとめると、ペイマスターは手数料支払いのプロセスを代行し、ユーザーがスムーズにトランザクションを送信できるよう支援するアカウントやコントラクトのことです。
-
userOpHash
(ユーザーオペレーションハッシュ)は、ユーザーオペレーション(署名を除く)とエントリーポイント、およびチェーンIDに対するハッシュです。 -
アカウントは以下の手順を実行する必要があります:
- 呼び出し元が信頼できるエントリーポイントであることを検証する必要があります。
- アカウントが署名集約をサポートしていない場合、署名がユーザーオペレーションハッシュの有効な署名であることを検証し、署名が不一致の場合には
SIG_VALIDATION_FAILED
を返す必要があります。- ただしリバートは行わないでください。
- その他のエラーの場合はリバートしてください。
- アカウントは、エントリーポイント(呼び出し元)に対して少なくとも「
missingAccountFunds
」を支払う必要があります。- 現在のアカウントの預金が十分に高い場合、ゼロになる可能性もあります。
- アカウントは、将来のトランザクションをカバーするために、この最小金額以上の金額を支払うことができます。
- 後で
withdrawTo
を使用して取り戻すことができます。
- 後で
-
戻り値には次の情報を格納する必要があります。
-
authorizer
- 有効な署名の場合は
0
、署名失敗の場合は1
。 - それ以外の場合は、
authorizer
コントラクトのアドレスを返します。 - このERCは、「署名アグリゲータ」を
authorizer
として定義しています。
- 有効な署名の場合は
-
validUntil
-
6
バイトのタイムスタンプ値で、無限の場合は0
です。 - ユーザーオペレーションはこの時刻まで有効です。
-
-
validAfter
-
6
バイトのタイムスタンプ。 - ユーザーオペレーションはこの時刻以降にのみ有効です。
-
-
authorizer
コントラクト
特定のアカウントがユーザーオペレーションの検証を行う際に、署名の有効性やその他の条件を確認するためのコントラクト。
ユーザーオペレーションの署名が正当であるかどうかを検証し、署名の成功または失敗を示す役割を果たします。
- 署名集約を処理するアカウントは、
validateUserOp
の「sigAuthorizer
」戻り値に署名アグリゲータのアドレスを返す必要があります。- また、この場合は
signature
フィールドを無視しても良いです。
- また、この場合は
アグリゲーターが必要とするコア・インターフェースは以下です。
interface IAggregator {
function validateUserOpSignature(UserOperation calldata userOp)
external view returns (bytes memory sigForUserOp);
function aggregateSignatures(UserOperation[] calldata userOps) external view returns (bytes memory aggregatesSignature);
function validateSignatures(UserOperation[] calldata userOps, bytes calldata signature) view external;
}
IAggregator
IAggregator
(アグリゲータ)インターフェースは、アカウントの検証や署名の処理を行うためのメソッドを提供します。
アグリゲータはユーザーオペレーションの署名検証や複数の署名の集約など、トランザクションの信頼性を確保するために使用されます。
validateUserOpSignature
概要
ユーザーオペレーションの署名を検証する関数。
詳細
このユーザーオペレーションの署名を検証し、正当な署名であればその署名を返します。
引数
-
userOp
- 検証するユーザーオペレーション。
戻り値
-
sigForUserOp
- ユーザーオペレーションの署名(正当な場合)のバイトデータ。
aggregateSignatures
概要
複数のユーザーオペレーションの署名を集約する関数。
詳細
複数のユーザーオペレーションの署名を集約し、集約された署名を返します。
引数
-
userOps
- 集約する複数のユーザーオペレーション。
戻り値
-
aggregatesSignature
- 集約された署名のバイトデータ。
validateSignatures
概要
特定の署名が複数のユーザーオペレーションの署名と一致するかを検証する関数。
詳細
引数として渡された署名が、複数のユーザーオペレーションの署名と一致するかを検証します。
引数
-
userOps
- 検証する複数のユーザーオペレーション。
-
signature
- 検証する署名のバイトデータ。
戻り値
なし。
アカウントがアグリゲータを使用する場合、そのアグリゲータのアドレスはvalidateUserOp
から返され、simulateValidation()
でValidationResultWithAggregator
として返されます。
これにより、従来のValidationResult
ではなく新しいエラータイプであることが示されます。
ユーザーオペレーションを受け入れるために、バンドラはvalidateUserOpSignature()
を呼び出してユーザーオペレーションの署名を検証する必要があります。
そして、aggregateSignatures()
メソッドは、すべてのユーザーオペレーションの署名を単一の値に集約します。
これらのメソッドは、バンドラが効率的に操作するためのヘルパーメソッドです。
バンドラは、同じ検証および集約ロジックを実現するために、ネイティブライブラリを使用するかもしれません。
そして、validateSignatures()
メソッドは、配列内のすべてのユーザーオペレーションの集約された署名が一致するかを検証し、一致しない場合にはトランザクションをリバート(取り消し)します。
この検証はhandleOps()
内でオンチェーンで行われます。
Semi-abstractedのノンスサポート
Ethereumプロトコルにおいて、トランザクションのノンス値は、リプレイ保護やトランザクションの正しい順序を確保するために使用されます。
同じ送信者が同じノンスを持つトランザクションは、二度と同じチェーンに含まれないため、トランザクションハッシュの一意性にも寄与します。
しかしながら、単一の連続したノンス値を要求することは、送信者がトランザクションの順序付けやリプレイ保護に関する独自のロジックを定義する能力を制限します。
そこで、連続的なノンスの代わりに、UserOperation
内に単一のuint256
ノンス値を用いていますが、それを以下の2つの値として扱います。
-
192
ビットの「キー」 -
64
ビットの「シーケンス」
これらの値はEntryPoint
コントラクト内でオンチェーンで表現されます。
EntryPoint
インターフェースには、以下のメソッドを通じてこれらの値を公開します:
function getNonce(address sender, uint192 key) external view returns (uint256 nonce);
各キーごとに、シーケンスはEntryPoint
によってUserOperation
毎に順次かつ単調に検証および増加されます。
ただし、新しいキーはいつでも任意の値で導入可能です。
このアプローチにより、UserOperation
ハッシュの一意性はプロトコルレベルで保持しつつ、32
バイトのワードに収まる192
ビットの「キー」フィールドを使用して、ウォレットが必要なカスタムロジックを実装できるようになります。
ノンスの読み取りと検証
UserOp
を準備する際、クライアントはgetNonce
関数を実行して、ノンスフィールドの適切な値を決定できます。
バンドラがUserOp
を検証する場合、トランザクションが適切なノンスを持つことを確認するために、getNonce
を使用するべきです。
もしバンドラが同じ送信者による複数のUserOperation
をそのMempoolに受け入れる意向がある場合、バンドラは既にMempoolに追加されたUserOperation
のキーとシーケンスの組を追跡する必要があります。
使用例
連続したノンス。
ウォレットがトランザクションを連続した整数値を使って送信する方法です。
このノンスは、トランザクションの順序付けとリプレイ保護を提供します。
require(userOp.nonce < type(uint64).max);
ユーザーオペレーション内のノンスが64
ビット未満の値であることを要求しています。
これにより、ユーザーオペレーションのノンスが整数値の範囲内に収まり、クラシックな連続したノンスの制約に従うことが保証されます。
順序付けられた管理イベント
アカウントが通常の操作と並行して「管理用」の操作チャネルを必要とする場合、特定のキーを使用してこれらの操作を区別することができます。
コード内の以下の部分がその例です。
bytes4 sig = bytes4(userOp.callData[0 : 4]);
uint key = userOp.nonce >> 64;
if (sig == ADMIN_METHODSIG) {
require(key == ADMIN_KEY, "管理操作のための誤ったノンスキーです");
} else {
require(key == 0, "通常操作のための誤ったノンスキーです");
}
このコードは、ユーザーオペレーション内のコールデータから関数のシグネチャ(最初の4
バイト)を取得し、その後、ノンス値の上位64
ビットを取得してキーとして使用します。
管理操作の場合、関数のシグネチャに基づいて正しいキーを持つことが要求され、通常操作の場合はキーが0
であることが要求されます。
アカウントは通常操作と管理操作を区別し、それぞれに異なるノンスのシーケンスを維持できます。
上記のコード例は、操作チャネルを実現するためのカスタムロジックの一例を示しています。
署名アグリゲータの使用方法
署名アグリゲータの使用方法は以下の通りです。
-
アカウントが署名アグリゲーションを使用する場合、
validateUserOp
からアグリゲータのアドレスを返します。これにより、このアカウントは署名アグリゲーションを利用することを示します。 -
simulateValidation
の中で、アグリゲータが返されます。
具体的には、ValidationResultWithAggregator
内でアグリゲータが含まれます。 -
バンドラは、まずアグリゲータを受け入れる必要があります。
これには、アグリゲータのステーク情報を検証し、アグリゲータがスロットルされていないかバンされていないかを確認する作業が含まれます。
ステーク情報の検証
アグリゲータが持っているステーキング(賭け金)の量と、アグリゲータによって提供されるサービスの重要性を確認します。
ステーキングはアグリゲータの信頼性を示すものであり、多くの場合、ステーキングが一定量以上でなければならない場合があります。
スロットルとバンの確認
ネットワークは、不正行為や過剰なリソース消費を防ぐために、アグリゲータに対してスロットル(制限)またはバン(禁止)を課す場合があります。
これにより、ネットワーク全体のパフォーマンスとセキュリティが保たれます。
アグリゲータがスロットルまたはバンされていないことを確認するためには、ネットワーク上の信頼性情報や制限情報をチェックする必要があります。
-
バンドラは、ユーザーオペレーションの検証に際して、
aggregator.validateUserOpSignature()
を使用してユーザーオペレーションの署名を検証する必要があります。 -
署名アグリゲータも、ペイマスターと同様にステークすることが推奨されます。
ただし、グローバルストレージにアクセスしないアグリゲータは免除される場合もあります。 -
バンドラは、上記の読み込み専用関数が呼び出される際にリソースを過剰に消費するか、もしくはリバートする場合、または署名の集約が失敗した場合に、アグリゲータをスロットリングしたりバンしたりすることができます。
リソースの過剰消費
バンドラは、アグリゲータが特定のメソッドを読み取り専用で呼び出す際に、ネットワーク全体のリソースを過剰に消費することを検知するかもしれません。
これにより、他の参加者のトランザクション処理が遅延する可能性があります。
バンドラはネットワーク全体の健全性を維持するために、このようなアグリゲータをスロットリング(制限)することがあります。
リバート
アグリゲータが読み取り専用でメソッドを呼び出す際にエラーが発生し、リバート(ロールバック)が発生する場合があります。
これは、アグリゲータがトランザクションをキャンセルする状況です。
バンドラは、このようなアグリゲータを一時的にスロットリングすることがあります。
なぜなら、アグリゲータがリバートを頻繁に行うことは、ネットワーク全体のパフォーマンスや正当性に影響を及ぼす可能性があるためです。
署名の集約の失敗
アグリゲータがユーザーオペレーションの署名の集約に失敗する場合があります。
これは、トランザクションの正当性を確保する重要なステップです。
バンドラは、署名の集約に失敗するアグリゲータを検知し、ネットワーク全体のセキュリティを保つためにスロットリングやバンの措置を取るかもしれません。
まとめると、アカウントが署名アグリゲーションを使用する場合、アグリゲータのアドレスを返し、バンドラはアグリゲータを受け入れ、そのステーク情報を検証してから、アグリゲータが提供するメソッドを使用して署名の検証を行う必要があるということです。
これにより、セキュアで効果的な署名アグリゲーションが実現されます。
エントリーポイントコントラクト機能
必要なエントリーポイントコントラクトの機能について説明します。
handleOps
-
handleOps
メソッドは、署名アグリゲータを必要としないアカウントのuserOps
を処理します。 -
handleOps
メソッドは、エントリーポイント内でアカウントのuserOps
を処理するための以下の2つの独立したループを行います。- 検証ループと実行ループ。
- 検証ループでは、各
UserOperation
に対して次の手順を実行します。-
UserOperation
に提供されたinitcode
を使用して、アカウントを作成します。- アカウントが存在しない場合で
initcode
が空であるか、またはsender
アドレスにコントラクトをデプロイしない場合、コールは失敗します。
- アカウントが存在しない場合で
- アカウント上で
validateUserOp
を呼び出し、UserOperation
、必要な手数料、およびアグリゲータ(存在する場合)を渡します。- アカウントは操作の署名を検証し、アカウントが操作を有効とみなす場合は手数料を支払います。
- いずれかの
validateUserOp
呼び出しが失敗した場合、handleOps
は少なくともその操作の実行をスキップし、完全にリバートできます。
- エントリーポイント内のアカウントのデポジットが、最大の実行ガスおよび検証済みのコスト(すでに行われた検証と最大の実行ガス)をカバーするだけの十分な額であることを検証します。
-
initCode
initCode
は、コントラクトの初期化コードを表すものです。
新しいスマートコントラクトを作成する際に、そのコントラクトの初期状態を定義するために使用されます。
具体的には、スマートコントラクトはコードの一連の命令で構成されていますが、コントラクトが作成される際に最初に実行されるコードブロックがあります。
これを初期化コードまたはinitCode
と呼びます。
initCode
には、スマートコントラクトの初期化に必要な情報や処理が含まれます。
このコードは、新しいコントラクトがブロックチェーン上で作成されるときに実行され、コントラクトの初期状態を設定します。
たとえば、コントラクトの変数の初期値を設定したり、特定の設定処理を行ったりするために使用されます。
提案中の文脈では、initCode
が非常に重要で、コントラクトのアドレス生成や初期化処理に関連しています。
initCode
には、アドレス情報や初期化に必要なデータが含まれており、これに基づいて新しいコントラクトが作成される際に実行されます。
handleAggregatedOps
-
handleAggregatedOps
メソッドは、複数のアグリゲータのuserOps
(およびアグリゲータなしの要求も含む)を含むバッチを処理できます。 -
handleAggregatedOps
メソッドは、handleOps
と同じロジックを実行しますが、各userOp
に正しいアグリゲータを転送し、すべてのアカウント検証が完了した後、各アグリゲータでvalidateSignatures
を呼び出す必要があります。
まとめると、handleOps
メソッドは、userOps
の処理を行う際に、アカウントの作成、検証、実行、デポジットの確認などの一連の手順を踏むものです。
また、handleAggregatedOps
メソッドは、複数のアグリゲータのuserOps
を処理する場合に同様の手順を実行しますが、アグリゲータの適切な転送と署名の検証を追加で行う点が異なります。
実行ループ内でのhandleOps
の動作について、具体的な手順を分かりやすく説明します。
-
アカウントの呼び出し
各UserOperation
のcalldata
を使用して、対応するアカウントを呼び出します。
アカウントは、calldata
を解析して、アカウントが行うべき1つ以上のコールのシリーズを特定します。
この段階でアカウント内のロジックが実行されます。
コールのシリーズ
アカウントがcalldata
を解析し、その中に含まれる指示に基づいて実行すべき操作の連続です。
つまり、calldata
内に含まれるデータを読み取り、それに従って実行される関数呼び出しや処理の流れを指します。
このコールのシリーズは、アカウント内の特定のロジックやプログラムの一部を実行するために必要な手順です。
アカウント内のロジック
アカウントが保持しているスマートコントラクトやプログラムの中で実行される一連の計算や操作のことを指します。
アカウントは、calldata
を解析し、その中に含まれるデータに基づいて適切な処理を行います。
例えば、特定の関数呼び出し、データの保存、他のコントラクトとのやり取りなどが含まれることがあります。
この段階でアカウント内のロジックが実行され、UserOperation
に指定された操作が実際に行われます。
-
simulateValidation
関数の呼び出しと検証
UserOperation
を受け入れる前に、バンドラはエントリーポイントのsimulateValidation
関数をローカルに呼び出すためのRPCメソッドを使用して、以下の検証を行います。- 署名が正しいかどうかの検証。
- 手数料が支払われるかどうかの検証。
もし検証に失敗した場合、ノード/バンドラはそのUserOperation
をmempoolに追加せずに破棄します。
要するに、handleOps
の実行ループでは、各UserOperation
に対して次の手順が行われます。
- 対応するアカウントを呼び出してアカウント内のロジックを実行する。
-
simulateValidation
関数を呼び出して、署名と手数料の検証を行い、検証に失敗した場合はUserOperation
を破棄する。
これによって、正確な処理と署名の検証を通じて、有効なUserOperation
の実行が確保されます。
引用: https://eips.ethereum.org/EIPS/eip-4337
2つのhandleOps
とhandleAggregatedOps
という異なるエントリーポイントメソッドを持っています。それらは
-
handleOps
- 署名アグリゲーターが必要ないアカウントのユーザー操作を処理します。
-
handleAggregatedOps
- 複数のアグリゲーターのユーザー操作を一括処理できます。
- また、アグリゲーターがないリクエストも処理できます。
これらの関数は、検証ループと実行ループの2つのフェーズから構成されています。
① アカウントの作成
- アカウントが存在しない場合、
UserOperation
で提供されたinitcode
を使用してアカウントを作成します。 - アカウントが存在しない場合、
initcode
が空であるか、送信者のアドレスでコントラクトをデプロイしない場合、呼び出しは失敗します。
② ユーザー操作の検証
-
validateUserOp
をアカウントに対して呼び出し、UserOperation
、必要な手数料、アグリゲーター(ある場合)を渡します。 - アカウントは操作の署名を検証し、操作が有効であると判断した場合に手数料を支払います。
- 何らかの
validateUserOp
呼び出しが失敗すると、handleOps
は少なくともその操作の実行をスキップし、完全に元に戻すこともあります。
③ デポジットの検証
- エントリーポイントでのアカウントのデポジットが、最大可能コスト(検証済みのガスと最大実行ガス)をカバーしているかを検証します。
④ ユーザー操作の実行
-
UserOperation
のcalldata
を使用してアカウントを呼び出します。 - アカウントが
calldata
を解析する方法はアカウントに任されており、通常はアカウントが行うべき1回以上の呼び出しとして解析されるとされています。
⑤ シミュレーションと検証
- バンドラは、ローカルでエントリーポイントの
simulateValidation
関数を呼び出し、署名が正しく、操作が実際に手数料を支払うかを確認する必要があります。 - ノード/バンドラは、検証に失敗した
UserOperation
をmempoolに追加してはいけません。
paymasters拡張
エントリーポイントの機能を拡張して、他のユーザーのトランザクションの手数料をサポートする「ペイマスター」と呼ばれる仕組みを導入します。
この機能は、例えばアプリケーション開発者がユーザーの手数料を補助するために利用したり、ユーザーが特定のトークンを使用して手数料を支払うために活用したりすることができます。
これによって、ユーザーエクスペリエンスを向上させたり、新しいユースケースを実現することが可能です。
ペイマスターがゼロアドレス(何も指定されていない状態)でない場合、エントリーポイントは異なる手順を実行するようになります。
要するに、エントリーポイントはペイマスターをサポートし、他のユーザーのトランザクションの手数料を支援する機能を提供します。
これにより、アプリケーション開発者やユーザーは手数料を補助したり、異なるトークンで支払ったりすることができるようになります。ペイマスターがゼロアドレスでない場合、エントリーポイントは異なる動作をします。
引用: https://eips.ethereum.org/EIPS/eip-4337
バリデーションループ中、handleOps
の実行では、以下のステップを行います。
-
各
UserOperation
に対して、validateUserOp
を呼び出す。 -
ペイマスターがエントリーポイントに十分なETHを預けているかを確認し、
validatePaymasterUserOp
を呼び出してオペレーションの支払いが可能かどうかを検証する。 -
validateUserOp
は、missingAccountFunds
を0として呼び出され、このuserOp
の支払いにアカウントのデポジットは使用されないことが反映される。- ペイマスターの
validatePaymasterUserOp
が「context
」を返す場合、handleOps
は以下の手順を実行します。
- ペイマスターの
-
メインの実行コールを行った後にペイマスター上で
postOp
を呼び出す。 -
postOp
の実行を保証するため、メインの実行を内部コールコンテキスト内で行い、もし内部コールコンテキストがリバートする場合は、外部コールコンテキスト内で再度postOp
を呼び出す。
また、悪意のあるペイマスターがシステムを攻撃する可能性があるため、reputationシステムを使用して防ぎます。
ペイマスターは、ストレージ使用量を制限するか、ステークを持つ必要があります。
これにより、ペイマスターの信頼性を確保し、攻撃からシステムを保護します。
reputationシステム
特定のエンティティやアクター(この場合はペイマスター)の信頼性や行動履歴を評価し、その評価に基づいて信頼度を割り当てる仕組みです。
システム内でのパートナーや参加者の信頼性を確保し、悪意のある行動からシステムを保護するために使用されます。
評判システムは、アクターが望ましい行動をするかどうかを判断し、それに応じて特定の権限やリソースを与えたり、制限したりすることができます。
例えば、ペイマスターはストレージ使用量を制限するか、ステークを持つことで信頼性を示す必要があります。
簡潔に言えば、handleOps
の実行中には、各UserOperation
に対してvalidateUserOp
とvalidatePaymasterUserOp
を呼び出し、適切な支払いと検証を行います。
ペイマスターがvalidatePaymasterUserOp
でcontext
を返す場合は、postOp
の実行も確保します。
reputationシステムを使用してペイマスターの信頼性を確保し、攻撃からシステムを守ります。
① バリデーションループの開始
バリデーションループ中に各UserOperation
に対して一連の検証が行われます。
② ユーザーオペレーションの検証
各UserOperation
に対してvalidateUserOp
を呼び出し、署名と手数料の検証を行います。
③ ペイマスターのETHの確認と検証
ペイマスターがエントリーポイントに十分なETHを預けているか確認し、validatePaymasterUserOp
を呼び出して操作の支払いが可能かどうかを検証します。
④ missingAccountFundsの反映
validateUserOp
は、missingAccountFunds
を0
として呼び出されます。
このuserOp
の支払いにアカウントのデポジットは使用されないことが反映されます。
⑤ メイン実行とpostOpの保証
ペイマスターのvalidatePaymasterUserOp
が「context
」を返す場合、メインの実行コールを行い、その後ペイマスター上でpostOp
を呼び出します。
postOp
の実行を保証するため、メインの実行を内部コールコンテキスト内で行い、リバートする場合は、外部コールコンテキストで再度postOp
を呼び出します。
⑥ リピュテーションシステム
悪意のあるペイマスターがシステムを攻撃する可能性があるため、リピュテーションシステムを使用して防ぎます。
ペイマスターはストレージ使用量を制限するか、ステークを持つ必要があります。
このプロセスにより、システムの信頼性と安全性が確保されます。
paymasterのインターフェースは以下になります。
function validatePaymasterUserOp
(UserOperation calldata userOp, bytes32 userOpHash, uint256 maxCost)
external returns (bytes memory context, uint256 validationData);
function postOp
(PostOpMode mode, bytes calldata context, uint256 actualGasCost)
external;
enum PostOpMode {
opSucceeded, // user op succeeded
opReverted, // user op reverted. still has to pay for gas.
postOpReverted // user op succeeded, but caused postOp to revert
}
validatePaymasterUserOp
概要
ペイマスターがユーザーオペレーションの検証を行う関数。
詳細
与えられたユーザーオペレーション、ユーザーオペレーションのハッシュ、および最大コストを使用して、ペイマスターがユーザーオペレーションを検証します。
検証が成功した場合、コンテキストと検証データが返されます。
引数
-
userOp
- ユーザーオペレーションのデータ。
-
userOpHash
- ユーザーオペレーションのハッシュ値。
-
maxCost
- ユーザーオペレーションの最大コスト。
戻り値:
-
context
- コンテキストデータ。
-
validationData
- 検証データ。
postOp
概要
ペイマスターがユーザーオペレーションの実行後に呼び出す関数。
詳細
実行モード、コンテキストデータ、および実際のガスコストを使用して、ペイマスターがユーザーオペレーションの実行後に特定のアクションを実行します。
引数
-
mode
- 実行モード(
opSucceeded
、opReverted
、postOpReverted
)。
- 実行モード(
-
context
- コンテキストデータ。
-
actualGasCost
- 実際のガスコスト。
PostOpMode
列挙型。
-
opSucceeded
- ユーザーオペレーションが成功したことを示す。
-
opReverted
- ユーザーオペレーションがリバートされたことを示す。
- ガス代が発生する。
-
postOpReverted
- ユーザーオペレーションは成功したが、
postOp
がリバートしたことを示す。
- ユーザーオペレーションは成功したが、
// add a paymaster stake (must be called by the paymaster)
function addStake(uint32 _unstakeDelaySec) external payable
// unlock the stake (must wait unstakeDelay before can withdraw)
function unlockStake() external
// withdraw the unlocked stake
function withdrawStake(address payable withdrawAddress) external
addStake
概要
ペイマスターがステークを追加する関数。
詳細
ペイマスターは、ステークを追加するためにこの関数を呼び出します。
ステークを追加する際にはアンステーク遅延時間を指定することができます。
引数
-
_unstakeDelaySec
- アンステーク遅延時間(秒)。
unlockStake
概要
ステークをアンロックする関数。
詳細
アンステーク遅延時間が経過すると、ペイマスターはステークをアンロックすることができます。
withdrawStake
概要
アンロックされたステークを引き出す関数。
詳細
アンステーク遅延時間が経過したステークは引き出すことができます。
引き出し先のアドレスを指定して引き出します。
ペイマスターは、ユーザーオペレーションのコストを支払うためのデポジットを保持する必要があります。
このデポジットは、エントリーポイントがユーザーオペレーションのコストを差し引くために使用されます。
デポジット(ガス料金の支払いに使用される)は、ステーク(ロックされた資金)とは別のものです。
エントリーポイントは、ペイマスターや必要に応じてアカウントがデポジットを管理できるようにするために、以下のインターフェースを実装する必要があります。
ペイマスターはデポジットを持ち、これはユーザーオペレーションのコストを支払うために使用されます。
デポジットはステークとは異なり、ロックされず、エントリーポイントはこのデポジットを管理するためのインターフェースを提供する必要があります。
// return the deposit of an account
function balanceOf(address account) public view returns (uint256)
// add to the deposit of the given account
function depositTo(address account) public payable
// withdraw from the deposit
function withdrawTo(address payable withdrawAddress, uint256 withdrawAmount) external
balanceOf
概要
指定されたアカウントのデポジット残高を取得する関数。
引数
-
account
- デポジット残高を取得する対象のアカウントのアドレス。
戻り値
-
uint256
- 指定されたアカウントのデポジット残高。
depositTo
概要
指定されたアカウントのデポジットに資金を追加する関数。
詳細
指定されたアカウントのデポジットにETHを追加するために使用されます。
引数
-
account
- デポジットに資金を追加する対象のアカウントのアドレス。
戻り値
なし
withdrawTo
概要
デポジットから資金を引き出す関数。
詳細
指定されたアカウントのデポジットから指定された金額のETHを引き出すために使用されます。
引数
-
withdrawAddress
- 引き出したETHを受け取るアカウントのアドレス。
-
withdrawAmount
- 引き出すETHの量。
戻り値
なし
ユーザーオペレーションを受け取るクライアントの振る舞い
クライアントがユーザーオペレーションを受け取る際に、まずいくつかの基本的なチェックを実行する必要があります。
具体的には以下の点を確認します。
ユーザーオペレーションを受け取るクライアントの振る舞いは以下の通りです。
- 送信者が既存のコントラクトであるか、または
initCode
が空でないことをチェックします。
ただし、どちらか一方のみが該当することが求められます。 - initCodeが空でない場合、最初の
20
バイトをファクトリーアドレスとして解析します。
この際、ファクトリーがステークされているかどうかを記録します。
後のシミュレーションにおいて、ファクトリーのステークが必要であることが示唆される場合に備えて、ファクトリーのステーキング状況を記録します。
また、ファクトリーがグローバルステートにアクセスする場合は、ステークが必要です。 -
verificationGasLimit
が十分に低い(MAX_VERIFICATION_GAS
以下)ことと、preVerificationGas
が十分に高い(UserOperation
のシリアル化にかかるcalldata
ガスコストにPRE_VERIFICATION_OVERHEAD_GAS
を足した値以上)ことを確認します。 -
paymasterAndData
が空であるか、またはペイマスターアドレスで始まることを確認します。
ペイマスターアドレスは、以下の条件を満たす必要があります。- (i)現在チェーン上に空ではないコード(コントラクトのロジック)が存在する。
- (ii)
UserOperation
の支払いに十分なデポジットがある。 - (iii)現在バンされていないコントラクトである。
シミュレーション中には、ペイマスターのステークも確認されます。
この際、ストレージ使用量に応じてステークのチェックが行われます。
-
callgas
が少なくとも1以上の値を持つCALL
のコスト以上であることを確認します。 -
maxFeePerGas
およびmaxPriorityFeePerGas
が、クライアントが受け入れる最小値を上回っていることを確認します。
これらの値は少なくとも、現在のblock.basefee
とともに現在のブロックに含まれるのに十分に高い値である必要があります。 - 送信者が既にプール内に同じ送信者と
nonce
を持つ別のユーザーオペレーションを持っていないことを確認します。
ただし、同じ送信者とnonce
を持つ既存のエントリーを、より高いmaxPriorityFeePerGas
と同じくらい増加したmaxFeePerGas
で置き換えることは可能です。
1つの送信者ごとに1つのユーザーオペレーションのみが単一のバッチに含まれることができます。
ただし、送信者がステークされている場合は、このルールには例外があり、プール内およびバッチ内に複数のユーザーオペレーションを持つことができます。
ただし、この例外は通常のアカウントには適用されません。
もしユーザーオペレーションがこれらのチェックをパスする場合、クライアントは次にオペレーションのシミュレーションを実行する必要があります。
もしシミュレーションが成功する場合、クライアントはそのオペレーションをプールに追加する必要があります。
バンドリング中にも、ユーザーオペレーションがまだ有効であるかを確認するために2回目のシミュレーションが行われる必要があります。
オペレーションのシミュレーション
実際にトランザクションを送信せずに、そのトランザクションがネットワーク上でどのように振る舞うかを予測するプロセスです。
これにより、トランザクションが成功するかどうかや、必要なガスコストがどれくらいか、どのような影響を持つかを評価することができます。
具体的には、オペレーションのシミュレーションでは、トランザクションが実行された場合の状態変化やエラーの可能性を検証します。
これには、トランザクションが正常に完了するか、エラーが発生するか、必要なガスコストが十分かどうかなどが含まれます。
シミュレーションは、実際のトランザクションを送信せずにこれらの詳細を把握するための手段として使用されます。
オペレーションのシミュレーションは、トランザクションの安全性と正確性を確認するために重要なステップであり、不正確な操作や意図しない結果を避けるために必要な手法です。
シミュレーション
シミュレーションの理由
ユーザーオペレーションをmempoolに追加し(そして後でバンドルにも追加するためには)、そのオペレーションが有効であり、実行に必要な支払いが可能であることを確認するために、「シミュレーション」と呼ばれる手続きを行います。
さらに、同じオペレーションが実際にチェーン上で実行された場合でも同じ条件が成り立つことを確認する必要があります。
このために、ユーザーオペレーションはシミュレーションと実際の実行の間に変わる可能性のある情報へのアクセスを制限されています。
たとえば、現在のブロックの時刻や番号、ハッシュなどの情報は含まれません。
また、ユーザーオペレーションは送信者のアドレスに関連するデータにのみアクセスできるように制限されています。
さらに、異なるユーザーオペレーションが同じストレージにアクセスできないようになっており、これによって一度の状態変更で多くのユーザーオペレーションを無効化することが防がれます。
また、特別な3つのコントラクト(ファクトリー、ペイマスター、シグネチャアグリゲータ)とのやり取りもあります。
これらのコントラクトも同様に、ユーザーオペレーションの検証が隔離されるようにストレージアクセスが制限されています。
これによって、ユーザーオペレーションの検証が正確に行われ、安全な取引が確保されます。
シミュレーションの仕様
ユーザーオペレーションの検証をシミュレートするために、クライアントはsimulateValidation(userop)
という関数を実行します。
この操作は、常に成功した応答としてrevert
してValidationResult
を返します。
もし他のエラーでrevert
した場合、クライアントはこのユーザーオペレーションを拒否します。
常にrevert
するということは、オンチェーンでの実行を想定していません。
理由としては、シミュレーション用の関数だからです。
この処理では以下の手順で完全な検証を行います。
- もし
initCode
が存在する場合、アカウントを作成します。 -
account.validateUserOp
を呼び出します。 - ペイマスターが指定されている場合、
paymaster.validatePaymasterUserOp
を呼び出します。 -
validateUserOp
またはvalidatePaymasterUserOp
は、「validAfter
」と「validUntil
」というタイムスタンプを返す可能性があります。
これは、このユーザーオペレーションがチェーン上で有効な時間範囲です。
simulateValidation
コールはこの時間範囲を返します。
ノードは、ユーザーオペレーションがあまりに早く期限切れになる場合(次のブロックに到達しない場合など)、ユーザーオペレーションを破棄する可能性があります。
もしValidationResult
にsigFail
が含まれている場合、クライアントはユーザーオペレーションを破棄すべきです。
これらの操作は、それぞれのオペコードの禁止ポリシーによって異なります。
これらの操作を区別するために、3つの関数間にはNUMBER
オペコード(block.number
)へのコールがあり、これが3つの関数を区切るための区切りとして使用されます。
ユーザーオペレーションの検証をシミュレートする際、クライアントは以下のことを確認する必要があります。
- 禁止されたオペコードを呼び出してはいけません。
-
GAS
オペコード(CALL
、DELEGATECALL
、CALLCODE
、STATICCALL
のいずれかに直後に続く場合を除く)を使用してはいけません。 - ストレージアクセスは次のように制限されます。
- 自己のストレージ(それぞれのファクトリー/ペイマスター)は、自己エンティティがステークされている場合にのみ許可されます。
- アカウントのストレージアクセスは許可されます。
- いずれの場合も、同じバンドル内の別のユーザーオペレーションの送信者が使用するストレージは使用してはいけません(ペイマスターやファクトリーは送信者として許可されていません)。
- 「
CALL
」オペコード(CALL
、DELEGATECALL
、CALLCODE
、STATICCALL
)に対する制限。- 値を使用してはいけません(アカウントからエントリーポイントへの場合を除く)。
- ガス不足で
revert
してはいけません。 - 宛先アドレスはコードを持っている必要があります(
EXTCODESIZE>0
)または0x01
から0x09
までのアドレスに定義されているEthereumの標準precompile
である必要があります。 - エントリーポイントのメソッドを呼び出すことはできません(再帰を避けるために
depositFor
のみ許可)。
- すべてのアドレスへのアクセス(任意のオペコードによって)の
EXTCODEHASH
は、オペレーションの最初と2回目のシミュレーションの間で変更されない必要があります。 -
EXTCODEHASH
、EXTCODELENGTH
、EXTCODECOPY
はコードのないアドレスにアクセスしてはいけません。 - もし
op.initcode.length != 0
ならば、最初の(展開)ブロックでCREATE2
オペコードの呼び出しを1回だけ許可します。- それ以外の場合、
CREATE2
を禁止します。
- それ以外の場合、
-
EIP1153で定義された
Transient Storage
スロットは、TLOAD(0x5c)
およびTSTORE(0x5d)
オペコードを使用してアクセスされ、Transient Storage
が有効な場合、永続的なストレージと同じ検証ルールに従う必要があります。
したがって、クライアントはユーザーオペレーションが有効であり、自己の実行に必要な条件をシミュレーションを通じて確認します。
これによって、安全なトランザクションとオペレーションのチェーン上での実行が確保されるのです。
特定のアドレスに関連するストレージ
ストレージスロットを「associated with an address(アドレスに関連する)」と定義すると、それはそのアドレスに固有に関連しており、他のどのアドレスとも関連付けられないスロットのことを指します。
Solidityでは、これにはコントラクト自体のすべてのストレージと、このコントラクトアドレスをマッピングキーとして使用する他のコントラクトの任意のストレージが含まれます。
具体的な例を挙げると、アドレスAには以下のケースがあります。
- コントラクト自体のストレージ
- コントラクトが保持する自身のデータを格納するためのスロットです。
- 他のコントラクトにおけるアドレスAのスロット
- アドレスAをキーとして使用する他のコントラクトが持つストレージスロットです。
-
keccak256(A || X) + n
という形式のスロット- このスロットはアドレスAと別のアドレスXを組み合わせたもので、通常は
mapping(address => value)
という形式で使用されます(例えば、ERC20トークンの残高を記録するため)。 - また、
n
は最大で128
までのオフセット値で、mapping(address => struct)
の形式で構造体のフィールドにアクセスするために使われます。
- このスロットはアドレスAと別のアドレスXを組み合わせたもので、通常は
代替Mempool
提供されたシミュレーションルールは厳格であり、ペイマスターやシグネチャアグリゲータがシステムに悪影響を及ぼすことを防ぐためのものです。
しかし、特定のペイマスターやシグネチャアグリゲータが手動で監査されて検証され、問題を引き起こす可能性がない場合、一部のルールを緩和する必要がある場合もあります。
ただし、特定のペイマスターからのリクエストを単にバンドラーが「ホワイトリスト」に追加するだけでは十分ではありません。
なぜなら、そのペイマスターがすべてのバンドラーによって受け入れられていない場合、そのサポートは不安定になる可能性があるからです。
ここで、「代替Mempool」という概念が導入されます。
「ホワイトリスト」に登録された特定のペイマスターやシグネチャアグリゲータを使用するユーザーオペレーションは、専用のmempoolに配置されます。
この専用のmempoolは、ホワイトリストをサポートするバンドラーのみが利用できるものです。
ここに含まれるユーザーオペレーションは、通常のメインmempool内のユーザーオペレーションと一緒にバンドルされる可能性があります。
この方法により、特定の信頼性の高いペイマスターやシグネチャアグリゲータによるリクエストを、一部のルールを緩和しながらも安全に処理できるようになります。
バンドリング
バンドリングの際、クライアントは以下の手順を実行する必要があります。
- 同じバッチ内の別の
UserOp
の送信者アドレスにアクセスするUserOp
を除外します。 - 同じバッチ内で他の
UserOp
のバリデーションによって作成されたアドレスにアクセスするUserOp
を除外します(ファクトリーを介して)。- これは通常、ファクトリーを通じて行われることがあります。
- 各バッチ内で使用されるペイマスターごとに、
UserOp
を追加しながら残高を追跡します。- すべての使用される
UserOp
に対して必要なデポジットがあることを確認します。
- すべての使用される
- アグリゲータごとに
UserOp
をソートし、アグリゲータごとのUserOp
リストを作成します。 - 各アグリゲータに対して、アグリゲータ固有のコードを実行して集約された署名を作成し、
UserOp
を更新します。
これらの手順に従うことで、バンドリングプロセスは効果的に実行され、複数のUserOp
が適切に処理されることが保証されます。
バッチを作成した後、トランザクションをブロックに含める前に、クライアントは以下の手順を実行する必要があります。
- 最大使用可能なガスで
debug_traceCall
を実行し、バリデーションオペコードとプリコンパイルの禁止ルールを強制し、ストレージアクセスルールを適用します。
またhandleOps
バッチトランザクション全体を検証し、実際のトランザクション実行に消費されたガスを使用します。 - もしコールが
revert
した場合は、FailedOp
イベントをチェックします。
handleOps
シミュレーション中のFailedOp
は予期しないイベントであり、通常は単一のUserOperation
シミュレーションでキャッチされるべきです。 - もし任意の検証コンテキストルールが違反された場合、バンドラーはこれを
UserOperation
がFailedOp
イベントをrevert
した場合と同様に扱うべきです。 - 問題のある
UserOperation
を現在のバンドルとメンプールから削除します。 - エラーがファクトリー(エラーコードが
AA1x
)またはペイマスター(エラーコードがAA3x
)によって引き起こされ、UserOp
の送信者がステークされたエンティティでない場合、対象のファクトリーやペイマスターに対して「禁止(特定のファクトリーやペイマスターなどのエンティティが、プロトコル内で特定の操作や動作を行うことを制限する措置)」を発行します。 - エラーがファクトリー(エラーコードが
AA1x
)またはペイマスター(エラーコードがAA3x
)によって引き起こされ、UserOp
の送信者がステークされたエンティティである場合、ファクトリー/ペイマスターをmempoolから禁止するのではなく、代わりにステークされた送信者エンティティに対して「禁止」を発行します。 -
debug_traceCall
が成功するまで繰り返します。
これにより、バンドルのトランザクションが安全に検証され、不適切な操作が適切に処理されることが保証されます。
ステーキングされたエントリーは、同じバンドル内の複数のユーザーオペレーション間でデータをやり取りするために一時的なストレージを使用する場合があります。
そのため、各個々のユーザーオペレーションと同じように、handleOps
のバリデーション全体で正確に同じオペコードやプリコンパイルの禁止ルール、およびストレージアクセスルールが適用されることが非常に重要です。
そうしないと、攻撃者は禁止されたオペコードを使用してチェーン上で実行中であることを検出し、FailedOp
のrevert
を引き起こす可能性があります。
特定のバンドラーに対する違反行為を禁止するためには、問題のエンティティのopsSeen
値を1000000
増やし、既にmempoolに存在するそのエンティティに関連するすべてのユーザーオペレーションを削除します。
この変更により、負のreputation
値は時間の経過とともに他の禁止理由と同様に悪化します。
エンティティの信用が低下するということを意味しています。
不正な振る舞いを繰り返すエンティティは、reputation
値がますます低くなり、その結果、システム内での信頼性が低下する可能性が高くなります。これによって、不正行為を行うリスクが増大し、安全なシステム運用が促進されることが期待されます。
上記の3つの条件のいずれかが違反された場合、クライアントはそのオペレーションを受け入れてはならず、拒否すべきです。
もし両方のコールが3つの条件を遵守して成功する場合(またはop.paymaster == ZERO_ADDRESS
であり、最初のコールが成功する場合)、クライアントはそのオペレーションを受け入れるべきです。
バンドラーノードでは、両方のコールでアクセスされたストレージキーは、ユーザーオペレーションのaccessList
として保存する必要があります。
バンドラーがブロックにバンドルを含める際には、そのブロック内の前のトランザクションがどのユーザーオペレーションも失敗させないように注意する必要があります。
このためには、アクセスリストを使用して競合を防ぐか、バンドルをブロック内の最初のトランザクションとして配置するかを選択します。
禁止されているオペコード
オペコードは、スマートコントラクト内で実行される個々の命令を表します。
一部のオペコードは、シミュレーションと実際のブロックチェーン上での実行の間で異なる結果をもたらす可能性があるため、特定のルールの下で禁止されています。
具体的には、深さ(depth
)が2よりも大きい場合、つまりファクトリー、アカウント、ペイマスター、またはそれらが呼び出す他のコントラクトが実行される場合、一部のオペコードは禁止されます。
これには、GASPRICE
、GASLIMIT
、DIFFICULTY
、TIMESTAMP
、BASEFEE
、BLOCKHASH
、NUMBER
、SELFBALANCE
、BALANCE
、ORIGIN
、GAS
、CREATE
、COINBASE
、SELFDESTRUCT
が含まれます。
ただし、これらのオペコードは検証中のみ禁止され、実行中は許可されます。
オペコードが禁止されている理由は、これらのオペコードを使用したシミュレーションと実際の実行の結果が異なる可能性があるためです。
したがって、シミュレーションだけでは、これらのオペコードを使用した呼び出しが後に実際のチェーン上でどのような結果をもたらすかを確実に予測することはできません。
ただし、例外も存在します。例えば、op.initcode.length != 0
の場合、CREATE2
オペコードが1回だけ許可されます。
このとき、以前に展開されていないUserOperation.sender
の展開結果である必要があります。
また、GAS
オペコードは、直後に{ CALL
、DELEGATECALL
、CALLCODE
、STATICCALL
}のいずれかが続く場合にのみ許可されます。
つまり、呼び出しを行うことは許可されますが、gasleft()
またはgas
オペコードを直接使用することは禁止されています。
これによって、シミュレーションと実行の間での不一致を防ぎ、システム全体の安全性と予測可能性を確保することが目的です。
評判スコアと制限・禁止に関する説明
評判スコアの背景
ユーザーオペレーションのストレージアクセスルールは、他のオペレーションと干渉しないようにしています。
ただし、「グローバル」なエンティティであるペイマスター、ファクトリー、アグリゲータは複数のユーザーオペレーションからアクセスされるため、複数の以前に有効だったユーザーオペレーションを無効にする可能性があります。
悪用を防ぐために、メンプール内の多数のユーザーオペレーションを無効にするエンティティの使用を制限(または一時的に完全に禁止)します。
このようなエンティティが「シビル攻撃」を行うのを防ぐために、システムにステークを行う必要があり、その結果、この種のDoS攻撃が非常に高コストになります。
なお、このステークは決して減少せず、ステーク解除遅延後にいつでも引き出すことができます。
ステークされていないエンティティは、以下のルールの下で許可されています。
ステークされていないエンティティの条件:**
特定の条件下では、ステークされていないエンティティも使用することができます。
- ストレージを一切使用しないエンティティ、または送信者のストレージのみを使用するエンティティ(エンティティ自体のストレージはステークが必要)。
-
UserOp
が新しいアカウントを作成しない場合(initCode
が空である場合)、またはステークされたファクトリーコントラクトを使用して新しいアカウントを作成する場合、エンティティは送信者に関連するストレージも使用できる -
postOp()
メソッドを持つペイマスター(つまり、validatePaymasterUserOp
が「context
」を返す場合)はステークが必要。
ステークの価値はチェーン内で強制されるのではなく、各ノードがトランザクションをシミュレートする際に特に考慮されます。
ステークはMIN_STAKE_VALUE
(最小ステーク値)を上回る必要があり、ステーク解除遅延はMIN_UNSTAKE_DELAY
(最小ステーク解除遅延)を上回る必要があります。
MIN_UNSTAKE_DELAY
の値は84600
(1日)であり、MIN_STAKE_VALUE
の値はチェーンごとに決まり、**"bundler specification test suite"**で指定されています。
これによって、ステークされたエンティティは、システム全体の健全性と安全性を保つために必要な対策を取ることができ、ステークされていないエンティティも特定の条件下で使用できるようになります。
仕様
以下の仕様において、「エンティティ」とは、UserOperation
で明示的に参照されるアドレスであり、送信者、ファクトリー、ペイマスター、およびアグリゲーターが該当します。
クライアントは、ステークされたエンティティに対して2つのマッピングを保持します。
-
opsSeen
Map[address, int]
-
opsIncluded
Map[address, int]
エンティティがまったくストレージを使用しないか、または「送信者」と関連するストレージのみを参照する場合、以下のルールは適用されません。
クライアントは、新しくステークされたエンティティを知ると、opsSeen[entity] = 0
およびopsIncluded[entity] = 0
と設定します。
クライアントは、エンティティが関与するオペレーションをUserOperationPool
に追加するたびに、opsSeen[entity] +=1
と設定し、また、UserOperationPool
に含まれるオペレーションがチェーン上に含まれるたびに、opsIncluded[entity] += 1
と設定します。
毎時、クライアントはすべてのエンティティに対して、opsSeen[entity] -= opsSeen[entity] // 24
および opsIncluded[entity] -= opsIncluded[entity] // 24
を設定します(これにより、両方の値は24時間の指数移動平均になります)。
この仕様によって、ステークされたエンティティがどれだけの頻度でオペレーションを実行しているか、およびそれがチェーン上で実行されたかが監視され、不正な操作を防ぐためのメカニズムが提供されます。
エンティティのステータスを以下のように定義します。
OK, THROTTLED, BANNED = 0, 1, 2
def status(paymaster: Address,
opsSeen: Map[Address, int],
opsIncluded: Map[Address, int]):
if paymaster not in opsSeen:
return OK
min_expected_included = opsSeen[paymaster] // MIN_INCLUSION_RATE_DENOMINATOR
if min_expected_included <= opsIncluded[paymaster] + THROTTLING_SLACK:
return OK
elif min_expected_included <= opsIncluded[paymaster] + BAN_SLACK:
return THROTTLED
else:
return BANNED
私たちはネットワーク上で見られるすべての操作のうち、最低でも1 / MIN_INCLUSION_RATE_DENOMINATOR
の割合が実行されることを望んでいます。
もし特定のエンティティがこの基準に大幅に達しない場合、そのエンティティは制限がかかります。
つまり、既に同じエンティティを持つ操作が存在する場合、クライアントはそのエンティティからの操作を受け付けないようにします。
また、オペレーションはプール内で最大で10
ブロックしか待機しません。
エンティティがさらに遅れると、そのエンティティは禁止されることがあります。
スロットリングと禁止は、指数移動平均のルールによって徐々に時間とともに弱まっていく性質があります。
1 / MIN_INCLUSION_RATE_DENOMINATOR
ネットワーク内で見られる操作のうち、実際に実行されるべき操作の割合を示しています。
この値が小さいほど、ネットワーク全体での操作の多くが実行されることが期待されます。
例えば、MIN_INCLUSION_RATE_DENOMINATOR
が100
の場合、ネットワーク内の操作のうち、少なくとも1%
が実行されることを意味します。
この値は、ネットワークの健全性と効率性を保つために設定される基準の一つです。
非バンドリングクライアントとバンドラーは、下記のパラメータに対して異なる設定を使用するべきです。
パラメータ | クライアントの設定 | バンドラーの設定 |
---|---|---|
MIN_INCLUSION_RATE_DENOMINATOR | 100 | 10 |
THROTTLING_SLACK | 10 | 10 |
BAN_SLACK | 50 | 50 |
表のパラメータは、クライアントとバンドラーが使用する設定を示しています。
MIN_INCLUSION_RATE_DENOMINATOR(最小含有率分母)
このパラメータは、ネットワーク内で処理されるオペレーションのうち、少なくとも何割が実際にブロックに含まれるかを示します。
クライアントの設定では、1つのブロック内で少なくとも1/100
(100
の逆数)のオペレーションが含まれることを期待しています。
一方、バンドラーの設定では、1つのブロック内で少なくとも1/10
のオペレーションが含まれることを期待しています。
バンドラーとクライアントの設定値が異なるのは、それぞれの役割と目的に合わせて調整されているためです。
クライアントの設定値である1/100
(100の逆数)の最小含有率分母は、ネットワーク全体でのオペレーションの含有率を示しています。
クライアントは、ネットワークの健全性と効率を維持するために、少なくともこの割合のオペレーションが実際にブロックに含まれることを期待しています。
一方、バンドラーの設定値である1/10
の最小含有率分母は、バンドラーがバンドルを作成し、ブロックに含めるオペレーションの含有率を示しています。
バンドラーは、より効率的で迅速なトランザクションの処理を促進するために、ユーザーオペレーションの高い含有率を目指しています。
これにより、ブロック内でのオペレーションの処理が効果的に最大化されることが期待されています。
異なる設定値を使用することで、クライアントとバンドラーはそれぞれの役割と目的に最適なネットワークの運用を実現し、攻撃や遅延などの悪影響を最小限に抑えることが狙われています。
THROTTLING_SLACK(スロットリングの余裕)
このパラメータは、エンティティが最小含有率分母に遅れる場合、どれだけの余裕を持たせるかを示します。
クライアントとバンドラーの設定は同じで、スロットリングが行われる基準を10
としています。
これは、ネットワーク内のオペレーションが少し遅れても、即座にスロットリングされないようにするためのものです。
BAN_SLACK(禁止の余裕)
このパラメータは、エンティティが最小含有率分母にさらに遅れる場合に禁止される前にどれだけの余裕を持たせるかを示します。
クライアントとバンドラーの設定は同じで、禁止が行われる基準を50
としています。
これは、エンティティがさらに遅れても即座に禁止されないようにするためのものです。
これらのパラメータは、エンティティの適切な運用とネットワークの健全性を確保するために調整されています。
これらのパラメータを理解するための例を考えてみましょう。
悪意のあるペイマスターが存在する場合、そのペイマスターは最大でネットワーク内で(ブロックチェーンではなく、P2Pネットワークのみで)1時間にBAN_SLACK * MIN_INCLUSION_RATE_DENOMINATOR / 24
個の非支払いオペレーションを処理させることができる可能性があります。
例えば、BAN_SLACK
が50
でMIN_INCLUSION_RATE_DENOMINATOR
が100
の場合、最大で50 * 100 / 24 = 208
個のオペレーションが1時間に処理されることが考えられます。
バンドラーの設定では、操作が実行される割合(MIN_INCLUSION_RATE_DENOMINATOR
)を低くし、スロットリングと禁止の基準(THROTTLING_SLACK
とBAN_SLACK
)を緩和しています。
これにより、悪意のある攻撃者が大量のオペレーションを送信してネットワークに影響を与えることを防ぎつつ、ネットワークの健全性と効率を保つことを狙っています。
補足
スマートコントラクトウォレットのアカウント抽象化システムでは、DoS(サービス拒否)攻撃を防ぐための課題があります。
ブロックを構築する際、操作を含める前に手数料が実際に支払われることを確認する方法が必要です。
しかし、ブロックビルダーに対して操作全体を最初に実行させることはDoS攻撃の脆弱性を持つ可能性があります。
攻撃者は多くの操作を送信し、手数料を支払う振りをして長時間の実行後にrevert
する攻撃が行える可能性があります。
同様に、P2Pネットワークのノードは、手数料を支払うかどうかを事前に確認してから操作を転送することで、メンプールを攻撃者による安価な混雑から守る必要があります。
この提案では、アカウントはvalidateUserOp
というメソッドを持つことが期待されています。
このメソッドは、UserOperation
を受け取り、署名を検証して手数料を支払う役割を果たします。
このメソッドはpure
である必要があり、アカウントのストレージにのみアクセス可能で、環境オペコード(例:TIMESTAMP
)を使用することはできず、アカウントのストレージを編集することやETHを送金すること(エントリーポイントの支払いに必要)のみが許可されます。
また、このメソッドはUserOperation
のverificationGasLimit
によってガス制限されます。
ノードは、verificationGasLimit
が高すぎる操作を拒否することも可能です。
これらの制約により、ブロックビルダーやネットワークノードは、操作の検証ステップをローカルでシミュレートし、実際に操作がブロックに含まれる際の結果と一致することを確信することができます。
このエントリーポイントベースのアプローチにより、検証と実行がきれいに分離され、アカウントのロジックがシンプルに保たれます。
代替案としては、アカウントに自己呼び出しして検証を行い、その後実行を行うテンプレートを要求するアプローチも考えられましたが、これは実装が複雑で、既存のコードコンパイレーションや検証ツールがテンプレート検証に対応していないため採用されませんでした。
ペイマスター
ペイマスターは、トランザクションの支払いを代行し、第三者がトランザクション手数料を支払うメカニズムを提供します。
これを実現するために、ペイマスターはユーザーオペレーションを自身でラップすることがあります。
しかし、このアプローチにはいくつかの制約があります。
- 「受動的」ペイマスターの存在
これは、オンチェーンのDEXから取得したERC20トークンの交換レートで手数料を受け入れるなど、特定の条件下で手数料を受け入れるペイマスターを指します。 - ペイマスターは、ユーザーがペイマスターに手数料を支払うように見せかけた操作を送信し、後でその振る舞いを変更する可能性があります。
ペイマスターは、トランザクションの支払いを管理するための仕組みです。
例えば、ERC20トークンペイマスターは、ユーザーがトランザクションの手数料を支払う場合にのみ、その支払いを行うことを保証します。
ペイマスターコントラクトは、ユーザーのトランザクションをチェックして、手数料の支払いが必要な場合に、それを代わりに行う役割を果たします。具体的には、ペイマスターはvalidatePaymasterUserOp
というメソッドを持ち、このメソッドを使ってユーザーの手数料支払いを確認します。
もし必要な支払いが承認されている場合、ペイマスターはそのERC20トークンを使って支払いを行います。
この支払いは、postOp
という別のメソッドを通じて行われます。
しかし、注意が必要なのは、ユーザーがトランザクションを送信した後に、操作内容を変える可能性があることです。
ペイマスターは、そのような操作を事前に防ぐことが難しいです。
そのため、ペイマスターコントラクトは、支払いの前後でトランザクションが変更されていないことを確認します。
もしトランザクションが変更されている場合、支払いをキャンセルする仕組みが備わっています。
簡単に言うと、ペイマスターは他の人の代わりに支払いを管理する仕組みで、ERC20トークンのようなものを使って支払いを行います。
しかし、トランザクションが後から変更されることを考慮して、支払いの前後でしっかりと確認を行います。
この仕組みにより、支払いと検証を効果的に結びつけ、ペイマスターがトランザクションの支払いを安全に行うことを可能にします。
この提案では、ユーザーが新しいウォレット(アカウント)を作成する際に、既存のアカウントに依存せず、煩雑な手続きを行う必要がありません。代わりに、ユーザーは単にローカルでアドレスを生成し、すぐに資金を受け取ることができます。
最初のアカウント作成
ウォレットの作成は、「ファクトリー」と呼ばれるコントラクトによって行われます。
このファクトリーは、ウォレット固有のデータを持ち、ウォレットを生成するためにCREATE2
(CREATE
ではなく)を使用します。
これにより、ウォレットの作成順序がアドレスの生成に影響を与えないようになります。
もしinitCode
フィールドが空でない場合、それは20
バイトのアドレスと、その後に引き渡す「calldata
」として解釈されます。
このメソッド呼び出しは、ウォレットを作成し、そのアドレスを返すことが期待されています。
ファクトリーがCREATE2
または他の確定的な方法でウォレットを作成する場合、ウォレットが既に作成されていても、ファクトリーはウォレットアドレスを返す必要があります。
これにより、クライアントはウォレットが既に存在しているかどうかを知らなくてもアドレスを問い合わせることができます。
これは、entryPoint.getSenderAddress()
を模倣してファクトリーを呼び出すことで実現されます。
もしinitCode
が指定されている場合、送信者アドレスが既に存在するコントラクトを指しているか、initCode
を呼び出した後でも送信者アドレスがまだ存在しない場合、操作は中止されます。
ただし、initCode
はエントリーポイントから直接呼び出すのではなく、別のアドレスから呼び出す必要があります。
このファクトリーメソッドによって作成されるコントラクトは、UserOp
の署名を検証するためにvalidateUserOp
を呼び出すことができるようになります。
セキュリティ上の理由から、生成されるコントラクトアドレスは最初の署名に依存する必要があります。
これにより、誰かが同じアドレスにウォレットを作成できたとしても、異なる認証情報を使用してウォレットを制御することはできません。
ファクトリーがグローバルストレージにアクセスする場合、ステークが必要です。
最後に注意ですが、ウォレットが「counterfactual
」アドレス(作成前のアドレス)を特定するためには、ウォレットがentryPoint.getSenderAddress()
を静的に呼び出す必要があります。
エントリーポイントのアップグレード
アカウントのアップグレードとは、アカウント内のコードや機能を向上させるための方法です。
アカウントは、DELEGATECALL
という仕組みを使って設計されています。
これにより、別のコントラクトのコードをアカウント内で実行できるようになります。
エントリーポイントとは、コントラクト内のコードの始まりの部分のことを指します。
このエントリーポイントは、コントラクトのコード内にハードコードされることで、実行時のガスの効率性が向上します。
もし新しい機能を追加したり、コードの効率性を改善したり、セキュリティの問題を修正したりする必要がある場合、新しいエントリーポイントを導入することがあります。
ユーザーは自分のアカウントのコードを、新しいコードに置き換えるためにセルフコールを行うことができます。
新しいコードには、新しいエントリーポイントを指すコードが含まれています。
アップグレードのプロセスでは、現在のコードと新しいコードが並行して実行されることになります。
つまり、アカウントのアップグレードは、アカウント内のコードや機能を改善する手段であり、ユーザーが自分のアカウントをアップデートしてより良い機能を享受できるようにする方法です。
DELEGATECALL
については以下を参照してください。
RPCメソッド(eth Namespace)
eth_sendUserOperation
ユーザーオペレーション(ユーザーが行う操作)のオブジェクトを取り、それをクライアントのユーザーオペレーションプールに送信するための手段です。
クライアントは、受け取ったユーザーオペレーションを検証し、結果を返します。
もしユーザーオペレーションがシミュレーションを通過して、クライアントのユーザーオペレーションプールで受け入れられた場合、クライアントはその結果としてuserOpHash
(ユーザーオペレーションのハッシュ値)を返します。
一方、検証やシミュレーション、ユーザーオペレーションプールへの収録に失敗した場合、結果は返されず、代わりに失敗の理由が返されます。
パラメータ:
-
UserOperation
- ユーザーオペレーションの詳細な情報を含むオブジェクトです。
- すべてのフィールドは
16
進数の値として設定されている必要があります。 - 空のバイトブロック(例:
initCode
が空)は0x
と設定されています。
-
EntryPoint
- リクエストが送信されるべきエントリーポイントのアドレスです。
- これは、クライアントがサポートするエントリーポイントの中から選ばれるべきです。
戻り値:
- ユーザーオペレーションが有効な場合、クライアントは
userOpHash
を計算して返します。- これは、ユーザーオペレーションの一意の識別子です。
- 失敗した場合、クライアントはエラーコードとメッセージを含むエラー結果オブジェクトを返します。
- エラーコードとメッセージは特定の状況に応じて設定されます。
- たとえば、無効なユーザーオペレーション構造やエントリーポイントの拒否、支払いマスターによる検証の失敗などが考えられます。
エラーコード
-
32602
- 無効なユーザーオペレーション構造体/フィールド。
- ユーザーオペレーションの構造体やフィールドが無効な場合に発生します。提供された情報が正しくない場合に使用されます。
-
32500
- エントリーポイントのシミュレーション検証によってトランザクションが拒否されました(ウォレットの作成または検証中)。
- ウォレットの作成や検証の際に、エントリーポイントのシミュレーション検証によってトランザクションが拒否された場合に発生します。
- エラーメッセージは、エントリーポイントから返される
FailedOp
のエラーメッセージに設定されます。
-
32501
- ペイマスターの
validatePaymasterUserOp
によってトランザクションが拒否された場合に発生します。 - エラーメッセージは、ペイマスターからの
revert
メッセージになるべきです。 - データフィールドにはペイマスターの値が含まれている必要があります。
- ペイマスターの
-
32502
- オペコード検証によってトランザクションが拒否されました。
- トランザクション内のオペコードが検証に合格しなかった場合に発生します。
-
32503
- ユーザーオペレーションが有効期限を超えています。
- ウォレットまたはペイマスターが有効期限を設定し、それが既に期限切れであるか、まもなく期限切れになる場合に発生します。
- データフィールドには、有効期限の
validUntil
とvalidAfter
の値が含まれている必要があります。 - また、このエラーがペイマスターによってトリガーされた場合、データフィールドにはペイマスターの値も含まれているべきです。
-
32504
- ペイマスターまたは署名アグリゲーターがスロットルまたは禁止されたためにトランザクションが拒否された場合に発生します。
- データフィールドには、失敗したエンティティに応じてペイマスターまたはアグリゲーターの値が含まれている必要があります。
-
32505
- ペイマスター(または署名アグリゲーター)のステークまたはアンステーク遅延が短すぎるため、トランザクションが拒否されました。
- ペイマスターまたは署名アグリゲーターのステークまたはアンステーク遅延が設定された最小値を下回るためにトランザクションが拒否された場合に発生します。
- データフィールドには、失敗したエンティティに応じてペイマスターまたはアグリゲーターの値と、最小ステークと最小アンステーク遅延が含まれている必要があります。
-
32506
- ウォレットがサポートされていない署名アグリゲーターを指定したためにトランザクションが拒否された場合に発生します。
- データフィールドには、アグリゲーターの値が含まれている必要があります。
-
32507
- ウォレットの署名チェックが失敗した場合、またはペイマスターがデータを署名として使用している場合、ペイマスターの署名チェックが失敗した場合に発生します。
参考
リクエスト
{
"jsonrpc": "2.0",
"id": 1,
"method": "eth_sendUserOperation",
"params": [
{
sender, // address
nonce, // uint256
initCode, // bytes
callData, // bytes
callGasLimit, // uint256
verificationGasLimit, // uint256
preVerificationGas, // uint256
maxFeePerGas, // uint256
maxPriorityFeePerGas, // uint256
paymasterAndData, // bytes
signature // bytes
},
entryPoint // address
]
}
レスポンス
{
"jsonrpc": "2.0",
"id": 1,
"result": "0x1234...5678"
}
レスポンス(失敗)
{
"jsonrpc": "2.0",
"id": 1,
"error": {
"message": "AA21 didn't pay prefund",
"code": -32500
}
}
{
"jsonrpc": "2.0",
"id": 1,
"error": {
"message": "paymaster stake too low",
"data": {
"paymaster": "0x123456789012345678901234567890123456790",
"minimumStake": "0xde0b6b3a7640000",
"minimumUnstakeDelay": "0x15180"
},
"code": -32504
}
}
eth_estimateUserOperationGas
ユーザーオペレーションの実行に必要なガス値を見積もる関数。
パラメータ
eth_sendUserOperation
と同じパラメータ(ガスリミットと価格のパラメータ)。
ただし、指定されている場合に使用されます。
maxFeePerGas
とmaxPriorityFeePerGas
はデフォルトでゼロに設定されているため、アカウントやペイマスターからの支払いは必要ありません。
戻り値:
-
preVerificationGas
- このユーザーオペレーションのガスオーバーヘッド。
-
verificationGasLimit
- このユーザーオペレーションの検証に使用される実際のガス。
-
callGasLimit
- 内部アカウント実行に使用されるガス。
エラーコード:
eth_sendUserOperation
と同様のエラーコード。
また、アカウントコントラクトへの内部呼び出しがrevert
する場合にもエラーが返される可能性があります。
このメソッドは、ユーザーオペレーションのガス見積もりを行う際に役立ちます。
特に、ガスリミットと価格が指定されていない場合に使用されます。
また、このメソッドを使用することで、ウォレットの承認は必要なく、一部正当な署名(長さが適切な署名)が必要な場合もあります。
eth_getUserOperationByHash
eth_sendUserOperation
によって返されたユーザーオペレーションのハッシュ(userOpHash
)に基づいて、ユーザーオペレーションを取得する関数。
パラメータ:
-
hash
-
eth_sendUserOperation
によって返されたuserOpHash
の値。
-
戻り値:
- ユーザーオペレーションがまだブロックに含まれていない場合、
null
が返されます。 - もしくは、ユーザーオペレーションがブロックに含まれている場合、
entryPoint
、blockNumber
、blockHash
、transactionHash
を追加した完全なユーザーオペレーションが返されます。
つまり、eth_getUserOperationByHash
は、特定のuserOpHash
に対応するユーザーオペレーションを取得するためのメソッドです。
もしユーザーオペレーションがまだブロックに含まれていない場合はnull
が返され、含まれている場合は詳細な情報を含むユーザーオペレーションが返されます。
eth_getUserOperationReceipt
eth_sendUserOperation
によって返されたユーザーオペレーションのハッシュ(userOpHash
)に基づいて、ユーザーオペレーションのレシートを取得する関数。
パラメータ:
-
hash
-
eth_sendUserOperation
によって返されたuserOpHash
の値。
-
戻り値:
- ユーザーオペレーションがまだブロックに含まれていない場合、
null
が返されます。 - もしくは、ユーザーオペレーションがブロックに含まれている場合、以下の情報を含むユーザーオペレーションのレシートが返されます。
-
userOpHash
- リクエストのハッシュ。
-
entryPoint
- エントリーポイントのアドレス。
-
sender
- 送信者のアドレス。
-
nonce
- ノンスの値。
-
paymaster
- このユーザーオペレーションに使用されたペイマスター(または空)。
-
actualGasCost
- このユーザーオペレーションによって支払われた実際のガス料金(アカウントまたはペイマスターによる)。
-
actualGasUsed
- このユーザーオペレーションによって使用された総ガス量(事前検証、作成、検証、実行を含む)。
-
success
- 実行が
revert
なしで完了した場合は真(true
)、リバートが発生した場合は偽(false
)。
- 実行が
-
reason
-
revert
が発生した場合のrevert
の理由。
-
-
logs
- このユーザーオペレーションによって生成されたログ(同じバンドル内の他のユーザーオペレーションのログは含まれません)。
-
receipt
- トランザクションレシートオブジェクト。
- ただし、返されるトランザクションレシートはバンドル全体のものであり、このユーザーオペレーションだけのものではありません。
-
要するに、eth_getUserOperationReceipt
は、特定のuserOpHash
に対応するユーザーオペレーションのレシートを取得するためのメソッドです。
ユーザーオペレーションがまだブロックに含まれていない場合はnull
が返され、含まれている場合は詳細なレシート情報が返されます。
eth_supportedEntryPoints
クライアントがサポートしているエントリーポイントアドレスの配列を返す関数。
配列の最初の要素は、クライアントが優先するエントリーポイントのアドレスです。
要するに、このメソッドは、クライアントがどのエントリーポイントアドレスをサポートしているかを示す配列を返すもので、配列の最初の要素はクライアントが特に好むエントリーポイントを表します。
# Request
{
"jsonrpc": "2.0",
"id": 1,
"method": "eth_supportedEntryPoints",
"params": []
}
# Response
{
"jsonrpc": "2.0",
"id": 1,
"result": [
"0xcd01C8aa8995A59eB7B2627E69b40e0524B5ecf8",
"0x7A0A0d159218E6a2f407B99173A2b12A6DDfC2a6"
]
}
eth_chainId
EIP155チェーンIDを返す関数。
# Request
{
"jsonrpc": "2.0",
"id": 1,
"method": "eth_chainId",
"params": []
}
# Response
{
"jsonrpc": "2.0",
"id": 1,
"result": "0x1"
}
RPCメソッド(debug Namespace)
このAPIはテストモードでのみ利用可能でなければならず、互換性テストスイートで必要とされます。
本番環境では、debug_*
rpc呼び出しはブロックされるべきです。
debug_bundler_clearState
ペイマスター/アカウント/ファクトリ/アグリゲータのバンドルメンプールとレピュテーションデータをクリアする関数。
-
ペイマスターメンプール
ペイマスターとは、支払い操作を管理するためのコントラクトです。
ペイマスターメンプールをクリアすると、過去の支払い関連データが削除され、新たな支払いが受け入れられるようになります。 -
アカウントメンプール
アカウントとは、ユーザーのウォレットを表すコントラクトです。
アカウントメンプールをクリアすると、以前のアカウント関連データが削除され、新しいアカウント作成が可能になります。 -
ファクトリメンプール
ファクトリは、ウォレットを作成するためのコントラクトです。
ファクトリメンプールをクリアすると、過去のファクトリに関連する情報がクリアされ、新たなウォレット作成が行えるようになります。 -
アグリゲータメンプール
アグリゲータは、複数の署名をまとめて処理するコントラクトです。
アグリゲータメンプールをクリアすると、以前のアグリゲータ関連データが消去され、新しい署名まとめ処理が可能になります。 -
レピュテーションデータ
レピュテーションデータは、ユーザーやコントラクトの信頼性を示す情報です。
これをクリアすると、以前の信頼性情報が削除され、新たな情報が収集される状態になります。
まとめると、上記の要素に関連する過去のデータを削除して、新しい操作や作成を行う準備をすることを指します。
# Request
{
"jsonrpc": "2.0",
"id": 1,
"method": "debug_bundler_clearState",
"params": []
}
# Response
{
"jsonrpc": "2.0",
"id": 1,
"result": "ok"
}
debug_bundler_dumpMempool
現在のユーザーオペレーションメンプールをダンプする関数。
パラメータ:
-
EntryPoint
-
eth_sendUserOperation
で使用されたエントリーポイントのアドレス。
-
戻り値:
-
array
- 現在のmempoolにあるユーザーオペレーションの配列。
指定されたエントリーポイントで実行されたeth_sendUserOperation
によって送信されたユーザーオペレーションが、現在のmempoolにどのように蓄積されているかを示すものです。
これは、メンプール内のユーザーオペレーションの状態や情報を確認するための手段です。
# Request
{
"jsonrpc": "2.0",
"id": 1,
"method": "debug_bundler_dumpMempool",
"params": ["0x1306b01bC3e4AD202612D3843387e94737673F53"]
}
# Response
{
"jsonrpc": "2.0",
"id": 1,
"result": [
{
sender, // address
nonce, // uint256
initCode, // bytes
callData, // bytes
callGasLimit, // uint256
verificationGasLimit, // uint256
preVerificationGas, // uint256
maxFeePerGas, // uint256
maxPriorityFeePerGas, // uint256
paymasterAndData, // bytes
signature // bytes
}
]
}
debug_bundler_sendBundleNow
バンドラに対して、mempoolからhandleOps()
トランザクションとしてバンドルを構築し実行するよう強制する関数。
戻り値:
-
transactionHash
- トランザクションのハッシュ値。
つまり、このメソッドはバンドラに対して、現在mempoolにあるユーザーオペレーションをバンドルとしてまとめ、
handleOps()
として実行するよう命令するものです。
その結果として、トランザクションが生成され、そのトランザクションのハッシュ値が返されます。
# Request
{
"jsonrpc": "2.0",
"id": 1,
"method": "debug_bundler_sendBundleNow",
"params": []
}
# Response
{
"jsonrpc": "2.0",
"id": 1,
"result": "0xdead9e43632ac70c46b4003434058b18db0ad809617bd29f3448d46ca9085576"
}
debug_bundler_setBundlingMode
bundling mode(バンドルモード)を設定する関数。
パラメータ:
-
mode
- 設定したいバンドルモード。
- 以下の2つの選択肢があります。
-
manual
- このモードに設定すると、バンドルを手動で送信するために
debug_bundler_sendBundleNow
を呼び出す必要があります。
- このモードに設定すると、バンドルを手動で送信するために
-
auto
- このモードに設定すると、バンドルが自動的に送信されます。
-
このメソッドを使用することで、バンドルモードを手動モード(manual
)または自動モード(auto
)に設定できます。
手動モードに設定した場合は、明示的にdebug_bundler_sendBundleNow
を呼び出すまで、バンドルの送信は行われません。
自動モードに設定した場合は、バンドルは自動的に送信されます。
# Request
{
"jsonrpc": "2.0",
"id": 1,
"method": "debug_bundler_setBundlingMode",
"params": ["manual"]
}
# Response
{
"jsonrpc": "2.0",
"id": 1,
"result": "ok"
}
debug_bundler_setReputation
指定されたアドレスのreputation
を設定する関数。
パラメータ:
-
reputation entries
- 評判情報を追加または置き換えるための評判エントリの配列です。
- 各エントリには以下のフィールドが含まれます。
-
address
- 評判を設定するアドレスです。
-
opsSeen
- そのエンティティを使用するユーザーオペレーションがメンプールに追加された回数です。
-
opsIncluded
- このエンティティを使用するユーザーオペレーションがオンチェーンに含まれた回数です。
-
status
- アドレスのバンドラー内でのステータスを示す文字列です。以下の3つの値があります。
-
ok
- 正常な状態です。
-
throttled
- 制限がかけられている状態です。
-
banned
- 禁止されている状態です。
-
-
EntryPoint
-
eth_sendUserOperation
で使用されるエントリーポイントです。
-
もしこの説明に関して疑問があれば、遠慮なくお知らせください。
# Request
{
"jsonrpc": "2.0",
"id": 1,
"method": "debug_bundler_setReputation",
"params": [
[
{
"address": "0x7A0A0d159218E6a2f407B99173A2b12A6DDfC2a6",
"opsSeen": 20,
"opsIncluded": 13
}
],
"0x1306b01bC3e4AD202612D3843387e94737673F53"
]
}
# Response
{
"jsonrpc": "2.0",
"id": 1,
"result": "ok"
}
debug_bundler_dumpReputation
全てのアドレスのreputation
データを返す関数。
reputation
オブジェクトの配列が返されます。
各オブジェクトは、debug_bundler_setReputation
で説明したフィールドを持っています。
パラメータ:
-
EntryPoint
-
eth_sendUserOperation
で使用されるエントリーポイントです。
-
戻り値:
-
reputation
エントリの配列が返されます。 - 各エントリには以下のフィールドが含まれます。
-
address
- 評判を設定するアドレスです。
-
opsSeen
- そのエンティティを使用するユーザーオペレーションがメンプールに追加された回数です。
-
opsIncluded
- このエンティティを使用するユーザーオペレーションがオンチェーンに含まれた回数です。
-
status
- アドレスのバンドラー内でのステータスを示す文字列です。以下の3つの値があります。
-
ok
- 正常な状態です。
-
throttled
- 制限がかけられている状態です。
-
banned
- 禁止されている状態です。
-
# Request
{
"jsonrpc": "2.0",
"id": 1,
"method": "debug_bundler_dumpReputation",
"params": ["0x1306b01bC3e4AD202612D3843387e94737673F53"]
}
# Response
{
"jsonrpc": "2.0",
"id": 1,
"result": [
{ "address": "0x7A0A0d159218E6a2f407B99173A2b12A6DDfC2a6",
"opsSeen": 20,
"opsIncluded": 19,
"status": "ok"
}
]
}
後方互換性
このEIP(Ethereum Improvement Proposal)は、コンセンサスレイヤーを変更しないため、Ethereum全体に逆互換性の問題はありません。
ただし、既存のERC4337以前のアカウントとは互換性が難しいです。
なぜなら、これらのアカウントにはvalidateUserOp
関数が存在しないからです。
もしアカウントに信頼できるオペレーション提出者を承認するための関数がある場合、ERC4337に対応したアカウントを作成し、そのアカウントが検証ロジックをラッパーとして再実装し、元のアカウントの信頼できるオペレーション提出者に設定することで、問題が解決できるかもしれません。
参考実装
セキュリティ考慮事項
エントリーポイントコントラクトは非常に厳密に監査され、形式的に検証される必要があります。
なぜなら、それがすべてのERC4337の中央信頼点として機能するためです。
全体として、このアーキテクチャはエコシステムの監査と形式的な検証の負荷を減少させます。
個々のアカウントが行う必要がある作業が大幅に少なくなるためです(validateUserOp
関数とその「署名の確認、nonceの増加、手数料の支払い」ロジックを検証するだけ)。
また、他の関数がmsg.sender == ENTRY_POINT
でゲートされていることも確認する必要があります(おそらくmsg.sender == self
も許可する)、ただし、エントリーポイントコントラクト内にセキュリティリスクを集中させるため、非常に堅牢であることが検証される必要があります。
検証は主に2つの主要な主張をカバーする必要があります(ペイマスターを保護するための主張やP2PレベルのDoS耐性を確立するための主張は除きます)。
- 任意のハイジャックに対する安全性
エントリーポイントは、特定のアカウントに対するvalidateUserOp
が合格した場合のみ、そのアカウントをジェネリックに呼び出します(かつ、呼び出しのcalldata
がジェネリック呼び出しのcalldata
と同じである場合)。 - 手数料排出に対する安全性
エントリーポイントがvalidateUserOp
を呼び出して合格した場合、そのエントリーポイントはop.calldata
と同じcalldata
でジェネリックな呼び出しを行う必要があります。
引用
Vitalik Buterin (@vbuterin), Yoav Weiss (@yoavw), Dror Tirosh (@drortirosh), Shahaf Nacson (@shahafn), Alex Forshtat (@forshtat), Kristof Gazso (@kristofgazso), Tjaden Hess (@tjade273), "ERC-4337: Account Abstraction Using Alt Mempool [DRAFT]," Ethereum Improvement Proposals, no. 4337, September 2021. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-4337.
AA・ECR4337関連記事
この章では、AA(AccountAbstraction)やERC4337についてのわかりやすい記事をまとめていきます。
ぜひこの章の記事を参考にしてください。
Account Abstraction(ERC4337)を、具体的な処理を追ってしっかりと理解してみましょう。
Account Abstraction勉強会
AA(Account Abstraction)の先にある、コントラクトウォレット中心の世界
AA(Account Abstraction)における署名集約技術の活用
【動画で学ぶブロックチェーン】【Ethereum】Account Abstraction— 中城元臣氏
ERC4337からAccountAbstractionを理解する
Account Abstraction(アカウント抽象化)とは?
ERC 4337: account abstraction without Ethereum protocol changes
Account Abstractionの誤解と真実
ERC-4337 Documentation
ERC-4337 Progress report
【EIP-4337】UserOperationをBundlerに投げてからTransactionが発行されるまで
Ethereum wallets today and tomorrow — EIP-3074 vs. ERC-4337
Account Abstractionの威力:RaisePay Walletの技術的概要
Why we need wide adoption of social recovery wallets
最後に
今回は「プロトコルの変更を回避して、アカウント抽象化を実装する提案であるERC4337」についてまとめてきました!
いかがだったでしょうか?
質問などがある方は以下のTwitterのDMなどからお気軽に質問してください!