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

> この規格により、ERC20トークンを元にVault（保管庫）とをトークン化する標準APIの実装が可能となります。

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

## はじめに

初めまして。
**CryptoGames**というブロックチェーンゲーム企業でエンジニアをしている **cardene（かるでね）** です！
スマートコントラクトを書いたり、フロントエンド・バックエンド・インフラと幅広く触れています。

https://cryptogames.co.jp/

代表的なゲームは**クリプトスペルズ**というブロックチェーンゲームです。

https://cryptospells.jp/

今回は、あるトークンを保有していることで報酬を受け取れる統一された実装を提案している**ERC4626**についてまとめていきます！

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

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

## 概要
この規格により、**ERC20**トークンを元に`Vault`（保管庫）とをトークン化する標準APIの実装が可能となります。

:::message
`Vault`とは、仮想通貨やDeFiにおいて、資産を保管・管理し、特定の機能やサービスを提供する仕組みやプラットフォームのことです。
以下のような目的で使用されます。
- 資産の保管と管理。
- 貸出市場
- 収益を生み出す
- トークン化された資産の取引
:::
この規格は、トークン化された`Vault`に対して基本的な機能を提供し、トークンの預入と引き出し、残高の読み取りを行うことができます。

## 動機
トークン化された`Vault`には標準化された実装がなく、さまざまな実装が存在します。
これには、貸出市場、アグリゲーター、イールドトークンなどが含まれます。

:::message
貸出市場（Lending Market）とは、借り手と貸し手が資金を提供し、それに対して利息を得る取引が行われる市場のことです。
借り手が資金を必要とする場合や、資産を預けて利息を得たい個人やプロトコルが貸し手として参加することができます。
これにより、資金を活用することができる一方、貸し手は預けた資産に対して利息収入を得ることができる仕組みになっています。
:::

:::message
アグリゲーターとは、複数の異なるサービスや情報を一箇所に集約し、統合して提供するプラットフォームやサービスのことです。
異なる取引所の価格情報をまとめて表示するなど、異なるプロトコルやサービスを一元化して利用者に提供します。
:::

:::message
イールドトークンとは、保有者に対して所有期間中に自動的に利息を生み出す仮想通貨トークンのことです。
通常のトークンや仮想通貨は、保有者に対して利息を支払うことはありません。
しかし、イールドトークンの場合保有者がトークンを保持しているだけで、定期的な間隔で利息がトークンの残高に追加される仕組みになっています。
ユーザーが特定のトークンを預けると、その預入量に応じた利息を定期的に受け取ることができます。
:::

これにより、多くの標準に適合する必要があるプロトコルのアグリデーターやプラグイン層での統合が困難になり、各プロトコルが独自のアダプタを実装する必要が生じ、エラーの発生につながることで開発リソースが浪費されることになります。

トークン化された`Vault`のための標準は、利回りを生む`Vault`に対する統合を簡単にし、より一貫性のある堅牢な実装パターンを作り出します。

## 仕様
すべての**ERC4626**のトークン化された`Vault`は、**ERC20**を実装し、シェアを表現するために使用します。Vaultが非譲渡可能である場合、transferまたはtransferFromの呼び出しでリバート（失敗）する場合があります。EIP-20の操作であるbalanceOf、transfer、totalSupplyなどは、Vaultの「シェア」に対して動作し、Vaultの基礎となる保有物の一部を所有する権利を表します。

すべてのEIP-4626トークン化されたVaultは、EIP-20のオプションのメタデータ拡張を実装する必要があります。nameとsymbol関数は、基礎となるトークンの名前とシンボルをある程度反映するべきです。

EIP-4626トークン化されたVaultは、さまざまな統合でシェアを承認するユーザーエクスペリエンスを向上させるために、EIP-2612を実装することができます。

### 用語の定義：

#### asset

`Vault`が管理するトークン。
BitcoinやEthereumなどを指します。
対応する**ERC20**規格のコントラクトで定義されます。

#### share
`Vault`をもとに発行されるトークン。
ユーザーが預け入れた`asset`と同じ量のトークンを発行します。
`Vault`の所有権を示し、mint（発行）/預入/引き出し/償還（redeem）に使用できます。
通常、アセットの量に応じて増減することがあり、`Vault`が運用によって利益を生み出すとその利益が`share`保有者に分配されることがあります。

