はじめに
『DApps開発入門』という本や色々記事を書いているかるでねです。
今回は、プロトコルの変更を回避して、アカウント抽象化を実装する提案であるERC4337についてまとめていきます!
以下にまとめられているものを翻訳・要約・補足しながらまとめていきます。
また、AA(AccountAbstraction)やERC4337についてのわかりやすい記事は、この記事の後半でまとめています。
この記事はあくまでERC4337の提案をまとめたものと認識してください。
ERC4337は現在Draft段階です。
そのため今後内容が変更される可能性があるのでご注意ください。
以下のより仕組みを噛み砕いてわかりやすく説明しています。
ERC4337について以下のスライドで簡単にまとめました。
ERC4337を含むAccount Abstraction関連の規格については以下の記事にまとめています。
概要
ERC4337は、Ethereumのコンセンサス層(プロトコルの最下層)を一切変更せずにアカウント抽象化(Account Abstraction)を実現するための仕組みを提案しています。
従来のトランザクション構造を変更せず、UserOperation
という構造体を導入します。
ユーザーは UserOperation
を専用の mempool に送信し、bundler
と呼ばれる特別な役割を持つノードがそれらを集約して、handleOps
関数を呼び出すトランザクションを作成します。
このトランザクションは通常通りブロックに含まれて処理されるため、既存のEthereumの仕組みと矛盾せずに機能します。
ざっくりとEthereumのレイヤー構造について紹介します。
Ethereumのネットワークレイヤー
これは、Ethereumの基盤となるネットワークです。
ノードが情報をやり取りし、トランザクションをブロックにまとめて確定させる層です。
このレイヤーは提案に変更を加えることなく維持されます。
コンセンサスレイヤー
ここでは、ネットワーク全体での合意が形成されます。
提案されているアカウント抽象化は、このレイヤーに変更を加えずに実現されます。
新しいプロトコル変更やトランザクションタイプの導入はありません。
アプリケーションレイヤー
ここに提案されているアカウント抽象化のアイデアが詳細に位置付けられます。
ユーザーオペレーションという新しい概念が導入され、これによってスマートコントラクトウォレット内で実行される操作が表現されます。
ユーザーはこれらの操作を作成し、専用のmempoolに送信します。
動機
詳細は以下のリンクを参照してください。
提案されている方法は、アカウント抽象化を実現する際に、新しいアプローチを採用しています。
具体的な目標とその方法について詳しく説明します。
Account Abstractionの目標
ERC4337は、ユーザーがEOA(Externally Owned Account)に依存せず、コントラクトアカウントのみで動作する環境を目指しています。
具体的には、ユーザーがスマートコントラクトウォレットを通じて任意の検証ロジックを持つアカウントをメインのアカウントとして使用できるようにすることを目指しています。
これにより、ユーザーはEOA(外部所有者アカウント)を持つ必要がなくなり、スマートコントラクトウォレットを使用して以下のような複雑な操作を実行できるようになります。
- 任意の署名方式(例:量子耐性署名)
- マルチシグやソーシャルリカバリー
- 任意トークン(例: ERC20)でのガス支払い
- ウォレットアップグレードやカスタムロジックの導入
分散化
ERC4337は、全てのやり取りを公開されているmempool上で完結させます。
ユーザーは特定のノードやIPを知る必要がなく、任意の bundler が UserOperation
を取り扱えるようになっています。
これにより、中央集権的なリレーやゲートウェイを排除し、より分散化された処理が可能になります。
Ethereumプロトコルへの変更を回避
コンセンサス層の変更には年単位での対応必要になり、非常に時間がかかります。
ERC4337はコンセンサス層に依存しないため、実装や導入の障壁が低く、実用化のスピードを加速できるという利点があります。
幅広いユースケースへの対応
ERC4337は、以下のようなユースケースもサポートできます。
- プライバシー保護型アプリケーション
- アトミックな複数操作(複数の操作を1つのトランザクションでまとめて実行)
- 開発者によるガス代の肩代わり(sponsored transaction)
- ERC20トークンでのガス支払い
- クロスチェーンに対応したガス支払いモデル
バリデーション(署名検証)・ガス支払い・実行の3要素をそれぞれ抽象化することで、これまで制限されていたトランザクション設計の自由度を大幅に拡張できます。
仕様
定義
UserOperation
ユーザーが実行したい操作に関するデータをまとめている構造体です。
Ethereumの通常のトランザクションと似ていますが、通常のtransactionと差別化するために「UserOperation
」と定義されています。
以下のような項目があります(詳細は後述)。
to
calldata
maxFeePerGas
maxPriorityFeePerGas
nonce
signature
署名(signature)の検証方法はEthereumプロトコルでは定義されておらず、各コントラクトアカウント側が独自に実装します。
Sender
UserOperationを送信するコントラクトアカウントです。
EOA(Externally Owned Account)ではなく、コードを持ったコントラクトアカウントです。
EntryPoint
全てのUserOperationを受け付けて処理する中央的なコントラクトです。
bundlerはこのコントラクトの handleOps()
関数を通じて、複数のUserOperationをまとめて実行します。
ネットワーク上で1つだけ存在するコントラクトです。
bundlerは対応するEntryPointを事前にホワイトリスト登録する必要があります。
Bundler
UserOperationを集め、EntryPointへのトランザクションを構築してブロックに含める役割を持つノードです。
以下の方法で動作可能です。
- 自身がブロックビルダーとして直接動作
-
mev-boost
やEIP7732
に準拠した提案者・ビルダー分離(PBS)インフラと連携 -
ERC-7796
のeth_sendRawTransactionConditional
RPCを活用(利用可能な場合)
Paymaster
ユーザーに代わってガス代を支払うスマートコントラクトです。
ユーザー自身がETHを持っていない場合でも、PaymasterがETHでガス代を肩代わりできます。
Factory
新しいコントラクトアカウント(sender)をまだ持っていない場合に、そのコントラクトをデプロイするコントラクトです。
CREATE2
などを用いて、事前にアドレスを決定することが可能です。
Aggregator
複数のUserOperationの署名検証を1つにまとめて検証するためのコントラクトです。
別名「authorizer contract(認証者コントラクト)」とも呼ばれ、仕様はERC7766で定義されています。
Canonical UserOperation mempool
ERC7562に準拠した、許可不要・分散型のP2Pネットワーク上のメモリプールです。
全ての bundler はここで UserOperation
を共有して有効性を検証します。
Alternative UserOperation mempool
ERC7562とは異なるルールで有効性を判断する、独自のP2Pメモリプールです。
最適化や特定用途向けに使用されます。
Deposit
Sender または Paymaster が EntryPoint コントラクトにあらかじめ預ける ETH(またはL2のネイティブ通貨)です。
UserOperationの実行時にガス代として差し引かれ、bundlerへの報酬支払いに使われます。
UserOperation
UserOperation
は、ユーザーがコントラクトアカウント(CA)を通じて操作を実行するための構造体です。
Ethereumのプロトコル(コンセンサス層)を変更せずにAccount Abstractionを実現するため、従来のトランザクション形式ではなく独自の構造体を使って処理されます。
この構造体は、専用の UserOperation mempool に送信され、bundlerによって集約・処理されます。
UserOperationの各フィールドの説明
フィールド名 | 型 | 説明 |
---|---|---|
sender |
address |
このUserOperationを実行するコントラクトアカウントのアドレス。 |
nonce |
uint256 |
リプレイ攻撃を防ぐための番号。アカウントごとにインクリメントされる。 |
factory |
address |
新しいアカウントを作成するためのファクトリーコントラクトのアドレス。 EIP7702の場合は 0x7702 、既存アカウントであれば address(0) 。 |
factoryData |
bytes |
factory に渡す初期化データ、またはEIP7702の初期化情報。使用しない場合は空配列。 |
callData |
bytes |
sender に渡す実行データ。処理の本体部分。 |
callGasLimit |
uint256 |
上記 callData を実行するために割り当てるガス量。 |
verificationGasLimit |
uint256 |
validateUserOp の検証処理に割り当てるガス量。 |
preVerificationGas |
uint256 |
Bundlerのために確保される追加ガス(パッキング処理やmempool対応のため)。 |
maxFeePerGas |
uint256 |
EIP1559における max_fee_per_gas に相当。トランザクション全体で許容される最大手数料。 |
maxPriorityFeePerGas |
uint256 |
EIP1559における max_priority_fee_per_gas に相当。Bundlerへの優先手数料。 |
paymaster |
address |
ガス代を肩代わりするPaymasterコントラクトのアドレス。自己負担の場合は空アドレス(0x0)。 |
paymasterVerificationGasLimit |
uint256 |
paymaster の検証コード実行に使うガス上限(指定されている場合のみ)。 |
paymasterPostOpGasLimit |
uint256 |
paymaster の後処理(postOp )で使われるガス上限(指定されている場合のみ)。 |
paymasterData |
bytes |
paymaster に渡す追加データ(署名などを含む場合も)。 |
signature |
bytes |
sender 内の認証ロジックに渡される署名データ。署名の構造や検証方法はSCAごとに自由に定義可能。 |
EIP1559については以下の記事を参考にしてください。
セキュリティに関する注意点
リプレイ攻撃(同じ操作を他のチェーンやEntryPointで再実行されるリスク)を防ぐために、署名は必ず chainid
と EntryPoint
アドレスに依存するように設計されている必要があります。
EIP7702との関係
EIP7702の「authorization tuple」は、UserOperationの構造体とは別で提供される値です。
これは外部的に UserOperation
に追加で付与される認可情報で、署名以外の柔軟な認証手法を補完します。
EIP7702については以下の記事を参考にしてください。
EntryPointインターフェース
ERC4337における中心的なコントラクトが EntryPoint
です。
このコントラクトは、複数の UserOperation
を一括で処理する役割を担います。
オンチェーンでの効率性とガスコスト削減のため、UserOperation
は圧縮された形式(PackedUserOperation
)としてEntryPointに渡されます。
PackedUserOperationの構造
フィールド名 | 型 | 説明 |
---|---|---|
sender |
address |
実行元となるコントラクトアカウントのアドレス。 |
nonce |
uint256 |
リプレイ攻撃防止のためのカウンター。 |
initCode |
bytes |
新規アカウントを作成する時のファクトリーアドレスと初期化データを結合したもの。 EIP7702にも対応。 |
callData |
bytes |
実際にアカウントで実行される処理内容。 |
accountGasLimits |
bytes32 |
verificationGasLimit (16バイト)と callGasLimit (16バイト)を結合したもの。 |
preVerificationGas |
uint256 |
bundlerが検証やパッキングに使うガスを補うための追加ガス。 |
gasFees |
bytes32 |
maxPriorityFeePerGas (16バイト)と maxFeePerGas (16バイト)を結合したもの。 |
paymasterAndData |
bytes |
paymaster関連のアドレス・検証データ・postOp 用データを結合したもの(使わない場合は空)。 |
signature |
bytes |
senderが定義する検証ロジックに渡される署名データ。 |
このように、複数の UserOperation
をあらかじめ効率的にパックすることで、処理時のガス効率を高め、EntryPoint内での一括処理が可能になります。
handleOps
function handleOps(PackedUserOperation[] calldata ops, address payable beneficiary);
ERC4337のコアとなる関数。
-
ops
- PackedUserOperationの配列(複数のUserOperation)
-
beneficiary
- UserOperationをブロックに含めたBundlerやExecutor(BundlerのようにUserOperationの実行を行うアドレス)に報酬として支払われるアドレス
処理の流れは以下です。
- EntryPointコントラクtpが
ops
を順番に検証し、必要に応じてウォレットやPaymasterを初期化 - 検証に成功した
UserOperation
のcallData
を実行 - 実行が完了したら使われたガス量に応じて手数料を
beneficiary
に支払う
この構造により、1つのトランザクション内で複数の UserOperation
を安全かつ効率的に処理できます。
コントラクトアカウントのインターフェース
IAccount
interface IAccount {
function validateUserOp(
PackedUserOperation calldata userOp,
bytes32 userOpHash,
uint256 missingAccountFunds
) external returns (uint256 validationData);
}
EntryPointコントラクトによって呼び出され、UserOperation
を検証して必要なガス代を支払う関数。
引数
引数名 | 型 | 説明 |
---|---|---|
userOp |
PackedUserOperation calldata |
署名を含む実行対象のUserOperation構造体。 |
userOpHash |
bytes32 |
userOp (署名を除く)、EntryPointコントラクトのアドレス、chainId を含めたハッシュ。 |
missingAccountFunds |
uint256 |
EntryPointコントラクトに預ける最低額。 senderのdepositが不足している場合は0は指定できません。 |
-
msg.sender
が信頼されたEntryPointコントラクトであることを確認する必要があります。 - 署名検証に失敗した場合、
SIG_VALIDATION_FAILED (1)
を返しrevertしないことが推奨されます。ただし、署名以外のエラーの場合は必ずrevertする必要があります。 - ガス見積もりのため、
SIG_VALIDATION_FAILED
を返す場合でも、途中で処理を打ち切らず最後まで関数の処理を行うべきです。 - EntryPointコントラクトに対して
missingAccountFunds
をdepositTo
関数 経由で支払う必要があります。必要以上に多く預けた場合は後でwithdrawTo
関数を使って引き出すことが可能です。
戻り値
戻り値は以下の3つの値をビット圧縮形式で返す必要があります。
変数名 | 説明 |
---|---|
aggregator/authorizer |
署名検証の状態を表します。0 = 有効な署名1 = 無効な署名それ以外 = Aggregator(署名検証委任先)のアドレス |
validUntil |
UserOperation の有効期限(最大6バイトのUNIXタイムスタンプ)。0は無期限です。 |
validAfter |
UserOperation の有効開始時刻(6バイトのUNIXタイムスタンプ)。この時刻より前は無効とみなされます。 |
IAccountExecute
拡張インターフェースです。
interface IAccountExecute {
function executeUserOp(
PackedUserOperation calldata userOp,
bytes32 userOpHash
) external;
}
実行したい処理のデータである callData
の実行を行う関数。
callData
の実行処理は通常EntryPointコントラクトから呼び出していましたが、この関数を呼び出すように拡張しています。
これにより、複雑な制御の追加など実行ロジックをカスタムすることができるようになります。
Semi-abstracted Nonce Support
Ethereumにおける従来のトランザクションでは、単一の nonce
によってリプレイ防止とトランザクション順序の制御を行います。
しかしこの仕組みでは、コントラクトアカウントが独自の順序管理や操作を行う柔軟性が制限されます。
ERC4337では、この制約を取り払いながらも安全性と一意性を保つために「Semi-abstracted Nonce」という新しい仕組みを導入しています。
nonce
構造の変更
ERC4337では UserOperation.nonce
(uint256
)を以下のように分割して扱います。
ビット | 意味 | 長さ |
---|---|---|
上位 192 ビット |
key (分類キー) |
192ビット |
下位 64 ビット |
sequence (順序番号) |
64ビット |
この形式により、1つのアカウントで複数の異なる操作カテゴリ(key)を持ち、それぞれに独立した順序管理(sequence)を持たせることが可能になります。
ノンスの取得方法と検証
EntryPointコントラクトには以下のインターフェースが用意されています。
function getNonce(address sender, uint192 key) external view returns (uint256 nonce);
指定したsenderのアカウントとkeyに対応する現在のsequence番号を取得する関数。
Bundlerは UserOperation
を検証する時、この関数を使用して nonce
が正しいか事前に確認する必要があります。
nonce
の仕様
- sequenceは常に昇順である必要があります。
- 同一のkeyに対して、2つ以上の
UserOperation
を同時に処理することはできません。 - 新しいkeyは任意の値で導入でき、その時点で
sequence = 0
から開始します。
これにより、異なるカテゴリの操作を並列に進行させるといった高度なアカウント制御が可能になります。
利用例
単純な順序制御(シーケンシャル nonce
)
従来通りの動作をさせたい場合、以下のように制限すれば良いです。
require(userOp.nonce < type(uint64).max);
これは、key = 0
固定、かつsequenceのみを使う単純なモデルです。
管理用操作と通常操作の分離
コントラクトアカウントに「管理操作(ADMIN)」と「通常操作(NORMAL)」の2種類の操作チャネルを持たせたい場合、keyを使い分けることができます。
bytes4 sig = bytes4(userOp.callData[0 : 4]);
uint key = userOp.nonce >> 64;
if (sig == ADMIN_METHODSIG) {
require(key == ADMIN_KEY, "wrong nonce-key for admin operation");
} else {
require(key == 0, "wrong nonce-key for normal operation");
}
このようにすることで、管理処理と通常処理を別々の順序で並列実行可能になり柔軟な操作制御が実現できます。
EntryPoint コントラクトの必須機能
ERC4337のEntryPointコントラクトの中心的な機能は、handleOps()
関数です。
この関数は複数の UserOperation
をまとめて処理するもので、トランザクションの検証から実行、ガス代の支払いまでを行います。
具体的な処理は以下のようになっています。
handleOps
の呼び出し
function handleOps(PackedUserOperation[] calldata ops, address payable beneficiary)
-
ops
- 処理対象の
UserOperation
の配列。
- 処理対象の
-
beneficiary
- 処理後にガス代を受け取るアドレス(通常はBundler)。
検証ループ(Verification Loop)
各 UserOperation
に対して以下の処理が行われます。
-
コントラクトアカウントの作成(
initCode
が指定されている場合)- senderがまだデプロイされていない場合、
UserOperation.initCode
を使って新しくデプロイされます。 - Factoryコントラクトが
0x7702
の場合は、EIP7702に従ってEOAと認証情報の整合性を検証します。 -
initCode
が空でsenderが存在しない、またはデプロイ先が一致しない場合は処理は失敗します。
- senderがまだデプロイされていない場合、
-
ガス代の最大見積もり
-
validationGasLimit
、callGasLimit
と現在のmaxFeePerGas
から、最大支払い額を計算します。
-
-
必要なデポジット金額の計算と確認
- すでにEntryPointコントラクトにdepositされているか、不足分を
missingAccountFunds
として計算します。
- すでにEntryPointコントラクトにdepositされているか、不足分を
-
validateUserOp()
の呼び出し- senderに対して署名検証、
nonce
検証、必要に応じてデポジット支払いが行われます。 - 署名が無効であれば
SIG_VALIDATION_FAILED
を返されますが、署名以外のエラーであればrevert
します。 - この段階で失敗した
UserOperation
は、以降の実行対象から除外されます(必要に応じてhandleOps
全体をrevert
させることも可能)。
- senderに対して署名検証、
-
デポジットの妥当性確認
- 全ての必要ガスコストをカバーできるだけのdepositがあるかを最終確認します。
実行ループ(Execution Loop)
検証に成功した UserOperation
に対して以下の処理が行われます。
-
callData の実行
-
callData
をそのまま呼び出すか、先頭の関数シグネチャがexecuteUserOp()
(IAccountExecute インターフェース)の場合は、コントラクトアカウント内の以下の関数を実行します。
executeUserOp(userOp, userOpHash)
-
-
未使用ガスに対するペナルティ
-
callGasLimit
やpaymasterPostOpGasLimit
に対して 未使用ガスが40,000以上残っている場合、10%のペナルティが差し引かれます。 - これは意図的に大量のガスを予約し、他のトランザクションをブロックする行為を防止するためです。
- ブロックに含められるガスの量には上限があるため、余計なガス代をかけることで他のトランザクションを含めるスペースが小さくなり、他のUserOperationの処理の実行が遅くなる可能性があるためです。
-
-
残りのガス代の返金処理
- pre-charged(前払い)されたガスと実際に使用されたガスとの差額をsenderに返金します。
-
beneficiary への報酬支払い
- 各
UserOperation
から徴収された手数料(gasUsed × effectiveGasPrice)を、まとめてbeneficiary
(通常はBundler) に送金します。
- 各
Bundler側での推奨処理
Bundlerは UserOperation
を受け入れる前に、EntryPoint.handleOps()
をローカルでシミュレーション実行することが推奨されています。
これにより以下の事前確認ができます。
- 署名が正しいか
- depositされているガス代が十分か
- ガス代がきちんと支払われるか
失敗する UserOperation
は mempool に追加せず、他のノードにも伝播させてはいけないです。
ERC4337におけるJSON-RPC API
ERC4337では、ユーザーが従来のトランザクションではなく UserOperation
を送信するために、Ethereumノードが提供する標準のJSON-RPC APIに新たなエンドポイントを追加する必要があります。
これにより、Bundlerが UserOperation
を受け取り、P2Pメモリプールで中継・共有できるようになります。
主なJSON-RPCメソッド
ERC4337では以下の2つのメソッドが特に重要です。
これらのRPCインターフェースは ERC7769 にて定義されています。
この仕様に準拠することで、ウォレット・アプリケーション・Bundlerが共通のプロトコルでやりとりできるようになります。
ERC7769については以下の記事を参考にしてください。
eth_sendUserOperation
UserOperation
オブジェクトをbundlerノードに送信するためのメソッド。
送信されたUserOperationは、bundlerのローカルメモリプールに追加され、他のノードにも中継されます。
eth_getUserOperationReceipt
指定した UserOperation
の実行結果(receipt
)を取得するためのメソッド。
実行済みであれば以下のような情報が返されます。
- 実行されたブロックの番号やハッシュ
- EntryPointコントラクトによる処理結果
- 成功/失敗の状態
- 使用されたガス量
EIP712署名への対応
ERC4337では、UserOperation
に対する署名検証を柔軟にするために、EIP712形式の署名をサポートしています。
EIP712は、構造化されたデータに対して署名を行うための規格です。
EIP712については以下の記事を参考にしてください。
userOpHash
の生成方法
UserOperation
を識別・署名対象とするハッシュ(userOpHash
)は、EIP712に準拠した以下の2つのステップで構成されます。
ドメイン情報のハッシュ(TYPE_HASH
)
まず、署名対象とするドメインを定義するため、以下の内容をハッシュ化します。
bytes32 constant TYPE_HASH =
keccak256(
"EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"
);
このドメイン情報には以下の内容が含まれます。
項目 | 説明 |
---|---|
name |
EIP712メッセージの名前 |
version |
バージョン情報 |
chainId |
チェーンID(例:Ethereum Mainnetは1) |
verifyingContract |
検証を行うコントラクト(EntryPointコントラクトのアドレス) |
このハッシュは署名ドメインの一意性を保証するために使用されます。
PackedUserOperation
の型情報のハッシュ
UserOperation
の本体に相当するデータ構造を次のように定義し、その型情報をハッシュ化します。
bytes32 constant PACKED_USEROP_TYPEHASH =
keccak256(
"PackedUserOperation(address sender,uint256 nonce,bytes initCode,bytes callData,bytes32 accountGasLimits,uint256 preVerificationGas,bytes32 gasFees,bytes paymasterAndData)"
);
この構造体には、トランザクションの詳細(送信元、コール内容、ガス制限など)を含めたデータが含まれており、この型に基づいて署名対象のハッシュが生成されます。
EIP7702署名認可への対応
ERC4337は、EIP7702に対応することで、EOA(Externally Owned Account)からコントラクトアカウントへの柔軟な移行や、特別な認可処理を可能にしています。
認可(CAに特定の操作の実行を許可)の情報をまとめたデータ構造(タプル)には以下のデータが含まれます。
- 認可を行うEOAアドレス
- 認可対象のコントラクトアカウント
- その認可に署名した署名データ(EOAによる署名)
このタプルをUserOperationに追加することで、EOAがあらかじめコントラクトアカウントに「この操作は正当だ」と認可しておく仕組みです。
eth_sendUserOperation
における eip7702Auth
パラメータ
EIP7702を有効にしているネットワークでは、eth_sendUserOperation
RPCメソッドで eip7702Auth
という追加のパラメータが使用可能です。
-
eip7702Auth
は、EIP7702に準拠した認可タプルである必要があります。 - sender(ユーザーのEOA)によって署名されている必要があります。
Bundlerは、全ての UserOperation
に含まれる eip7702Auth
を authorizationList
に集約し、SET_CODE_TX_TYPE
という特殊なトランザクションタイプでバンドルを実行します。
initCode
による特殊な処理(EIP7702対応)
UserOperation
の initCode
が特別な形式の場合、以下のような処理が行われます。
通常の initCode
処理
- 先頭20バイト
- Factoryコントラクトアドレス
- それ以降
- そのFactoryコントラクトに渡す初期化用
calldata
- そのFactoryコントラクトに渡す初期化用
EntryPointコントラクトはFactoryコントラクトを呼び出してCAを生成します。
EIP7702対応時の特別な処理
-
initCode
が0x7702
(EIP7702フラグ)で始まる - 以降がゼロ埋めされている
この UserOperation
はFactoryコントラクト経由ではなく、EOAによる委任(delegate) という形でコントラクトアカウントが作成されたとみなされます
この場合、ハッシュ計算(userOpHash
)の際、通常 initCode
の先頭にあるはずのFactoryコントラクトアドレスを、EXTCODECOPY
命令を使って取得した「デリゲート先コントラクトアドレス」に置き換えます。
さらに、initCode
の残りの部分(20バイトより後)は、コントラクトアカウント自身の初期化処理を呼び出すために使われます。
この設計により、Factoryコントラクトを用いずに、EOAが自分で認可したアカウントを直接初期化できる柔軟性を提供します。
なお、initCode
を空にすることも可能です。
その場合、EntryPointコントラクトはEIP7702のデリゲート情報をハッシュに含めず、既存のアカウントに対して実行されることになります。
認可処理のガスコスト
EIP7702における認可処理の実行には、1件あたり PER_EMPTY_ACCOUNT_COST = 25000
gas の消費が必要とされます。
このガス消費はEntryPointコントラクト外で行われるため検知できるません。
そのため、ユーザーは preVerificationGas
にその分を上乗せして指定する必要があります。
Paymasterによるスポンサー機能の拡張
ERC4337では、EntryPoint
の処理フローにPaymasterが追加されることで、アプリ開発者がユーザーのガス代を肩代わりしたりユーザーがERC20トークンで手数料を支払えるような多様なユースケースを実現できます。
以下のフローで処理が実行されます。
Paymaster付き UserOperation の処理フロー
UserOperation
の paymasterAndData
フィールドが空でない場合、EntryPoint.handleOps()
は以下の拡張された処理を行います。
検証フェーズ(Validation)
-
アカウント側の
validateUserOp
を呼び出す- このとき
missingAccountFunds = 0
として呼ばれる(=Accountのdepositは使用しない)。
- このとき
-
Paymasterの預け金を確認
- PaymasterがEntryPointコントラクトに十分な量のdeposit(支払い用のETH)をしているか確認。
-
Paymaster による検証
-
validatePaymasterUserOp(userOp, userOpHash, maxCost)
を呼び出す。 - 戻り値の
context
が空でなければ、この情報は後続のpostOp
呼び出しに使用される。 -
validationData
は署名検証や有効期限などに使われる。
-
context
は、Paymasterの validatePaymasterUserOp()
関数が返す任意のバイト列データです。
この context
は検証フェーズで生成され、実行フェーズの postOp()
関数にそのまま渡されます。
この仕組みは、検証時にPaymaster側で一時的に計算・取得した値を保持しておき、後続の処理で再利用したい場合に使います。
例えば以下のようなケースがあります。
- トークン支払い型Paymasterで、ユーザーが支払うべき手数料のトークン残高やレートを取得
- アカウントに紐づく状態やアクセス情報を一時保存
これにより、Paymasterはガスを節約しつつ必要な情報を postOp()
に渡して、成功・失敗に応じたロジック(課金・リファンドなど)を実装できます。
validationData
は、validatePaymasterUserOp()
または validateUserOp()
の戻り値として返される uint256
型のビットデータです。
| フィールド | 内容 |
| 下位 20bit| シグネチャ検証の結果(例: 1 = 署名無効) |
| 中位 64bit| validUntil
:UserOperationが有効な期限 |
| 上位 64bit| validAfter
:UserOperationが有効になる開始時刻 |
このデータにより、UserOperationが特定の期間内のみ有効であることを表現でき、署名の有効性や期限制御が可能になります。
実行フェーズ(Execution)
-
アカウントの executeUserOp を実行(または callData 呼び出し)
-
Paymaster に対して
postOp
を呼び出す(必要な場合)-
validatePaymasterUserOp
の戻り値context
が空でなければ、以下の形式で呼ばれる。
postOp(mode, context, actualGasCost, actualUserOpFeePerGas)
-
mode
は以下のいずれかの値です。-
opSucceeded
-
UserOperation
が成功した場合
-
-
opReverted
-
UserOperation
が失敗した場合(この場合でもガス代はPaymasterが支払う)
-
-
-
Paymaster のガス代支払いの仕組み
Paymasterは、ユーザーの代わりにEntryPointコントラクトへのdepositからガス代を支払うことになります。
このdepositは、Paymaster自身が EntryPoint.depositTo()
を使って事前に預けておく必要があります。
Stake(ステーク)による信頼確保
悪意のあるPaymasterによる以下のようなDoS攻撃を仕掛けるのを防ぐために、Paymasterにはステーク(ロックされたETH)を保持させることが推奨されています。
- 検証は通るが実行時に意図的に
revert
する - ガスを大量に消費してBundlerや他のUserOperationに影響を与える
このような攻撃を防ぐために、addStake()
で一定期間引き出せないETHを預けさせ、問題行動が検知された場合に「一定期間トランザクション受付を停止する」などの制御が可能になります。
ステーク管理用関数(EntryPoint)
function addStake(uint32 _unstakeDelaySec) external payable;
function unlockStake() external;
function withdrawStake(address payable withdrawAddress) external;
-
addStake
- ETHをステークとしてロック。
-
unstakeDelaySec
(アンロック可能になるまでの待機時間)を指定。
-
unlockStake
- アンロック要求(一定時間待つ必要あり)
-
withdrawStake
- アンロック後にステークを引き出す
デポジット管理(EntryPoint)
function balanceOf(address account) public view returns (uint256);
function depositTo(address account) public payable;
receive() external payable;
function withdrawTo(address payable withdrawAddress, uint256 withdrawAmount) external;
-
depositTo
- ETHを指定アカウントのdepositとして追加
-
withdrawTo
- depositからETHを出金
-
balanceOf
- 現在のdeposit残高を確認
Paymasterだけでなく、Account側も同じインターフェースを使ってdepositを管理できます。
Bundlerの動作:UserOperation受信後の処理フロー
ERC4337において、Bundlerは UserOperation
を受け取ってEntryPointコントラクトに送る役割を担います。
この流れはEthereumのトランザクション実行までのフローと似ていますが、複数段階のバリデーションやmempool制御が含まれており、より高度な処理が求められます。
以下がBundlerの動作と各種検証ロジックのフローです。
Bundlerの基本処理ステップ
-
UserOperationの受信
- クライアントが
eth_sendUserOperation
をRPCで呼び出し、UserOperation
をBundlerに送信します。
- クライアントが
-
1回目の検証(受信時)
- Bundlerは
UserOpをmempool
に追加する前に、初期検証を行います。 - この検証に失敗した
UserOperation
をmempoolは破棄されエラーが返されます。
- Bundlerは
-
2回目の検証(バンドル構築中)
- mempool内の
UserOperation
を使ってバンドルを作成する時各UserOperation
を再検証します。 - 有効なものはバンドルに追加し、失敗したものはスキップされます。
- mempool内の
-
3回目の検証(ブロック提出直前)
- 最終的に構築されたバンドル全体に対して、再度検証を行います。
- 無効な
UserOperation
が含まれていた場合、それらを除外して評判スコアを更新します(詳細はERC7562参照)。
ERC7562については以下の記事を参考にしてください。
初期検証
Bundlerが UserOperation
をmempoolに追加する前に実行する基本チェック項目は以下のとおりです。
チェック項目 | 内容 |
---|---|
アカウント存在の整合性 | senderが既存のコントラクトか、initCode が空でないこと(どちらか一方のみ)。 |
initCode の整形 |
先頭20バイトがFactoryコントラクトアドレスまたは 0x7702 フラグであること。 |
Factoryステーク状態の記録 | Factoryコントラクトがグローバルステートを読む場合、ステークが必要。 |
検証ガス制限 |
verificationGasLimit と paymasterVerificationGasLimit が MAX_VERIFICATION_GAS (=500000) 未満であること。 |
preVerificationGas |
シリアライズ済み calldata の gas + PRE_VERIFICATION_OVERHEAD_GAS (=50000) をカバーすること。 |
Paymasterの妥当性 |
paymasterAndData が空でない場合:① コントラクトコードが存在する。② 十分なdepositがある。③ banされていないこと。 |
callGasLimit |
CALL 処理(valueあり)の最小ガスを上回っていること。 |
fee設定の下限 |
maxFeePerGas および maxPriorityFeePerGas が、basefeeおよびBundler設定値以上であること |
senderの一意性 | 同じsender+ nonce の UserOperation がすでに存在しないこと(ある場合は、より高いfeeでの置き換えのみ許可)※ステーク済みのsenderのみ例外的に複数 UserOperation を許可。 |
バンドル構築とブロック提出のイメージ
- AliceとBobが
UserOperation
を送信 → Bundler RPCに到達 - Bundlerはそれらをmempoolに追加(検証済みのもののみ)
- 次のブロックに向けたバンドル作成時、再度検証を実施
- 検証成功した
UserOperation
だけを含むバンドルがEntryPoint.handleOps()
を通じて実行される
UserOperationのSimulation
ERC4337では、UserOperation
の検証を事前にするために、simulationという新しい仕組みを導入しています。
これは、Ethereumの通常のトランザクション検証とは異なり、EVMのステートや他のトランザクションによって結果が変わる可能性がある UserOperation
に対応するために必要です。
なぜシミュレーションが必要か
Ethereumの通常のトランザクションでは、以下のような静的検証が可能です。
-
ecrecover
による署名者(EOA)の検証 -
nonce
が現在のEOAのnonce
と一致するか - アカウント残高が十分か
-
gasLimit
が基本コストを超えているか -
chainId
が現在のチェーンと一致するか
これらは他のトランザクションに依存せず、EVMのステートに影響されないため単純な検証が可能です。
一方、UserOperation
は以下のような状態依存の検証が必要です。
-
account.validateUserOp
の呼び出し-
UserOperation
を検証する関数。
-
-
paymaster.validatePaymasterUserOp
の呼び出し- Paymasterがガス代の支払いを検証
-
initCode
によるコントラクトアカウントのデプロイ - 時間制限(
validAfter
,validUntil
)の確認-
validAfter
-
UserOperation
が有効になる開始時刻(Unix timestamp)
-
-
validUntil
-
UserOperation
が期限切れになる時刻
-
-
このような状態依存の処理を事前に安全に確認するには、EVM上でその UserOperation
を仮実行して結果を確認する必要があるため、simulationが導入されています。
シミュレーション仕様
実行方法
Bundlerは handleOps()
に対象の UserOperation
だけを含めて読み取り呼び出しを行います。
これはチェーンには書き込まれず、オフチェーンでの検証に使われます。
シミュレーション範囲
検証フェーズのみを実行対象とし、実行フェーズ(callData
の実行)は対象外です。
シミュレーションの早期終了のために、UserOperation
の配列の2つ目に「常に失敗する UserOperation
」をバンドルに含めることも許可されています。
実行内容
シミュレーションでは以下の検証を行います。
-
initCode
があればsenderアカウントを作成 -
account.validateUserOp()
を呼び出し -
paymaster.validatePaymasterUserOp()
(Paymasterが指定されている場合) -
validateUserOp
またはvalidatePaymasterUserOp
の戻り値にvalidAfter
/validUntil
が含まれていれば、現在のブロック時間がその範囲内であることを確認
注意点
- シミュレーションが失敗した場合、その
UserOperation
は無効とされ、mempoolから破棄されます。 -
UserOperation
がすぐに期限切れになるような状態(validUntil
が近すぎる)である場合も、BundlerはそのUserOperation
を破棄する必要があります。 - Bundlerは、
validAfter
/validUntil
を確認するためにEVMのトレース(trace)を用いて戻り値をデコードする必要があります。
DoS防止と制約
BundlerがこのシミュレーションによりDoS攻撃を受けないよう、以下のような制限が必要です。
-
validateUserOp()
やvalidatePaymasterUserOp()
内で使用可能なopcodeやストレージ操作に制限を設ける - これらのルールはERC7562で定義されており、シミュレーションが安全に完了するように設計されています
preVerificationGas
の推定
ERC4337における preVerificationGas
は、UserOperation
をブロックチェーン上で実行する前に必要となるガス量を示すデータです。
この値は UserOperation
のデータ送信や検証に関わる固定的なガスコストを表し、Bundlerが適切なコスト回収を行えるように設計されています。
preVerificationGas
とは
preVerificationGas
は、UserOperation
の実行する処理データである calldata
の送付・固定化されているデータ・静的なメモリ確保にかかるガス量です。
実際の処理(callData
の実行)とは別に、EntryPointコントラクトの検証処理以前に最低限かかるガスをカバーするためのものです。
Bundlerが UserOperation
を安全に処理するためには、この値を正確に見積もる必要があります。
この値が適切に見積もられていないと、Bundlerが事前コストを回収できなくなるなど、トランザクションが 途中で失敗する可能性があります。
推定値の要素
仕様では準的な計算式は定められていませんが、以下のコストを含める必要があります。
ベースのトランザクションコスト
Ethereumのトランザクションには最低限 21000 gas
が必要。
バンドル内のUserOperation数に応じて分割される。
例:バンドルに3つのUserOperation → 21000 / 3 = 7000 gas
calldata
のガスコスト(EIP2028準拠)
calldata
に含まれる各バイトは、値に応じて以下のガスがかかる。
- 0バイト
-
4
gas
-
- 非0バイト
-
16
gas
-
EIP2028については以下の記事を参考にしてください。
EntryPointコントラクトの静的コードの処理
validateUserOp
の呼び出しに伴う、EntryPointコントラクト内部の静的なコード実行のためのガス。
固定長フィールドのメモリ読み込みコスト
UserOperation
構造体に含まれる固定フィールド(例:nonce
, sender等)をEVMメモリに読み込む際の静的メモリ使用量。
Paymasterの validatePaymasterUserOp
のcontextによるメモリコスト(任意)
Paymasterが context
を返す場合、そのメモリ領域の読み取り・展開・保持に関わるガス消費。
innerHandleOp()
の外部呼び出しガス
- EntryPointコントラクトの内部処理の主要部分である
innerHandleOp()
関数の呼び出しコスト。 - この値はバンドル内の位置によって変動する。
EIP7702の認可コスト(任意)
-
EIP7702の認可が含まれている場合、追加で
25000 gas
が想定される。
EIP7623による calldata
ガスの底上げ(floor price)
-
tx.gasUsed
の新しい計算式に基づき、以下を推定値として用いる。- シミュレーション中の検証処理(アカウント作成、
validateUserOp
、paymaster)のガス使用合計 - 実行ガス+postOpガスの合計の10%
- シミュレーション中の検証処理(アカウント作成、
Bundlerに求められる対応
preVerificationGas
には将来のメモリ拡張やバンドル内での位置の変動に備えて余裕を持たせる必要があります。
preVerificationGas
を必要最小限に見積もった場合、以下のようなリスクがあります。
- EntryPointコントラクト内で
validateUserOp()
を実行中にガス不足により失敗 - ガス不足により UserOperation全体が失敗し、バンドルされた他の操作にも影響が出る
- Bundlerが肩代わりしたガス代の一部を回収できなくなる
Alternative Mempools(代替メモプール)について
ERC4337では、UserOperation
の検証におけるセキュリティを保つために非常に厳格なシミュレーションルールが定められています。
これにより、不正なPaymasterによるDoS攻撃やネットワークの不安定化を防止できます。
しかし、一部のPaymasterに対しては、安全性が保証されていることが確認できるにもかかわらず、標準ルールでは許可されないケースも存在します。
このような状況に対応するために導入された概念がAlternative Mempools(代替メモプール)です。
なぜ代替メモプールが必要か
通常のメモプールでは、ERC7562に定義されたopcodeの制限やストレージアクセス制限などが厳格に適用されます。
これは未知または悪意あるPaymasterがシミュレーション時に不正な動作をしないようにするための措置です。
しかし、この制約に縛られずにPaymasterを使用したいという場合があります。
代替メモプールとは何か
通常のメモプールとは異なる独自の検証ルールを適用するためのメカニズムです。
特定の制限(例:opcodeの使用やガス上限)を緩和するために用いられ、一部のBundlerだけで共有されるP2Pネットワーク内の独立したメモプールとして動作します。
これにより、特定の条件を満たすPaymasterのみ使用可能など、より柔軟な検証ルールが適用可能になります。
なぜ単純なホワイトリストでは不十分か
単に「このPaymasterは安全だからOK」としてBundlerが個別に対応すると、その UserOperation
は一部のバンドラーにしか伝播せず断片化される可能性があります。
ユーザーから見て予測できない挙動になり、ネットワーク全体の一貫性が損なわれます。
解決策としての「代替メモプール」
全ての参加バンドラーが特定の緩和ルールを採用する明確な意思表示を行い、それに基づいた共通の検証・伝播ルールを持つ別メモプールを形成します。
これにより、緩和ルールに準拠した UserOperation
が、確実かつ広範に伝播・処理されることが保証されます。
ERC7562
代替メモプールの定義とルールは ERC7562に規定されています。
どのようなルールを緩和するか、どうやってBundler間で伝播するか、どうやってreputationを扱うかといった詳細も記載されています。
ERC7562については以下の記事を参考にしてください。
Bundling
Bundlingとは、Bundlerが複数の UserOperation
を集めて、1つのトランザクションとしてブロックチェーンに送信するプロセスです。
バンドリング中の必須処理
Bundlerはバンドルを作成する時に、以下の条件を満たす必要があります。
- 他の
UserOperation
のsenderアドレスにアクセスするUserOperation
は除外する - 他の
UserOperation
のバリデーション中にFactoryコントラクトにより生成されたアドレスにアクセスするUserOperation
も除外する - 同じPaymasterを使う複数の
UserOperation
に対しては、Paymasterの残高を追跡して全ての手数料を支払えるだけの残高があるか確認する
トランザクション生成後に行うべき検証
Bundlerはバンドルをブロックに含める前に、以下の処理が推奨されます。
-
debug_traceCall
を使って最大限のガスでシミュレーションを実行し、handleOps
全体が正しく動作するか検証する。
(opcodeやストレージアクセスの制限に違反していないかも確認) - もしシミュレーションが失敗た場合は、最後にEntryPointコントラクトから
CALL
されたコントラクトアカウントを特定してそれが原因であるとみなす
※ Bundlerは発行されたFailedOp
イベントだけを根拠に原因を決める蹴ることは避ける必要があります。
不正な UserOperation
の処理
不正が見つかった UserOperation
はバンドルとMempoolの両方から削除する必要があります。
エラーの原因がFactoryコントラクトまたはPaymasterの場合、以下の処理を行います。
- 送信者(sender)がステークしていない場合
- その Factoryコントラクト / Paymaster を「ban(利用禁止)」にする
- 送信者がステークしている場合
- Factoryコントラクト / Paymasterはbanせず、そのsenderをbanする
-
debug_traceCall
が成功するまで上記の修正を繰り返す
セキュリティ上の注意点
ステークされたエンティティ(sender、factoryコントラクト、paymaster)は、他の UserOperation
と一時的にデータを共有する場合があります。
そのため、個別の UserOperation
と同様に、handleOps
全体のバリデーションにも同じ制約(opcode禁止・ストレージアクセス制限)を適用する必要があります。
これをしないと、攻撃者が禁止されたopcodeを使ってon-chain実行中かどうかを判別し、FailedOp
イベントを意図的に引き起こす可能性があります。
ブロックにバンドルを含める際の注意点
ブロック内の先行トランザクションが原因で、UserOperation
が失敗することを避ける必要があります。
そのために以下の対策が推奨されます。
- EIP2930のアクセスリストを活用して競合を防ぐ。
- もしくはバンドルをブロックの最初のトランザクションとして配置する。
EIP2930については以下の記事を参考にしてください。
エラーコードの扱い
EntryPointコントラクトでは、UserOperation
の検証中にエラーが発生した場合、revert
を行う必要があります。
ただし、そのエラーがどのエンティティ(sender、factoryコントラクト、paymaster)によって発生したかを、Bundlerが正確に判断できるようにすることが重要です。
エラー原因の特定方法
失敗の原因となったエンティティ(sender、factoryコントラクト、paymaster)は、以下のようにして特定されます。
EntryPointコントラクトによる処理中に発生したrevert
の直前に呼び出された(CALL
された)コントラクトをコールトレースで特定。
コールトレースとは、EVM上で実行された処理の呼び出し履歴を追跡・記録する仕組みです。
EVM上の処理では、コントラクトから別のコントラクトへ CALL
命令を使って関数を呼び出すことがあります。
コールトレースを取得すると、「どのコントラクトがどの順番でどの関数を呼び出したか」が詳細にわかります。
コールトレースは debug_traceCall
などのRPCを通じて取得できます。
- その最後に呼び出されたエンティティ(sender、factoryコントラクト、paymaster)がエラーの原因とみなされます。
使用されるエラー形式
EntryPointコントラクトは、シミュレーション中や本番時に以下の明示的なエラー型のみを使って`revertする必要があります。
SignatureValidationFailed()
FailedOp()
FailedOpWithRevert()
これにより、バンドラーはcall-tracing
と組み合わせてエラーの原因を正確に判断できます。
エラーコードの命名規則(AA##)
各エラーには識別用のイベントコード(AA##)が付与されており、接頭辞でエラーの種別を分類しています。
イベントコード | 発生箇所 | 意味 |
---|---|---|
AA1x |
sender作成時 | Factoryでのアカウント作成エラー |
AA2x |
sender検証時 |
validateUserOp の実行エラー |
AA3x |
paymaster検証時 |
validatePaymasterUserOp の実行エラー |
このように、イベントコードはエラーの種類を迅速に特定するために用いられます。
補足
DoS対策としてのバリデーションと実行の分離
Account Abstractionをコントラクトアカウントだけで実現しようとすると、「ガス代を本当に支払うか確認するには実行してみるしかない」という問題があります。
このような仕組みでは、攻撃者が実行の最後で revert
をする悪意ある UserOperation
を大量に送ることで、バンドラーやブロックビルダーに過剰な負荷を与えるDoS攻撃が可能になってしまいます。
この問題を防ぐため、ERC4337では「バリデーション(署名検証と支払い能力の確認)」と「実行」を完全に分離します。
アカウントには validateUserOp
関数を実装させ、事前に署名の検証や支払い確認のみを行い、成功した場合にのみ callData
による実行を行うようにします。
バリデーションの制限による安全性確保
Bundlerが誤って無効な UserOperation
を含めてしまうことを防ぐため、バリデーションで使用できるストレージやオペコードに制限を設けます。
以下のようなケースに備えています。
- 個別ではバリデーションに成功しても、ブロックに含める段階で失敗するような
UserOperation
- 他の
UserOperation
と干渉してバリデーションに失敗するようなUserOperation
これらを防ぐために、バンドラーはERC7562に定義された検証ルールに従って UserOperation
を受け入れる必要があります。
レピュテーション(評判)による制御とスロットリング
PaymasterやFactoryコントラクトのようなグローバルエンティティは複数の UserOperation
に共有されるため、1つの不正が複数の UserOperation
を無効にしてしまう可能性があります。
これを防ぐため、以下の仕組みが用意されています。
- 一定の割合で
UserOperation
を無効化したエンティティは、スロットリング(処理速度の低下)または一時的なbun(mempoolから除外)の対象となります。 - ステーク(ETHの預け入れ)を要求することで、攻撃コストを高くしてSybil攻撃(偽装エンティティの大量作成)を防止します。
- ステークは没収(スラッシュ)されず、所定の
unstakeDelay
を過ぎれば引き出せます。
Paymasterによるガス抽象化
Paymasterは、トランザクションの送信者以外がガス代を負担する仕組みを実現するためのコントラクトです。
例えば、ERC20トークンでガス代を支払う「トークンペイマスター」は、あらかじめ最大の支払い見込み額を請求し、最終的な実行後に余剰分をユーザーに返金することができます。
コントラクトアカウントの初回作成
コントラクトアカウントの作成には、Factoryコントラクトを利用します。
これは CREATE2
(オペコード 0xF5)を使ってアドレスを決定的に生成することで、未作成のアカウントであっても事前にそのアドレスを予測可能とします。
-
initCode
フィールドには、20バイトのFactoryコントラクトアドレスとそれに渡す初期化用のcalldata
が含まれます。 -
initCode
の結果、指定したsenderアドレスにコントラクトが存在しない場合や、存在していた場合に正しくないコントラクトだった場合、UserOperation
は中止されます。 - Factoryコントラクトはストレージにアクセスする場合、DoS対策の観点からステークが必要になります。
この仕組みにより、ユーザーは事前にコントラクトを作成しなくても、安全にアドレスを用意し、そこに資金を送ることができます。
これによりEOAと同様に、即座に受け取り可能なアカウントという体験を実現します。
互換性
ERC4337はEthereumのコンセンサスレイヤー(ブロック生成やトランザクションの確定に関わる根幹部分)を変更しないため、Ethereum全体としての互換性の問題はありません。
しかし、ERC4337以前に作成されたコントラクトアカウントとは互換性がありません。
これは、ERC4337において重要な validateUserOp
関数が、それらのアカウントには実装されていないためです。
一方で、既存のアカウントに「信頼できるUserOperation送信者を許可する」ような仕組み(例:許可されたコントローラによるアクセス制御)がある場合には、そのアカウントの代わりにERC4337対応のアカウントを作成し、元のアカウントの検証ロジックをラップした上で、その新しいアカウントを「信頼できる送信者」として設定することで互換性を保つことができます。
参考実装
セキュリティ
ERC4337では、EntryPointコントラクトが全てのトランザクション処理の中心に位置するため、徹底した監査と形式的検証が必要です。
この設計により、個々のコントラクトアカウントが担う責任は減少し、検証すべき範囲も限定的になりますが、その代わりにEntryPointコントラクトが単一障害点となるため、高い堅牢性が求められます。
EntryPointの安全性
EntryPointコントラクトの検証において、以下の2点が特に重要です。
-
不正な呼び出しの防止
EntryPointは、事前にvalidateUserOp
が成功した送信者アカウントに対してのみcallData
を実行する必要があります。 -
不正な手数料支払いの防止
validateUserOp
が成功した場合に限り、EntryPointはそのUserOperationに指定されたcallData
を実行する必要があります。
各種コントラクトの安全な呼び出し元検証
EntryPointコントラクトをセキュリティモデルでは、以下のように各コントラクトが呼び出し元を限定する必要があります。
-
Factoryコントラクト
createAccount()
関数は必ずentryPoint.senderCreator()
アドレスからのみ呼び出されるように確認する必要があります。 -
Paymasterコントラクト
validatePaymasterUserOp()
とpostOp()
関数は、EntryPointからのみ呼び出されるようにする必要があります。 -
Aggregatorコントラクト
validateSignatures()
関数も、EntryPointコントラクトからの呼び出しのみを受け入れるよう制御する必要があります。 -
EIP7702に基づくアカウント
初期化関数はentryPoint.senderCreator()
からのみ呼び出されるようにし、同じコードが複数回実行されないようアカウント側で制御する必要があります。
なお、EntryPointコントラクト自体はEIP7702アカウントの初期化状態を判別できないため、Walletアプリケーション側で initCode
を繰り返し送信しないようにするべきです。
コントラクトアカウントにおける追加の注意点
-
ストレージレイアウトの衝突回避
多くのアカウントはアップグレード可能な設計になっており、異なる実装間でのストレージの衝突を防ぐ必要があります。そのためには、ERC7201で定義されている「ダイヤモンドストレージ」パターンのような手法を用いることが推奨されます。
ERC7201については以下の記事を参考にしてください。
-
一時ストレージ(Transient Storage)の扱い
EIP1153による一時的なストレージを使う場合、ERC4337では異なる送信者の複数のUserOperation
が1つのトランザクションに含まれる可能性があるため、アクセス制御や機密情報に関わる場合は手動でクリーンアップ処理を行う必要があります。
引用
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.
引用
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などからお気軽に質問してください!