[ERC6682] ERC3156を拡張したERC721 NFTフラッシュローンの仕組みを理解しよう！

# はじめに

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

https://cryptogames.co.jp/

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

https://cryptospells.jp/

今回は、既存のフラッシュローン標準**ERC3156**を拡張して、**ERC721** NFTのフラッシュローンをサポートする新しい方法を提案している**EIP6682**についてまとめていきます！

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

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

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

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

# 概要

**ERC3156**は、イーサリアムのスマートコントラクトの標準の一つで、フラッシュローン（短期間の貸し付けと返済を行う仕組み）に関するものです。

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

https://qiita.com/cardene/items/0158dd70155b165abf8d

この標準は主に**ERC20**トークン（イーサリアム上で最も一般的なトークンの標準）のフラッシュローンをサポートしています。

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

https://chaldene.net/erc20

しかし、**ERC721**トークン（一意性を持つ非代替トークン、NFTとしても知られている）に対するフラッシュローンの需要が高まっているため、**ERC3156**を拡張して**ERC721** NFTのフラッシュローンをサポートする新しい標準の提案がなされています。

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

https://chaldene.net/erc721

### ERC721 NFTフラッシュローンの概要

**ERC721** NFTフラッシュローンの標準は、フラッシュローンの提供者がNFTをコントラクトに貸し出す方法を提案しています。
重要な点は、このローンが同一トランザクション内で返済されなければならないという条件です。
加えて、いくらかの手数料が必要になります。

この標準の目的は、ユーザーやスマートコントラクトが一時的にNFTを借りることを可能にし、そのNFTを使って特定の操作（例えば、ゲーム内での一時的な能力向上、コレクションの一時的な完了、あるいは特定のDeFiプロトコルでの活用）を行い、トランザクションが完了する前にNFTを返却し、手数料を支払う仕組みを提供します。

### 基本的なプロセス

1. **ローンのリクエスト**
    - スマートコントラクトが特定のNFTのフラッシュローンをリクエストします。
2. **NFTの転送**
    - フラッシュローン提供者は、リクエストされたNFTをリクエスト者のコントラクトに一時的に`transfer`します。
3. **操作の実行**
    - リクエスト者はNFTを使用して、特定の操作（トレード、ゲーム内アクションなど）を実行します。
4. **ローンの返済**
    - 操作後、リクエスト者は同一トランザクション内でNFTをフラッシュローン提供者に返却します。
    - この時、約定された手数料も一緒に支払われます。
5. **トランザクションの完了**
    - 全ての条件が満たされた場合、トランザクションは成功となります。
    - もしNFTが返却されない場合や手数料が支払われない場合は、トランザクションは失敗し、全ての操作が元に戻ります（イーサリアムのアトミック性による）。

### 利点と応用

この標準の利点は、NFTの流動性と利用可能性を高めることにあります。
特に、高価なNFTを直接購入することなく、一時的に利用したいユーザーやコントラクトにとって有益です。
また、特定のアプリケーションでの短期的な利用や、複雑な金融操作、ゲーム内での特定のシナリオのテストなど、さまざまな応用が考えられます。

このような拡張は、デジタルアセットの新しい使用法を開拓し、NFTとDeFiの間の相互運用性を強化することで、イーサリアムエコシステム全体の発展に寄与することが期待されています。

# 動機

現行のフラッシュローン標準である**ERC3156**は、**ERC20**トークンのみをサポートしています。
**ERC20**トークンは「**代替可能なトークン**」として設計されており、各トークンは同じ価値と機能を持っています。
一方で、**ERC721**トークンは「**非代替トークン**」(NFT)として設計されており、それぞれが一意の価値と属性を持つため、**ERC20**トークンとは根本的に異なります。

### NFTフラッシュローンの必要性

この根本的な違いにより、**ERC721** NFTを扱うためには、既存のフラッシュローン標準を拡張する必要があります。
NFTフラッシュローンは、NFTの所有権がチェックされるあらゆるアクションにおいて有用です。

- **エアドロップの請求**
    - 特定のNFTを保持しているユーザーのみがアクセスできるエアドロップを請求する時、一時的にそのNFTを借りて請求を完了し、その後すぐに返却することができます。
- **ステーキング報酬の請求**
    - NFTをステーキングしていると報酬を得られるシステムで、NFTの所有権を一時的に借りて報酬を請求し、操作完了後に返却します。
- **インゲームアクション**
    - 特定のNFTを持っていることで可能になるゲーム内アクション（例えば、特定のリソースを収穫する）を行うために、一時的にNFTを借りることができます。

### 機能と応用

