はじめに
『DApps開発入門』という本や色々記事を書いているかるでねです。
今回は、ERC4626を拡張して入金と償還のリクエストと処理を分離する仕組みを提案しているERC7540についてまとめていきます!
以下にまとめられているものを翻訳・要約・補足しながらまとめていきます。
他にも様々なEIPについてまとめています。
概要
ERC7540は、ERC4626を拡張し、非同期的な入金(Deposit)および償還(Redemption) をサポートする規格です。
非同期的な処理は「リクエスト」と呼ばれ、新しいメソッドを追加することで実行できます。
これにより、入金や償還をリクエストとして送信し、そのリクエストの状態を確認できるようになります。
既存のERC4626のメソッド(deposit
、mint
、withdraw
、redeem
)は、その時点で処理可能なリクエストを実行するために使用されます。
ERC4626については以下の記事を参考にしてください。
動機
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
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
)。
- 資産をVaultへ送金するアドレス(通常は
戻り値
-
requestId
- リクエストを識別するためのID。
pendingDepositRequest
function pendingDepositRequest(uint256 requestId, address controller) view returns (uint256 assets)
概要
指定されたリクエストIDと controller
に対応するPending状態の資産量を返す関数。
詳細
requestDeposit
を実行した際に Pending
状態となっている資産の総量を取得します。
この値には、Claimable状態に移行した資産は含まれません。
呼び出し元(caller
)によって値が変動することはなく、整数オーバーフローが発生しない限りrevert
しません。。
引数
-
requestId
- 確認するリクエストのID。
-
controller
- リクエストを管理するアドレス。
戻り値
-
assets
-
Pending
状態の資産量。
-
claimableDepositRequest
function claimableDepositRequest(uint256 requestId, address controller) view returns (uint256 assets)
概要
指定されたリクエストIDと controller
に対応するClaimable状態の資産量を返す関数。
詳細
requestDeposit
によりPending状態だった資産がClaimable状態へ移行した時にその総量を取得します。
この値にはPending状態の資産は含まれません。
呼び出し元による変動はなく、整数オーバーフローが発生しない限りrevert
しません。
引数
-
requestId
- 確認するリクエストのID。
-
controller
- リクエストを管理するアドレス。
戻り値
-
assets
-
Claimable
状態の資産量。
-
requestRedeem
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
)。
- シェアをVaultへ送付するアドレス(通常は
戻り値
-
requestId
- リクエストを識別するためのID。
pendingRedeemRequest
function pendingRedeemRequest(uint256 requestId, address controller) view returns (uint256 shares)
概要
指定されたリクエストIDと controller
に対応するPending状態のシェア量を返す関数。
詳細
requestRedeem
を実行した時に Pending
状態となっているシェアの総量を取得します。
この値には、Claimable状態に移行したシェアは含まれません。
呼び出し元(caller
)によって値が変動することはなく、整数オーバーフローが発生しない限りrevert
しません。
引数
-
requestId
- 確認するリクエストのID。
-
controller
- リクエストを管理するアドレス。
戻り値
-
shares
-
Pending
状態のシェア量。
-
claimableRedeemRequest
function claimableRedeemRequest(uint256 requestId, address controller) view returns (uint256 shares)
概要
指定されたリクエストIDと controller
に対応するClaimable状態のシェア量を返す関数。
詳細
requestRedeem
により Pending
状態だったシェアがClaimable状態へ移行した時にその総量を取得します。
この値にはPending状態のシェアは含まれません。
呼び出し元による変動はなく、整数オーバーフローが発生しない限りrevert
しません。
引数
-
requestId
- 確認するリクエストのID。
-
controller
- リクエストを管理するアドレス。
戻り値
-
shares
-
Claimable
状態のシェア量。
-
isOperator
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
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
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
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
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
)。
- Vault に資産を送金したアドレス(通常は
-
requestId
- この入金リクエストを識別する一意のID。
-
sender
-
requestDeposit
を実行したアドレス(オペレーターの場合もある)。
-
-
assets
- リクエストされた資産の数量。
RedeemRequest
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
)。
- Vault にシェアを送付したアドレス(通常は
-
requestId
- この償還リクエストを識別する一意のID。
-
sender
-
requestRedeem
を実行したアドレス(オペレーターの場合もある)。
-
-
shares
- リクエストされたシェアの数量。
OperatorSet
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
は、あるコントラクトが特定のインターフェースをサポートしているかどうかを判別するための関数です。
ERC165については以下の記事を参考にしてください。
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の機能がより広範なエコシステムと統合しやすくなり、異なるプロトコル間での相互運用性が向上します。
ERC7575については以下の記事を参考にしてください。
補足
リクエスト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の設計に委ねられています。
ERC20については以下の記事を参考にしてください。
ERC20については以下の記事を参考にしてください。
ERC20については以下の記事を参考にしてください。
クレームの即時処理を許可しない理由
クレーム処理を即時実行すると以下の問題が発生します。
- スケーラビリティの問題
ユーザー数が増加すると、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
の仕様はそれぞれ異なります。
参考実装
// 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などからお気軽に質問してください!
他の媒体でも情報発信しているのでぜひ他も見ていってください!