# [ERC4337] プロトコルの変更をせずにAAを実装する仕組みを理解しよう！

> ERC4337は、Ethereumのコンセンサス層（プロトコルの最下層）を一切変更せずにアカウント抽象化（Account Abstraction）を実現するための仕組みを提案しています。

- URL: https://cardene.dev/articles/8c97ca7c93c4557ebfc2
- 公開日: 2023-08-21
- 更新日: 2023-08-21
- トピック: Blockchain
- タグ: ethereum, solidity, smartcontract, ブロックチェーン

## はじめに

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

https://amzn.asia/d/gxvJ0Pw

今回は、プロトコルの変更を回避して、アカウント抽象化を実装する提案である**ERC4337**についてまとめていきます！

:::message
以下にまとめられているものを翻訳・要約・補足しながらまとめていきます。
:::

https://eips.ethereum.org/EIPS/eip-4337

:::message alert
また、**AA（AccountAbstraction）**や**ERC4337**についてのわかりやすい記事は、この記事の後半でまとめています。
この記事はあくまで**ERC4337**の提案をまとめたものと認識してください。
:::

:::message alert
**ERC4337**は現在**Draft**段階です。
そのため今後内容が変更される可能性があるのでご注意ください。
:::

以下のより仕組みを噛み砕いてわかりやすく説明しています。

https://zenn.dev/heku/books/88e65212339061

ERC4337について以下のスライドで簡単にまとめました。

https://www.canva.com/design/DAFs7D783_g/wL5iABgvQQ6lSKLbVgxTqA/view?utm_content=DAFs7D783_g&utm_campaign=designshare&utm_medium=link&utm_source=publishsharelink

:::message alert
**ERC4337**を含むAccount Abstraction関連の規格については以下の記事にまとめています。
:::

https://mirror.xyz/0xcE77b9fCd390847627c84359fC1Bc02fC78f0e58/HaPTTeXZeXRG3K5JfXesmKet9eKTbcFUixW6IaMvt4c

## 概要

**ERC4337**は、Ethereumのコンセンサス層（プロトコルの最下層）を一切変更せずにアカウント抽象化（Account Abstraction）を実現するための仕組みを提案しています。
従来のトランザクション構造を変更せず、`UserOperation` という構造体を導入します。

ユーザーは `UserOperation` を専用の mempool に送信し、`bundler` と呼ばれる特別な役割を持つノードがそれらを集約して、`handleOps` 関数を呼び出すトランザクションを作成します。
このトランザクションは通常通りブロックに含まれて処理されるため、既存のEthereumの仕組みと矛盾せずに機能します。

:::message
ざっくりとEthereumのレイヤー構造について紹介します。

**Ethereumのネットワークレイヤー**
これは、Ethereumの基盤となるネットワークです。
ノードが情報をやり取りし、トランザクションをブロックにまとめて確定させる層です。
このレイヤーは提案に変更を加えることなく維持されます。

**コンセンサスレイヤー**
ここでは、ネットワーク全体での合意が形成されます。
提案されているアカウント抽象化は、このレイヤーに変更を加えずに実現されます。
新しいプロトコル変更やトランザクションタイプの導入はありません。

**アプリケーションレイヤー**
ここに提案されているアカウント抽象化のアイデアが詳細に位置付けられます。
ユーザーオペレーションという新しい概念が導入され、これによってスマートコントラクトウォレット内で実行される操作が表現されます。
ユーザーはこれらの操作を作成し、専用のmempoolに送信します。
:::

## 動機

詳細は以下のリンクを参照してください。

https://ethereum-magicians.org/t/implementing-account-abstraction-as-part-of-eth1-x/4020 

https://eips.ethereum.org/EIPS/eip-2938

提案されている方法は、アカウント抽象化を実現する際に、新しいアプローチを採用しています。
具体的な目標とその方法について詳しく説明します。

