Alert Source Discuss
⚠️ Draft Standards Track: ERC

ERC-7444: 时间锁到期时间

用于传递时间锁定系统解锁时间的接口

Authors Thanh Trinh (@thanhtrinh2003) <thanh@revest.finance>, Joshua Weintraub (@jhweintraub) <josh@revest.finance>, Rob Montgomery (@RobAnon) <rob@revest.finance>
Created 2023-06-05
Discussion Link https://ethereum-magicians.org/t/eip-idea-timelock-maturity/15321
Requires EIP-165

摘要

本 EIP 定义了一种标准化的方法来传达时间锁定系统将解锁的日期。这允许确定各种资产类别的到期日,并提高这些资产的估值容易度。

动机

时间锁很常见,但目前还没有关于如何确定它们解锁日期的标准。时间锁定的资产会经历 theta 衰减,剩余到解锁的时间决定了它们的价值。提供一个通用的标准来查看它们的到期日,可以改进这些非流动性资产权利的链上估值,这在通过半流动性资产(如 ERC-721ERC-1155)在所有者之间传递这些非流动性资产权利的情况下尤其有用。

规范

本文档中的关键词“必须 (MUST)”,“禁止 (MUST NOT)”,“必需 (REQUIRED)”,“应当 (SHALL)”,“不应当 (SHALL NOT)”,“应该 (SHOULD)”,“不应该 (SHOULD NOT)”,“推荐 (RECOMMENDED)”,“不推荐 (NOT RECOMMENDED)”,“可以 (MAY)”和“可选 (OPTIONAL)”应按照 RFC 2119 和 RFC 8174 中的描述进行解释。

每个符合 ERC-7444 的合约都必须实现 ERC-165 接口检测

// SPDX-License-Identifier: CC0-1.0

pragma solidity ^0.8.0;

interface ERC-7444 {
    /
     * @notice 这个函数返回 `id` 指定的时间锁解锁的时间戳
     * @param  id 描述特定时间锁的标识符
     * @return maturity 时间锁解锁的时间戳
     */
    function getMaturity(bytes32 id)
        external
        view
        returns (uint256 maturity);

}

到期时间返回参数应以 Unix 时间戳标准实现,该标准已在 solidity 中广泛使用。例如,block.timestamp 表示区块被挖掘时的 Unix 时间戳,值为 256 位。

对于可替代资产上的单例实现,传递给 id 的值应该被忽略,对此类实现的查询应该传入 0x0

理由

锁定资产的通用到期日

锁定资产越来越受欢迎,并被用于 defi 的不同部分,例如 yield farming 和 vested escrow 概念。这增加了对所有这些时间锁定资产的形式化和定义通用接口的需求。

通过 Black-Scholes 模型对锁定资产进行估值

锁定资产不能正常估值,因为这些资产的价值可能会随着时间的推移而变化,并且在整个锁定时间内还受到许多其他不同因素的影响。例如,Black-Scholes 模型或 Black-Scholes-Merton 模型是一个合适的模型示例,用于估计资产的理论价值,同时考虑时间和其他潜在风险的影响。

Black-Sholes 模型

  • $C=\text{看涨期权价格}$
  • $N=\text{正态分布的 CDF}$
  • $S_t=\text{资产的现货价格}$
  • $K=\text{行权价}$
  • $r=\text{无风险利率}$
  • $t=\text{到期时间}$
  • $\sigma=\text{资产的波动性}$

到期时间在评估时间锁定资产的价格方面起着重要作用,因此,拥有一个通用的接口来检索数据是不可避免的。

向后兼容性

该标准可以作为对具有时间锁定功能的 ERC-721 和/或 ERC-1155 代币的扩展来实现,其中许多可以通过指定的合约进行改造,以确定其时间锁释放的点。

参考实现

锁定 ERC-20 实现

// SPDX-License-Identifier: CC0-1.0

pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";

contract LockedERC20ExampleContract implements ERC-7444{
    ERC20 public immutable token;
    uint256 public totalLocked;

    //Timelock struct
    struct TimeLock {
        address owner;
        uint256 amount;
        uint256 maturity;
        bytes32 lockId;
    }

    //maps lockId to balance of the lock
    mapping(bytes32 => TimeLock) public idToLock;    

    function constructor(
        address _token,
    ) public {
        token = ERC20(_token);
    }

    //Maturity is not appropriate
    error LockPeriodOngoing();
    error InvalidReceiver();
    error TransferFailed();

    /// @dev Deposit tokens to be locked in the requested locking period
    /// @param amount The amount of tokens to deposit
    /// @param lockingPeriod length of locking period for the tokens to be locked
    function deposit(uint256 amount, uint256 lockingPeriod) external returns (bytes32 lockId) {
        uint256 maturity = block.timestamp + lockingPeriod;
        lockId = keccack256(abi.encode(msg.sender, amount, maturity));

        require(idToLock[lockId].maturity == 0, "lock already exists");

        if (!token.transferFrom(msg.sender, address(this), amount)) {
            revert TransferFailed();
        }

        TimeLock memory newLock = TimeLock(msg.sender, amount, maturity, lockedId);

        totalLocked += amount;

        idToLock[lockId] = newLock;
        
    }

    /// @dev Withdraw tokens in the lock after the end of the locking period
    /// @param lockId id of the lock that user have deposited in
    function withdraw(bytes32 lockId) external {
        TimeLock memory lock = idToLock[lockId];

        if (msg.sender != lock.owner) {
            revert InvalidReceiver();
        }

        if (block.timestamp > lock.maturity) {
            revert LockPeriodOngoing();
        }

        totalLocked -= lock.amount;

        //State cleanup
        delete idToLock[lockId];

        if (!token.transfer(msg.sender, lock.amount)) {
            revert TransferFailed();
        }

    }

    function getMaturity(bytes32 id) external view returns (uint256 maturity) {
        return idToLock[id].maturity;
    }
}

安全注意事项

可扩展的时间锁

用户或开发者应注意潜在的可扩展时间锁,其中返回的时间戳可以通过协议进行修改。 用户或协议在与他人进行交易或借贷之前应仔细检查时间戳。

版权

版权及相关权利通过 CC0 放弃。

Citation

Please cite this document as:

Thanh Trinh (@thanhtrinh2003) <thanh@revest.finance>, Joshua Weintraub (@jhweintraub) <josh@revest.finance>, Rob Montgomery (@RobAnon) <rob@revest.finance>, "ERC-7444: 时间锁到期时间 [DRAFT]," Ethereum Improvement Proposals, no. 7444, June 2023. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-7444.