NFTフラッシュローンを利用することで、ユーザーは以下のような利点を享受できます。

- **流動性の向上**
    - 高価値のNFTを直接購入することなく、特定の目的のために一時的にアクセスすることができます。
- **新しい金融戦略の開発**
    - NFTを活用した新しい種類のDeFi戦略が可能になります。
    - 例えば、短期間での価値増加を見込んでNFTを借り、その期間内に価値を最大化する戦略などです。
- **ゲーム内戦略の拡張**
    - ゲーム内で特定のNFTが必要なアクションを実行するために、一時的にNFTを借りることで、新しい戦略やゲームプレイの機会が生まれます。

:::note info
NFTフラッシュローンの導入は、NFTの所有権を活用した様々なアクションに新しい可能性をもたらします。
これにより、NFTの流動性が向上し、デジタルアセットの利用方法が拡張されることが期待されます。
しかし、この新しい標準を実装し、安全に運用するためには、様々な技術的およびセキュリティ上の課題を解決する必要があります。
:::

# 仕様

```ts
pragma solidity ^0.8.19;

interface IERC6682 {
    /// @dev The address of the token used to pay flash loan fees.
    function flashFeeToken() external view returns (address);

    /// @dev Whether or not the NFT is available for a flash loan.
    /// @param token The address of the NFT contract.
    /// @param tokenId The ID of the NFT.
    function availableForFlashLoan(address token, uint256 tokenId) external view returns (bool);
}
```

上記のコントラクトインターフェースは、**ERC721** NFTフラッシュローンのための新しい標準の一部である**IERC6682**を定義しています。
このインターフェースを通じて、フラッシュローンの提供者はNFTの貸し出しを管理し、フラッシュローンの手数料を支払うために使用されるトークンの種類を指定できます。

### `flashFeeToken`関数

この関数は、フラッシュローンの手数料を支払うために使用されるトークンのアドレスを返します。
手数料がETHで支払われる場合は、`address(0)`を返す必要があります。
これは、イーサリアムのアドレス体系において`address(0)`がETHを指す特別な値であることを反映しています。

### `availableForFlashLoan`関数

この関数は、特定のNFT（`token`アドレスと`tokenId`によって指定）がフラッシュローンで利用可能かどうかを返します。
もし指定された`tokenId`のNFTが現在フラッシュローンで利用不可能な場合、関数は`false`を返す必要があります。
この関数は、利用不可能な場合にエラーを投げる（`revert`する）のではなく、`false`を返すように設計されています。

### IERC3156FlashLenderの実装要件

**IERC6682**を実装するコントラクトは、**IERC3156FlashLender**インターフェースも実装する必要があります。
**IERC3156**は、**ERC20**トークンのフラッシュローンを扱うための標準であり、**IERC6682**によるNFTフラッシュローンの提供は、この既存のフラッシュローン標準に基づいています。
これは、フラッシュローンの機能を一貫した方法で提供し、**ERC20**トークンと**ERC721** NFTの両方でフラッシュローンを行う際の互換性を保証するためです。

:::note info
このインターフェースの提案は、NFT市場の流動性とアクセシビリティを高めるための重要なステップです。
フラッシュローンを通じて、ユーザーは特定のNFTを一時的に借りることができ、これにより新しい種類のアプリケーションや金融戦略が可能になります。
同時に、手数料の支払い方法や利用可能性のチェックなど、実装にあたっては注意深く設計されたルールに従う必要があります。
:::

# 補足

既存のフラッシュローン標準である**ERC3156**をNFTに対応させるための拡張を考える時、できるだけシンプルな変更を加えることが重要です。
これは、**ERC3156**が既に広く採用されており、NFTをサポートするために必要な変更が少ないためです。
このアプローチにより、既存のインフラストラクチャとの互換性を維持しつつ、新しい機能を追加できます。

### 手数料の支払い

フラッシュローンにおいて、特にNFTの場合手数料の支払いは通常、貸し出されたNFTとは別の通貨で行われることが望ましいです。
NFT自体は非代替性が特徴であり、分割が不可能な場合が多いためです。
例えば、フラッシュローン提供者が各NFTフラッシュローンに対して`0.1 ETH`の手数料を請求する場合、借り手が各NFTに対する手数料率と手数料の支払い通貨を決定できるメソッドをインターフェースが提供する必要があります。

### 手数料率と支払い通貨の決定

この要件を満たすためには、インターフェースに次のような機能が必要です。

- **手数料率の取得**
    - 借り手が特定のNFTに対するフラッシュローンの手数料率を事前に知ることができるメソッド。
