[ERC7565] ロックした資産を表すNFTを担保にDeFiで資金を借りることができる仕組みを理解しよう！

# はじめに

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

https://cryptogames.co.jp/

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

https://cryptospells.jp/

今回は、ロックした資産を表すNFTを担保として、流動性の供給を行いながらDeFiで資金を借りることができる仕組みを提案している**ERC7565**についてまとめていきます！

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

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

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

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

# 概要

この規格では、ロックされた資産を表すNFTを担保として資金を借りることができる仕組みを提案しています。
このNFTは、設定された満期後に預けた資産と得られた利益を受け取ることができます。

# 動機

急速に発展するDeFiの世界では、資産をロックすることで利子や投票権などの利益を得る仕組みが導入されています。
しかし、資産のロックがロックされている間の流動性維持が課題です。
この提案では、**ERC721**と**ERC4907**を使用してロックされた資産から利益を生み出す方法を提案して、この課題に対処します。

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

https://chaldene.net/erc721

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

https://qiita.com/cardene/items/253672c18241edcc57eb

自動マーケットメーカー（AMM）を実行するDeFiサービスでは、流動性プロバイダーはプールに資産を提供してその持分を表すNFTを受け取っています。
これらのNFTは資産に対する権利と利益を示しますが、同時にロックされた資産は一時的に利用できなくなるため、緊急時に資産を引き出す必要が生じると、プール全体の流動性が低下してスリッページ（価格変動リスク）が増大する可能性があります。

このERCでは、これらの課題に対処するため、ロックされた資産を表すNFT（非代替性トークン）を担保として使用する方法を提案しています。
このアプローチにより、流動性提供者は資産を引き出さずに一時的な流動性を確保できてプールの流動性が維持されます。
また、流動性提供だけでなく貸付や取引など資産ロックを行う他のDeFiサービスにも適用可能です。
これにより、幅広いDeFiサービスにおいて、資産ロック中の流動性維持が可能となります。

さらに、このERCは「**永久契約NFT**」という新しいNFTの形態を導入しています。
仮想通貨デリバティブ市場の永久契約（perpetual contract）のアイデアを利用したもので、NFTh永久契約の権利と担保を表し、DeFiコンポーザビリティの担保として活用できます。
永久的契約NFTは、DeFiアプリケーションにおいて資産ロックの利益を保持しつつ流動性を提供することで大きな優位性をもたらします。

# 仕様

## インターフェース

```js
interface IPerpetualContractNFT {

    // Emitted when an NFT is collateralized for obtaining a loan
    event Collateralized(uint256 indexed tokenId, address indexed owner, uint256 loanAmount, uint256 interestRate, uint256 loanDuration);

    // Emitted when a loan secured by an NFT is fully repaid, releasing the NFT from collateral
    event LoanRepaid(uint256 indexed tokenId, address indexed owner);

    // Emitted when a loan defaults, resulting in the transfer of the NFT to the lender
    event Defaulted(uint256 indexed tokenId, address indexed lender);

    // Enables an NFT owner to collateralize their NFT in exchange for a loan
    // @param tokenId The NFT to be used as collateral
    // @param loanAmount The amount of funds to be borrowed
    // @param interestRate The interest rate for the loan
    // @param loanDuration The duration of the loan
    function collateralize(uint256 tokenId, uint256 loanAmount, uint256 interestRate, uint64 loanDuration) external;

    // Enables a borrower to repay their loan and regain ownership of the collateralized NFT
    // @param tokenId The NFT that was used as collateral
    // @param repayAmount The amount of funds to be repaid
    function repayLoan(uint256 tokenId, uint256 repayAmount) external;

    // Allows querying the loan terms for a given NFT
    // @param tokenId The NFT used as collateral
    // @return loanAmount The amount of funds borrowed
    // @return interestRate The interest rate for the loan
    // @return loanDuration The duration of the loan
    // @return loanDueDate The due date for the loan repayment
    function getLoanTerms(uint256 tokenId) external view returns (uint256 loanAmount, uint256 interestRate, uint256 loanDuration, uint256 loanDueDate);

    // Allows querying the current owner of the NFT
    // @param tokenId The NFT in question
    // @return The address of the current owner
    function currentOwner(uint256 tokenId) external view returns (address);

    // View the total amount required to repay the loan for a given NFT
    // @param tokenId The NFT used as collateral
    // @return The total amount required to repay the loan, including interest
    function viewRepayAmount(uint256 tokenId) external view returns (uint256);
}
```

### `Collateralized`
NFTがローンの担保として使用されたときに発行されるイベントです。
ローン金額、利率、ローン期間などの重要な詳細を記録します。

### `LoanRepaid`
ローンが返済され、NFTが担保から解放されたときに発行されるイベントです。
ローンが返済されたことを記録します。