### 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` | 新しいアカウントを作成するためのファクトリーコントラクトのアドレス。<br>**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` に相当。<br>トランザクション全体で許容される最大手数料。 |
| `maxPriorityFeePerGas` | `uint256` | **EIP1559**における `max_priority_fee_per_gas` に相当。<br>Bundlerへの優先手数料。 |
| `paymaster` | `address` | ガス代を肩代わりするPaymasterコントラクトのアドレス。自己負担の場合は空アドレス（0x0）。 |
| `paymasterVerificationGasLimit` | `uint256` | `paymaster` の検証コード実行に使うガス上限（指定されている場合のみ）。 |
| `paymasterPostOpGasLimit` | `uint256` | `paymaster` の後処理（`postOp`）で使われるガス上限（指定されている場合のみ）。 |
| `paymasterData` | `bytes` | `paymaster` に渡す追加データ（署名などを含む場合も）。 |
| `signature` | `bytes` | `sender` 内の認証ロジックに渡される署名データ。署名の構造や検証方法はSCAごとに自由に定義可能。 |

:::message
**EIP1559**については以下の記事を参考にしてください。
:::

[\[EIP1559\] Ethereumの新たなガス価格設定メカニズムを理解しよう！](/articles/ce77124940010280717b)

#### セキュリティに関する注意点

リプレイ攻撃（同じ操作を他のチェーンやEntryPointで再実行されるリスク）を防ぐために、署名は必ず `chainid` と `EntryPoint` アドレスに依存するように設計されている必要があります。

#### EIP7702との関係

**EIP7702**の「authorization tuple」は、UserOperationの構造体とは別で提供される値です。
これは外部的に `UserOperation` に追加で付与される認可情報で、署名以外の柔軟な認証手法を補完します。

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

[\[EIP7702\] EOAをコントラクトアカウントに変換する仕組みを理解しよう！](/articles/13a828c97786aab953e4)

### EntryPointインターフェース

**ERC4337**における中心的なコントラクトが `EntryPoint` です。
このコントラクトは、複数の `UserOperation` を一括で処理する役割を担います。
オンチェーンでの効率性とガスコスト削減のため、`UserOperation` は圧縮された形式（`PackedUserOperation`）としてEntryPointに渡されます。

#### PackedUserOperationの構造

| フィールド名 | 型 | 説明 |
| ---------- | -- | --- |
| `sender` | `address` | 実行元となるコントラクトアカウントのアドレス。 |
| `nonce` | `uint256` | リプレイ攻撃防止のためのカウンター。 |
| `initCode` | `bytes` | 新規アカウントを作成する時のファクトリーアドレスと初期化データを結合したもの。<br>**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`

```js
function handleOps(PackedUserOperation[] calldata ops, address payable beneficiary);
```

**ERC4337**のコアとなる関数。

* `ops`
    * PackedUserOperationの配列（複数のUserOperation）
* `beneficiary`
    * UserOperationをブロックに含めたBundlerやExecutor（BundlerのようにUserOperationの実行を行うアドレス）に報酬として支払われるアドレス

処理の流れは以下です。

1. EntryPointコントラクtpが `ops` を順番に検証し、必要に応じてウォレットやPaymasterを初期化
2. 検証に成功した `UserOperation` の `callData` を実行
3. 実行が完了したら使われたガス量に応じて手数料を `beneficiary` に支払う

この構造により、1つのトランザクション内で複数の `UserOperation` を安全かつ効率的に処理できます。

### コントラクトアカウントのインターフェース

#### IAccount

```js
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コントラクトに預ける最低額。<br>senderのdepositが不足している場合は0は指定できません。  |

* `msg.sender` が信頼されたEntryPointコントラクトであることを確認する必要があります。
* 署名検証に失敗した場合、`SIG_VALIDATION_FAILED (1)` を返しrevertしないことが推奨されます。ただし、署名以外のエラーの場合は必ずrevertする必要があります。
* ガス見積もりのため、`SIG_VALIDATION_FAILED` を返す場合でも、途中で処理を打ち切らず最後まで関数の処理を行うべきです。
* EntryPointコントラクトに対して `missingAccountFunds` を `depositTo` 関数 経由で支払う必要があります。必要以上に多く預けた場合は後で `withdrawTo` 関数を使って引き出すことが可能です。

##### 戻り値

戻り値は以下の3つの値をビット圧縮形式で返す必要があります。