- **支払い通貨の指定**
    - 手数料の支払いに使用される通貨の種類を指定するメソッド。
    - これは、フラッシュローンの手数料がETHやその他の**ERC20**トークンなど、異なる形式で支払われる場合に対応するためです。

### 既存標準の拡張のメリット

**ERC3156**の拡張を選択する主なメリットは、既に広く採用されている標準に基づいているため、開発者やプロジェクトにとっての学習コストや統合コストが低く抑えられる点です。
また、**ERC3156**に準拠した既存のツールやインフラを利用して、新しいNFTフラッシュローン機能を迅速に実装し、展開することが可能になります。

:::note info
NFTをサポートするために既存のフラッシュローン標準を拡張することは、イーサリアムエコシステムにおけるNFTの利用可能性と流動性を向上させるための効果的なアプローチです。
手数料の支払い方法を含め、シンプルで理解しやすい変更を加えることで、NFT市場のさらなる成長とイノベーションを促進することが期待されます。
:::

# 互換性

提案されたEIP（イーサリアム改善提案）は、**ERC3156**（既存のフラッシュローン標準）と完全に後方互換性を持ちますが、`maxFlashLoan`メソッドに関しては例外です。
**ERC3156**は主に**ERC20**トークン（代替可能なトークン）のフラッシュローンを対象としており、`maxFlashLoan`メソッドは貸し出すことができるトークンの最大量を示すものです。
しかし、**ERC721**トークン（NFT）は非代替性を持つため、このメソッドはNFTのコンテキストでは意味を成しません。

### `maxFlashLoan`メソッドの役割

**ERC3156**で定義されている`maxFlashLoan`メソッドは、指定されたトークンの最大貸出可能量を返すために使用されます。
これは**ERC20**トークンにとっては理にかなっていますが、各NFTが固有であり、1つずつしか存在しないことを考えると、NFTには適用できません。

### NFTフラッシュローンにおける`maxFlashLoan`の取り扱い

NFTフラッシュローンをサポートするにあたり、このEIPは`maxFlashLoan`メソッドが常に`1`を返すべきであると推奨しています。
これは、フラッシュローンの呼び出し毎にフラッシュローンできるNFTが1つだけであることを反映しています。
もしコントラクトが**ERC20**トークンのフラッシュローンもサポートしている場合は、代わりに何らかの値を返すことができます。
これにより、同一のインターフェースを介して両方のタイプの資産（**ERC20**と**ERC721**）のフラッシュローンをサポートすることができます。

### 後方互換性の維持

このEIPは、既存のフラッシュローン標準（**ERC3156**）との完全な後方互換性を維持しつつ、NFTのフラッシュローンを可能にするための最小限の変更を提案しています。
`maxFlashLoan`メソッドを変更することなく、**ERC20**トークンのフラッシュローンをサポートしているコントラクトが、NFTのフラッシュローンもサポートできるようにすることは、既存のインフラストラクチャとの互換性を保ちながら新しい機能を導入する上で重要です。

:::note info
このアプローチにより、開発者はフラッシュローンの既存の標準を拡張してNFTをサポートでき、既存のフラッシュローンコントラクトやエコシステムとの互換性を保ちつつ、NFT市場の新しい可能性を開くことができます。
このように、後方互換性を維持しつつ、新しい技術的な課題に対処する方法を提供することは、ブロックチェーン技術の進化において重要な側面です。
:::

# 実装

```js
// SPDX-License-Identifier: CC0-1.0
pragma solidity ^0.8.19;

import "../interfaces/IERC20.sol";
import "../interfaces/IERC721.sol";
import "../interfaces/IERC3156FlashBorrower.sol";
import "../interfaces/IERC3156FlashLender.sol";
import "../interfaces/IERC6682.sol";

contract ExampleFlashLender is IERC6682, IERC3156FlashLender {
    uint256 internal _feePerNFT;
    address internal _flashFeeToken;

    constructor(uint256 feePerNFT_, address flashFeeToken_) {
        _feePerNFT = feePerNFT_;
        _flashFeeToken = flashFeeToken_;
    }

    function flashFeeToken() public view returns (address) {
        return _flashFeeToken;
    }

    function availableForFlashLoan(address token, uint256 tokenId) public view returns (bool) {
        // return if the NFT is owned by this contract
        try IERC721(token).ownerOf(tokenId) returns (address result) {
            return result == address(this);
        } catch {
            return false;
        }
    }

    function flashFee(address token, uint256 tokenId) public view returns (uint256) {
        return _feePerNFT;
    }

    function flashLoan(IERC3156FlashBorrower receiver, address token, uint256 tokenId, bytes calldata data)
        public
        returns (bool)
    {
        // check that the NFT is available for a flash loan
        require(availableForFlashLoan(token, tokenId), "IERC6682: NFT not available for flash loan");

        // transfer the NFT to the borrower
        IERC721(token).safeTransferFrom(address(this), address(receiver), tokenId);

        // calculate the fee
        uint256 fee = flashFee(token, tokenId);

        // call the borrower
        bool success =
            receiver.onFlashLoan(msg.sender, token, tokenId, fee, data) == keccak256("ERC3156FlashBorrower.onFlashLoan");

        // check that flashloan was successful
        require(success, "IERC6682: Flash loan failed");
        
        // check that the NFT was returned by the borrower
        require(IERC721(token).ownerOf(tokenId) == address(this), "IERC6682: NFT not returned by borrower");

        // transfer the fee from the borrower
        IERC20(flashFeeToken()).transferFrom(msg.sender, address(this), fee);

        return success;
    }

    function maxFlashLoan(address token) public pure override returns (uint256) {
        // if a contract also supports flash loans for ERC20 tokens then it can
        // return some value here instead of 1
        return 1;
    }

    function onERC721Received(address, address, uint256, bytes memory) public returns (bytes4) {
        return this.onERC721Received.selector;
    }
}
```