### `Defaulted`
ローンがデフォルトし、NFTが貸し手に`transfer`されたときに発行されるイベントです。
ローンのデフォルトとNFTの移転を記録します。

:::note info
ローンのデフォルトとは、借り手がローン契約の条件を満たすことができず、規定された返済期日までに借りた資金を返済できなかった状態を指します。

- **返済期限の超過**
    - 借り手がローンの返済期限までに元本および利息を返済しなかった場合。
- **条件違反**
    - 借り手がローン契約で定められたその他の条件（例えば、追加の担保提供や一定の財務比率の維持など）を満たさなかった場合。

ローンがデフォルトすると、通常貸し手は担保として提供された資産を回収する権利を持ちます。
この場合、担保として提供されたNFTが貸し手に`transfer`され、借り手はNFTの所有権を失います。

デフォルトの例として、借り手が`10`ETHのローンを受けており、返済期限が1ヶ月後で利息が5%と設定されているとします。
借り手が期限までに`10.5`ETH（元本`10`ETH + 利息`0.5`ETH）を返済できなかった場合、ローンはデフォルトとなり、貸し手は担保として提供されたNFTを回収する権利を行使します。
:::

### `collateralize`
NFT所有者がNFTを担保にしてローンを受ける関数です。
パラメータには、担保として使用されるNFTのID（`tokenId`）、借りる資金の額（`loanAmount`）、ローンの利率（`interestRate`）、ローンの期間（`loanDuration`）が含まれます。

### `repayLoan`
借り手がローンを返済し、担保としてのNFTを取り戻す関数です。
パラメータには、担保として使用されたNFTのID（`tokenId`）と返済する資金の額（`repayAmount`）が含まれます。

### `getLoanTerms`
特定のNFTに対するローン条件を照会する関数です。
パラメータには、担保として使用されたNFTのID（`tokenId`）が含まれます。
戻り値には、借りた資金の額（`loanAmount`）、ローンの利率（`interestRate`）、ローンの期間（`loanDuration`）、ローンの返済期日（`loanDueDate`）が含まれます。

### `currentOwner`
特定のNFTの現在の所有者を照会する関数です。
パラメータには、対象のNFTのID（`tokenId`）が含まれます。
戻り値は、現在の所有者のアドレスです。

### `viewRepayAmount`
特定のNFTに対するローンの総返済額を照会する関数です。
パラメータには、担保として使用されたNFTのID（`tokenId`）が含まれます。
戻り値は、総返済額（利息を含む）です。

# 補足

## デザインの動機

この規格では、DeFi（分散型金融）分野での担保としてロックされた資産の流動性と管理についての課題を解決するために設計されています。
従来のDeFiメカニズムでは、資産保有者は貸付、ステーキング、イールドファーミングに参加するために資産をロックする必要があり、その結果、流動性を失うことになります。
この標準は、資産保有者が資産をロックしている間も流動性を一部保持できるようにすることで、DeFiの利便性と魅力を向上させることを目指しています。

## デザインの決定

ー **二重ロールシステム（資産所有者とDeFiプラットフォーム/コントラクト）**
    - NFT所有者（資産保有者）とNFTを担保として利用するDeFiプラットフォームまたはコントラクトの間に明確な区分を設けています。
    - この区分により、権利と責任の管理が簡素化されて潜在的なコンフリクト軽減されます。

- **資産ロックのメリットを損なわずに流動性を強化**
    - この標準の主要な特徴は、資産所有者がロックされた資産を表すNFTを担保としてローンを利用できるようにすることです。。
    - このアプローチにより、資産所有者はプールやステーキングプログラムから資産を引き出すことなく流動性にアクセスでき、利息や投票権などのメリットを引き続き得ることができます。

- **自動化されたローンおよび担保管理**
    - 担保つきNFTの条件の自動管理機能を統合することで、取引コストと複雑さを最小限に抑えるように設計されています。

- **DeFiコンポーザビリティ**
    - 資産ロックと担保化サービスの統合に戦略的な重点を置くことで、この標準の採用を多様なDeFiプラットフォームやサービスにおいて円滑に進めることを目指しています。
    - このアプローチにより、DeFiエコシステム内でのシームレスな接続が促進されます。

### 代替デザインおよび関連作業

- **ERC4907との比較**
    - **ERC4907**もNFTの二重ロールモデル（所有者とユーザー）を導入していますが、この標準は金融取引におけるNFTの担保化に特化しています。
    - **ERC4907**のレンタル指向のアプローチとは異なります。

- **従来の担保化方法に対する改善**
    - 従来のDeFi担保化では資産の完全なロックを必要としますが、この標準はより動的で柔軟なモデルを提案して流動性アクセスを継続的に可能にします。

# 互換性

**ERC721**と完全に互換性があり、**ERC4907**と統合してNFTをレンタルできます。

# 実装

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