| 変数名 | 説明 |
| ----- | ---- |
| `aggregator/authorizer` | 署名検証の状態を表します。<br>`0` = 有効な署名<br>`1` = 無効な署名<br>それ以外 = Aggregator（署名検証委任先）のアドレス |
| `validUntil` | `UserOperation` の有効期限（最大6バイトのUNIXタイムスタンプ）。<br>0は無期限です。                            |
| `validAfter` | `UserOperation` の有効開始時刻（6バイトのUNIXタイムスタンプ）。<br>この時刻より前は無効とみなされます。 |

#### `IAccountExecute`

拡張インターフェースです。

```js
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コントラクトには以下のインターフェースが用意されています。

```js
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`）

従来通りの動作をさせたい場合、以下のように制限すれば良いです。

```js
require(userOp.nonce < type(uint64).max);
```

これは、`key = 0` 固定、かつsequenceのみを使う単純なモデルです。

##### 管理用操作と通常操作の分離

コントラクトアカウントに「管理操作（ADMIN）」と「通常操作（NORMAL）」の2種類の操作チャネルを持たせたい場合、keyを使い分けることができます。

```js
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` をまとめて処理するもので、トランザクションの検証から実行、ガス代の支払いまでを行います。

具体的な処理は以下のようになっています。

![bundle-seq.png](/images/qiita-880b211b-4007-4f98-9dfe-425977db7093.webp)

#### `handleOps` の呼び出し

```js
function handleOps(PackedUserOperation[] calldata ops, address payable beneficiary)
```

* `ops`
    * 処理対象の `UserOperation` の配列。
* `beneficiary`
    * 処理後にガス代を受け取るアドレス（通常はBundler）。

#### 検証ループ（Verification Loop）

各 `UserOperation` に対して以下の処理が行われます。

1. **コントラクトアカウントの作成（`initCode` が指定されている場合）**
    * senderがまだデプロイされていない場合、`UserOperation.initCode` を使って新しくデプロイされます。
    * Factoryコントラクトが `0x7702` の場合は、**EIP7702**に従ってEOAと認証情報の整合性を検証します。
    * `initCode` が空でsenderが存在しない、またはデプロイ先が一致しない場合は処理は失敗します。

2. **ガス代の最大見積もり**
    * `validationGasLimit`、`callGasLimit` と現在の `maxFeePerGas` から、最大支払い額を計算します。

3. **必要なデポジット金額の計算と確認**
    * すでにEntryPointコントラクトにdepositされているか、不足分を `missingAccountFunds` として計算します。

4. **`validateUserOp()` の呼び出し**
    * senderに対して署名検証、`nonce`検証、必要に応じてデポジット支払いが行われます。
    * 署名が無効であれば `SIG_VALIDATION_FAILED` を返されますが、署名以外のエラーであれば`revert`します。
    * この段階で失敗した `UserOperation` は、以降の実行対象から除外されます（必要に応じて `handleOps` 全体を `revert` させることも可能）。

5. **デポジットの妥当性確認**
    * 全ての必要ガスコストをカバーできるだけのdepositがあるかを最終確認します。

#### 実行ループ（Execution Loop）

検証に成功した `UserOperation` に対して以下の処理が行われます。

1. **callData の実行**
    * `callData` をそのまま呼び出すか、先頭の関数シグネチャが `executeUserOp()`（IAccountExecute インターフェース）の場合は、コントラクトアカウント内の以下の関数を実行します。

     ```js
     executeUserOp(userOp, userOpHash)
     ```

2. **未使用ガスに対するペナルティ**
    * `callGasLimit` や `paymasterPostOpGasLimit` に対して 未使用ガスが40,000以上残っている場合、10%のペナルティが差し引かれます。
    * これは意図的に大量のガスを予約し、他のトランザクションをブロックする行為を防止するためです。
    * ブロックに含められるガスの量には上限があるため、余計なガス代をかけることで他のトランザクションを含めるスペースが小さくなり、他のUserOperationの処理の実行が遅くなる可能性があるためです。

3. **残りのガス代の返金処理**
    * pre-charged（前払い）されたガスと実際に使用されたガスとの差額をsenderに返金します。

4. **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**](https://eips.ethereum.org/EIPS/eip-7769) にて定義されています。
この仕様に準拠することで、ウォレット・アプリケーション・Bundlerが共通のプロトコルでやりとりできるようになります。

:::message
**ERC7769**については以下の記事を参考にしてください。
:::

[\[ERC7769\] ERC4337に準拠したコントラクトアカウントのためのJSON-RPC APIを理解しよう！](/articles/0b6d631ed4363ba67d2a)

##### `eth_sendUserOperation`

`UserOperation` オブジェクトをbundlerノードに送信するためのメソッド。
送信されたUserOperationは、bundlerのローカルメモリプールに追加され、他のノードにも中継されます。

#### `eth_getUserOperationReceipt`

指定した `UserOperation` の実行結果（`receipt`）を取得するためのメソッド。
実行済みであれば以下のような情報が返されます。
* 実行されたブロックの番号やハッシュ
* EntryPointコントラクトによる処理結果
* 成功／失敗の状態
* 使用されたガス量

### EIP712署名への対応

**ERC4337**では、`UserOperation` に対する署名検証を柔軟にするために、**EIP712**形式の署名をサポートしています。
**EIP712**は、構造化されたデータに対して署名を行うための規格です。

:::message
**EIP712**については以下の記事を参考にしてください。
:::

[\[EIP712\] 型付き構造化データのハッシュ化と署名の仕組みを理解しよう！](/articles/f54bc85cb66de5a54257)

#### `userOpHash` の生成方法

`UserOperation` を識別・署名対象とするハッシュ（`userOpHash`）は、**EIP712**に準拠した以下の2つのステップで構成されます。

##### ドメイン情報のハッシュ（`TYPE_HASH`）

まず、署名対象とするドメインを定義するため、以下の内容をハッシュ化します。

```js
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`の本体に相当するデータ構造を次のように定義し、その型情報をハッシュ化します。

```js
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）からコントラクトアカウントへの柔軟な移行や、特別な認可処理を可能にしています。

