⚠️ This EIP is not recommended for general use or implementation as it is likely to change.

# EIP-4834: Hierarchical Domain Standard Source

### A standard for extremely generic name resolution.

Author Pandapip1 https://ethereum-magicians.org/t/erc-4834-hierarchical-domains-standard/8388 Draft Standards Track ERC 2022-02-22 20, 137, 165, 173

## Abstract

This is a standard for generic name resolution with access control. It permits a contract that implements EIP-4834 (referred to as a “domain” hereafter) to be addressable with a more human-friendly name, with a similar purpose to EIP-137 (referred to as “ENS” hereafter).

Unlike ENS, any program that resolves domains should treat domains as equivalent to their resolved addresses. In practice, this means users of DApps that implement EIP-4834 name resolution may specify an address that looks like dai.token instead of 0x6b175474e89094c44da98b954eedeac495271d0f. In this instance, dai.token and 0x6b175474e89094c44da98b954eedeac495271d0f are not different, unlike ENS, where names are simply keys to be hashed and inputted into a storage contract that then resolves the name to an address.

Another notable divergence from ENS is that access control can be arbitrarily complex. ENS domains have a defined owner that has full permission to create, update, and delete subdomains, as well as update the metadata of the domain in resolver contracts. While this can be made more strict by delegating control of the ENS domain to a smart contract, EIP-4834 takes a different approach, and permits any access control patterns to be implemented. This could allow for interesting use-cases, such as an EIP-20 token that could be “redeemed” for subdomains by linking permission to create subdomains to ownership of a specific amount of that token and having that much token burned when a domain is created.

## Motivation

The advantage of this EIP over those existing standards is that it provides a minimal interface that supports name resolution, adds standardized access control, and has a simple architecture. ENS, although useful, has a comparatively complex architecture.

In addition, all domains (including subdomains, TLDs, and even the root itself) are actually implemented as domains, meaning that name resolution is a simple iterative algorithm.

## Specification

The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119.

### Contract Interface

Solidity Interface with NatSpec & OpenZeppelin v4 Interfaces (also available at IDomain.sol):

interface IDomain is IERC165 {
//// Events

/// @notice     Must be emitted when a new subdomain is created (eg. through createDomain.)
/// @param      sender msg.sender for createDomain
/// @param      name name for createDomain
/// @param      subdomain subdomain in createDomain

/// @notice     Must be emitted when the resolved address for a domain is changed (eg. with setDomain)
/// @param      sender msg.sender for setDomain
/// @param      name name for setDomain
/// @param      subdomain subdomain in setDomain
/// @param      oldSubdomain the old subdomain

/// @notice     Must be emitted when a domain is unmapped (eg. with deleteDomain)
/// @param      sender msg.sender for deleteDomain
/// @param      name name for deleteDomain
/// @param      subdomain the old subdomain

//// CRUD

/// @notice     Query if a domain has a subdomain with a given name
/// @param      name The subdomain to query
/// @return     true if the domain has a subdomain with the given name, false otherwise
function hasDomain(string memory name) external view returns (bool);

/// @notice     Fetch the subdomain with a given name
/// @dev        This should revert if hasDomain(name) is false
/// @param      name The subdomain to fetch
/// @return     The subdomain with the given name
function getDomain(string memory name) external view returns (address);

/// @notice     Create a subdomain with a given name
/// @dev        This should revert if canCreateDomain(msg.sender, name, pointer) is false or if the domain exists
/// @param      name The subdomain name to be created
/// @param      subdomain The subdomain to create
function createDomain(string memory name, address subdomain) external;

/// @notice     Update a subdomain with a given name
/// @dev        This should revert if canSetDomain(msg.sender, name, pointer) is false of if the domain doesn't exist
/// @param      name The subdomain name to be updated
/// @param      subdomain The subdomain to set
function setDomain(string memory name, address subdomain) external;

/// @notice     Delete the subdomain with a given name
/// @dev        This should revert if the domain doesn't exist or if
///             canDeleteDomain(msg.sender, name, this) is false
/// @param      name The subdomain to delete
function deleteDomain(string memory name) external;

//// Parent Domain Access Control

/// @notice     Get if an account can create a subdomain with a given name
/// @dev        This must return false if hasDomain(name) is true.
/// @param      updater The account that may or may not be able to create/update a subdomain
/// @param      name The subdomain name that would be created/updated
/// @param      subdomain The subdomain that would be set
/// @return     Whether an account can update or create the subdomain
function canCreateDomain(address updater, string memory name, address subdomain) external view returns (bool);

/// @notice     Get if an account can update or create a subdomain with a given name
/// @dev        This must return false if hasDomain(name) is false.
///             If getDomain(name) is also a domain, this should return false if
///             getDomain(name).canMoveSubdomain(msg.sender, this, subdomain) is false.
/// @param      updater The account that may or may not be able to create/update a subdomain
/// @param      name The subdomain name that would be created/updated
/// @param      subdomain The subdomain that would be set
/// @return     Whether an account can update or create the subdomain
function canSetDomain(address updater, string memory name, address subdomain) external view returns (bool);

/// @notice     Get if an account can delete the subdomain with a given name
/// @dev        This must return false if hasDomain(name) is false.
///             If getDomain(name) is a domain, this should return false if
///             getDomain(name).canDeleteSubdomain(msg.sender, this, subdomain) is false.
/// @param      updater The account that may or may not be able to delete a subdomain
/// @param      name The subdomain to delete
/// @return     Whether an account can delete the subdomain
function canDeleteDomain(address updater, string memory name) external view returns (bool);
}


