ERC-7699: ERC-20 转账引用扩展
为每个 ERC-20 交易包含一个唯一的标识符(转账或“支付”引用),以将转账与订单/发票关联起来。
Authors | Radek Svarz (@radeksvarz) |
---|---|
Created | 2024-04-26 |
Discussion Link | https://ethereum-magicians.org/t/erc-7699-erc20-payment-reference-extension/19826 |
Requires | EIP-20, EIP-165 |
摘要
ERC-20 代币标准没有提供内置的机制,用于在代币转账中包含支付转账引用(接收方消息)。本提案通过添加最小的方法来扩展现有的 ERC-20 代币标准,以在 token 转账和 transferFrom 操作中包含转账引用。引用的添加可以帮助用户、商户和服务提供商将各个交易与特定订单或发票关联和对账。
动机
本提案的主要动机是通过提供一种在代币转账中包含支付引用的机制来改进 ERC-20 代币标准的功能,类似于传统金融系统,在传统金融系统中,支付引用通常用于将交易与特定订单、发票或其他财务记录关联和对账。
目前,想要在其交易中包含支付引用的用户和商家必须依赖于链下外部系统或自定义支付代理实现。在传统金融系统中,支付引用通常包含在电汇和其他类型的电子支付中,使用户和商家可以轻松管理和对账他们的交易。例如:
- SWIFT MT103:字段 70“汇款信息”通常用于此类内容(例如“PAYMENT FOR INVOICE 998877”)。还有字段 72“发件人到收件人信息”。
- ISO 20022(对于 SEPA):PAIN.001 有一个名为 RmtInf(汇款信息)的字段
通过使用付款转账参考功能扩展现有的 ERC-20 代币标准,本提案将有助于弥合传统金融系统与去中心化金融世界之间的差距,从而为用户、商家和服务提供商提供更无缝的体验。
规范
本文档中的关键词“MUST”、“MUST NOT”、“REQUIRED”、“SHALL”、“SHALL NOT”、“SHOULD”、“SHOULD NOT”、“RECOMMENDED”、“MAY”和“OPTIONAL”应按照 RFC 2119 中的描述进行解释。
任何符合 ERC-20 并在本 ERC 中扩展的合约,都必须实现以下接口:
// The EIP-165 identifier of this interface is 0x1522573a
interface IERC7699 {
function transfer(address to, uint256 amount, bytes calldata transferReference) external returns (bool);
function transferFrom(address from, address to, uint256 amount, bytes calldata transferReference) external returns (bool);
event TransferReference(bytes32 indexed loggedReference);
}
除了标准的转账行为外,这些 transfer
和 transferFrom
函数还必须发出一个带有 loggedReference
参数的 transferReference
事件(只有以下定义的例外情况)。
相应的 ERC-20 Transfer
事件必须在 TransferReference
事件之后发出,理想情况下是紧随其后,以便客户端能够寻找相关的 Transfer
事件日志记录。
发出的 loggedReference
MAY 是 transferReference
的精确副本(当小于 33 字节时),或者是来自富 transferReference
结构和其他处理的派生数据。这取决于实施者。ONE MUST NOT 期望 transferReference
和 loggedReference
数据相等。
loggedReference
参数 MAY 包含任何 bytes32 类型的数据。
transferReference
参数 MAY 为空。在这种情况下,并且仅在这种情况下,MUST NOT 发出 TransferReference
事件,从而有效地模拟没有任何转账引用的常规 ERC-20 转账。
transferReference
参数的长度在设计上不受限制,由于 calldata 和执行 gas 成本,用户有动机保持其简短。
TransferReference
事件 MUST NOT 使用 anonymous
说明符声明。这是为了确保记录事件签名,并可以用作过滤器。
0 金额的转账 MUST 被视为正常转账并同样触发 TransferReference
事件。
理由
参数名称
选择将添加的参数命名为 transferReference
是为了与传统银行术语保持一致,在传统银行术语中,支付引用被广泛用于将交易与特定订单、发票或其他财务记录相关联和对账。
transferReference
参数名称还有助于清楚地传达参数的目的及其在促进交易的关联和对账中的作用。通过采用在金融行业中行之有效的术语,该提案旨在促进对扩展的 ERC-20 代币标准的更多理解和采用。
参数类型
transferReference
类型是 bytes。
最初考虑将 transferReference
类型设置为 bytes32,以激励用户使用短引用(如 TradFi 中常见的那样),或者使用引用内容的 Keccak 256 哈希。结论是,应该保持选项开放,以便能够使用结构化数据进行调用;例如,传递包含签名的引用数据,从而可以进行额外的处理检查。
发出的数据
loggedReference
类型是 bytes32。
曾经考虑以 bytes calldata
的形式记录引用。但是,由于事件订阅过滤器所需的日志主题索引,引用内容将在事件日志中进行哈希处理。生成的已记录主题始终为 bytes32 形式。Bytes32 类型支持记录长达 32 字节的公开可读(非哈希)引用内容。
向后兼容性
此扩展与现有的 ERC-20 代币标准完全向后兼容。新函数可以与现有的 transfer 和 transferFrom 函数一起使用。现有的可升级 ERC-20 代币可以升级以包含新功能,而不会影响存储布局;新的 ERC-20 代币可以选择根据其特定需求实现支付参考功能。
参考实现
// SPDX-License-Identifier: CC0-1.0
pragma solidity >=0.8.4 <0.9.0;
import {ERC20} from "@openzeppelin/token/ERC20/ERC20.sol";
interface IERC7699 {
/**
* @notice 当非空的 `transferReference` 添加到 `transfer` 调用时发出。
*/
event TransferReference(bytes32 indexed loggedReference);
/**
* @notice 使用 `transferReference` 将 `amount` 代币从调用者的帐户移动到 `to`。
*
* @dev 返回一个布尔值,指示操作是否成功。
*
* MUST 发出此 ERCS 的 {TransferReference} 事件,后跟相应的 {ERC20.Transfer} 事件
* (为了符合 ERC-20)。
*/
function transfer(address to, uint256 amount, bytes calldata transferReference) external returns (bool);
/**
* @notice 使用授权机制将 `amount` 代币从 `from` 移动到 `to`,并带有 `transferReference`。然后从调用者的授权中扣除 `amount`。
*
* @dev 返回一个布尔值,指示操作是否成功。
*
* MUST 发出此 ERCS 的 {TransferReference} 事件,后跟相应的 {ERC20.Transfer} 事件
* (为了符合 ERC-20)。
*/
function transferFrom(address from, address to, uint256 amount, bytes calldata transferReference)
external
returns (bool);
}
/**
* @dev ERC20 转账引用扩展的实现。
*/
contract ERC20TransferReference is ERC20, IERC7699 {
constructor() ERC20("ERC20 Transfer Reference Example", "TXRE") {
_mint(msg.sender, 987654321 * 1e18);
}
/**
* @dev 发出具有派生 `loggedReference` 数据的 `TransferReference` 事件
*/
function _logReference(bytes calldata transferReference) internal virtual {
// transferReference 为空时 MUST NOT 发出
if (transferReference.length > 0) {
// 有效地从 transferReference calldata 字节中提取前 32 个字节
// 注意:这是一个示例。loggedReference 的推导完全取决于实现。
// 例如,整个 transferReference 的 keccak 哈希等。
bytes32 loggedReference;
// solhint-disable-next-line no-inline-assembly
assembly {
loggedReference := calldataload(transferReference.offset)
}
emit TransferReference(loggedReference);
}
}
/**
* @notice 带有可选转账引用的标准 ERC20 代币转账
* @dev 假定底层 `transfer` 函数处理实际的代币转账逻辑。
* @param to 代币将发送到的接收者的地址。
* @param amount 要转移的代币数量。
* @param transferReference 用于包含转账引用、引用签名或其他相关
* 参考数据的字节字段。
* @return 一个布尔值,指示转账是否成功。
*/
function transfer(address to, uint256 amount, bytes calldata transferReference) public virtual returns (bool) {
_logReference(transferReference);
// ERC20.Transfer 事件在 TransferReference 事件之后立即发出
return transfer(to, amount);
}
/**
* @notice 带有可选转账引用的委托代币转账
* @dev 需要代币所有者的事先批准。假定底层 `transferFrom` 函数处理
* 授权和转账逻辑。
* @param from 授权转账的代币所有者的地址。
* @param to 代币将发送到的接收者的地址。
* @param amount 要转移的代币数量。
* @param transferReference 用于包含转账引用、引用签名或其他相关
* 参考数据的字节字段。
* @return 一个布尔值,指示转账是否成功。
*/
function transferFrom(address from, address to, uint256 amount, bytes calldata transferReference)
public
virtual
returns (bool)
{
_logReference(transferReference);
// ERC20.Transfer 事件在 TransferReference 事件之后立即发出
return transferFrom(from, to, amount);
}
}
安全注意事项
隐私注意事项
参考数据隐私:在代币转账中包括支付引用可能会暴露有关交易或相关方的敏感信息。实施者和用户应仔细考虑隐私影响,并确保支付引用不泄露敏感信息。为了减轻此风险,实施者可以考虑使用加密或其他增强隐私的技术来保护支付参考数据。
示例:如果记录了引用 0x20240002,则交易会公开暴露这与 2024 年收件人的第二张发票有关。
操纵支付引用
本 ERC 不规定对引用数据的验证。恶意行为者可能会尝试操纵支付引用来误导用户、商家或服务提供商。这可能导致:
-
法律风险:如果攻击者使用非法资金,冒充受益人或标记受益人参与洗钱或其他非法活动,受益人可能会面临法律和合规风险。
-
争议和退款:用户可能会发现他们没有付款,要求退款或提出争议,从而给受益人带来额外的管理工作。
为了减轻此风险,实施者可以考虑使用方法来识别正确的发送者并生成唯一且可验证的相关支付引用。但是,此类实现不在本标准的范围内,而是对其进行扩展。
版权
版权及相关权利通过 CC0 放弃。
Citation
Please cite this document as:
Radek Svarz (@radeksvarz), "ERC-7699: ERC-20 转账引用扩展 [DRAFT]," Ethereum Improvement Proposals, no. 7699, April 2024. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-7699.