:::message
認可（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`

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**トークンで手数料を支払えるような多様なユースケースを実現できます。
以下のフローで処理が実行されます。

![bundle-seq-pm.png](/images/qiita-50c9cae5-b83d-4fb4-9484-4fe7445bb3e2.webp)

#### Paymaster付き UserOperation の処理フロー

`UserOperation` の `paymasterAndData` フィールドが空でない場合、`EntryPoint.handleOps()` は以下の拡張された処理を行います。

#### 検証フェーズ（Validation）

1. **アカウント側の `validateUserOp` を呼び出す**
    * このとき `missingAccountFunds = 0` として呼ばれる（＝Accountのdepositは使用しない）。

2. **Paymasterの預け金を確認**
    * PaymasterがEntryPointコントラクトに十分な量のdeposit（支払い用のETH）をしているか確認。

3. **Paymaster による検証**
    * `validatePaymasterUserOp(userOp, userOpHash, maxCost)` を呼び出す。
    * 戻り値の `context` が空でなければ、この情報は後続の `postOp` 呼び出しに使用される。
    * `validationData` は署名検証や有効期限などに使われる。

:::message
`context` は、Paymasterの `validatePaymasterUserOp()` 関数が返す任意のバイト列データです。
この `context` は検証フェーズで生成され、実行フェーズの `postOp()` 関数にそのまま渡されます。

この仕組みは、検証時にPaymaster側で一時的に計算・取得した値を保持しておき、後続の処理で再利用したい場合に使います。
例えば以下のようなケースがあります。

- トークン支払い型Paymasterで、ユーザーが支払うべき手数料のトークン残高やレートを取得
- アカウントに紐づく状態やアクセス情報を一時保存

これにより、Paymasterはガスを節約しつつ必要な情報を `postOp()` に渡して、成功・失敗に応じたロジック（課金・リファンドなど）を実装できます。
:::

:::message
`validationData` は、`validatePaymasterUserOp()` または `validateUserOp()` の戻り値として返される `uint256` 型のビットデータです。

| フィールド | 内容 |
| 下位 20bit| シグネチャ検証の結果（例: 1 = 署名無効） |
| 中位 64bit| `validUntil`：UserOperationが有効な期限 |
| 上位 64bit| `validAfter`：UserOperationが有効になる開始時刻 |

このデータにより、UserOperationが特定の期間内のみ有効であることを表現でき、署名の有効性や期限制御が可能になります。
:::

#### 実行フェーズ（Execution）

1. **アカウントの executeUserOp を実行（または callData 呼び出し）**

2. **Paymaster に対して `postOp` を呼び出す（必要な場合）**
    * `validatePaymasterUserOp` の戻り値 `context` が空でなければ、以下の形式で呼ばれる。

     ```js
     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）

