ERC-6604: 抽象 Token
根据需要将 token 移至链上和链下,从而在保持链上可组合性的同时实现零成本铸造
Authors | Chris Walker (@cr-walker) <chris@ckwalker.com> |
---|---|
Created | 2023-03-03 |
Discussion Link | https://ethereum-magicians.org/t/draft-eip-abstract-token-standard/13152 |
Requires | EIP-20, EIP-165, EIP-721, EIP-1155 |
Table of Contents
摘要
抽象 token 提供了一个标准接口来:
- 将 token 作为消息在链下铸造
- 通过智能合约在链上具体化 token
- 将 token 重新还原为消息
抽象 token 可以符合现有的标准,如 ERC-20、ERC-721 和 ERC-1155。该标准允许钱包和其他应用程序在链上发生任何依赖于共识的事件之前更好地处理潜在的 token。
动机
抽象 token 能够实现零成本的 token 铸造,通过允许 token 持有者根据需要具体化 token(将 token 放在链上)来促进高容量的应用程序。示例用例:
- 空投
- POAP / 收据
- 身份 / 访问凭证
Merkle 树通常用于大型 token 分发,以将铸造/领取成本分摊给参与者,但它们要求参与者在领取 token 时提供 merkle 证明。此标准旨在改进类似分发的领取过程:
- 通用:与 merkle 树、数字签名或其他资格证明兼容
- 可读:用户可以查询抽象 token 合约以了解他们潜在的 token(例如,token ID、数量或 URI)
- 包含:用户无需了解特定 token 实现合约使用的证明机制
规范
本文档中的关键词“必须”、“禁止”、“必需”、“应该”、“不应该”、“推荐”、“不推荐”、“可以”和“可选”应按照 RFC 2119 和 RFC 8174 中的描述进行解释。
数据类型
Token 消息
Token 消息定义了一个或多个 token,以及使用智能合约具体化 token 所需的上下文。
chainId
& implementation
:将 token 消息的域设置为特定的链和合约:这是可以具体化 token 的地方
owner
:在消息中定义的 token 被具体化时,拥有这些 token 的地址
meta
:具体实现所需的上下文,用于具体化定义的 token,例如 ID、数量或 URI。
proof
:具体实现所需的授权,用于具体化定义的 token。
nonce
:当需要多个其他方面相同的抽象 token 消息时,可以递增的计数器
struct AbstractTokenMessage {
uint256 chainId;
address implementation;
address owner;
bytes meta;
uint256 nonce;
bytes proof;
}
消息状态
可以为每个(抽象 token 合约,抽象 token 消息)对定义消息状态。
invalid
:合约无法与消息交互
valid
:合约可以与消息交互
used
:合约已经与消息交互
enum AbstractTokenMessageStatus {
invalid,
valid,
used
}
方法
reify
将 token 从消息移动到合约
function reify(AbstractTokenMessage calldata message) external;
Token 合约必须具体化有效的 token 消息。
具体化必须是幂等的:特定的 token 消息最多只能用于具体化 token 一次。使用已使用的 token 消息调用 reify
可能会成功或回退。
status
返回特定消息的状态
function status(AbstractTokenMessage calldata message) external view returns (AbstractTokenMessageStatus status);
dereify
将 token 从合约移动到旨在用于另一个合约和/或链的消息。
function dereify(AbstractTokenMessage calldata message) external;
可选 - 允许通过从一个上下文中取消具体化 token 并将其具体化到另一个上下文中,在合约和/或链之间移动 token。 取消具体化必须是幂等的:特定的 token 消息必须最多使用一次来取消具体化 token。
如果已实现,则取消具体化:
- 必须从持有者处销毁 token 消息中定义的精确 token
- 不得取消具体化范围限定为同一合约和链的 token 消息。
- 如果 token 消息已被使用,则可能会成功或回退。
- 必须仅在第一次使用特定 token 消息调用
reify
时,才发出Reify
事件
id
返回 token 消息中定义的 token 的 ID。
function id(AbstractTokenMessage calldata message) external view returns (uint256);
可选 - 没有明确定义的 token ID 的抽象 token 合约(例如 ERC-20)可以返回 0
或不实现此方法。
amount
返回 token 消息中定义的 token 的数量。
function amount(AbstractTokenMessage calldata message) external view returns (uint256);
可选 - 没有明确定义的 token 数量的抽象 token 合约(例如 ERC-721)可以返回 0
或不实现此方法。
uri
返回 token 消息中定义的 token 的 URI。
function uri(AbstractTokenMessage calldata message) external view returns (string memory);
可选 - 没有明确定义的 URI 的抽象 token 合约(例如 ERC-20)可以返回 ""
或不实现此方法。
supportsInterface
所有抽象 token 合约必须支持 ERC-165,并在其支持的接口中包含抽象 Token 接口 ID。
事件
Reify
当 token 消息被具体化为 token 时,必须发出 Reify 事件
event Reify(AbstractTokenMessage);
Dereify
当 token 被取消具体化为消息时,必须发出 Dereify 事件
event Dereify(AbstractTokenMessage);
应用于现有 token 标准
与现有 token 标准兼容的抽象 token 必须重载现有的 token 转移函数,以允许从抽象 token 消息中转移。
抽象 ERC-20
interface IAbstractERC20 is IAbstractToken, IERC20, IERC165 {
// 具体化消息,然后转移 token
function transfer(
address to,
uint256 amount,
AbstractTokenMessage calldata message
) external returns (bool);
// 具体化消息,然后从 token 中转移
function transferFrom(
address from,
address to,
uint256 amount,
AbstractTokenMessage calldata message
) external returns (bool);
}
抽象 ERC-721
interface IAbstractERC721 is IAbstractToken, IERC721 {
function safeTransferFrom(
address from,
address to,
uint256 tokenId,
bytes calldata _data,
AbstractTokenMessage calldata message
) external;
function transferFrom(
address from,
address to,
uint256 tokenId,
AbstractTokenMessage calldata message
) external;
}
抽象 ERC-1155
interface IAbstractERC1155 is IAbstractToken, IERC1155 {
function safeTransferFrom(
address from,
address to,
uint256 id,
uint256 amount,
bytes calldata data,
AbstractTokenMessage calldata message
) external;
function safeBatchTransferFrom(
address from,
address to,
uint256[] calldata ids,
uint256[] calldata amounts,
bytes calldata data,
AbstractTokenMessage[] calldata messages
) external;
}
理由
Meta 格式
抽象 token 消息 meta
字段只是一个字节数组,以保持最广泛的可用性。
- 处理抽象 token 的应用程序可以与实现合约交互以获取 token 元数据,而不是解析此字段,因此可读性是次要的
- 可以在实现合约中将字节数组解码为结构体并检查错误
- 未来的 token 标准将包括不可预测的元数据
Proof 格式
定义 proof
字段为纯字节数组时也考虑了类似的因素:
- 此字段的内容可能会有所不同,例如
bytes32
merkle 树节点的数组或 65 字节的签名。 - 字节数组以增加消息大小为代价处理所有潜在的用例。
向后兼容性
未发现向后兼容性问题。
参考实现
请参阅 此处。
安全注意事项
强调了几个问题。
消息丢失
由于 token 消息未保存在链上,因此消息丢失可能导致 token 丢失。向用户发布抽象 token 的应用程序可以自己存储消息,但理想情况下,用户应该能够在他们的加密钱包中存储和交互抽象 token 消息。
授权具体化
只有在 token 消息包含有效性证明时,才能具体化 token 消息。虽然证明机制本身不在本标准的范围之内,但设计证明机制的人员应考虑:
- 是否需要在链上和/或链下审核总供应量?
- 该机制是否需要持续访问密钥(例如数字签名)还是不可变的(例如 merkle 证明)?
- 攻击者是否可以以任何方式阻止具体化原本有效的 token 消息?
非所有者(取消)具体化
非所有者是否可以代表所有者(取消)具体化 token 消息?
优点:支持应用程序应该能够处理此问题,因为一旦存在有效消息,所有者可以随时(取消)具体化该消息 缺点:如果 token 合约在(取消)具体化已使用的消息时回退,则攻击者可以通过抢先交易来损害所有者
抽象 Token 桥双重支出
抽象 token 可用于特定于 token 的桥:
- 使用消息 M 从链 A 中取消具体化 token
- 使用消息 M 在链 B 上具体化 token
由于抽象 token 标准未指定任何跨链消息传递,因此链 A 和链 B 上的抽象 token 合约无法知道消息 M 的(取消)具体化是否已在另一条链上发生。
一个简单的桥会受到双重支出攻击:
- 攻击者请求将他们在链 A 上持有的 token 桥接到链 B
- 桥接机制创建抽象 token 消息 M
- 攻击者在链 B 上具体化消息 M,但不在链 A 上取消具体化消息 M
- 攻击者继续使用 token
需要某种预言机机制来阻止在链 B 上具体化消息 M,直到链 A 上的相应 token 被取消具体化。
版权
版权及相关权利通过 CC0 放弃。
Citation
Please cite this document as:
Chris Walker (@cr-walker) <chris@ckwalker.com>, "ERC-6604: 抽象 Token [DRAFT]," Ethereum Improvement Proposals, no. 6604, March 2023. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-6604.