ERC-4834: 分层域名
极其通用的名称解析
Authors | Gavin John (@Pandapip1) |
---|---|
Created | 2022-02-22 |
Table of Contents
摘要
这是一个用于通用名称解析的标准,具有任意复杂的访问控制和解析。它允许实现此 EIP 的合约(以下称为“域”)可以使用更友好的名称进行寻址,其目的与 ERC-137(也称为“ENS”)类似。
动机
此 EIP 优于现有标准的优势在于,它提供了一个支持名称解析的最小接口,添加了标准化访问控制,并且具有简单的架构。ENS 虽然有用,但其架构相对复杂,并且没有标准访问控制。
此外,所有域(包括子域、TLD 甚至根域本身)实际上都是作为域来实现的,这意味着名称解析是一个简单的迭代算法,与 DNS 本身非常相似。
规范
本文档中的关键词“MUST”、“MUST NOT”、“REQUIRED”、“SHALL”、“SHALL NOT”、“SHOULD”、“SHOULD NOT”、“RECOMMENDED”、“MAY”和“OPTIONAL”应按照 RFC 2119 中的描述进行解释。
合约接口
interface IDomain {
/// @notice 查询域是否具有具有给定名称的子域
/// @param name 要查询的子域,从右到左的顺序
/// @return 如果域具有具有给定名称的子域,则为 `true`,否则为 `false`
function hasDomain(string[] memory name) external view returns (bool);
/// @notice 获取具有给定名称的子域
/// @dev 如果 `hasDomain(name)` 为 `false`,则应恢复
/// @param name 要获取的子域,从右到左的顺序
/// @return 具有给定名称的子域
function getDomain(string[] memory name) external view returns (address);
}
名称解析
要解析名称(如 "a.b.c"
),请按分隔符将其拆分(产生类似于 ["a", "b", "c"]
的结果)。最初将 domain
设置为根域,并将 path
设置为空列表。
弹出数组的最后一个元素 ("c"
) 并将其添加到路径,然后调用 domain.hasDomain(path)
。如果它是 false
,则域名解析失败。否则,将域设置为 domain.getDomain(path)
。重复此操作,直到拆分段的列表为空。
可能的嵌套数量没有限制。例如,如果根包含 z
,并且 z
包含 y
,依此类推,则 0.1.2.3.4.5.6.7.8.9.a.b.c.d.e.f.g.h.i.j.k.l.m.n.o.p.q.r.s.t.u.v.w.x.y.z
将是有效的。
这是一个解析名称的 solidity 函数:
function resolve(string[] calldata splitName, IDomain root) public view returns (address) {
IDomain current = root;
string[] memory path = [];
for (uint i = splitName.length - 1; i >= 0; i--) {
// Append to back of list
path.push(splitName[i]);
// Require that the current domain has a domain
require(current.hasDomain(path), "Name resolution failed");
// Resolve subdomain
current = current.getDomain(path);
}
return current;
}
可选扩展:可注册
interface IDomainRegisterable is IDomain {
//// Events
/// @notice 当创建新的子域时必须发出(例如,通过 `createDomain`)
/// @param sender createDomain 的 msg.sender
/// @param name createDomain 的名称
/// @param subdomain createDomain 中的子域
event SubdomainCreate(address indexed sender, string name, address subdomain);
/// @notice 当域的已解析地址更改时必须发出(例如,使用 `setDomain`)
/// @param sender setDomain 的 msg.sender
/// @param name setDomain 的名称
/// @param subdomain setDomain 中的子域
/// @param oldSubdomain 旧子域
event SubdomainUpdate(address indexed sender, string name, address subdomain, address oldSubdomain);
/// @notice 当域被取消映射时必须发出(例如,使用 `deleteDomain`)
/// @param sender deleteDomain 的 msg.sender
/// @param name deleteDomain 的名称
/// @param subdomain 旧子域
event SubdomainDelete(address indexed sender, string name, address subdomain);
//// CRUD
/// @notice 使用给定的名称创建子域
/// @dev 如果 `canCreateDomain(msg.sender, name, pointer)` 为 `false` 或域存在,则应恢复
/// @param name 要创建的子域名
/// @param subdomain 要创建的子域
function createDomain(string memory name, address subdomain) external payable;
/// @notice 使用给定的名称更新子域
/// @dev 如果 `canSetDomain(msg.sender, name, pointer)` 为 `false` 或域不存在,则应恢复
/// @param name 要更新的子域名
/// @param subdomain 要设置的子域
function setDomain(string memory name, address subdomain) external;
/// @notice 删除具有给定名称的子域
/// @dev 如果域不存在或 `canDeleteDomain(msg.sender, name)` 为 `false`,则应恢复
/// @param name 要删除的子域
function deleteDomain(string memory name) external;
//// 父域访问控制
/// @notice 获取帐户是否可以使用给定的名称创建子域
/// @dev 如果 `hasDomain(name)` 为 `true`,则必须返回 `false`。
/// @param updater 可能能够或可能无法创建/更新子域的帐户
/// @param name 将被创建/更新的子域名
/// @param subdomain 将被设置的子域
/// @return 帐户是否可以更新或创建子域
function canCreateDomain(address updater, string memory name, address subdomain) external view returns (bool);
/// @notice 获取帐户是否可以更新或创建具有给定名称的子域
/// @dev 如果 `hasDomain(name)` 为 `false`,则必须返回 `false`。
/// 如果 `getDomain(name)` 也是实现子域访问控制扩展的域,如果 `getDomain(name).canMoveSubdomain(msg.sender, this, subdomain)` 为 `false`,则应返回 `false`。
/// @param updater 可能能够或可能无法创建/更新子域的帐户
/// @param name 将被创建/更新的子域名
/// @param subdomain 将被设置的子域
/// @return 帐户是否可以更新或创建子域
function canSetDomain(address updater, string memory name, address subdomain) external view returns (bool);
/// @notice 获取帐户是否可以删除具有给定名称的子域
/// @dev 如果 `hasDomain(name)` 为 `false`,则必须返回 `false`。
/// 如果 `getDomain(name)` 是实现子域访问控制扩展的域,如果 `getDomain(name).canDeleteSubdomain(msg.sender, this, subdomain)` 为 `false`,则应返回 `false`。
/// @param updater 可能能够或可能无法删除子域的帐户
/// @param name 要删除的子域
/// @return 帐户是否可以删除子域
function canDeleteDomain(address updater, string memory name) external view returns (bool);
}
可选扩展:可枚举
interface IDomainEnumerable is IDomain {
/// @notice 查询所有子域。如果域的数量未知或无限,则必须恢复。
/// @return 具有给定索引的子域。
function subdomainByIndex(uint256 index) external view returns (string memory);
/// @notice 获取子域的总数。如果域的数量未知或无限,则必须恢复。
/// @return 子域的总数。
function totalSubdomains() external view returns (uint256);
}
可选扩展:访问控制
interface IDomainAccessControl is IDomain {
/// @notice 获取帐户是否可以将子域从当前域移开
/// @dev 可能由父域的 `canSetDomain` 调用 - 在此处实现访问控制!!!
/// @param updater 可能正在移动子域的帐户
/// @param name 子域名
/// @param parent 父域
/// @param newSubdomain 接下来要设置的域
/// @return 帐户是否可以更新子域
function canMoveSubdomain(address updater, string memory name, IDomain parent, address newSubdomain) external view returns (bool);
/// @notice 获取帐户是否可以取消设置此域作为子域
/// @dev 可能由父域的 `canDeleteDomain` 调用 - 在此处实现访问控制!!!
/// @param updater 可能能够或可能无法删除子域的帐户。
/// @param name 要删除的子域
/// @param parent 父域
/// @return 帐户是否可以删除子域
function canDeleteSubdomain(address updater, string memory name, IDomain parent) external view returns (bool);
}
理由
如摘要中所述,此 EIP 的目标是拥有一个用于解析名称的简单接口。以下是一些设计决策以及做出这些决策的原因:
- 名称解析算法
- 与 ENS 的解析算法不同,此 EIP 的名称解析完全由解析路径上的合约控制。
- 这种行为对用户来说更直观。
- 这种行为允许更大的灵活性 - 例如,一个根据一天中的时间更改其解析内容的合约。
- 父域访问控制
- 没有使用简单的“可拥有”接口,因为此规范旨在尽可能通用。如果需要可拥有实现,则可以实现它。
- 这也使父域能够调用子域的访问控制方法,以便子域也可以选择他们想要的任何访问控制机制
- 子域访问控制
- 包含这些方法是为了使子域不总是受限于其父域的访问控制
- 根域可以由具有具有相等份额的不可转让令牌的 DAO 控制,TLD 可以由具有表示股份的令牌的 DAO 控制,该 TLD 的域可以由单个所有者控制,该域的子域可以由链接到 NFT 的单个所有者控制,依此类推。
- 子域访问控制功能是建议:可拥有的域可能会实现所有者覆盖,因此如果密钥丢失,可能会恢复子域。
向后兼容性
此 EIP 足够通用以支持 ENS,但 ENS 不够通用以支持此 EIP。
安全考虑
恶意 canMoveSubdomain(黑洞)
描述:恶意 canMoveSubdomain
使用 setDomain
移动子域是一种潜在的危险操作。
根据父域的实现,如果恶意的新的子域意外地在 canMoveSubdomain
上返回 false
,则该子域可以有效地锁定域的所有权。
或者,它可能会在不希望的时候返回 true
(即后门),允许合约所有者接管该域。
缓解措施:恶意 canMoveSubdomain
如果新子域的 canMoveSubdomain
或 canDeleteSubdomain
更改为 false
,客户端应通过发出警告来提供帮助。但重要的是要注意,由于这些是函数,因此该值可能会根据是否已链接而变化。它仍然有可能意外地返回 true。因此,建议在调用 setDomain
之前始终审计新子域的源代码。
父域解析
描述:父域解析
父域完全控制其子域的名称解析。如果一个特定的域名链接到 a.b.c
,那么 b.c
可以根据其代码,将 a.b.c
设置为任何域名,而 c
可以将 b.c
本身设置为任何域名。
缓解措施:父域解析
在获取已预先链接的域之前,建议始终审核合约以及直到根目录的所有父目录。
版权
在 CC0 下放弃版权和相关权利。
Citation
Please cite this document as:
Gavin John (@Pandapip1), "ERC-4834: 分层域名," Ethereum Improvement Proposals, no. 4834, February 2022. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-4834.