### Name Resolution

To resolve a name (like "a.b.c"), you first split it by the delimiter (resulting in something like ["a", "b", "c"]). Set domain initially to the root domain.

Pop off the last element of the array ("c"), then call domain.hasDomain(lastElement). If it’s false, then the domain resolution fails. Otherwise, set the domain to domain.getDomain(lastElement). Repeat until the list of split segments is empty.

There is no limit to the amount of nesting that is possible. For example, 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 would be valid if the root contains z, and z contains y, and so on.

Here is a solidity function that resolves a name:

function resolve(string[] calldata splitName, IDomain root) public view returns (address) {
IDomain current = root;
for (uint i = splitName.length - 1; i >= 0; i--) {
// Require that the current domain has a domain
require(current.hasDomain(splitName[i]), "Name resolution failed: );
// Resolve subdomain
current = current.getDomain(splitName[i]);
}
return current;
}


### Optional Extension: Enumerable

Solidity Interface with NatSpec & OpenZeppelin v4 Interfaces (also available at IDomainEnumerable.sol):

interface IDomainEnumerable is IDomain {
/// @notice     Query all subdomains. Must revert if the list of domains is unknown or infinite.
/// @return     The list of all subdomains.
function listDomains() external view returns (string[] memory);
}


### Optional Extension: Access Control

Solidity Interface with NatSpec & OpenZeppelin v4 Interfaces (also available at IDomainAccessControl.sol):

interface IDomainAccessControl is IDomain {
/// @notice     Get if an account can move the subdomain away from the current domain
/// @dev        May be called by canSetDomain of the parent domain - implement access control here!!!
/// @param      updater The account that may be moving the subdomain
/// @param      name The subdomain name
/// @param      parent The parent domain
/// @param      newSubdomain The domain that will be set next
/// @return     Whether an account can update the subdomain
function canMoveSubdomain(address updater, string memory name, IDomain parent, address newSubdomain) external view returns (bool);

/// @notice     Get if an account can unset this domain as a subdomain
/// @dev        May be called by canDeleteDomain of the parent domain - implement access control here!!!
/// @param      updater The account that may or may not be able to delete a subdomain
/// @param      name The subdomain to delete
/// @param      parent The parent domain
/// @return     Whether an account can delete the subdomain
function canDeleteSubdomain(address updater, string memory name, IDomain parent) external view returns (bool);
}


## Rationale

This EIP’s goal, as mentioned in the abstract, is to have a simple interface for resolving names. Here are a few design decisions and why they were made:

• Name resolution algorithm
• Unlike ENS’s resolution algorithm, EIP-4834’s name resolution is fully under the control of the contracts along the resolution path.
• This behavior is more intuitive to users.
• This behavior allows for greater flexibility - e.g. a contract that changes what it resolves to based on the time of day.
• Parent domain access control
• A simple “ownable” implementation was not used because this specification was designed to be as generic as possible. If an ownable implementation is desired, it can be implemented.
• This also gives parent domains the ability to call subdomains’ access control methods so that subdomains, too, can choose whatever access control mechanism they desire
• Subdomain access control
• These methods are included so that subdomains aren’t always limited to their parent domain’s access control
• The root domain can be controlled by a DAO with a non-transferable token with equal shares, a TLD can be controlled by a DAO with an EIP-20 token representing stake, a domain of that TLD can be controlled by a single owner, a subdomain of that domain can be controlled by a single owner linked to an NFT, and so on.
• Subdomain access control functions are suggestions: an ownable domain might implement an owner override, so that perhaps subdomains might be recovered if the keys are lost.

## Backwards Compatibility

There is no compatibility with ENS because ENS domains are indexed by the hash of the entire domain, while EIP-4834 domains are indexed by the subdomain name.

## Reference Implementations

Some of these implementations use some contracts from OpenZeppelin for ease of understandability.

Naive Domain Implementation: NaiveDomain.sol

EIP-173 (Ownable) Domain Implementation: OwnableDomain.sol

## Security Considerations

### Malicious canMoveSubdomain (Black Hole)

#### Description

Moving a subdomain using setDomain is a dangerous operation.

Depending on the parent domain’s implementation, if a malicious new subdomain unexpectedly returns false on canMoveSubdomain, that subdomain can effectively lock the ownership of the domain.

Alternatively, it might return true when it isn’t expected (i.e. a backdoor), allowing the contract owner to take over the domain.

#### Mitigation

Clients should help by warning if canMoveSubdomain or canDeleteSubdomain for the new subdomain changes to false. It is important to note, however, that since these are functions, it is possible for the value to change depending on whether or not it has already been linked. It is also still possible for it to unexpectedly return true. It is therefore recommended to always audit the new subdomain’s source code before calling setDomain.

### Parent Domain Resolution

#### Description

Parent domains have full control of name resolution for their subdomains. If a particular domain is linked to a.b.c, then b.c can, depending on its code, set a.b.c to any domain, and c can set b.c itself to any domain.

#### Examples

The reference Ownable domain implementation: OwnableDomain.sol

#### Mitigation

Before acquiring a domain that has been pre-linked, it is recommended to always have the contract and all the parents up to the root audited.

Copyright and related rights waived via CC0.