### `flashFeeToken`

```javascript
function flashFeeToken() public view returns (address) {
    return _flashFeeToken;
}
```

**概要**

フラッシュローンの手数料を支払うために使用されるトークンのアドレスを返す関数。

**詳細**

この関数は、フラッシュローンの手数料の支払いに使用される**ERC20**トークンのアドレスを返します。
手数料がETHで支払われる場合、この関数は`address(0)`を返すように設定されています。

**戻り値**

- `address`
    - 手数料の支払いに使用される**ERC20**トークンのアドレス。

---

### `availableForFlashLoan`

```javascript
function availableForFlashLoan(address token, uint256 tokenId) public view returns (bool) {
    // return if the NFT is owned by this contract
    try IERC721(token).ownerOf(tokenId) returns (address result) {
        return result == address(this);
    } catch {
        return false;
    }
}
```

**概要**

指定されたNFTがフラッシュローンで利用可能かどうかを確認する関数。

**詳細**

この関数は、指定された**ERC721**トークン（`token`）とトークンID（`tokenId`）がこのコントラクトによって所有されているかどうかを確認します。
所有されている場合は`true`を、そうでない場合は`false`を返します。
NFTがこのコントラクトに所有されていない場合、フラッシュローンの対象外とみなされます。

**引数**

- `token`
    - **ERC721**トークンのアドレス。
- `tokenId`
    - 確認するNFTのID。

**戻り値**

- `bool`
    - 指定されたNFTがフラッシュローンで利用可能かどうか。

---

### `flashFee`

```javascript
function flashFee(address token, uint256 tokenId) public view returns (uint256) {
    return _feePerNFT;
}
```

**概要**

指定されたNFTのフラッシュローン手数料を計算する関数。

**詳細**

この関数は、フラッシュローンの際に発生する手数料の額を返します。
手数料の額はコントラクトの内部変数`_feePerNFT`によって定義されています。

**引数**

- `token`
    - **ERC721**トークンのアドレス（この例では使用されていません）。
- `tokenId`
    - NFTのID（この例では使用されていません）。

**戻り値**

- `uint256`
    - フラッシュローンの手数料。

---

### `flashLoan`

```javascript
function flashLoan(IERC3156FlashBorrower receiver, address token, uint256 tokenId, bytes calldata data)
    public
    returns (bool)
{
    // check that the NFT is available for a flash loan
    require(availableForFlashLoan(token, tokenId), "IERC6682: NFT not available for flash loan");

    // transfer the NFT to the borrower
    IERC721(token).safeTransferFrom(address(this), address(receiver), tokenId);

    // calculate the fee
    uint256 fee = flashFee(token, tokenId);

    // call the borrower
    bool success =
        receiver.onFlashLoan(msg.sender, token, tokenId, fee, data) == keccak256("ERC3156FlashBorrower.onFlashLoan");

    // check that flashloan was successful
    require(success, "IERC6682: Flash loan failed");
    
    // check that the NFT was returned by the borrower
    require(IERC721(token).ownerOf(tokenId) == address(this), "IERC6682: NFT not returned by borrower");

    // transfer the fee from the borrower
    IERC20(flashFeeToken()).transferFrom(msg.sender, address(this), fee);

    return success;
}
```

**概要**

指定されたNFTに対してフラッシュローンを実行する関数。

**詳細**

