# [ERC7540] ERC4626の入金と償還のリクエストと処理実行を分離する仕組みを理解しよう！

> ERC7540は、ERC4626を拡張し、非同期的な入金（Deposit）と償還（Redemption） をサポートする規格です。非同期的な処理は「リクエスト（Request）」と呼ばれ、新しいメソッドを追加することで実行できます。

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

## はじめに

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

https://amzn.asia/d/gxvJ0Pw

今回は、**ERC4626**を拡張して入金と償還のリクエストと処理を分離する仕組みを提案している**ERC7540**についてまとめていきます！

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

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

他にも様々なEIPについてまとめています。

https://cardene.notion.site/ERC-EIP-2a03fa3ea33d43baa9ed82288f98d4a9?pvs=4

## 概要

**ERC7540**は、**ERC4626**を拡張し、非同期的な入金（Deposit）と償還（Redemption） をサポートする規格です。
非同期的な処理は「**リクエスト（Request）**」と呼ばれ、新しいメソッドを追加することで実行できます。
**ERC4626**は即時（アトミック）に入金や償還が行われる仕組みでしたが、**ERC7540**では「**入金を申請する**」、「**償還を申請する**」といった非同期のリクエストが可能になります。
さらに、リクエストの進行状況や状態を確認できる仕組みが追加されています。
最終的な資産（トークン）の受け取りは、既存の `deposit`、`mint`、`withdraw`、`redeem` 関数を用いて、申請済みのリクエストを「クレーム（請求）」する形で行います。実装者は、入金フロー、償還フロー、またはその両方に非同期処理を導入するかどうかを柔軟に選択できます。

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

[\[ERC4626\] あるトークンを保有していると報酬を受け取れる標準規格の仕組みを理解しよう！](/articles/99db4ee3074ab6c8c4e2)

## 動機

**ERC4626**は、利回りを生むトークンとの互換性を高め、DeFiにおいて重要な規格の1つとなっています。
しかし、**ERC4626**は 一度に処理できる入金・償還の上限が決められています。
この上限を超えると、新たな入金や償還が受け付けられなくなるため、特定のユースケースには適していません。

例えば、以下のようなシステムでは、非同期的な処理が必要となることが多いです。

- リアルワールドアセット（RWA）プロトコル
    現実資産をトークン化する場合、資産の取得や清算には時間がかかるため即時処理が難しい。
- undercollateralizedレンディングプロトコル
    十分な担保なしに融資を行う仕組みでは、審査や信用スコアの確認が必要になるため即時に資金を引き出せない。
- クロスチェーンレンディングプロトコル
    異なるブロックチェーンをまたぐ資産のやり取りには、ブリッジを介した遅延が発生するため即時処理ができない。
- リキッドステーキングトークン（LST）
    ステーキングした資産を流動性のあるトークンとして扱う場合、基盤となるバリデータの処理に時間がかかるためすぐに償還できない。
- insuranceセーフティモジュール
    保険の支払いにはリスク評価や審査が必要となるため、即座に引き出すことができない。

**ERC7540**では、上記のような非同期処理を前提としたユースケースにも対応できるように、**ERC4626**のインターフェースを拡張します。
非同期リクエストを導入することで、これらのユースケースにもVaultを活用できるようにし、**ERC4626**の適用範囲を大幅に広げています。
既存のインターフェースをそのまま利用して「**クレーム処理**」が可能であるため、互換性も維持されています。

## 仕様

### 定義

#### Request  

