Alert Source Discuss
⚠️ Draft Standards Track: ERC

ERC-7565: 将永久合约 NFT 作为抵押品

将金融资产锁定为 NFT,并将其用作 DeFi 中借款的抵押品,从而促进流动性提供。

Authors Hyoungsung Kim (@HyoungsungKim) <hyougnsung@keti.re.kr>, Yong-Suk Park <yspark@keti.re.kr>, Hyun-Sik Kim <hskim@keti.re.kr>
Created 2023-11-27
Discussion Link https://ethereum-magicians.org/t/erc-7565-proposal-perpetual-contract-nft-for-defi-composability/16790
Requires EIP-721, EIP-4907

摘要

本 ERC 提出了一种机制,其中个人(称为“资产所有者”)可以将代表锁定的存款或资产的 NFT 作为抵押品,以借入资金。 这些 NFT 代表在预定义的到期日之后,索取基础资产以及任何应计收益的权利。[1]

动机

DeFi 的快速发展引入了各种资产锁定机制,提供了诸如利息和投票权等好处。然而,这个领域的一个重大挑战是在锁定这些资产的同时保持流动性。本 ERC 通过提出一种使用 ERC-721ERC-4907 从锁定资产中产生利润的方法来应对这一挑战。

在运行自动化做市商 (AMM) 的 DeFi 服务中,流动性提供者将资产贡献给资金池并获得代表其股份的 NFT。这些 NFT 表示对资产的权利和相关利益,但它们也将资产锁定在资金池中,通常会对提供者造成流动性挑战。目前的做法要求提供者提取其资产以满足紧急流动性需求,这对资金池的流动性产生不利影响,并可能增加资产交换期间的滑点。

我们的提案允许将这些代表流动性池中锁定资产的 NFT 用作抵押品。这种方法使流动性提供者能够在不提取资产的情况下获得临时流动性,从而保持资金池的流动性水平。此外,它还扩展到更广泛的 DeFi 服务,包括贷款和交易,在这些服务中,资产锁定非常普遍。通过允许通过 NFT 对锁定资产表示进行抵押,我们的方法旨在为各种 DeFi 服务提供通用的流动性解决方案,从而使生态系统内的不同用户群体受益。

我们引入的永久合约 NFT 的概念,利用了加密货币衍生品市场中永久期货合约的思想。这些 NFT 代表永久合约及其抵押品的权利,使其能够有效地用作 DeFi 可组合性的抵押品。永久合约 NFT 提供了一种新的 NFT 形式,提高了锁定资产的效用,通过在保留资产锁定优势的同时提供流动性,在 DeFi 应用程序中提供了显着的优势。

规范

本文档中的关键词“必须”,“不得”,“必需”,“应”,“不应”,“建议”,“不建议”,“可以”和“可选”应按照 RFC 2119 和 RFC 8174 中的描述进行解释。

合约接口

Solidity 接口。

    interface IPerpetualContractNFT {

        // 当 NFT 被抵押以获得贷款时发出
        event Collateralized(uint256 indexed tokenId, address indexed owner, uint256 loanAmount, uint256 interestRate, uint256 loanDuration);

        // 当由 NFT 担保的贷款全部偿还时发出,从抵押品中释放 NFT
        event LoanRepaid(uint256 indexed tokenId, address indexed owner);

        // 当贷款违约时发出,导致 NFT 转移给贷款人
        event Defaulted(uint256 indexed tokenId, address indexed lender);

        // 使 NFT 所有者能够抵押其 NFT 以换取贷款
        // @param tokenId 要用作抵押品的 NFT
        // @param loanAmount 要借入的资金额
        // @param interestRate 贷款利率
        // @param loanDuration 贷款期限
        function collateralize(uint256 tokenId, uint256 loanAmount, uint256 interestRate, uint64 loanDuration) external;

        // 使借款人能够偿还贷款并重新获得抵押 NFT 的所有权
        // @param tokenId 用作抵押品的 NFT
        // @param repayAmount 要偿还的资金额
        function repayLoan(uint256 tokenId, uint256 repayAmount) external;

        // 允许查询给定 NFT 的贷款条款
        // @param tokenId 用作抵押品的 NFT
        // @return loanAmount 借入的资金额
        // @return interestRate 贷款利率
        // @return loanDuration 贷款期限
        // @return loanDueDate 贷款到期日
        function getLoanTerms(uint256 tokenId) external view returns (uint256 loanAmount, uint256 interestRate, uint256 loanDuration, uint256 loanDueDate);

        // 允许查询 NFT 的当前所有者
        // @param tokenId 有问题的 NFT
        // @return 当前所有者的地址
        function currentOwner(uint256 tokenId) external view returns (address);

        // 查看偿还给定 NFT 贷款所需的总金额
        // @param tokenId 用作抵押品的 NFT
        // @return 偿还贷款所需的总金额,包括利息
        function viewRepayAmount(uint256 tokenId) external view returns (uint256);
    }