この関数は、フラッシュローンのプロセスを実行します。
まず、指定されたNFTがフラッシュローンで利用可能かどうかを確認し、次にNFTを借り手に`transfer`します。
借り手は指定された手数料と共にNFTを返却する必要があります。
手数料は、`flashFee`関数によって計算され、`flashFeeToken`関数で指定されたトークンで支払われます。
すべての操作が成功した場合、`true`を返します。

**引数**

- `receiver`
    - **IERC3156FlashBorrower**インターフェースを実装する借り手のコントラクト。
- `token`
    - **ERC721**トークンのアドレス。
- `tokenId`
    - フラッシュローンされるNFTのID。
- `data`
    - 借り手がフラッシュローンのコールバックで使用する任意のデータ。

**戻り値**

- `bool`
    - フラッシュローンの操作が成功したかどうか。

---

### `maxFlashLoan`

```javascript
function maxFlashLoan(address token) public pure override returns (uint256) {
    return 1;
}
```

**概要**

フラッシュローンで貸し出せる最大のNFTの数を返す関数。

**詳細**

この関数は、フラッシュローンで一度に貸し出せるNFTの最大数を示します。
NFTは非代替性があるため、この関数は常に`1`を返します。

**引数**

- `token`
    - **ERC721**トークンのアドレス（この例では使用されていません）。

**戻り値**

- `uint256`
    - フラッシュローンで一度に貸し出せるNFTの最大数。

---

### `onERC721Received`

```javascript
function onERC721Received(address, address, uint256, bytes memory) public returns (bytes4) {
    return this.onERC721Received.selector;
}
```

**概要**

**ERC721**トークンがこのコントラクトに`transfer`されたときに呼び出されるフック関数。

**詳細**

この関数は、**ERC721**トークンがこのコントラクトに安全に`transfer`されたことを確認するためのものです。
**ERC721**標準に従って、トークンが`transfer`される時にはこの関数が呼び出され、正しく受け取られたことを示すために特定のセレクタを返します。

**戻り値**

- `bytes4`
    - この関数が正しく呼び出されたことを示すセレクタ。


# セキュリティ

`flashFeeToken`メソッドが悪意のあるコントラクトのアドレスを返す可能性があるため、借り手は`flashFeeToken`メソッドから返されたアドレスを使用してコールを行う時に注意が必要です。
返されたアドレスが悪意のあるコントラクトである場合、借り手はそのコントラクトによって不正な操作を受けるリスクがあります。
例えば、手数料を支払うためにトークンを送信する時、悪意のあるコントラクトはそれを悪用して借り手の資産を盗み出すことが考えられます。

この問題に対処する一つの方法は、`flashFeeToken`メソッドから返されたアドレスが、ユーザーが入力または期待しているアドレスと一致するかを検証することです。
この検証プロセスにより、借り手は自分が意図しているコントラクトとのみやり取りを保証することができ、悪意のあるコントラクトから自身を守ることができます。

### 検討が必要な点

この問題に対してはさらに以下のような議論が必要です。

- **信頼できるリストの活用**
    - フラッシュローンを提供するプラットフォーム側で、信頼できる手数料トークンのリストを管理し、`flashFeeToken`メソッドで返すアドレスがこのリストに含まれているかを検証する仕組みを導入する。
- **コントラクトの検証**
    - `flashFeeToken`メソッドから返されたコントラクトが開示しているコードをチェックし、その振る舞いが正当であることを確認する。
    - これには、ブロックチェーン上でのコントラクトの過去の取引履歴を調べることや、コミュニティでの評判を確認することが含まれます。
- **ユーザー教育**
    - ユーザーが自身でリスクを評価し、不審なアドレスに対して慎重になるよう教育する。
    - 例えば、フラッシュローンを利用する際のガイドラインや、悪意のあるコントラクトを識別するためのチェックリストを提供する。

このように、`flashFeeToken`メソッドの安全性を確保するためには、複数のアプローチを組み合わせて検討する必要があります。
個々のユーザーや開発者だけでなく、プラットフォーム全体での取り組みが求められます。

# 引用

out.eth (@outdoteth), "ERC-6682: NFT Flashloans [DRAFT]," Ethereum Improvement Proposals, no. 6682, March 2023. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-6682.

# 最後に

今回は「既存のフラッシュローン標準**ERC3156**を拡張して、**ERC721** NFTのフラッシュローンをサポートする新しい方法を提案している**EIP6682**」についてまとめてきました！
いかがだったでしょうか？

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

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

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

https://chaldene.net/

https://zenn.dev/heku

https://mirror.xyz/0xcE77b9fCd390847627c84359fC1Bc02fC78f0e58

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