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

# はじめに

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

https://amzn.asia/d/gxvJ0Pw

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

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

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

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

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

# 概要

**ERC7540**は、**ERC4626**を拡張し、非同期的な入金（Deposit）および償還（Redemption） をサポートする規格です。
非同期的な処理は「リクエスト」と呼ばれ、新しいメソッドを追加することで実行できます。
これにより、入金や償還をリクエストとして送信し、そのリクエストの状態を確認できるようになります。
既存の**ERC4626**のメソッド（`deposit`、`mint`、`withdraw`、`redeem`）は、その時点で処理可能なリクエストを実行するために使用されます。

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

https://qiita.com/cardene/items/99db4ee3074ab6c8c4e2

# 動機

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

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

### リアルワールドアセット（RWA）プロトコル

現実資産をトークン化する場合、資産の取得や清算には時間がかかるため、即時処理が難しい。

### undercollateralizedレンディングプロトコル

十分な担保なしに融資を行う仕組みでは、審査や信用スコアの確認が必要になるため、即時に資金を引き出せない。

### クロスチェーンレンディングプロトコル

異なるブロックチェーンをまたぐ資産のやり取りには、ブリッジを介した遅延が発生するため、即時処理ができない。

### リキッドステーキングトークン（LST）

ステーキングした資産を流動性のあるトークンとして扱う場合、基盤となるバリデータの処理に時間がかかるため、すぐに償還できない。

### insuranceセーフティモジュール

保険の支払いにはリスク評価や審査が必要となるため、即座に引き出すことができない。

**ERC7540**では、上記のような非同期処理を前提としたユースケースにも対応できるように、**ERC4626**のインターフェースを拡張します。
これにより、既存のVaultのメソッドをそのまま活用しながら、リクエスト方式を導入して入金や償還の受付と処理を分離することが可能になります。

# 仕様

## 定義

### Request  

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

### Pending

リクエストが作成されたものの、まだ実行可能（Claimable）になっていない状態です。
これは、Vault側での処理待ちの段階を示します。

### Claimable

リクエストが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の仕様変更

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

まず、`deposit` および `mint` メソッドが資産をVaultへ送金しないようになります。
これは、すでに `requestDeposit` の段階で資産がVaultに送金されているためです。
従来の**ERC4626**では、`deposit` を実行すると同時に資産がVaultに送られますが、**ERC7540**では資産の送金とシェアの発行が分離されます。

また、`previewDeposit` および `previewMint` は`revert`させる必要があります。
これらのメソッドは、即時処理の前提で動作するため、非同期フローとは整合性が取れないためです。

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

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

まず、`redeem` および `withdraw` メソッドがシェアをVaultへ送金しない** ようになります。これは、すでに `requestRedeem` の段階でシェアの送付が完了しているためです。
従来の**ERC4626**では、`redeem` を実行すると同時にVaultにシェアが送られますが、**ERC7540**では、シェアの送付と資産の払い戻しが分離されます。

さらに、`redeem` および `withdraw` の `owner` 引数は `controller` に名称変更され、デフォルトでは `msg.sender` をコントローラーとして扱います。
ただし、コントローラーが `msg.sender` 以外のアカウントをオペレーターとして承認している場合、そのオペレーターもリクエストを管理できます。

また、`previewRedeem` および `previewWithdraw` は`revert`させる必要があります。
これらのメソッドは、即時処理を前提としており、非同期フローでは適用できないためです。

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

リクエストは、Pending（ペンディング）、Claimable（クレーム可能）、Claimed（クレーム済み）の3つの段階を経て完了します。
これは、Vault（資産管理コントラクト）が非同期的な入金や償還をどのように処理するかを示しています。
具体的に、入金のリクエスト（`requestDeposit`）の流れを例に説明します。

まず、ユーザーが `requestDeposit` を実行するとPending状態になります。
このとき、Vaultは`asset.transferFrom(owner, vault, assets)` を実行し、資産を所有者からVaultへ送金します。
そして、Vault内部では `pendingDepositRequest[controller] += assets` として記録されます。
この状態では、ユーザーはまだVaultのシェアを受け取っておらず、単に入金をリクエストした段階です。

