ERC-7092: 金融债券
代表实体向投资者发行的债务。
Authors | Samuel Gwlanold Edoumou (@Edoumou) |
---|---|
Created | 2023-05-28 |
Requires | EIP-165 |
Table of Contents
摘要
本提案引入了具有关键特征的固定收益金融债券,这些特征被定义为便于在一级市场发行债券,并能够在二级市场买卖债券。该标准还为跨多个区块链的债券操作和管理提供了跨链功能。
动机
固定收益工具是公司和其他实体筹集资金时广泛使用的资产类别。然而,由于现有的标准(如 ERC-3475)引入了不熟悉的概念并导致不必要的 gas 消耗,因此过渡到代币化债券具有挑战性。此外,缺少像票息、到期日和本金这样的命名变量,使得实现 ERC-3475 变得困难,因为开发人员需要记住哪个元数据被分配给每个参数。
规范
本文档中的关键词“必须(MUST)”,“禁止(MUST NOT)”,“必需(REQUIRED)”,“应做(SHALL)”,“不应做(SHALL NOT)”,“应该(SHOULD)”,“不应该(SHOULD NOT)”,“推荐(RECOMMENDED)”,“不推荐(NOT RECOMMENDED)”,“可以(MAY)”和“可选(OPTIONAL)”应按照 RFC 2119 和 RFC 8174 中的描述进行解释。
每个符合此 ERC 的合约必须实现以下 Token Interface 以及 ERC-165 接口:
// SPDX-License-Identifier: CC0-1.0
pragma solidity ^0.8.0;
/**
* @title ERC-7092 Financial Bonds Standard
*/
interface IERC7092 /** is ERC165 */ {
// events
/**
* @notice 当债券代币被转移、发行或赎回时,必须发出此事件,合约创建期间除外
* @param _from 拥有债券的账户
* @param _to 接收债券的账户
* @param _amount 要转移的债券代币数量
*/
event Transfer(address indexed _from, address indexed _to, uint256 _amount);
/**
* @notice 当一个账户被批准或津贴减少时,必须发出此事件
* @param _owner 债券代币的所有者
* @param _spender 被允许花费债券的账户
* @param _amount `_owner` 允许 `_spender` 花费的债券代币数量
* 或减少来自 `_spender` 的津贴的债券代币数量
*/
event Approval(address indexed _owner, address indexed _spender, uint256 _amount);
/**
* @notice 当多个债券代币被转移、发行或赎回时,必须发出此事件,合约创建期间除外
* @param _from 债券持有人账户数组
* @param _to 要将债券转移到的账户数组
* @param _amount 要转移的债券代币数量数组
*
** OPTIONAL - 接口和其他合约不应期望此函数存在。必须在 `batchTransfer` 和 `batchTransferFrom` 函数中发出
*/
event TransferBatch(address[] _from, address[] _to, uint256[] _amount);
/**
* @notice 当多个账户被批准或多个账户的津贴减少时,必须发出此事件
* @param _owner 债券持有人账户
* @param _spender 允许花费债券的账户数组,或减少津贴的账户数组
* @param _amount `_owner` 允许 `_spender` 中的多个账户花费的债券代币数量数组。
*
** OPTIONAL - 接口和其他合约不应期望此函数存在。必须在 `batchApprove` 和 `batchDecreaseAllowance` 函数中发出
*/
event ApprovalBatch(address indexed _owner, address[] _spender, uint256[] _amount);
// getter functions
/**
* @notice 返回债券的 isin
*/
function isin() external view returns(string memory);
/**
* @notice 返回债券名称
*/
function name() external view returns(string memory);
/**
* @notice 返回债券符号
* 建议将符号表示为发行人的 Issuer'shorter name 和到期日的组合
* 例如:如果一家名为 Green Energy 的公司发行将于 2030 年 10 月 25 日到期的债券,则债券符号可能是 `GE30` 或 `GE2030` 或 `GE102530`
*/
function symbol() external view returns(string memory);
/**
* @notice 返回债券货币。这是用于支付和返还债券本金的代币的合约地址
*/
function currency() external view returns(address);
/**
* @notice 返回债券面额。这是可以发行债券的最小金额。它必须以本金货币的单位表示
* 例如:如果面额等于 1,000,货币是 USDC,则债券面额等于 1,000 USDC
*/
function denomination() external view returns(uint256);
/**
* @notice 返回发行量(总债务金额)。建议以面额单位表示发行量。
*/
function issueVolume() external view returns(uint256);
/**
* @notice 返回债券利率。建议以基点单位表示利率。
* 1 个基点 = 0.01% = 0.0001
* 例如:如果利率 = 5%,则 coupon() => 500 个基点
*/
function couponRate() external view returns(uint256);
/**
* @notice 返回债券发行给投资者的日期。这是一个 Unix 时间戳,如 block.timestamp 返回的时间戳
*/
function issueDate() external view returns(uint256);
/**
* @notice 返回债券到期日,即偿还本金的日期。这是一个 Unix 时间戳,如 block.timestamp 返回的时间戳
* 到期日必须大于发行日
*/
function maturityDate() external view returns(uint256);
/**
* @notice 返回一个账户的本金。建议以债券货币单位(USDC、DAI 等)表示本金
* @param _account 账户地址
*/
function principalOf(address _account) external view returns(uint256);
/**
* @notice 返回 `_spender` 账户已被 `_owner` 授权管理其债券的代币数量
* 帐户来管理他们的债券
* @param _owner 债券持有人地址
* @param _spender 已被债券持有人授权的地址
*/
function allowance(address _owner, address _spender) external view returns(uint256);
// setter functions
/**
* @notice 授权 `_spender` 账户管理 `_amount` 的债券代币
* @param _spender 要由债券持有人授权的地址
* @param _amount 要批准的债券代币数量
*/
function approve(address _spender, uint256 _amount) external returns(bool);
/**
* @notice 将 `_spender` 的津贴减少 `_amount`
* @param _spender 要由债券持有人授权的地址
* @param _amount 要从津贴中移除的债券代币数量
*/
function decreaseAllowance(address _spender, uint256 _amount) external returns(bool);
/**
* @notice 将 `_amount` 债券转移到地址 `_to`。此方法还允许将数据附加到正在转移的代币
* @param _to 要将债券发送到的地址
* @param _amount 要转移的债券代币数量
* @param _data 由代币持有人提供的附加信息
*/
function transfer(address _to, uint256 _amount, bytes calldata _data) external returns(bool);
/**
* @notice 从已通过 approve 函数授权调用者的账户转移 `_amount` 债券
* 此方法还允许将数据附加到正在转移的代币
* @param _from 债券持有人地址
* @param _to 要将债券转移到的地址
* @param _amount 要转移的债券代币数量。
* @param _data 由代币持有人提供的附加信息
*/
function transferFrom(address _from, address _to, uint256 _amount, bytes calldata _data) external returns(bool);
// batch functions
/**
* @notice 授权多个支出账户来管理债券持有人代币的指定 `_amount`
* @param _spender 要由债券持有人授权的账户数组
* @param _amount 要批准的债券代币数量数组
*
* OPTIONAL - 接口和其他合约不应期望这些值存在。该方法用于提高可用性。
*/
function batchApprove(address[] calldata _spender, uint256[] calldata _amount) external returns(bool);
/**
* @notice 按 `_amount` 中的相应金额减少多个支出者的津贴
* @param _spender 要由债券持有人授权的账户数组
* @param _amount 要从中减少津贴的债券代币数量数组
*
* OPTIONAL - 接口和其他合约不应期望此函数存在。该方法用于减少代币津贴。
*/
function batchDecreaseAllowance(address[] calldata _spender, uint256[] calldata _amount) external returns(bool);
/**
* @notice 将多个债券(金额在数组 `_amount` 中指定)转移到数组 `_to` 中的相应账户,可以选择附加额外数据
* @param _to 要将债券发送到的账户数组
* @param _amount 要转移的债券代币数量数组
* @param _data 由代币持有人提供的附加信息数组
*
* OPTIONAL - 接口和其他合约不应期望此函数存在。
*/
function batchTransfer(address[] calldata _to, uint256[] calldata _amount, bytes[] calldata _data) external returns(bool);
/**
* @notice 将多个债券(金额在数组 `_amount` 中指定)从数组 `_from` 中的账户转移到数组 `_to` 中的相应账户,这些账户已获得 `_from` 账户的授权
* 此方法还允许将数据附加到正在转移的代币
* @param _from 债券持有人账户数组
* @param _to 要将债券代币转移到的账户数组
* @param _amount 要转移的债券代币数量数组
* @param _data 由代币持有人提供的附加信息数组
*
** OPTIONAL - 接口和其他合约不应期望此函数存在。
*/
function batchTransferFrom(address[] calldata _from, address[] calldata _to, uint256[] calldata _amount, bytes[] calldata _data) external returns(bool);
}
额外的债券参数接口
对于实现此提议的合约,IERC7092ESG
接口是可选的(OPTIONAL)。此接口可以(MAY)用于提高标准的可用性。
currencyOfCoupon
用于支付息票的货币可能与用于偿还本金的货币不同couponType
可用于表示发行人承诺支付给投资者的利率,利率可以有多种形式,例如零息票、固定利率、浮动利率等。couponFrequency
是指债券向其债券持有人支付利息的频率,通常以时间段表示,例如:每年、每半年、每季度或每月。dayCountBasis
用于计算两个息票支付日或其他特定期间之间债券的应计利息。一些日计数基础包括:实际/实际、30/360、实际/360、实际/365 或 30/365
// SPDX-License-Identifier: CC0-1.0
pragma solidity ^0.8.0;
interface IERC7092ESG /** is ERC165 */ {
/**
* @notice 返回债券使用的小数位数。例如,如果它返回 `10`,则意味着代币金额必须乘以 10000000000 才能获得标准表示。
*/
function decimals() external view returns(uint8);
/**
* @notice 返回息票货币,该货币由用于支付息票的代币的合约地址表示。它可以与用于本金的货币相同
*/
function currencyOfCoupon() external view returns(address);
/**
* @notice 返回息票类型
* 例如,0 可以表示零息票、1 可以表示固定利率、2 可以表示浮动利率,依此类推
*/
function couponType() external view returns(uint8);
/**
* @notice 返回息票频率,即一年内支付息票的次数。
*/
function couponFrequency() external view returns(uint256);
/**
* @notice 返回日计数基础
* 例如,0 可以表示实际/实际,1 可以表示实际/360,依此类推
*/
function dayCountBasis() external view returns(uint8);
}
跨链接口
该标准允许实现 IERC7092CrossChain
接口以用于债券代币的跨链管理。此接口是可选的(OPTIONAL),应用程序可以使用它来允许跨链交易。任何发起跨链交易的函数必须显式定义目标链标识符 destinationChainID
并指定目标智能合约 destinationContract
。
// SPDX-License-Identifier: CC0-1.0
pragma solidity ^0.8.0;
interface IERC7092CrossChain /** is ERC165 */ {
// events
/**
* @notice 当债券代币在跨链交易中被转移或赎回时,必须发出此事件
* @param _from 债券持有人账户
* @param _to 将债券代币转移到的账户
* @param _amount 要转移的债券代币数量
* @param _destinationChainID 标识目标链的唯一 ID
*/
event CrossChainTransfer(address indexed _from, address indexed _to, uint256 _amount, bytes32 _destinationChainID);
/**
* @notice 当多个债券代币在跨链交易中被转移或赎回时,必须发出此事件
* @param _from 债券持有人账户数组
* @param _to 接收债券的账户数组
* @param _amount 要转移的债券代币数量数组
* @param _destinationChainID 标识目标链的唯一 ID 数组
*/
event CrossChainTransferBatch(address[] _from, address[] _to, uint256[] _amount, bytes32[] _destinationChainID);
/**
* @notice 当一个账户被批准在与当前链不同的链上花费债券持有人的代币时,必须发出此事件
* @param _owner 债券持有人账户
* @param _spender 被允许花费债券的账户
* @param _amount `_owner`` 允许 `_spender` 花费的债券代币数量
* @param _destinationChainID 标识目标链的唯一 ID
*/
event CrossChainApproval(address indexed _owner, address indexed _spender, uint256 _amount, bytes32 _destinationChainID);
/**
* @notice 当数组 `_spender` 中的多个账户被批准或数组 `_spender` 中的多个账户的津贴在目标链上减少时,必须发出此事件,目标链必须与当前链不同
* @param _owner 债券代币的所有者
* @param _spender 被允许花费债券的账户数组
* @param _amount `_owner` 允许 `_spender` 花费的债券代币数量数组
* @param _destinationChainID 标识目标链的唯一 ID 数组
*/
event CrossChainApprovalBatch(address indexed _owner, address[] _spender, uint256[] _amount, bytes32[] _destinationChainID);
// functions
/**
* @notice 授权 `_spender` 账户在目标链上管理债券持有人债券代币的指定 `_amount`
* @param _spender 要由债券持有人授权的账户
* @param _amount 要批准的债券代币数量
* @param _destinationChainID 标识目标链的唯一 ID。
* @param _destinationContract 要在目标链中交互的智能合约
*/
function crossChainApprove(address _spender, uint256 _amount, bytes32 _destinationChainID, address _destinationContract) external returns(bool);
/**
* @notice 授权 `_spender` 中的多个支出账户在目标链上管理债券持有人代币的指定数量 `_amount`
* @param _spender 要由债券持有人授权的账户数组
* @param _amount 要批准的债券代币数量数组
* @param _destinationChainID 标识目标链的唯一 ID 数组。
* @param _destinationContract 要在目标链中交互的智能合约数组,以便存款或铸造转移的代币。
*/
function crossChainBatchApprove(address[] calldata _spender, uint256[] calldata _amount, bytes32[] calldata _destinationChainID, address[] calldata _destinationContract) external returns(bool);
/**
* @notice 在目标链上将 `_spender` 的津贴减少指定的 `_amount`
* @param _spender 要由债券持有人授权的地址
* @param _amount 要从津贴中移除的债券代币数量
* @param _destinationChainID 标识目标链的唯一 ID。
* @param _destinationContract 要在目标链中交互的智能合约,以便存款或铸造转移的代币。
*/
function crossChainDecreaseAllowance(address _spender, uint256 _amount, bytes32 _destinationChainID, address _destinationContract) external returns(bool);
/**
* @notice 在目标链上,按数组 `_amount` 中指定的相应金额减少 `_spender` 中的多个支出者的津贴
* @param _spender 要由债券持有人授权的账户数组
* @param _amount 要从中减少津贴的债券代币数量数组
* @param _destinationChainID 标识目标链的唯一 ID 数组。
* @param _destinationContract 要在目标链中交互的智能合约数组,以便存款或铸造转移的代币。
*/
function crossChainBatchDecreaseAllowance(address[] calldata _spender, uint256[] calldata _amount, bytes32[] calldata _destinationChainID, address[] calldata _destinationContract) external returns(bool);
/**
* @notice 将 `_amount` 债券代币从当前链移动到另一个链上的地址 `_to`(例如,将代币从 Ethereum 移动到 Polygon)。
* 此方法还允许将数据附加到正在转移的代币
* @param _to 要将债券代币发送到的账户
* @param _amount 要转移的债券代币数量
* @param _data 由债券持有人提供的附加信息
* @param _destinationChainID 标识目标链的唯一 ID。
* @param _destinationContract 要在目标链中交互的智能合约,以便存款或铸造转移的债券代币。
*/
function crossChainTransfer(address _to, uint256 _amount, bytes calldata _data, bytes32 _destinationChainID, address _destinationContract) external returns(bool);
/**
* @notice 将多个债券代币(金额在数组 `_amount` 中指定)从当前链移动到另一个链上的数组 `_to` 中的相应账户(例如,将代币从 Ethereum 移动到 Polygon)。
* 此方法还允许将数据附加到正在转移的代币
* @param _to 要将债券发送到的账户数组
* @param _amount 要转移的债券代币数量数组
* @param _data 由债券持有人提供的附加信息数组
* @param _destinationChainID 标识目标链的唯一 ID 数组。
* @param _destinationContract 要在目标链中交互的智能合约数组,以便存款或铸造转移的债券代币。
*/
function crossChainBatchTransfer(address[] calldata _to, uint256[] calldata _amount, bytes[] calldata _data, bytes32[] calldata _destinationChainID, address[] calldata _destinationContract) external returns(bool);
/**
* @notice 将 `_amount` 债券代币从 `_from` 账户转移到 `_to` 账户,从当前链转移到另一个链。调用者必须获得 `_from` 地址的批准。
* 此方法还允许将数据附加到正在转移的代币
* @param _from 债券持有人地址
* @param _to 要将债券转移到的账户
* @param _amount 要转移的债券代币数量
* @param _data 由代币持有人提供的附加信息
* @param _destinationChainID 标识目标链的唯一 ID。
* @param _destinationContract 要在目标链中交互的智能合约,以便存款或铸造转移的代币。
*/
function crossChainTransferFrom(address _from, address _to, uint256 _amount, bytes calldata _data, bytes32 _destinationChainID, address _destinationContract) external returns(bool);
/**
* @notice 将几个债券代币(金额在数组 `_amount` 中指定)从数组 `_from` 中的账户转移到数组 `_to` 中的账户,从当前链转移到另一个链。
* 调用者必须获得 `_from` 账户的批准才能花费数组 `_amount` 中指定的相应金额
* 此方法还允许将数据附加到正在转移的代币
* @param _from 债券持有人地址数组
* @param _to 要将债券转移到的账户数组
* @param _amount 要转移的债券代币数量数组
* @param _data 由代币持有人提供的附加信息数组
* @param _destinationChainID 标识目标链的唯一 ID 数组。
* @param _destinationContract 要在目标链中交互的智能合约数组,以便存款或铸造转移的代币。
*/
function crossChainBatchTransferFrom(address[] calldata _from, address[] calldata _to, uint256[] calldata _amount, bytes[] calldata _data, bytes32[] calldata _destinationChainID, address[] calldata _destinationContract) external returns(bool);
}
理由
此 ERC 的设计旨在通过保持与传统债券标准的一致性来简化向代币化债券的迁移。这种方法允许将固定收益工具表示为链上代币,通过钱包进行管理,并供分散交易等应用程序使用,同时避免与其他标准相关的复杂性和低效率。此 ERC 有助于创建具有类似于传统债券特征的新债券代币,从而提高债券交易和管理的可访问性、流动性和成本效益。
使用传统的金融术语,如 issueVolume
和 principalOf
,旨在保持与传统债券语言的一致性,这有助于传统实体的适应。
总供应量和账户余额
totalSupply
和 balanceOf
函数未定义,因为它们可以从 issueVolume
、principalOf
和 denomination
中派生。但是,这些函数可以添加到实现此标准的任何合约中,以确保这些值之间的适当关系。
function totalSupply() external view returns(uint256) {
return issueVolume() / denomination();
}
function balance0f(account) external view returns(uint256) {
return principal(account) / denomination();
}
向后兼容性
由于缺少 totalSupply
或 balanceOf
等某些函数,因此此 ERC 与现有的标准(如 ERC-20 或 ERC-1155)不向后兼容。建议对发行代币化债券采用此标准的纯粹实现,因为与提到的其他标准的任何混合解决方案都应失败。
参考实现
完整的参考实现可以在 这里 找到。
可以通过继承集成了建议接口的参考 ERC7092.sol
来创建具有嵌入式期权(如可赎回债券、可回售债券或可转换债券)的债券。
可赎回债券:
pragma solidity ^0.8.0;
import 'ERC7092.sol';
contract ERC7092Callable is ERC7092 {
// 编写逻辑以允许发行人赎回债券
// 需要的状态变量和函数
/**
* @notice 赎回 `_investor` 拥有的债券
* 必须仅由发行人调用
*/
function call(address _investor) public {
require(msg.sender == _issuer[bondISIN].issuerAddress, "ERC7092Callable: ONLY_ISSUER");
require(_principals[_investor] > 0, "ERC7092Callable: NO_BONDS");
require(block.timestamp < _bond[bondISIN].maturityDate, "ERC7092Callable: BOND_MATURED");
uint256 principal = _principals[_investor];
_principals[_investor] = 0;
// 在这里添加逻辑
}
}
可回售债券:
pragma solidity ^0.8.0;
import 'ERC7092.sol';
contract ERC7092Puttable is ERC7092 {
// 编写逻辑以允许投资者回售债券
// 需要的状态变量和函数
/**
* @notice 回售债券
* 必须由拥有债券的投资者调用
*/
function put() public {
require(_principals[msg.sender] > 0, "ERC7092Puttable: ONLY_INVESTORS");
require(block.timestamp < _bond[bondISIN].maturityDate, "ERC7092Puttable: BOND_MATURED");
uint256 principal = _principals[msg.sender];
_principals[msg.sender] = 0;
// 添加逻辑
}
}
可转换债券:
pragma solidity ^0.8.0;
import 'ERC7092.sol';
contract ERC7092Convertible is ERC7092 {
// 编写逻辑以允许投资者或发行人将债券转换为股权
// 需要的状态变量和函数
/**
* @notice 将债券转换为股权。在这里,我们假设投资者必须将其债券转换为股权
* 发行人也可以将投资者的债券转换为股权。
*/
function convert() public {
require(_principals[msg.sender] > 0, "ERC7092Convertible: ONLY_INVESTORS");
require(block.timestamp < _bond[bondISIN].maturityDate, "ERC7092Convertible: BOND_MATURED");
uint256 principal = _principals[msg.sender];
_principals[msg.sender] = 0;
// 在这添加逻辑
}
}
身份注册表
此标准专门为代币化债券而设计。它本身并不管理与债券持有人身份有关的信息。但是,为了提高对监管要求的合规性并提高透明度,可以在此标准之上添加身份注册表以存储所有授权投资者的身份。
通过维护身份注册表,发行人可以确保根据 ERC7092
标准发行的债券代币仅转移给已注册和授权的实体。这种做法符合法规合规措施,并提供了一种结构化的方式来管理和验证债券持有人的身份。它还有助于防止未经授权或不合规的债券代币转移。
安全考虑
实施此 ERC 需要仔细考虑与批准操作员管理所有者债券的函数以及允许债券转移的函数相关的安全风险。使用这些函数需要强大的验证,以确保只有债券所有者或批准的帐户才能调用它们。
版权
版权和相关权利已通过 CC0 放弃。
Citation
Please cite this document as:
Samuel Gwlanold Edoumou (@Edoumou), "ERC-7092: 金融债券," Ethereum Improvement Proposals, no. 7092, May 2023. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-7092.