#### fee
ユーザーが`Vault`に対して支払う資産、または`share`の量などの手数料を指します。
手数料は、預入、利回り（yield）、管理資産（AUM）、引き出し、または`Vault`によって指定される他の何かのために存在します。
例えば、ユーザーが`Vault`に資産を預ける際に手数料を支払う必要がある場合や、`Vault`から資産を引き出す際に手数料を支払う必要がある場合があります。

#### slippage
取引を行った際に、実際の取引価格と予想していた価格との間に生じる差異のことです。
特に、大きな取引量を行う場合や市場の流動性が低い場合に影響が出やすい現象です。

例えば、あるトークンを購入するために市場で注文を出したとします。
予想していた価格は1トークンあたり100ドルでしたが、実際に取引が成立した時点での価格は105ドルになっていたとします。
この場合、5ドルの価格差が生じ、この5ドルがスリッページとなります。

## 関数

### asset

```yaml
- name: asset
  type: function
  stateMutability: view

  inputs: []

  outputs:
    - name: assetTokenAddress
      type: address
```

`Valut`で使用される元となるトークンのアドレスを取得する関数。
`Valut`はトークン量の確認、預入、引き出しの際に元となるトークンを使用します。
Read関数であり、戻り値として「`assetTokenAddress`」というアドレス型の変数が返されます。
このアドレスは、`Valut`で使用されている**ERC20**トークンコントラクトのアドレスを指します。

### totalAssets

```yaml
- name: totalAssets
  type: function
  stateMutability: view

  inputs: []

  outputs:
    - name: totalManagedAssets
      type: uint256
```

`Vault`が「管理」している`asset`の総量を取得する関数。
総量には利回りによる複利の成長も含まれます。
また、`Vault`によって`asset`に対して課金される手数料も含まれます。
Read関数であり、出力として「`totalManagedAssets`」という符号なし整数型（uint256）の変数が返されます。
この値は、`Vault`が現在「管理」している`asset`の総量を表します。

### convertToShares

```yaml
- name: convertToShares
  type: function
  stateMutability: view

  inputs:
    - name: assets
      type: uint256

  outputs:
    - name: shares
      type: uint256
```

引数に渡された`asset`に対して、`Vault`がどれだけの`share`と交換できるか計算する関数。

関数の実装にあたり、以下の条件があります。
- 計算において手数料などの費用は含まれない。
- 誰が呼び出しても同じ結果が得られる。
- 実際の交換時には、スリッページが起きない。
- 計算結果の小数点は切り捨てられる。
- 計算は「**ユーザーごと**」の価格（価格ごとのシェア数）を反映するものではなく、「**平均ユーザー**」価格（平均的なユーザーが交換時に期待できる価格）を反映する必要があります。


### convertToAssets

```yaml
- name: convertToAssets
  type: function
  stateMutability: view

  inputs:
    - name: shares
      type: uint256

  outputs:
    - name: assets
      type: uint256
```

引数に渡された`share`に対して、`Vault`がどれだけの`asset`と交換できるか計算する関数。

関数の実装にあたり、以下の条件があります。
- 計算において手数料などの費用は含まれない。
- 誰が呼び出しても同じ結果が得られる。
- 実際の交換時には、スリッページが起きない。
- 計算結果の小数点は切り捨てられる。
- 計算は「**ユーザーごと**」の価格（価格ごとのシェア数）を反映するものではなく、「**平均ユーザー**」価格（平均的なユーザーが交換時に期待できる価格）を反映する必要があります。

### maxDeposit

```yaml
- name: maxDeposit
  type: function
  stateMutability: view

  inputs:
    - name: receiver
      type: address

  outputs:
    - name: maxAssets
      type: uint256
```

引数で指定された受取アドレス（`receiver`）が`Vault`に預けることのできる、最大の`asset`量を計算する関数。
関数の実装にあたり、以下の条件があります。
- 計算は、受取アドレスが無限の`asset`を持っていると仮定して行われ、実際の預入制限よりも低い値を返す。
- 預入が完全に無効になっている場合（一時的にでも）は、`0`を返す。
- 無制限の預入が許可されている場合は、`2 ** 256 - 1`を返す。

### previewDeposit

```yaml
- name: previewDeposit
  type: function
  stateMutability: view

  inputs:
    - name: assets
      type: uint256

  outputs:
    - name: shares
      type: uint256
```