//import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "./IPerpetualContractNFT.sol";
import "./ERC4907/ERC4907.sol";

contract PerpetualContractNFT is ERC4907, IPerpetualContractNFT {
    struct LoanInfo {
        address borrower;   // Address that borrowed against the NFT
        uint256 loanAmount; // Amount of funds borrowed
        uint256 interestRate; // Interest rate for the loan
        uint64 loanDuration; // Duration of the loan
        uint256 loanStartTime; // Timestamp when the loan starts
    }

    mapping(uint256 => LoanInfo) internal _loans;

    //Constructor to initialize the Perpetual Contract NFT contract with the given name and symbo
    constructor(string memory name_, string memory symbol_)
        ERC4907(name_, symbol_)
    {}

    function collateralize(uint256 tokenId, uint256 loanAmount, uint256 interestRate, uint64 loanDuration) public override {
        require(ownerOf(tokenId) == msg.sender || isApprovedForAll(ownerOf(tokenId), msg.sender) || getApproved(tokenId) == msg.sender, "Not owner nor approved");

        LoanInfo storage info = _loans[tokenId];
        info.borrower = msg.sender;
        // The loan amount should reflect the asset's value as represented by the NFT, considering an appropriate loan-to-value (LTV) ratio.
        info.loanAmount = loanAmount;
        info.interestRate = interestRate;
        info.loanDuration = loanDuration;
        info.loanStartTime = block.timestamp;

        setUser(tokenId, address(this), loanDuration);
        emit Collateralized(tokenId, msg.sender, loanAmount, interestRate, loanDuration);

        // Further logic can be implemented here to manage the lending of assets
    }

    function repayLoan(uint256 tokenId, uint256 repayAmount) public override {
        require(_loans[tokenId].borrower == msg.sender, "Not the borrower.");

        // Calculate the total amount due for repayment
        uint256 totalDue = viewRepayAmount(tokenId);

        // Check if the repayAmount is sufficient to cover at least a part of the total due amount
        require(repayAmount <= totalDue, "Repay amount exceeds total due.");

        // Calculate the remaining loan amount after repayment
        _loans[tokenId].loanAmount = totalDue - repayAmount;

        // Resets the user of the NFT to the default state if the entire loan amount is fully repaid
        if(_loans[tokenId].loanAmount == 0) {
            setUser(tokenId, address(0), 0);
        }

        emit LoanRepaid(tokenId, msg.sender);
    }


    function getLoanTerms(uint256 tokenId) public view override returns (uint256, uint256, uint256, uint256) {
        LoanInfo storage info = _loans[tokenId];
        return (info.loanAmount, info.interestRate, info.loanDuration, info.loanStartTime);
    }

    function currentOwner(uint256 tokenId) public view override returns (address) {
        return ownerOf(tokenId);
    }

    function viewRepayAmount(uint256 tokenId) public view returns (uint256) {
        if (_loans[tokenId].loanAmount == 0) {
            // If the loan amount is zero, there is nothing to repay
            return 0;
        }

        // The interest is calculated on an hourly basis, prorated based on the actual duration for which the loan was held.
        // If the borrower repays before the loan duration ends, they are charged interest only for the time the loan was held.
        // For example, if the annual interest rate is 5% and the borrower repays in half the loan term, they pay only 2.5% interest.
        uint256 elapsed = block.timestamp > (_loans[tokenId].loanStartTime + _loans[tokenId].loanDuration) 
                        ? _loans[tokenId].loanDuration  / 1 hours
                        : (block.timestamp - _loans[tokenId].loanStartTime) / 1 hours;

        // Round up
        // Example: 15/4 = 3.75
        // round((15 + 4 - 1)/4) = 4, round((15/4) = 3)
        uint256 interest = ((_loans[tokenId].loanAmount * _loans[tokenId].interestRate / 100) * elapsed + (_loans[tokenId].loanDuration / 1 hours) - 1) / 
                    (_loans[tokenId].loanDuration / 1 hours);

        // Calculate the total amount due
        uint256 totalDue = _loans[tokenId].loanAmount + interest;

        return totalDue;
    }

    // Additional functions and logic to handle loan defaults, transfers, and other aspects of the NFT lifecycle
}
```

# 引用

Hyoungsung Kim (@HyoungsungKim) <hyougnsung@keti.re.kr>, Yong-Suk Park <yspark@keti.re.kr>, Hyun-Sik Kim <hskim@keti.re.kr>, "ERC-7565: Perpetual Contract NFTs as Collateral [DRAFT]," Ethereum Improvement Proposals, no. 7565, November 2023. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-7565.

# 最後に

今回は「ロックした資産を表すNFTを担保として、流動性の供給を行いながらDeFiで資金を借りることができる仕組みを提案している**ERC7565**」についてまとめてきました！
いかがだったでしょうか？

質問などがある方は以下の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