```js
function addStake(uint32 _unstakeDelaySec) external payable;
function unlockStake() external;
function withdrawStake(address payable withdrawAddress) external;
```

* `addStake`
    * ETHをステークとしてロック。
    * `unstakeDelaySec` （アンロック可能になるまでの待機時間）を指定。
* `unlockStake`
    * アンロック要求（一定時間待つ必要あり）
* `withdrawStake`
    * アンロック後にステークを引き出す

##### デポジット管理（EntryPoint）

```js
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の動作と各種検証ロジックのフローです。

![bundle-build-full-seq.png](/images/qiita-1f9e484b-47a3-4428-851d-81fe13ce8e25.webp)

#### Bundlerの基本処理ステップ

1. **UserOperationの受信**
    * クライアントが `eth_sendUserOperation` をRPCで呼び出し、`UserOperation` をBundlerに送信します。

2. **1回目の検証（受信時）**
    * Bundlerは `UserOpをmempool` に追加する前に、初期検証を行います。
    * この検証に失敗した `UserOperation` をmempoolは破棄されエラーが返されます。

3. **2回目の検証（バンドル構築中）**
    * mempool内の `UserOperation` を使ってバンドルを作成する時各 `UserOperation` を再検証します。
    * 有効なものはバンドルに追加し、失敗したものはスキップされます。

4. **3回目の検証（ブロック提出直前）**
    * 最終的に構築されたバンドル全体に対して、再度検証を行います。
    * 無効な `UserOperation` が含まれていた場合、それらを除外して評判スコアを更新します（詳細は**ERC7562**参照）。

:::message
**ERC7562**については以下の記事を参考にしてください。
:::

[\[ERC7562\] Account Abstractionのトランザクション検証ルールについて理解しよう！](/articles/213a17c8dd12a29f8f2c)

#### 初期検証

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` が空でない場合：<br>① コントラクトコードが存在する。② 十分なdepositがある。③ banされていないこと。 |
| `callGasLimit` | `CALL` 処理（valueあり）の最小ガスを上回っていること。 |
| fee設定の下限 | `maxFeePerGas` および `maxPriorityFeePerGas` が、basefeeおよびBundler設定値以上であること |
| senderの一意性 | 同じsender＋ `nonce` の `UserOperation` がすでに存在しないこと（ある場合は、より高いfeeでの置き換えのみ許可）<br>※ステーク済みのsenderのみ例外的に複数 `UserOperation` を許可。 |

#### バンドル構築とブロック提出のイメージ

1. AliceとBobが `UserOperation` を送信 → Bundler RPCに到達
2. Bundlerはそれらをmempoolに追加（検証済みのもののみ）
3. 次のブロックに向けたバンドル作成時、再度検証を実施
4. 検証成功した `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`」をバンドルに含めることも許可されています。

##### 実行内容

シミュレーションでは以下の検証を行います。