Vaultに入金（`requestDeposit`）または償還（`requestRedeem）をリクエストのことを指します。
これは即時に処理されずに、後で処理が完了するとユーザーが資産を受け取ることができます。

#### Pending

ユーザーが入金や償還のリクエストを送った段階で、まだ実際の処理が行われていない状態です。
Vault側の処理待ちになります。

#### Claimable

リクエストがVaultで処理され、ユーザーが対応する資産（償還時）またはシェア（入金時）を請求できる状態です。
例えば、入金リクエストならシェア（Vaultの持分）、償還リクエストなら元の資産を請求できます。
#### Claimed

ユーザーがリクエストを最終的に処理し、シェアや資産を受け取った状態です。
これにより、リクエストは完了します。

#### Claim関数

リクエストを最終的に完了させるためのVaultメソッドです。
例えば、`requestDeposit` に対する `deposit` や `mint` などが該当します。

#### 非同期入金Vault

入金リクエストを処理できるVaultのことです。
リクエストがClaimableになるまで即座にシェアは発行されません。

#### 非同期償還Vault

償還リクエストを処理できるVaultのことです。
リクエストがClaimableになるまで即座に資産は返還されません。

#### 完全非同期Vault

入金と償還の両方を非同期リクエスト方式で処理するVaultのことです。

#### controller

リクエストの所有者であり、リクエストに関する操作（Claimや管理など）を行う権限を持つアカウントです。

#### operator

他のアカウントのリクエストを管理することが許可されたアカウントです。
コントローラーが特定のアカウントに操作権限を与えた場合、そのアカウントがオペレーターとなります。

### 非同期リクエストのフロー

**ERC7540**のVaultは、必ず「**非同期の入金フロー**」と「**非同期の償還フロー**」、あるいはその両方のいずれかを実装する必要があります。
どちらか一方を実装しない場合は、その部分については従来の**ERC4626**に定義されている「**同期的な処理**」をそのまま利用します。
つまり、非同期リクエストの仕組みと同期的な仕組みを組み合わせることが可能です。

全ての**ERC7540**に準拠したVaultは、**ERC4626**の仕様に基づいて動作しながら、非同期処理を可能にするための挙動の変更を行います。

#### 非同期入金Vaultの仕様変更

非同期入金（DepositやMint）をサポートするVaultでは、**ERC4626**の入金に関する処理が変更されます。

1. `deposit` および `mint`
    これらの関数はVaultに資産を送る動作を行いません。
    なぜなら、資産はすでに `requestDeposit` の段階でVaultに送付されているからです。
    ここでは「**リクエストの請求（`claim`）**」という役割だけを担います。
2. `previewDeposit` および `previewMint`
    入金前にシミュレーションする機能ですが、非同期Vaultでは意味を持たないため、必ず呼び出しを失敗（`revert`）させる必要があります。

#### 非同期償還Vaultの仕様変更

非同期償還（RedeemやWithdraw）をサポートするVaultでは、**ERC4626**の償還に関する処理が変更されます。

1. `redeem` および `withdraw`
    これらの関数はVaultにシェア（Vaultの持分トークン）を送る動作を行いません。
    シェアはすでに `requestRedeem` の段階でVaultに送られているからです。
    ここでも請求処理だけを行います。

2. `owner` フィールドの名称変更
    **ERC4626**では `redeem` や `withdraw` において `owner` という指定がありましたが、非同期フローではこれを `controller` に置き換えます。
    `controller` は通常 `msg.sender`（呼び出しを行ったアカウント）であり、もし他のアカウントが代行する場合は、あらかじめ `controller` からオペレーターとして承認を受けている必要があります。

3. `previewRedeem` および `previewWithdraw`
    これらのシミュレーション用関数も非同期フローでは意味を持たないため、必ず失敗（`revert`）させます。

#### リクエストのライフサイクル

**ERC7540**におけるリクエストは、**Pending（保留中） → Claimable（請求可能） → Claimed（請求済み）** の3段階を順番に経て進行します。
これは「**入金リクエスト（deposit request）**」でも「**償還リクエスト（redeem request）**」でも同様です。
ここでは入金リクエストを例に説明します。

#### 入金リクエストの流れ

リクエストは必ず以下の順序で処理されます。
ユーザーは必ず「**リクエストを送る（requestDeposit）**」と「**請求する（deposit）**」という2つの操作を行う必要があります。
両方を同じブロック内で行うことも可能ですが省略はできません。
また、Vaultからユーザーに自動的にトークンが送られることはなく、必ずユーザーが請求して取り出す必要があります。

| 状態 | ユーザーの操作 | Vaultの動作|
| --- | ------------ | --------- |
| Pending（保留中） | `requestDeposit(assets, controller, owner)` | `asset.transferFrom(owner, vault, assets)` を実行し、`pendingDepositRequest[controller]` に資産を加算する。 |
| Claimable（請求可能） | （ユーザー操作なし、Vault内部処理） | Vault内部で処理を行い、`pendingDepositRequest[controller]` を減算し、`claimableDepositRequest[controller]` に資産を加算する。 |
| Claimed（請求済み） | `deposit(assets, receiver, controller)`     | `claimableDepositRequest[controller]` を減算し、Vault内で `balanceOf[receiver]` にシェアを加算する。 |

また、`maxDeposit` の値は `claimableDepositRequest` の増減に応じて変動します。

#### 請求状態の重要性

**ERC7540**では、リクエストが必ず「`Claimable`」を経由します。
つまり、リクエストを送信しただけでは資産やシェアを取得できず、必ず請求関数を呼び出して「**取りに行く（pull）**」必要があります。
Vault側が勝手に「**送りつける（push）**」ことはできません。

#### 非同期Vaultにおける交換レート

非同期Vaultでは、資産とシェアの交換レート（利回りや手数料を含む）はVaultの実装によって決まります。
そのため、Pendingの段階では利回りが発生しない場合や、固定の交換レートが保証されない場合があります。
この点は、通常の同期型Vaultと異なり、利用者が注意すべき仕様です。

### リクエストIDの管理

**ERC7540**におけるリクエストには、`requestId` という識別子が割り当てられます。
このIDは `requestDeposit` や `requestRedeem` を呼び出した時に返され、リクエストを追跡するために使われます。

ただし、リクエストを区別するのは `requestId` と `controller` の組み合わせであり、同じ `requestId` を持つ複数のリクエストが存在することも可能です。

#### 同じリクエストIDを持つ場合のルール

同じ `requestId` を持つリクエストは、基本的に同じ性質を持つものとして扱われます。
具体的には、以下のような動作になります。

* 全てのリクエストが同じタイミングで **Pending → Claimable** に移行します。
* 資産とシェアの交換レートも統一されます。
* 部分的に請求可能（Claimable）になる場合も、同じ `requestId` のすべてのリクエストがプロラタ（比率に応じた分配）で一斉に請求可能になります。

**ERC7540**によって、同じグループのリクエストは公平かつ一貫した処理が保証されます。

#### 異なるリクエストIDを持つ場合

異なる `requestId` のリクエストは、それぞれ独立して扱われます。
つまり、Claimableに移行するタイミングや交換レートに関して、順序や関連性は一切強制されません。
Vaultの実装次第で自由に管理されます。

#### 特殊なリクエストID = 0 の場合

`requestId == 0` は特別な扱いになります。
この場合、Vaultはリクエストを `controller` のみで区別します。
つまり、同じコントローラーが送った複数のリクエストはまとめて管理され、PendingやClaimableの状態も合算されます。

さらに、もしVaultがあるリクエストで `requestId = 0` を返した場合は、そのVault内のすべてのリクエストで `requestId = 0` を返す必要があります。

### 関数

#### `requestDeposit`

```javascript
function requestDeposit(uint256 assets, address controller, address owner) returns (uint256 requestId)
```

指定された資産をVaultへ送金し、非同期的な入金リクエストを作成する関数。
`requestDeposit` を実行すると、`assets` 分の資産が `owner` から Vault に送金され、そのリクエストがPending状態になります。
対応する `requestId` が発行され、リクエストを識別するために `controller`（リクエストの管理者）と組み合わせて使用されます。  
リクエストがClaimable（クレーム可能）状態になると、`claimableDepositRequest` に `assets` 分が加算され、`controller` は `deposit` または `mint` を呼び出してシェアを受け取ることができるようになります。  
リクエストは `Claimable` 状態をスキップすることは許可されていないため、必ず `requestDeposit` の後に `deposit` または `mint` を実行する必要があります。  
`convertToShares(assets)` のレートは、リクエスト時と `Claimable` 状態になった後で変動する可能性があります。  
また、`owner` は `msg.sender` でなければならず、例外として `msg.sender` が `owner` によってオペレーターとして承認されている場合にのみ `owner` の代理として実行できます。  

**引数** 

- `assets`  
    - 入金する資産の数量。  
- `controller`  
    - リクエストを管理するアドレス。  
- `owner`  
    - 資産をVaultへ送金するアドレス（通常は `msg.sender`）。  

**戻り値**  

- `requestId`  
    - リクエストを識別するためのID。  

#### `pendingDepositRequest`

```javascript
function pendingDepositRequest(uint256 requestId, address controller) view returns (uint256 assets)
```

指定されたリクエストIDと `controller` に対応するPending状態の資産量を返す関数。
`requestDeposit` を実行した際に `Pending` 状態となっている資産の総量を取得します。  
この値には、Claimable状態に移行した資産は含まれません。  
呼び出し元（`caller`）によって値が変動することはなく、整数オーバーフローが発生しない限り`revert`しません。。

**引数**

- `requestId`  
    - 確認するリクエストのID。  
- `controller`  
    - リクエストを管理するアドレス。  

**戻り値** 

- `assets`  
    - `Pending` 状態の資産量。  

#### `claimableDepositRequest`

```javascript
function claimableDepositRequest(uint256 requestId, address controller) view returns (uint256 assets)
```

指定されたリクエストIDと `controller` に対応するClaimable状態の資産量を返す関数。
`requestDeposit` によりPending状態だった資産がClaimable状態へ移行した時にその総量を取得します。  
この値にはPending状態の資産は含まれません。  
呼び出し元による変動はなく、整数オーバーフローが発生しない限り`revert`しません。

**引数**

- `requestId`  
    - 確認するリクエストのID。  
- `controller`  
    - リクエストを管理するアドレス。  

**戻り値**

- `assets`  
    - `Claimable` 状態の資産量。  

#### `requestRedeem`

```javascript
function requestRedeem(uint256 shares, address controller, address owner) returns (uint256 requestId)
```

指定されたシェアをVaultへ送付し、非同期的な償還リクエストを作成する関数。
`requestRedeem` を実行すると、`shares` 分のシェアが `owner` からVaultへ送られ、そのリクエストがPending状態になります。  
このリクエストには `requestId` が発行され、`controller` がリクエストを管理します。  
リクエストがClaimable状態になると、`claimableRedeemRequest` に `shares` 分が加算され、`controller` は `redeem` または `withdraw` を呼び出して資産を受け取ることができます。  
リクエストは `Claimable` 状態をスキップすることは許可されていないため、必ず `requestRedeem` の後に `redeem` または `withdraw` を実行する必要があります。  
`convertToAssets(shares)` のレートは、リクエスト時とClaimable状態になった後で変動する可能性があります。  
また、`owner` は `msg.sender` でなければならず、例外として `msg.sender` が `owner` によってオペレーターとして承認されている場合にのみ `owner` の代理として実行できます。  

**引数**

- `shares`  
    - 償還するシェアの数量。  
- `controller`  
    - リクエストを管理するアドレス。  
- `owner`  
    - シェアをVaultへ送付するアドレス（通常は `msg.sender`）。  

**戻り値**

- `requestId`  
    - リクエストを識別するためのID。  

#### `pendingRedeemRequest`

```javascript
function pendingRedeemRequest(uint256 requestId, address controller) view returns (uint256 shares)
```

指定されたリクエストIDと `controller` に対応するPending状態のシェア量を返す関数。
`requestRedeem` を実行した時に `Pending` 状態となっているシェアの総量を取得します。  
この値には、Claimable状態に移行したシェアは含まれません。  
呼び出し元（`caller`）によって値が変動することはなく、整数オーバーフローが発生しない限り`revert`しません。

**引数**

- `requestId`  
    - 確認するリクエストのID。  
- `controller`  
    - リクエストを管理するアドレス。  

**戻り値**

- `shares`  
    - `Pending` 状態のシェア量。  

#### `claimableRedeemRequest`

```javascript
function claimableRedeemRequest(uint256 requestId, address controller) view returns (uint256 shares)
```

指定されたリクエストIDと `controller` に対応するClaimable状態のシェア量を返す関数。
`requestRedeem` により `Pending` 状態だったシェアがClaimable状態へ移行した時にその総量を取得します。  
この値にはPending状態のシェアは含まれません。  
呼び出し元による変動はなく、整数オーバーフローが発生しない限り`revert`しません。

**引数**

- `requestId`  
    - 確認するリクエストのID。  
- `controller`  
    - リクエストを管理するアドレス。  

**戻り値**

- `shares`  
    - `Claimable` 状態のシェア量。  

#### `isOperator`

```javascript
function isOperator(address controller, address operator) view returns (bool status)
```

指定された `operator` が `controller` のオペレーターとして承認されているかを確認する関数。
`controller` が `operator` をオペレーターとして承認している場合に `true` を返し、それ以外の場合は `false` を返します。  
オペレーターは、`controller` に代わってリクエストを管理する権限を持ちます。  

**引数**

- `controller`  
    - リクエストの管理者。  
- `operator`  
    - `controller` に代わってリクエストを管理する可能性のあるアドレス。  

**戻り値**

- `status`  
    - `operator` が `controller` のオペレーターであれば `true`、そうでなければ `false`。  

#### `setOperator`

```javascript
function setOperator(address operator, bool approved) returns (bool success)
```

指定された `operator` に対して、リクエスト管理の権限を付与または解除する関数。
この関数を実行すると、`msg.sender` が `operator` に対してリクエスト管理の権限を与えるか（`approved = true`）、権限を解除するか（`approved = false`）を設定できます。  
関数実行成功時、`OperatorSet` イベントが発行されて `true` が返されます。

**引数**

- `operator`  
    - リクエスト管理の権限を付与または解除する対象のアドレス。  
- `approved`  
    - `true` の場合は `operator` をオペレーターとして承認、`false` の場合はオペレーター権限を削除。  

**戻り値**

- `success`  
    - 操作が成功した場合は `true`。  

#### `deposit`

```javascript
function deposit(uint256 assets, address receiver, address controller)
```

非同期入金リクエストのうち `controller` が管理するClaimable状態の資産を指定の `receiver` に対してシェアとして付与する関数。
この関数は**ERC4626**の `deposit` 関数を拡張したもので、追加の `controller` 引数を持ちます。  
呼び出し時に、`controller` が `msg.sender` であるか、`controller` によってオペレーターとして承認されていなければ`revert`します。
資産の請求時には、`Deposit` イベントを発行し、最初の引数に `controller`、2番目の引数に `receiver` を設定します。

**引数**

- `assets`  
    - `Claimable` 状態の資産量。  
- `receiver`  
    - 受取人のアドレス。  
- `controller`  
    - リクエストを管理するアドレス。  

#### `mint`

```javascript
function mint(uint256 shares, address receiver, address controller)
```

非同期入金リクエストのうち `controller` が管理するClaimable状態の資産を指定の `receiver` に対してシェアとして付与する関数。
この関数は**ERC4626**の `mint` を拡張したもので、追加の `controller` 引数を持ちます。  
呼び出し時に、`controller` が `msg.sender` であるか、`controller` によってオペレーターとして承認されていなければ`revert`します。  
シェアの請求時には、`Deposit` イベントを発行し、最初の引数に `controller`、2番目の引数に `receiver` を設定します。

**引数**

- `shares`  
    - Claimable状態のシェア量。  
- `receiver`  
    - 受取アドレス。  
- `controller`  
    - リクエストを管理するアドレス。  

### イベント

#### `DepositRequest`

```javascript
event DepositRequest(
    address indexed controller,
    address indexed owner,
    uint256 indexed requestId,
    address sender,
    uint256 assets
);
```

`requestDeposit` 関数が実行されたときに発行されるイベント。
Vault に資産がロックされ、入金リクエストが作成されたことを示す。
このイベントには、リクエストを管理する `controller`、資産を提供した `owner`、リクエストを一意に識別する `requestId`、実際に `requestDeposit` を呼び出した `sender`、および入金した `assets` の数量が含まれます。  
`sender` は必ずしも `owner` と同じとは限りません（オペレーターが代理で実行するケースがある）。  

**パラメータ**

- `controller`  
    - このリクエストを管理するアドレス。  
- `owner`  
    - Vault に資産を送金したアドレス（通常は `msg.sender`）。  
- `requestId`  
    - この入金リクエストを識別する一意のID。  
- `sender`  
    - `requestDeposit` を実行したアドレス（オペレーターの場合もある）。  
- `assets`  
    - リクエストされた資産の数量。  

#### `RedeemRequest`

```javascript
event RedeemRequest(
    address indexed controller,
    address indexed owner,
    uint256 indexed requestId,
    address sender,
    uint256 shares
);
```

`requestRedeem` 関数が実行されたときに発行されるイベント。
Vault にシェアがロックされ、償還リクエストが作成されたことを示します。
このイベントには、リクエストを管理する `controller`、シェアの所有者である `owner`、リクエストを識別する `requestId`、実際に `requestRedeem` を呼び出した `sender`、および償還リクエストされた `shares` の数量が含まれます。  
`sender` は必ずしも `owner` と同じとは限らず、オペレーターが代理で実行することもできます。  

**パラメータ**

- `controller`  
    - このリクエストを管理するアドレス。  
- `owner`  
    - Vault にシェアを送付したアドレス（通常は `msg.sender`）。  
- `requestId`  
    - この償還リクエストを識別する一意のID。  
- `sender`  
    - `requestRedeem` を実行したアドレス（オペレーターの場合もある）。  
- `shares`  
    - リクエストされたシェアの数量。  

#### `OperatorSet`

```javascript
event OperatorSet(
    address indexed controller,
    address indexed operator,
    bool approved
);
```

`setOperator` 関数が実行されたときに発行されるイベント。
オペレーターの設定が変更されたことを示します。
このイベントには、オペレーターを設定する `controller`、設定された `operator`、および `operator` が承認（`true`）されたのか、解除（`false`）されたのかを示す `approved` の3つの情報が含まれます。  
`setOperator` が呼ばれるたびに、このイベントがログに記録されるため、特定の `controller` に対してどのオペレーターが設定されたかを確認できます。  
また、同じオペレーターに対して `approved` が変更されない場合でも、このイベントが発行されることがあります。  

**パラメータ**

- `controller`  
    - `setOperator` を実行したアドレス（リクエストを管理するアカウント）。
- `operator`  
    - 設定されたオペレーターのアドレス。  
- `approved`  
    - `true` の場合はオペレーターとして承認、`false` の場合はオペレーター権限を削除。  

### ERC165のサポート

**ERC7540**を実装するコントラクトは、**ERC165**の `supportsInterface` 関数を実装する必要があります。
`supportsInterface` は、あるコントラクトが特定のインターフェースをサポートしているかどうかを判別するための関数です。

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

[\[ERC165\] コントラクトが特定のインターフェースをサポートしているか確認する仕組みを理解しよう！](/articles/541184977a5cba51b759)

**ERC7540**に対応するVaultは、`supportsInterface` に特定の `interfaceID` を渡した時に `true` を返すことで、対応する機能を実装していることを示します。  
以下の `interfaceID` に対する動作が定義されています。

- **0xe3bc4e65**  
    全ての**ERC7540**のVaultが実装するオペレーター管理機能（`setOperator` など）に対応していることを示します。  
    `supportsInterface(0xe3bc4e65)` を呼び出した時に `true` を返す必要があります。  

- **0x2f0a18c5**  
    **ERC7540**のインターフェースをサポートしていることを示します。  
    `supportsInterface(0x2f0a18c5)` を呼び出した時に `true` を返す必要がある。  

- **0xce3bbe50**  
    非同期入金Vaultであることを示します。  
    `supportsInterface(0xce3bbe50)` を呼び出した時に `true` を返す必要があります。  
    これは、`requestDeposit` などの非同期入金処理が適用されていることを保証します。  

- **0x620ee8e4**  
    非同期償還Vaultであることを示します。  
    `supportsInterface(0x620ee8e4)` を呼び出した時に `true` を返す必要があります。  
    これは、`requestRedeem` などの非同期償還処理が適用されていることを保証します。  


#### ER7575のサポート

**ERC7540**を実装するVaultコントラクトは、**ERC7575**の仕様も実装する必要があります。
特に、シェアに関するメソッドの実装が必須となります。

**ERC7575**は、トークン化された資産管理に関する標準であり、特定の機能をVaultに提供します。
`share` メソッドは、Vault内で管理されるシェアを取得するための関数であり、これに対応することで、Vaultの資産管理機能が標準化され、他のDeFiプロトコルと互換性を持つようになります。

この仕様により、Vaultの機能がより広範なエコシステムと統合しやすくなり、異なるプロトコル間での相互運用性が向上します。

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

[\[ERC7575\] ERC4626を拡張してマルチアセット保管を可能にする仕組みを理解しよう！](/articles/13daf479168d2cb653ba)

## 補足

### リクエストIDの導入とIDによるクレームメソッドの非採用

非同期VaultにおけるRequestは、NFTまたはセミファンジブルトークンのような性質を持っています。
これは、リクエストが個別に識別され状態遷移を伴うためです。
しかし、**ERC7540**のVaultに**ERC721**や**ERC1155を強制的に適用するとインターフェースの肥大化を招き、実装の自由度を損なう可能性があります。

そのため、リクエストの識別には `requestId` と `controller` の組み合わせを用いることで、Vaultの基本仕様にできる限り従い、拡張性を持たせています。

特に `requestId==0` の場合、**ERC4626**の既存のメソッドを用いてクレームを行うことを想定しています。
リクエストIDを使用しないVaultでは、リクエストごとの識別を行わず、Vault全体の管理を単純化できるためです。
リクエストIDを用いた明確なクレーム方法が求められる場合は、将来的に新しい標準が策定される可能性があります。

### 非同期Vaultにおけるリクエストの対象と関数の非対称性

**ERC4626**では、資産とシェアの変換を `deposit`/`withdraw` および `mint`/`redeem` の2つの軸で完全に対称な関数構成にしています。
しかし、非同期Vaultでは、リクエスト時点で確定している数量のみを扱うため、この対称性を維持できません。

具体的には、入金リクエストは、確定している資産の数量を基に処理されるため `deposit` を使用します。
しかし、`mint` は、Vault内の価格変動によって変動するためリクエスト処理には適しません。

同様に、償還リクエスト は、確定しているシェアの数量を基に処理されるため `redeem` を使用します。
一方、`withdraw`は、Vaultの資産価格の変動による影響を受けるため、リクエスト処理には適しません。

この設計により、リクエスト時に確定している数量を基にVaultが適切に処理できるようになっています。

### 片側のみ非同期のVaultの対応

非同期Vaultは、入金と償還の両方が非同期である必要はないです。
あるプロトコルでは、一方のみを非同期処理にしてもう一方を同期的に処理する場合があります。

例えば、リキッドステーキングトークン（LST）の場合、資産の入金は即座に処理できるため、入金は同期的に処理できます。
しかし、バリデータノードによるステーキング解除には一定期間が必要になるため、償還は非同期にする必要があります。

このように、Vaultのユースケースに応じて、非同期処理を部分的に適用できるように設計されています。

### リクエストのキャンセル機能が含まれていない理由

Vaultにおいて、リクエストのキャンセルが簡単な処理でない場合があります。  
キャンセルの実装はVaultごとに異なり、以下のような問題を生じる可能性があります。

- **キャンセルの状態遷移が複雑になる**  
    Vaultによっては、キャンセルが即時処理（同期的）される場合もあれば、別の処理が必要（非同期的）になる場合もあります。  
    これを標準化しようとすると、Vaultの動作を制約しすぎる可能性があります。

- **Vaultの他の機能との統合が難しくなる**  
    例えば、一部のVaultでは資産をリアルワールドアセット（RWA）や流動性プールに組み込むため、リクエストのキャンセルが技術的に難しい場合があります。  
    このため、**ERC7540**ではリクエストのキャンセル機能は含まれておらず、別のEIPとして標準化されるべきとしています。

### リクエスト実装の柔軟性

**ERC7540**は、リクエストの管理方法に柔軟性を持たせています。  
Vaultは以下のような方法でリクエストを管理できます。

- 内部会計を用いてVault内で各リクエストを管理する方法。  
- グローバルまたはユーザーレベルでのリクエスト管理。  
- **ERC20**, **ERC721**, **ERC1155** などを活用したトークンベースの管理**。  

また、償還リクエストに関しては、利回りの発生有無や、交換レート（固定または変動）などもVaultの設計に委ねられています。

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

https://chaldene.net/erc20

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

https://chaldene.net/erc721

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

https://chaldene.net/erc1155

### クレームの即時処理を許可しない理由

クレーム処理を即時実行すると以下の問題が発生します。

- **スケーラビリティの問題**  
    ユーザー数が増加すると、Vault運営者がすべてのクレーム処理コストを負担する必要があり非現実的。  

- **外部統合の複雑化**  
    一部のVaultが1ステップで処理し、他のVaultが2ステップで処理すると、外部プロトコルが両方のパターンに対応する必要があり統合が難しくなリマス。  

このため、**ERC7540**では必ず2ステップ（Request → Claim）で処理を実行し、ユーザーがクレームを明示的に実行する仕様となっています。

### リクエスト関数の出力を省略する理由

`requestDeposit` や `requestRedeem` の時点では、リクエストがクレームされる時の交換レートが確定していないため、戻り値として受け取る資産やシェアの数量を返すことができません。

また、リクエストの処理時間を予測するためにタイムスタンプを返す案もありますが、Vaultによっては信頼できるタイムスタンプを提供できないため仕様には含めていません。

### Claimable状態のイベントが存在しない理由

リクエストが**Pending → Claimable** に遷移するタイミングは、Vaultの内部処理によって決まります。  
一部のVaultでは、リクエストを一括でClaimableに変更する場合もあれば、特定の時間経過後に自動的に遷移する場合もあります。

全てのVaultにClaimable状態のイベント発行を義務付けると、Vaultの設計を制限することになるため、標準には含まれていません。

## 互換性

**ERC4626**と完全な互換性があります。
`deposit`, `mint`, `redeem`、`withdraw`の仕様はそれぞれ異なります。

## 参考実装

```js
// This code snippet is incomplete pseudocode used for example only and is no way intended to be used in production or guaranteed to be secure