オンチェーンまたはオフチェーンのユーザーが現在のオンチェーンの状況で預入をシミュレートする関数。
関数の実装にあたり、以下の条件があります。
- 同じトランザクション内で`deposit`関数を呼び出した場合と同じか、それ以上の`share`を返す。
- `maxDeposit`関数の制限などを考慮しない。
- 常に預入が受け入れられると仮定。
- 預入手数料も考慮する必要があるが、その他の`Vault`の制限によりリバートする可能性がある。

### deposit

```yaml
- name: deposit
  type: function
  stateMutability: nonpayable

  inputs:
    - name: assets
      type: uint256
    - name: receiver
      type: address

  outputs:
    - name: shares
      type: uint256
```

指定された受取アドレス（`receiver`）に対して、引数で指定した量の`asset`に対応する`Vault`の`share`を発行する関数。
関数の実装にあたり、以下の条件があります。
- **ERC20**の`approve` / `transferFrom`をサポートし、受け取った`asset`トークンを預けるフローをサポートする。
- すべての`asset`を預けられない場合（預入制限に達した、スリッページ、ユーザーが十分なトークンを承認していないなど）、`revert`する。
- 実装によっては`Vault`の`asset`に対して事前承認（`approve`）が必要。

### maxMint

```yaml
- name: maxMint
  type: function
  stateMutability: view

  inputs:
    - name: receiver
      type: address

  outputs:
    - name: maxShares
      type: uint256
```

引数で渡された受取アドレス（`receiver`）をもとに、`Vault`から新たに発行できる最大の`share`量を計算する関数。
関数の実装にあたり、以下の条件があります。

- 計算は、受取アドレスが無限の`asset`を持っていると仮定して行われ、実際の預入制限よりも低い値を返す。
- 預入が完全に無効になっている場合（一時的にでも）は、`0`を返す。
- 無制限の預入が許可されている場合は、`2 ** 256 - 1`を返す。


### previewMint

```yaml
- name: previewMint
  type: function
  stateMutability: view

  inputs:
    - name: shares
      type: uint256

  outputs:
    - name: assets
      type: uint256
```

オンチェーンまたはオフチェーンのユーザーが現在のオンチェーンの状況で発行をシミュレートするための関数。

関数の実装にあたり、以下の条件があります。
- 同じトランザクション内で`mint`関数を呼び出した場合と同じか、それ以下の`asset`量を返す。
- `maxMint`関数の制限などを考慮しない。
- 常に発行が受け入れられると仮定。
- 預入手数料も考慮する必要があるが、その他の`Vault`の制限によりリバートする可能性がある。

### mint

```yaml
- name: mint
  type: function
  stateMutability: nonpayable

  inputs:
    - name: shares
      type: uint256
    - name: receiver
      type: address

  outputs:
    - name: assets
      type: uint256
```

引数で渡された受取アドレス（`receiver`）に対して、指定した量の`share`を新たに発行する関数。

関数の実装にあたり、以下の条件があります。
- **ERC20**の`approve` / `transferFrom`をサポートし、受け取った`asset`トークンを預けるフローをサポートする。
- すべての`asset`を預けられない場合（預入制限に達した、スリッページ、ユーザーが十分なトークンを承認していないなど）、`revert`する。
- 実装によっては`Vault`の`asset`に対して事前承認（`approve`）が必要。

### maxWithdraw

```yaml
- name: maxWithdraw
  type: function
  stateMutability: view

  inputs:
    - name: owner
      type: address

  outputs:
    - name: maxAssets
      type: uint256
```

引数に渡されたオーナー（`owner`）が`Vault`の保有量から引き出すことのできる最大の`asset`量を計算する関数。

関数の実装にあたり、以下の条件があります。

- 計算は、実際の引き出し制限よりも低い値を返す。
- 引き出しが完全に無効になっている場合（一時的にでも）は、`0`を返す。

### previewWithdraw

```yaml
- name: previewWithdraw
  type: function
  stateMutability: view

  inputs:
    - name: assets
      type: uint256

  outputs:
    - name: shares
      type: uint256
```

オンチェーンまたはオフチェーンのユーザーが、現在のオンチェーンの状況で引き出しをシミュレートする関数。