事件 Collateralized

  • collateralize 函数成功执行时,必须发出 Collateralized 事件。
  • 用法:记录 NFT 用作贷款抵押品的事件,捕获诸如贷款金额、利率和贷款期限等重要详细信息。

事件 LoanRepaid

  • repayLoan 函数成功执行时,必须发出 LoanRepaid 事件。
  • 用法:记录贷款已偿还且相应的 NFT 已从抵押品中释放的事件。

事件 Defaulted

  • 在贷款违约且 NFT 被转移给贷款人的情况下,必须发出 Defaulted 事件。
  • 用法:用于记录贷款违约和 NFT 转移给贷款人的事件。

函数 collateralize

  • collateralize 事件应实现为 external
  • 用法:允许 NFT 所有者抵押其 NFT 以获得贷款。

函数 repayLoan

  • repayLoan 函数应实现为 external
  • 用法:使 NFT 所有者能够偿还贷款并赎回其 NFT。

函数 getLoanTerms

  • getLoanTerms 函数可以实现为 external view
  • 用法:允许查询给定 NFT 的贷款条款。

函数 currentOwner

  • currentOwner 函数可以实现为 external view
  • 用法:允许查询特定 NFT 的当前所有者。

函数 viewRepayAmount

  • viewRepayAmount 函数可以实现为 external view
  • 用法:允许查询特定 NFT 的当前偿还金额。

理由

设计动机

该标准的设计受到解决 DeFi 领域特定挑战的需求的驱动,特别是关于锁定为抵押品的资产的流动性和管理。DeFi 中的传统机制通常要求资产持有者锁定其资产以参与诸如贷款、质押或收益耕作等活动,这会导致流动性损失。本标准旨在引入一种更灵活的方法,允许资产持有者在锁定其资产的同时保留一定的流动性,从而提高 DeFi 产品的效用和吸引力。

设计决策

  • 双重角色系统(资产所有者和 DeFi 平台/合约):在 NFT 所有者(资产持有者)和使用 NFT 作为抵押品的 DeFi 平台或合约之间建立了明确的划分。这种区分简化了权利和责任的管理,增强了清晰度并减少了潜在的冲突。

  • 在不影响资产锁定收益的情况下提高流动性:该标准的一个关键特性是使资产所有者能够使用其代表锁定资产的 NFT 作为抵押品来获得贷款。这种方法允许资产所有者访问流动性,而无需从资金池或质押计划中提取其资产,从而保留了诸如利息累积或投票权等相关利益。

  • 自动化贷款和抵押品管理:集成用于管理抵押 NFT 条款和条件的自动化功能是一种有意的选择,旨在最大程度地降低交易成本和复杂性。

  • DeFi 可组合性:策略性地强调 DeFi 可组合性,特别是资产锁定和抵押服务之间的集成,对于此标准至关重要。这种方法旨在简化该标准在各种 DeFi 平台和服务中的采用,从而促进 DeFi 生态系统内的无缝连接。

替代设计和相关工作

  • ERC-4907 的比较:虽然 ERC-4907 也为 NFT 引入了双重角色模型(所有者和用户),但我们的标准专门关注 NFT 在金融交易中用作抵押品,这与 ERC-4907 以租赁为导向的方法不同。

  • 改进传统抵押方法:与通常需要完全锁定资产的传统 DeFi 抵押相比,本标准提出了一种更动态和灵活的模型,该模型允许持续访问流动性。

