ERC-7496: NFT 动态特征
用于动态链上特征的 ERC-721 和 ERC-1155 的扩展
Authors | Adam Montgomery (@montasaurus), Ryan Ghods (@ryanio), 0age (@0age), James Wenzel (@emo-eth), Stephan Min (@stephankmin) |
---|---|
Created | 2023-07-28 |
Discussion Link | https://ethereum-magicians.org/t/erc-7496-nft-dynamic-traits/15484 |
Requires | EIP-165, EIP-721, EIP-1155 |
摘要
本规范引入了一个新的接口,扩展了 ERC-721 和 ERC-1155,定义了用于设置和获取与非同质化代币相关的动态链上特征的方法。这些动态特征可以用来表示属性、特征、可赎回的权利或其他可以随时间变化的属性。通过在链上定义这些特征,它们可以被其他链上合约使用和修改。
动机
非同质化代币的特征值通常存储在链下。这使得在合约代码中查询和变更这些值变得困难。指定在链上设置和获取特征的能力,可以实现新的使用场景,例如赎回链上权利和基于代币特征进行交易。
链上特征可以被合约用于各种不同的场景。例如,一个想要授予代币可消费权益(例如,可赎回物)的合约可以稳健地在链上反映这一点。市场可以在不依赖链下状态并将用户暴露于抢先交易攻击的情况下,允许基于这些代币的特征值进行竞标。本提案背后的动机是用以保护用户免受市场上抢先交易攻击,在这些市场上,用户可以列出具有某些特征的 NFT,并期望在履行过程中维护这些特征。
规范
本文档中的关键词“必须”,“禁止”,“需要”,“应该”,“不应该”,“推荐”,“不推荐”,“可以”和“可选”应按照 RFC 2119 和 RFC 8174 中的描述进行解释。
实现此 EIP 的合约必须包含如下定义的事件、getter 和 setter,并且对于此 ERC 的 4 字节 interfaceId
0xaf332f3e
,必须为 ERC-165 supportsInterface
返回 true
。
interface IERC7496 {
/* Events */
event TraitUpdated(bytes32 indexed traitKey, uint256 tokenId, bytes32 traitValue);
event TraitUpdatedRange(bytes32 indexed traitKey, uint256 fromTokenId, uint256 toTokenId);
event TraitUpdatedRangeUniformValue(bytes32 indexed traitKey, uint256 fromTokenId, uint256 toTokenId, bytes32 traitValue);
event TraitUpdatedList(bytes32 indexed traitKey, uint256[] tokenIds);
event TraitUpdatedListUniformValue(bytes32 indexed traitKey, uint256[] tokenIds, bytes32 traitValue);
event TraitMetadataURIUpdated();
/* Getters */
function getTraitValue(uint256 tokenId, bytes32 traitKey) external view returns (bytes32 traitValue);
function getTraitValues(uint256 tokenId, bytes32[] calldata traitKeys) external view returns (bytes32[] traitValues);
function getTraitMetadataURI() external view returns (string memory uri);
/* Setters */
function setTrait(uint256 tokenId, bytes32 traitKey, bytes32 newValue) external;
}
键 & 名称
traitKey
用于标识一个特征。 traitKey
必须是标识单个特征的唯一 bytes32
值。
traitKey
应该是人类可读的特征名称的 keccak256
哈希值。
元数据
特征元数据对于提供有关合约中存在的哪些特征、如何显示特征名称和值以及其他可选功能的信息是必要的。
特征元数据必须符合指定的模式。
特征元数据 URI 可以是数据 URI 或指向链下资源。
traits
对象中的键必须是唯一的特征名称。如果特征名称是 以 0x
开头的 32 字节十六进制字符串,则它被解释为文字 traitKey
。否则,traitKey
定义为特征名称的 keccak256
哈希值。文字 traitKey
不能与元数据中定义的任何其他特征的 keccak256
哈希值冲突。
displayName
值必须是唯一的,并且不得与元数据中定义的任何其他特征的 displayName
冲突。
validateOnSale
值向市场提供了一个信号,说明在出售代币时如何验证特征值。如果未满足验证标准,市场合约必须不允许销售。如果指定了 validateOnSale
的值,则该值必须是以下之一(或者假定为 none
):
none
:无需验证。requireEq
:bytes32
traitValue
必须等于发出购买要约时的值。requireUintGte
:bytes32
traitValue
必须大于或等于发出购买要约时的值。此比较使用bytes32
值的uint256
表示形式进行。requireUintLte
:bytes32
traitValue
必须小于或等于发出购买要约时的值。此比较使用bytes32
值的uint256
表示形式进行。
请注意,即使本规范要求市场验证所需的特征值,买方和卖方也不能完全依赖市场来执行此操作,并且还必须采取自己的预防措施,在发起交易之前研究当前的特征值。
这是一个指定的模式的例子:
{
"traits": {
"color": {
"displayName": "Color",
"dataType": {
"type": "string",
}
},
"points": {
"displayName": "Total Score",
"dataType": {
"type": "decimal",
"signed": false,
"decimals": 0
},
"validateOnSale": "requireUintGte"
},
"name": {
"displayName": "Name",
"dataType": {
"type": "string",
"minLength": 1,
"maxLength": 32,
"valueMappings": {
"0x0000000000000000000000000000000000000000000000000000000000000000": "Unnamed",
"0x92e75d5e42b80de937d204558acf69c8ea586a244fe88bc0181323fe3b9e3ebf": "🙂"
}
},
"tokenOwnerCanUpdateValue": true
},
"birthday": {
"displayName": "Birthday",
"dataType": {
"type": "epochSeconds",
"valueMappings": {
"0x0000000000000000000000000000000000000000000000000000000000000000": null
}
}
},
"0x77c2fd45bd8bdef5b5bc773f46759bb8d169f3468caab64d7d5f2db16bb867a8": {
"displayName": "🚢 📅",
"dataType": {
"type": "epochSeconds",
"valueMappings": {
"0x0000000000000000000000000000000000000000000000000000000000000000": 1696702201
}
}
}
}
}
string
元数据类型
string
元数据类型允许为特征设置字符串值。
dataType
对象可以定义一个 minLength
和 maxLength
值。如果未指定 minLength
,则假定为 0。如果未指定 maxLength
,则假定为合理的长度。
dataType
对象可以定义一个 valueMappings
对象。如果定义了 valueMappings
对象,则 valueMappings
对象必须是 bytes32
值到 string
或未设置 null
值的映射。 bytes32
值应该是 string
值的 keccak256
哈希值。 string
值必须是唯一的。如果代币的特征更新为 null
,则预计链下索引器会删除代币的特征。
decimal
元数据类型
decimal
元数据类型允许以十进制形式为特征设置数值。
dataType
对象可以定义一个 signed
值。如果未指定 signed
,则假定为 false
。这决定了返回的 traitValue
是被解释为有符号整数还是无符号整数。
dataType
对象可以定义 minValue
和 maxValue
值。这些值应该使用指定的十进制格式化。如果未指定 minValue
,则假定为 signed
和 decimals
的最小值。如果未指定 maxValue
,则假定为 signed
和 decimals
的最大值。
dataType
对象可以定义一个 decimals
值。 decimals
值必须是非负整数。 decimals
值确定链上返回的 traitValue
中包含的小数位数。如果未指定 decimals
,则假定为 0。
dataType
对象可以定义一个 valueMappings
对象。如果定义了 valueMappings
对象,则 valueMappings
对象必须是一个 bytes32
值到数值或未设置 null
值的映射。
boolean
元数据类型
boolean
元数据类型允许为特征设置布尔值。
dataType
对象可以定义一个 valueMappings
对象。如果定义了 valueMappings
对象,则 valueMappings
对象必须是 bytes32
值到 boolean
或未设置 null
值的映射。 boolean
值必须是唯一的。
如果未使用 valueMappings
,则 boolean
的默认特征值应为 false
的 bytes32(0)
和 true
的 bytes32(uint256(1))
(0x0000000000000000000000000000000000000000000000000000000000000001
)。
epochSeconds
元数据类型
epochSeconds
元数据类型允许为特征设置自 Unix 纪元以来的秒数的数值。
dataType
对象可以定义一个 valueMappings
对象。如果定义了 valueMappings
对象,则 valueMappings
对象必须是 bytes32
值到整数或未设置 null
值的映射。
事件
更新特征必须发出以下事件之一:
TraitUpdated
TraitUpdatedRange
TraitUpdatedRangeUniformValue
TraitUpdatedList
TraitUpdatedListUniformValue
对于 Range
事件,fromTokenId
和 toTokenId
必须是连续的代币 ID 范围,并且必须被视为包含范围。
对于 List
事件,tokenIds
可以是任何顺序。
建议在所有代币 ID 的特征值相同时使用 UniformValue
事件,以便链下索引器可以更快地处理批量更新,而无需单独获取每个特征值。
更新特征元数据必须发出事件 TraitMetadataURIUpdated
,以便可以通知链下索引器通过 getTraitMetadataURI()
查询合约以获取最新更改。
setTrait
如果某个特征将 tokenOwnerCanUpdateValue
定义为 true
,则必须允许代币所有者通过调用 setTrait
来更新特征值。
如果代币所有者尝试设置的值无效,则交易必须回滚。如果该值有效,则必须更新该特征值,并且必须发出其中一个 TraitUpdated
事件。
如果该特征为其被设置的期望值定义了一个 valueMappings
条目,则必须使用相应的 traitValue
调用 setTrait
。
理由
本规范的设计主要是一种用于最大灵活性的键值映射。选择此特征接口而不是依赖于使用常规的 getFoo()
和 setFoo()
样式函数的原因是为了在定义、设置和获取特征时实现简洁。否则,合约需要知道 getter 和 setter 函数选择器,包括随之而来的参数。在定义通用但显式的 get 和 set 函数时,函数签名是已知的,只需要特征键和值即可查询和设置值。合约还可以在将来添加新特征,而无需修改合约代码。
特征元数据允许显示和行为的定制。 valueMappings
属性可以定义人类可读的值以增强特征,例如,可以将 0
值的默认标签(例如,如果键为“已赎回”,“0”可以映射到“否”,而“1”可以映射到“是”)。 validateOnSale
属性让代币创建者可以定义在订单创建和履行时应保护哪些特征,以保护最终用户免受抢先交易的侵害。
向后兼容性
作为一个新的 EIP,不存在向后兼容性问题,除了上述规范中的一点,即明确要求链上特征必须覆盖 ERC-721 或 ERC-1155 元数据 URI 指定的任何冲突值。
测试用例
作者已在 assets 文件夹 中包含了涵盖规范功能的 Foundry 测试。
参考实现
作者已在 assets 文件夹 中包含了该规范的参考实现。
安全考虑
暴露在外部的 set* 方法必须经过授权,因此它们不能被所有人调用,而只能被选定的角色或地址调用。
市场不应信任特征的链下状态,因为它们可以被抢先交易。市场应在转移时检查链上特征的当前状态。市场可以检查某些改变 NFT 价值的特征(例如,赎回状态,由具有 validateOnSale
属性的元数据值定义),或者他们可以哈希所有特征值以保证订单创建时的相同状态。
版权
版权和相关权利已通过 CC0 放弃。
Citation
Please cite this document as:
Adam Montgomery (@montasaurus), Ryan Ghods (@ryanio), 0age (@0age), James Wenzel (@emo-eth), Stephan Min (@stephankmin), "ERC-7496: NFT 动态特征 [DRAFT]," Ethereum Improvement Proposals, no. 7496, July 2023. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-7496.