関数の実装にあたり、以下の条件があります。
- 同じトランザクション内で`withdraw`関数を呼び出した場合と同じか、それ以下の`share`量を返す。
- `maxWithdraw`関数の制限などを考慮しない。
- 常に引き出しが受け入れられると仮定。
- 引き出し手数料も考慮する必要があるが、その他の`Vault`の制限によりリバートする可能性がある。

### withdraw

```yaml
- name: withdraw
  type: function
  stateMutability: nonpayable

  inputs:
    - name: assets
      type: uint256
    - name: receiver
      type: address
    - name: owner
      type: address

  outputs:
    - name: shares
      type: uint256
```

引数に渡されたオーナー（`owner`）から`share`をバーン（破棄）し、指定した量の`aseet`トークンを受取アドレス（`receiver`）に送信する関数。

関数の実装にあたり、以下の条件があります。
- `Withdraw`イベントを発行する。
- 2つの引き出しフローをサポート。
    - 1つはオーナーが`msg.sender`である場合に`share`を直接燃やすフロー。
    - もう1つは、`msg.sender`が`owner`の`share`に対して**ERC20**承認を持つ場合に`share`を直接燃やすフロー。
- 通常、実装によっては`Vault`に対して事前承認（`approve`）が必要。

### maxRedeem

```yaml
- name: maxRedeem
  type: function
  stateMutability: view

  inputs:
    - name: owner
      type: address

  outputs:
    - name: maxShares
      type: uint256
```

引数に渡されたオーナー（`owner`）が`Vault`の保有量から、取り戻すことのできる最大の`share`量を計算する関数。
関数の実装にあたり、以下の条件があります。
- 計算は、実際の取り戻し制限よりも低い値を返す。
- 取り戻しが完全に無効になっている場合（一時的にでも）は、`0`を返す。

### previewRedeem

```yaml
- name: previewRedeem
  type: function
  stateMutability: view

  inputs:
    - name: shares
      type: uint256

  outputs:
    - name: assets
      type: uint256
```

オンチェーンまたはオフチェーンのユーザーが、現在のオンチェーンの状況で取り戻しをシミュレートする関数。
関数の実装にあたり、以下の条件があります。
- 同じトランザクション内で`redeem`関数を呼び出した場合と同じか、それ以下の`asset`量を返す。
- `maxRedeem`関数の制限などを考慮しない。
- 常に取り戻しが受け入れられると仮定。
- 引き出し手数料も考慮する必要があるが、その他の`Vault`の制限によりリバートする可能性がある。

### redeem

```yaml
- name: redeem
  type: function
  stateMutability: nonpayable

  inputs:
    - name: shares
      type: uint256
    - name: receiver
      type: address
    - name: owner
      type: address

  outputs:
    - name: assets
      type: uint256
```

引数に渡されたオーナー（`owner`）から`share`を取り戻し、指定した量の`asset`トークンを受取人（`receiver`）に送信する関数。
関数の実装にあたり、以下の条件があります。
- `Withdraw`イベントを発行する。
- 2つの引き出しフローをサポート。
    - 1つはオーナーが`msg.sender`である場合に`share`を直接取り戻すフロー。
    - もう1つは、`msg.sender`が`owner`の`share`に対して**ERC20**承認を持つ場合に`share`を直接取り戻すフロー。
- 通常、実装によっては`Vault`に対して事前承認（`approve`）が必要。


## Event

### Deposit

```yaml
- name: Deposit
  type: event

  inputs:
    - name: sender
      indexed: true
      type: address
    - name: owner
      indexed: true
      type: address
    - name: assets
      indexed: false
      type: uint256
    - name: shares
      indexed: false
      type: uint256
```

トークンが`mint`関数や`deposit`関数を通じて`Vault`に預けられたときに発行されるイベント。
また、預けられた`aseet`が`share`に交換され、それらの`share`が`owner`に転送されたときに発行される。

- `sender`
    - 預けたユーザー（送信者）のアドレス。
- `owner`
    - `Vault`のオーナーのアドレス。
- `assets`
    - 預けられた`asset`の量。
- `shares`
    - 交換された`share`の量。

### Withdraw

```yaml
- name: Withdraw
  type: event

  inputs:
    - name: sender
      indexed: true
      type: address
    - name: receiver
      indexed: true
      type: address
    - name: owner
      indexed: true
      type: address
    - name: assets
      indexed: false
      type: uint256
    - name: shares
      indexed: false
      type: uint256
```