向后兼容性

ERC-721 完全兼容,并与 ERC-4907 集成以用于租赁 NFT。

测试用例

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

import "./PerpetualContractNFT.sol";

contract PerpetualContractNFTDemo is PerpetualContractNFT {

    constructor(string memory name, string memory symbol)
        PerpetualContractNFT(name, symbol)
    {         
    }

    function mint(uint256 tokenId, address to) public {
        _mint(to, tokenId);
    }
}
import { expect } from "chai";
import { ethers } from "hardhat";

describe("PerpetualContractNFTDemo", function () {
    it("should allow an owner to collateralize an NFT, rent it to a contract, and then have the owner repay the loan", async function () {
        const [owner] = await ethers.getSigners();

        const PerpetualContractNFTDemo = await ethers.getContractFactory("PerpetualContractNFTDemo");
        const demo = await PerpetualContractNFTDemo.deploy("DemoNFT", "DNFT");
        await demo.waitForDeployment();
        expect(demo.target).to.be.properAddress;

        // Mint an NFT to the owner
        // 将 NFT 铸造给所有者
        await demo.mint(1, owner.address);

        // Owner collateralizes the NFT for a loan
        // 所有者抵押 NFT 以获得贷款
        const loanAmount = ethers.parseUnits("1", "ether"); // 1 Ether in Wei. Use Wei to avoid precision error.
        const interest = 5; // 5% interest
        const expiration = Math.floor(new Date().getTime() / 1000) + 3600; // Expire after 60 minutes (3600 seconds), convert it to seconds because `hours` in solidity converted to seconds
        
        await demo.connect(owner).collateralize(1, loanAmount, interest, expiration); // tokenId, loanAmount, interestRate, loanDuration

        // Check current user of the NFT (should be the contract address)
        // 检查 NFT 的当前用户(应该是合约地址)
        expect(await demo.userOf(1)).to.equal(demo.target);

        // Borrower repays the loan to release the NFT
        // 借款人偿还贷款以释放 NFT
        const repayAmountWei = await demo.connect(owner).viewRepayAmount(1);
        await demo.connect(owner).repayLoan(1, repayAmountWei);
        
        // Check if the NFT is returned to the original owner after the loan is repaid
        // 检查贷款偿还后 NFT 是否返回给原始所有者
        expect(await demo.userOf(1)).to.equal("0x0000000000000000000000000000000000000000");
    });
    });

在终端中运行:

npx hardhat test

参考实现

// 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
    //构造函数用于使用给定的名称和符号初始化永久合约 NFT 合约
    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.
        // 贷款金额应反映 NFT 所代表的资产价值,同时考虑到适当的贷款价值比 (LTV)。
        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
        // 检查 repayAmount 是否足以至少支付一部分到期总额
        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
        // 如果全部贷款金额已全部偿还,则将 NFT 的用户重置为默认状态
        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.
        // 利息按小时计算,按贷款实际持有时间按比例计算。
        // 如果借款人在贷款期限结束前偿还贷款,则仅收取贷款持有期间的利息。
        // 例如,如果年利率为 5%,并且借款人在贷款期限的一半时偿还贷款,则他们仅支付 2.5% 的利息。
        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
        // 示例:15/4 = 3.75
        // round((15 + 4 - 1)/4) = 4, round((15/4) = 3)
        // 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
    // 其他功能和逻辑,用于处理贷款违约、转移和 NFT 生命周期的其他方面
}

安全考虑

版权

Copyright and related rights waived via CC0.

Citation

Please cite this document as:

Hyoungsung Kim (@HyoungsungKim) <hyougnsung@keti.re.kr>, Yong-Suk Park <yspark@keti.re.kr>, Hyun-Sik Kim <hskim@keti.re.kr>, "ERC-7565: 将永久合约 NFT 作为抵押品 [DRAFT]," Ethereum Improvement Proposals, no. 7565, November 2023. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-7565.