次に、Vault内部でリクエストが処理されるとClaimable状態へ移行します。
Vaultは `pendingDepositRequest[controller] -= assets` とし、代わりに `claimableDepositRequest[controller] += assets` に資産を移動します。
これにより、ユーザーは `deposit` 関数を呼び出すことで、シェアを受け取る準備が整います。

最後に、ユーザーが `deposit(assets, receiver, controller)` を実行するとClaimed状態になります。
この時点で、Vaultは `claimableDepositRequest[controller] -= assets` とし、`vault.balanceOf[receiver] += shares` を実行して、ユーザーにシェアを付与します。
これにより、リクエストは完全に完了し、Vaultの資産とシェアの記録も更新されます。

このフローでは、`maxDeposit`（最大入金可能額）は `claimableDepositRequest` の増減に応じて変動する仕組みになっています。
つまり、Claimable状態のリクエストが増えれば、Vaultは新たな入金を受け付けにくくなり、逆にClaimable状態のリクエストが処理されれば、入金可能な容量が増える仕組みになっています。

### クレーム処理のルール

リクエストの処理において、重要なルールとして**Claimable状態をスキップすることは許可されていません**。すなわち、リクエストを作成するだけでは資産やシェアを受け取ることはできず、必ずユーザーが `requestDeposit` や `requestRedeem` を実行した後に、対応する `deposit` または `redeem` などのClaim関数を別途呼び出さなければなりません。これは、Vault側がリクエストを処理した後に、一方的にユーザーへ資産を送る（プッシュする）のではなく、**ユーザーが明示的に請求（プルする）する方式を採用している** ためです。

また、非同期Vaultでは、**シェアと資産の交換レート（手数料や利回りを含む）はVaultの実装に委ねられています**。そのため、償還リクエストをしたとしても、その間に発生する利回りの影響を受けない可能性があり、また固定の交換レートが保証されるわけではありません。つまり、リクエストを行った時点の資産価値と、最終的にClaim関数を実行して受け取る際の資産価値が異なることがあり得ます。

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

各リクエストは、一意の識別子（`requestId`）を持ちます
`requestDeposit` や `requestRedeem` を実行すると、それに対応する `requestId` が返されます。
ただし、同じ`requestId`を持つ複数のリクエストが存在する場合があり、リクエストの特定には `requestId` に加えて `controller`（リクエストの所有者）の情報が必要です。

同じ `requestId` を持つリクエストは全て一斉に Pending から Claimable に移行し、同じ交換レートで処理されます。例えば、リクエストが部分的にClaimableになった場合、同じ `requestId` を持つすべてのリクエストが同じ割合でClaimableになる必要があります。
これは、Vaultの資産管理が一貫したレートで行われるようにするための設計です。

異なる `requestId` を持つリクエストではこの同期性は不要であり、Vaultの処理スケジュールに応じて異なるタイミングや交換レートでClaimable状態に移行することもあります。

特例として、`requestId == 0` の場合、Vaultは `controller` の情報のみを基準にリクエストを管理しなければなりません。
この場合、同じ `controller` から送信された複数のリクエストは、それぞれ個別に管理されるのではなく、まとめてPendingおよびClaimableの状態を管理する必要があります。
つまり、同じ `controller` に対するリクエストの状態は一括して扱われ、1つがClaimableになると、同じ `controller` に紐づく他のリクエストも同時にClaimableになります。

また、Vaultがあるリクエストに `requestId == 0` を割り当てた場合、そのVault内のすべてのリクエストも `requestId == 0` でなければいけません。
これは、`requestId == 0` のVaultが `controller` のみを識別情報として使用するため、一部のリクエストだけ `requestId` を持たせるとVaultの管理が一貫しなくなるからです。

## 関数

### `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` は、あるコントラクトが特定のインターフェースをサポートしているかどうかを判別するための関数です。

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

https://qiita.com/cardene/items/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` などの非同期償還処理が適用されていることを保証します。  


### ER-7575のサポート

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

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

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

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

https://qiita.com/cardene/items/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の設計に委ねられています。

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

https://chaldene.net/erc20

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

https://chaldene.net/erc721

:::note info
**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