`redeem`関数や`withdraw`関数を通じて、`Vault`から`share`が引き出されたときに発行されるイベント。
また、引き出された`share`は`owner`が所有しているため、その`share`が`asset`に交換され受取アドレスに転送されたときに発行される。

- `sender`
    - 引き出したユーザー（送信者）のアドレス。
- `receiver`
    - `asset`を受け取ったユーザー（受取人）のアドレス。
- `owner`
    - `share`の`owner`のアドレス。
- `assets`
    - 引き出された`asset`の量。
- `shares`
    - 交換された`share`の量。

## 補足

### インテグレーター向け最適化
`Vault`インターフェースは、インテグレーター（システムやアプリケーションを統合する役割）を最適化するように設計されています。
つまり、`Vault`を効果的に活用するための機能は完備している一方で、詳細な仕組みなどは意図的に指定されていません。
これにより、`Vault`はオンチェーン上ではブラックボックスとして扱われ、使用前にオフチェーンで検討されることが期待されています。

### ERC20の適用
**ERC20**とは、Ethereumトークンの標準的なインターフェース仕様を指すもので、トークンの承認や残高の計算などの実装詳細が`Vault`の`share`の計算に直接反映されることから、**ERC20**が適用されています。
これにより、`Vault`は**ERC20**を利用するすべてのユースケースと互換性があります。

### mint関数の追加

`mint`関数は対称性と機能の完全性のために含まれています。
多くの現行の`share`べースの`Vault`のユースケースでは、ユーザーが特定の数の`share`（`mint`）を最適化するのではなく、特定の量の`asset`トークン（`deposit`）を最適化する傾向があります。
しかし、将来的には独自の有用な`share`表現を持つ`Vault`戦略が考えられるため、`mint`関数が追加されています。

### convertTo関数の利用
`convertTo`関数は、手数料などの操作に関連する詳細を考慮せずにおおよその見積もりを提供します。
これは、`share`や`asset`の平均値を必要とするフロントエンドやアプリケーションに役立ちますが、スリッページやその他の手数料を含む正確な値を必要とする場合には、それに対応するプレビュー関数が用意されています。
これらの関数は、`deposit`や`withdraw`の制限を考慮しないようにするため、簡単に組み合わせることができるように、`max`関数も提供されています。


## 互換性`

**ERC4626**は、**ERC20*標準と完全に互換性があり、他の標準との既知の互換性の問題はありません。
**ERC4626**を使用しない`Vault`のプロダクション実装の場合、ラッパーアダプターを開発して使用することができます。

## 参考実装

[Solmate EIP-4626](https://github.com/transmissions11/solmate/blob/main/src/mixins/ERC4626.sol)や[Vyper EIP-4626](https://github.com/fubuloubu/ERC4626)などで参考実装が提供されています。
これらは標準の最小限の実装であり、開発者が独自のロジックを簡単に挿入できるフックが用意されています。

## セキュリティ考慮事項:

完全に許可された使用事例では、インターフェースに準拠しているが仕様に準拠していない悪意のある実装に引っかかる可能性があります。
`Vault`のユーザーが預けれた資産を失う可能性のある実装をレビューすることをすべてのインテグレーターにお勧めします。
EOAアカウントのアクセスを直接サポートする場合、正確な出力量が得られない場合にトランザクションをリバートする他の手段がないため、スリッページ損失や予期しない`deposit`や`withdrawLimit`に対応するための関数呼び出しを追加することを検討する必要があります。

## 引用

Joey Santoro (@joeysantoro), t11s (@transmissions11), Jet Jadeja (@JetJadeja), Alberto Cuesta Cañada (@alcueca), Señor Doggo (@fubuloubu), "ERC-4626: Tokenized Vaults," Ethereum Improvement Proposals, no. 4626, December 2021. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-4626.

## 最後に

今回は「あるトークンを保有していることで報酬を受け取れる統一された実装を提案している**ERC4626**」についてまとめてきました！
いかがだったでしょうか？

実装については今後追記していきます。

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

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

## 採用強化中！

CryptoGamesでは一緒に働く仲間を大募集中です。

https://www.wantedly.com/companies/cryptogames

この記事で書いた自分の経験からもわかるように、裁量権を持って働くことができて一気に成長できる環境です。
「ブロックチェーンやWeb3、NFTに興味がある」、「スマートコントラクトの開発に携わりたい」など、少しでも興味を持っている方はまずはお話ししましょう！