mapping(address => uint256) public pendingDepositRequest;

mapping(address => uint256) public claimableDepositRequest;

mapping(address controller => mapping(address operator => bool)) public isOperator;

function requestDeposit(uint256 assets, address controller, address owner) external returns (uint256 requestId) {
    require(assets != 0);
    require(owner == msg.sender || isOperator[owner][msg.sender]);

    requestId = 0; // no requestId associated with this request

    asset.safeTransferFrom(owner, address(this), assets); // asset here is the Vault underlying asset

    pendingDepositRequest[controller] += assets;

    emit DepositRequest(controller, owner, requestId, msg.sender, assets);
    return requestId;
}

/**
 * Include some arbitrary transition logic here from Pending to Claimable
 */

function deposit(uint256 assets, address receiver, address controller) external returns (uint256 shares) {
    require(assets != 0);
    require(controller == msg.sender || isOperator[controller][msg.sender]);

    claimableDepositRequest[controller] -= assets; // underflow would revert if not enough claimable assets

    shares = convertToShares(assets); // this naive example uses the instantaneous exchange rate. It may be more common to use the rate locked in upon Claimable stage.

    balanceOf[receiver] += shares;

    emit Deposit(controller, receiver, assets, shares);
}

function setOperator(address operator, bool approved) public returns (bool) {
    isOperator[msg.sender][operator] = approved;
    emit OperatorSet(msg.sender, operator, approved);
    return true;
}
```

## セキュリティ

非同期処理を伴うVaultでは、状態遷移が複雑になりセキュリティリスクが増大します。  
Vaultの動作を安全に保つためには、アクセス制御の適切な実装、ステート遷移の明確な仕様化、不変条件チェックを徹底することが重要です。
これにより、予期しない動作や悪意のある攻撃からVaultを保護できます。

### 状態遷移の可視化に関するリスク

非同期Vaultでは、リクエストがPending）→ Claimable）→ Claimedという3つの段階を経るため、ユーザーがリクエストの状態を正確に把握することが難しくなります。

特に `pendingDepositRequest` や `claimableDepositRequest` などの読み取り関数は、Vaultの現在の状態を確認するために提供されていますが、これらはあくまで「推定値」としての情報を提供するものであり、完全に正確な情報ではない可能性があります。  

例えば、リクエストの処理がすでに進んでいるにもかかわらず、ユーザーが古い状態を見て判断してしまうことで間違った操作を行う可能性があります。
このため、Vaultを利用する時は、状態遷移の仕組みを十分に理解して交換レートの変動リスクに注意する必要があります。

また、非同期Vaultではリクエスト時点での交換レートと、クレーム時点での交換レートが異なる可能性があるため、最終的に受け取るシェアや資産の量が事前に正確に分からないという課題があります。  
これは、Vaultがリクエストの処理をどのように計算し、どのようにリクエストを満たすかに依存するため、Vaultの実装を信頼する必要があるというリスクが生じます。

### リクエストがPending状態でスタックするリスク

Vaultが適切にリクエストを処理しない場合、資産やシェアがPending状態のままロックされてしまう可能性があります。  
これにより、ユーザーは資産やシェアを引き出すことができず資産が拘束されるリスクがあります。

Vaultの実装によっては、このようなリスクを軽減するためにPending状態のリクエストをファンジブルトークン扱うように設計することが可能です。
例えば、Pending状態のリクエストを**ERC20**トークンや**ERC1155**トークンとして発行することで、リクエスト自体をトークン化して流動性を持たせることができます。

また、一部のVaultでは、リクエストをキャンセルする機能の提供も考えられます。  
例えば、一定期間内にリクエストが処理されない場合、自動的に資産が返還される仕組みを設けることで、ユーザーが資産を取り戻せるようにすることが可能です。ただし、キャンセル機能を実装すること自体がVaultの設計を複雑にするため、全てのVaultがキャンセル機能を提供するわけではないことにも注意が必要です。

### オペレーターのリスク管理

Vaultでは、オペレーターを指定することで、他のアカウントにリクエストの管理を委任することができます。
しかし、オペレーターを許可することには、以下のようなリスクが伴います。

- **オペレーターはVault内の資産を`transfer`できる**  
    承認されたオペレーターは、Vault内の資産を任意のアドレスへ`transfer`できる権限を持ちます。  
    そのため、オペレーターを誤って信頼できないアカウントに設定してしまうと、資産が不正に引き出されるリスクがあります。

- **オペレーターはVaultのシェアも管理できる**  
    Vaultにおけるシェアのクレームや譲渡もオペレーターが代行できるため、Vaultに対する操作権を実質的に委譲することになります。  
    悪意のあるオペレーターが設定されると、Vaultのシェアを不正に操作される可能性があります。

このため、オペレーターを許可する場合は十分に信頼できるアカウントを選ぶことが重要です。  
また、Vaultの実装によってはオペレーターが行える操作に制限を設けることで、セキュリティリスクを軽減することができます。
例えば、「オペレーターが管理できるのは特定のリクエストのみ」といった制約を設けることで、被害範囲を限定することが可能になります。

## 最後に

今回は「**ERC4626**を拡張して入金と償還のリクエストと処理を分離する仕組みを提案している**ERC7540**」についてまとめてきました！
いかがだったでしょうか？

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

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

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

https://chaldene.net/

https://zenn.dev/heku

https://cardene.substack.com/

https://mirror.xyz/0xcE77b9fCd390847627c84359fC1Bc02fC78f0e58

https://cardene.notion.site/ERC-EIP-2a03fa3ea33d43baa9ed82288f98d4a9?pvs=4