1. `initCode` があればsenderアカウントを作成
2. `account.validateUserOp()` を呼び出し
3. `paymaster.validatePaymasterUserOp()`（Paymasterが指定されている場合）
4. `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

:::message
**EIP2028**については以下の記事を参考にしてください。
:::

[\[EIP2028\] Calldataのガスコストを削減する提案について理解しよう！](/articles/8fb1dea76c23122fb7c7)

##### 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を扱うかといった詳細も記載されています。

:::message
**ERC7562**については以下の記事を参考にしてください。
:::

[\[ERC7562\] Account Abstractionのトランザクション検証ルールについて理解しよう！](/articles/213a17c8dd12a29f8f2c)

### 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**のアクセスリストを活用して競合を防ぐ。
* もしくはバンドルをブロックの最初のトランザクションとして配置する。

:::message
**EIP2930**については以下の記事を参考にしてください。
:::

[\[EIP2930\] アクセスリストを含む新しいトランザクションタイプの導入提案を理解しよう！](/articles/632180dfb44d66344e70)

### エラーコードの扱い

EntryPointコントラクトでは、`UserOperation` の検証中にエラーが発生した場合、`revert` を行う必要があります。
ただし、そのエラーがどのエンティティ（sender、factoryコントラクト、paymaster）によって発生したかを、Bundlerが正確に判断できるようにすることが重要です。

#### エラー原因の特定方法

失敗の原因となったエンティティ（sender、factoryコントラクト、paymaster）は、以下のようにして特定されます。
EntryPointコントラクトによる処理中に発生した`revert`の直前に呼び出された（`CALL`された）コントラクトをコールトレースで特定。

:::message
コールトレースとは、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**対応のアカウントを作成し、元のアカウントの検証ロジックをラップした上で、その新しいアカウントを「**信頼できる送信者**」として設定することで互換性を保つことができます。

## 参考実装

https://github.com/eth-infinitism/account-abstraction/tree/main/contracts

## セキュリティ

**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**で定義されている「ダイヤモンドストレージ」パターンのような手法を用いることが推奨されます。

:::message
**ERC7201**については以下の記事を参考にしてください。
:::

[\[ERC7201\] 名前空間を用いた効率的なストレージ管理の仕組みを理解しよう！](/articles/64ef137f8bd096681713)

* **一時ストレージ（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)を、具体的な処理を追ってしっかりと理解してみましょう。

https://zenn.dev/yuki2020/articles/00242351b3b3aa

### Account Abstraction勉強会

https://www.youtube.com/watch?v=1RGPtE6ey80

### AA（Account Abstraction）の先にある、コントラクトウォレット中心の世界

https://zenn.dev/sivira/articles/d041f1ac44ca1e

### AA（Account Abstraction）における署名集約技術の活用

https://zenn.dev/sivira/articles/34e8147f206ff5

### 【動画で学ぶブロックチェーン】【Ethereum】Account Abstraction—　中城元臣氏

https://www.youtube.com/watch?v=Ob1s_w01HT8

### ERC4337からAccountAbstractionを理解する

https://recruit.gmo.jp/engineer/jisedai/blog/erc4337/

### Account Abstraction（アカウント抽象化）とは？

https://medium.com/@bluesky-aozora/account-abstraction-%E3%82%A2%E3%82%AB%E3%82%A6%E3%83%B3%E3%83%88%E6%8A%BD%E8%B1%A1%E5%8C%96-%E3%81%A8%E3%81%AF-9fa34a5de876

### ERC 4337: account abstraction without Ethereum protocol changes

https://medium.com/infinitism/erc-4337-account-abstraction-without-ethereum-protocol-changes-d75c9d94dc4a

### Account Abstractionの誤解と真実

https://zenn.dev/inaridiy/articles/09429120aaba43

### ERC-4337 Documentation

https://www.erc4337.io/docs

### ERC-4337 Progress report

https://hackmd.io/@erc4337

### 【EIP-4337】UserOperationをBundlerに投げてからTransactionが発行されるまで

https://zenn.dev/taxio/articles/834e6a04bd6b80

### Ethereum wallets today and tomorrow — EIP-3074 vs. ERC-4337

https://medium.com/nethermind-eth/ethereum-wallets-today-and-tomorrow-eip-3074-vs-erc-4337-a7732b81efc8

### Account Abstractionの威力：RaisePay Walletの技術的概要

https://medium.com/@bluesky-aozora/account-abstraction%E3%81%AE%E5%A8%81%E5%8A%9B-raisepay-wallet%E3%81%AE%E6%8A%80%E8%A1%93%E7%9A%84%E6%A6%82%E8%A6%81-76d6f9898e37

### Why we need wide adoption of social recovery wallets

https://vitalik.ca/general/2021/01/11/recovery.html

## 最後に

今回は「プロトコルの変更を回避して、アカウント抽象化を実装する提案である**ERC4337**」についてまとめてきました！
いかがだったでしょうか？

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

[Twitter @cardene777](https://twitter.com/cardene777)
