IDotnsRegistry

Git Source

Title: IDotnsRegistry

Author: Parity

Minimal on-chain registry for hierarchical name ownership and resolution.

Tokenised second-level nodes are owned through the registrar (ERC-721); subnodes are owned directly by the address stored in Record.owner.

Note: security-contact: admin@parity.io

Functions

setSubnodeOwner

Creates or reassigns a subnode and assigns its owner.

Callable only by the current owner of record.parentNode, otherwise

Notes:

  • reverts: NotAuthorised. The new owner address must be non-zero, otherwise

  • reverts: NotAllowed. record.subLabel must be a single canonical DNS label (otherwise @custom:reverts InvalidLabel) and record.parentLabel must be a name path of at most StringUtils.MAX_NAME_PATH_OCTETS octets whose namehash matches record.parentNode (otherwise

  • reverts: ParentLabelMismatch). Subnodes are parent-sovereign: the current record.parentNode owner may reassign or rotate a subnode's resolver at any time without the prior subnode owner's consent. On reassignment the resolver pointer is reset to the protocol-registered default reverse resolver so a prior subnode owner's resolver cannot be inherited by the next holder (records on other resolver contracts are keyed by node and are not cleared by this function; downstream consumers should gate resolver reads on current ownership). Indexes the subnode under the new owner's LabelStore keyed by the namehashed subnode so off-chain consumers can enumerate names per address. Indexing into the owner's LabelStore is governed by record.persist (see @custom:struct SubnodeRecord); the ownership and resolver record is written either way. A record.persist of false defers the store write and is restricted to registered controllers (@custom:function IDotnsRegistrar.controllers), otherwise @custom:reverts NotAuthorised. Creating a subname, as opposed to reassigning one that resolves, requires it to sit at most MAX_SUBNAME_DEPTH levels below its second-level name, otherwise @custom:reverts SubnameTooDeep. A subname still on record but cut off by a clear above it is created again from nothing: its versions are bumped, emitting

  • emits: RecordsCleared and @custom:emits SubnamesCleared, so none of its old records or subnames come back. Emits @custom:emits NewOwner on each successful assignment.

function setSubnodeOwner(SubnodeRecord calldata record) external returns (bytes32 subnode);

setSubnodeResolver

Sets the resolver for an existing subnode.

Callable only by the current owner of record.parentNode, otherwise

Notes:

  • reverts: NotAuthorised. The subnode owner can still update the resolver directly via setResolver. Both entry points emit the same NewResolver(subnode, ...) event, so the parent can silently override a subnode owner's chosen resolver: this is the parent-sovereign hierarchy applied to resolution. Off-chain consumers that surface trust signals to subnode owners should treat any resolver rotation as a re-attestation prompt. record.subLabel must be a single canonical DNS label (otherwise @custom:reverts InvalidLabel) and record.parentLabel must be a name path of at most StringUtils.MAX_NAME_PATH_OCTETS octets whose namehash matches record.parentNode (otherwise

  • reverts: ParentLabelMismatch). The resulting subnode must exist and resolve, otherwise @custom:reverts NotAuthorised. Emits @custom:emits NewResolver on success.

function setSubnodeResolver(SubnodeResolverRecord calldata record) external;

setOwner

Creates or resets a node record for a tokenised second-level registration.

Restricted to the registrar's controllers, otherwise @custom:reverts NotAuthorised. newOwner must be non-zero (otherwise @custom:reverts NotAllowed) and must match the ERC-721 owner reported by the registrar (otherwise

Notes:

  • reverts: NotAuthorised). The function is callable both on a fresh registration and on every reclaim from escrow: each call rewrites records[node].resolver to the protocol-registered default reverse resolver so a prior owner's resolver pointer (and the records keyed under it) cannot be inherited by the next holder across that recycle. A secondary-market ERC-721 transferFrom does not call the registry, so a name sold directly keeps the seller's resolver pointer until the buyer overwrites it. That covers the node itself and not the nodes beneath it: a subname stores its own owner, and @custom:function isAuthorised returns on that owner before consulting the registrar, so after the sale the seller keeps write authority over every subname whose stored owner is still the seller, until the buyer deletes each one through @custom:function deleteSubname or clears them all at once through @custom:function clearSubnames. Reassigning one through

  • function: setSubnodeOwner keeps its records and the subnames beneath it. A subname created for someone else stays with that owner and leaves the seller no write path. The set is derivable from

  • emits: NewOwner, which indexes the parent node. Stores owner = address(0) as a sentinel so reads delegate to IDotnsRegistrar.ownerOf and ERC-721 transfers remain authoritative. Emits

  • emits: NodeTransferred on success.

function setOwner(bytes32 node, address newOwner) external;

setResolver

Sets or clears the resolver for a node.

Callable only by the current node owner, otherwise @custom:reverts NotAuthorised. For tokenised nodes, authorisation falls back to ERC-721 owner / approved / operator-for-all via the registrar. The registry does not validate resolverAddr against any interface or code presence; off-chain consumers must verify resolver shape before trusting reads. Emits @custom:emits NewResolver on success.

function setResolver(bytes32 node, address resolverAddr) external;

Parameters

NameTypeDescription
nodebytes32
resolverAddraddressResolver contract address (zero clears).

parentOf

Returns the recorded parent of a subname.

Zero for a tokenised name, for a node that was never created or was deleted, and for a subname whose parent was never recorded and that has not been linked since (@custom:function linkSubname). A backfill reads it to find the subnames still to link.

function parentOf(bytes32 node) external view returns (bytes32 parent);

Parameters

NameTypeDescription
nodebytes32Node identifier.

Returns

NameTypeDescription
parentbytes32Recorded parent node, or zero.

owner

Returns the owner of a node.

For tokenised nodes the stored owner is the zero sentinel; the implementation falls back to IDotnsRegistrar.ownerOf(uint256(node)). A subname that no longer resolves, because it or a name above it was cleared or deleted, has no owner and returns zero, as do @custom:function resolver, @custom:function recordExists and

Note: function: isAuthorised for it.

function owner(bytes32 node) external view returns (address);

resolver

Returns the resolver of a node, or zero when the node does not resolve.

function resolver(bytes32 node) external view returns (address);

recordExists

Returns whether a node exists and resolves.

function recordExists(bytes32 node) external view returns (bool);

isAuthorised

Returns whether account is authorised to manage node.

For subnodes, authority is the explicit stored owner. For tokenised nodes it is the ERC-721 owner, an address approved for the token, or an operator approved for all of the owner's tokens via the registrar. This is the canonical authorisation check the registry enforces on owner-gated entry points; sibling contracts may consult it so a single registrar-level approval delegates management across the protocol. Returns false for a node that does not exist.

function isAuthorised(bytes32 node, address account) external view returns (bool authorisedFlag);

Parameters

NameTypeDescription
nodebytes32Node identifier.
accountaddressAddress whose authority is being checked.

Returns

NameTypeDescription
authorisedFlagboolTrue when account may manage node.

clearRecords

Clears the records of node on the resolvers that check recordVersion (DotnsResolver and DotnsContentResolver), and points it back at the default resolver.

Callable by anyone @custom:function isAuthorised admits for node, otherwise

Notes:

  • reverts: NotAuthorised. Constant cost whatever the number of records: the node's record version is bumped rather than any record being visited. Subnames are untouched; see @custom:function clearSubnames. Emits @custom:emits RecordsCleared and

  • emits: NewResolver.

function clearRecords(bytes32 node) external;

Parameters

NameTypeDescription
nodebytes32Node to clear.

clearSubnames

Clears every subname beneath node, at any depth, together with their records.

Callable by anyone @custom:function isAuthorised admits for node, otherwise

Note: reverts: NotAuthorised. Constant cost whatever the size or depth of the subtree: the node's subname version is bumped, and a subname resolves only while the version it was created under is still current at every level above it. A cleared subname can be created again through @custom:function setSubnodeOwner, and starts with no records and no subnames. The node's own records are untouched; see @custom:function clearRecords. Emits @custom:emits SubnamesCleared.

function clearSubnames(bytes32 node) external;

Parameters

NameTypeDescription
nodebytes32Node whose subnames are cleared.

clear

Clears every record and every subname of node.

Equivalent to @custom:function clearRecords followed by @custom:function clearSubnames in one call, with the same authority. The escrow calls it on release, once it holds the token, which is what makes a released name start clean for whoever gets it next.

function clear(bytes32 node) external;

Parameters

NameTypeDescription
nodebytes32Node to clear.

deleteSubname

Deletes one subname of parentNode, together with its records and every subname beneath it.

Callable by anyone @custom:function isAuthorised admits for parentNode, otherwise

Notes:

  • reverts: NotAuthorised: subnames are parent-sovereign, so the parent decides, not the subname's holder. The subname's record and parent link are zeroed, releasing their storage deposit to the signer of the transaction; one version slot stays so the subname, created again, starts empty. The subname need not resolve: one a clear has already cut off can still be deleted by whoever controls parentNode now, after a transfer included. Subnames beneath it are cleared but keep their storage, and once their own parent stops resolving nobody can delete them, so freeing every deposit means deleting from the deepest subname up, each by whoever holds that subname's parent, before clearing anything above them. A subname must exist there, otherwise

  • reverts: SubnameNotFound. Emits @custom:emits NewOwner with a zero owner, the deletion convention indexers already follow, and @custom:emits RecordsCleared and

  • emits: SubnamesCleared for the subname.

function deleteSubname(bytes32 parentNode, bytes32 labelhash) external;

Parameters

NameTypeDescription
parentNodebytes32Parent of the subname.
labelhashbytes32Labelhash of the subname under parentNode.

linkSubname

Records the parent of a subname whose parent was never recorded, so that clearing an ancestor reaches it.

Permissionless: the parent is proven by the namehash, since subnode is keccak256(parentNode, labelhash), so a caller cannot link a subname anywhere else. The subname is linked as created under its parent's first subname version, so a subname whose ancestor was cleared before it was linked reads as cleared once it and every level between it and that ancestor are linked; an unlinked level still ends the walk as resolving. Every successful write path that keeps the subname and names its parent (@custom:function setSubnodeOwner, @custom:function setSubnodeResolver) links it as a side effect; this entry point covers the rest. @custom:reverts SubnameNotFound when no subname exists there. A subname already linked is left as it is. Emits

Note: emits: SubnameLinked when it links.

function linkSubname(bytes32 parentNode, bytes32 labelhash) external;

Parameters

NameTypeDescription
parentNodebytes32Parent of the subname.
labelhashbytes32Labelhash of the subname under parentNode.

revokeName

Clears a tokenised name that is being revoked and deletes its record.

Callable only by the registrar, otherwise @custom:reverts NotRegistrar. The registrar calls it from @custom:function IDotnsRegistrar.revoke and

Notes:

  • function: IDotnsRegistrar.revokeIssued before burning the token. Bumps both versions of node, so its records read as unset and every linked subname beneath it stops resolving, including after the name is registered again. A subname that was never linked keeps resolving until @custom:function linkSubname links it. Deletes the record, so the node reads as unregistered (@custom:function owner returns zero) rather than deferring to the burned token. Emits @custom:emits RecordsCleared,

  • emits: SubnamesCleared, @custom:emits NewResolver with a zero resolver and

  • emits: NodeTransferred with a zero owner.

function revokeName(bytes32 node) external;

Parameters

NameTypeDescription
nodebytes32Node of the name being revoked.

revokeSubname

Deletes one subname of parentNode, together with its records and every subname beneath it, on governance's authority rather than the parent's.

Callable only under a Root origin, otherwise @custom:reverts NotRoot. Does what

Notes:

  • function: deleteSubname does, with the same events and the same

  • reverts: SubnameNotFound, whoever holds the parent. A device name, a subname of a container the PoP controller holds, reverts @custom:reverts IssuedByGateway. A second-level name has no stored owner in this contract, so it reverts

  • reverts: SubnameNotFound here and is revoked through

  • function: IDotnsRegistrar.revoke.

function revokeSubname(bytes32 parentNode, bytes32 labelhash) external;

Parameters

NameTypeDescription
parentNodebytes32Parent of the subname.
labelhashbytes32Labelhash of the subname under parentNode.

recordVersion

Returns whether node resolves and the record version its records are written under.

Resolvers stamp each write with recordsVersion and treat a record whose stamp differs, or any record of a node that does not resolve, as unset. A tokenised name resolves while its record exists; a subname resolves while no ancestor has cleared or deleted it. Version 0 is the version every node starts at, and a record stamped 0 matches it, so a node that was never cleared reads every record stamped 0. Checking a subname walks its ancestors, so the cost grows with its depth.

function recordVersion(bytes32 node) external view returns (bool live, uint64 recordsVersion);

Parameters

NameTypeDescription
nodebytes32Node identifier.

Returns

NameTypeDescription
liveboolTrue when node currently resolves.
recordsVersionuint64Record version of node.

Events

NewOwner

Emitted when a new subnode owner is set.

event NewOwner(bytes32 indexed node, bytes32 indexed label, address owner);

Parameters

NameTypeDescription
nodebytes32Parent node.
labelbytes32Labelhash of the created subnode.
owneraddress

NodeTransferred

Emitted when ownership of a node is transferred.

event NodeTransferred(bytes32 indexed node, address owner);

NewResolver

Emitted when a resolver is set or updated.

event NewResolver(bytes32 indexed node, address resolver);

RecordsCleared

Emitted when every record of node stops resolving.

Records live on resolver contracts and cannot be listed, so nothing is erased: the node's record version moves to version and every resolver that stamps its writes with

Note: function: recordVersion reads anything written under an earlier version as unset. Consumers that cache records must drop every cached record of node on this event.

event RecordsCleared(bytes32 indexed node, uint64 version);

Parameters

NameTypeDescription
nodebytes32Node whose records were cleared.
versionuint64Record version the node's records are now written under.

SubnamesCleared

Emitted when every subname beneath node, at any depth, stops resolving.

One event covers the whole subtree, because the subtree cannot be listed. Descendants emit nothing of their own: consumers that index subnames must treat this event as the removal of every node below node, together with their records.

event SubnamesCleared(bytes32 indexed node, uint64 version);

Parameters

NameTypeDescription
nodebytes32Node whose subnames were cleared.
versionuint64Subname version a new child of node is created under.

SubnameLinked

Emitted when a subname created without a recorded parent is linked to it.

event SubnameLinked(bytes32 indexed node, bytes32 indexed parent);

Parameters

NameTypeDescription
nodebytes32The subname.
parentbytes32Its parent node.

Errors

NotAllowed

Thrown when an invalid (zero) address is provided.

error NotAllowed();

NotAuthorised

Thrown when the caller is not authorised.

error NotAuthorised();

NotRegistryController

Thrown when the caller is not the registry controller.

error NotRegistryController();

NodeAlreadyOwned

Thrown when attempting to create a node that already exists.

error NodeAlreadyOwned(bytes32 node);

SubnameNotFound

Thrown when a subname that should exist under a parent does not, or no longer resolves.

error SubnameNotFound(bytes32 node);

Parameters

NameTypeDescription
nodebytes32The subname.

SubnameTooDeep

Thrown when a new subname would sit deeper below its second-level name than MAX_SUBNAME_DEPTH allows.

error SubnameTooDeep(uint256 depth, uint256 maxDepth);

Parameters

NameTypeDescription
depthuint256Depth the subname would have.
maxDepthuint256Deepest allowed.

InvalidLabel

Thrown when a sublabel is not a canonical lowercase ASCII DNS label.

error InvalidLabel();

ParentLabelMismatch

Thrown when the supplied parent label does not match the parent node, or is not a valid name path.

The two cases share one error because both mean the caller's parentLabel cannot be trusted to name parentNode: a mismatched namehash, a malformed segment, or a path over StringUtils.MAX_NAME_PATH_OCTETS. That last bound is on the path in octets, so it limits nesting only through how much of the budget each level spends. It exists because the composed full name is stored in a LabelStore row that has no delete path.

error ParentLabelMismatch();

NotRoot

Thrown when @custom:function revokeSubname is called outside a Root origin.

error NotRoot();

NotRegistrar

Thrown when @custom:function revokeName is called by anything other than the registrar.

error NotRegistrar(address caller);

IssuedByGateway

Thrown when @custom:function revokeSubname targets a device name.

Device names are subnames of containers the PoP controller holds, and the gateway keeps state about them that only @custom:function IDotnsPopGovernance.revokeName clears.

error IssuedByGateway(bytes32 node);

Structs

SubnodeRecord

Record describing a subnode creation request.

struct SubnodeRecord {
    bytes32 parentNode;
    string subLabel;
    string parentLabel;
    address owner;
    bool persist;
}

Properties

NameTypeDescription
parentNodebytes32
subLabelstringHuman readable subnode label e.g "alice".
parentLabelstringCanonical parent name without the TLD suffix e.g. bob or child.bob. At most StringUtils.MAX_NAME_PATH_OCTETS octets. The bound is on the path in octets, so it limits nesting only through how much of that budget each level spends.
owneraddressAddress to assign as owner of the created subnode.
persistboolWhether to index the subnode into the owner's LabelStore, deploying it on demand. When false the ownership and resolver record is still written but the store is left untouched, and an authorised store writer backfills the label later by deploying the owner's store and writing to it. Deferring the store write this way is restricted to registered controllers (@custom:function IDotnsRegistrar.controllers): any other caller must pass true, otherwise @custom:reverts NotAuthorised, because it could never backfill a deferred label. The registry writes the store only on creation or on a reassignment to a new owner, so a later same-owner re-call with true does not backfill it; the authorised writer backfills it directly.

Record

Record describing the state of a node.

struct Record {
    address owner;
    address resolver;
    bool exists;
}

Properties

NameTypeDescription
owneraddressAddress that owns the node, or address(0) sentinel for tokenised nodes.
resolveraddressAddress of the resolver associated with the node.
existsboolWhether the node has been explicitly created.

SubnodeResolverRecord

Record describing a subnode resolver update request.

struct SubnodeResolverRecord {
    bytes32 parentNode;
    string subLabel;
    string parentLabel;
    address resolver;
}

Properties

NameTypeDescription
parentNodebytes32
subLabelstringHuman-readable subnode label e.g "alice".
parentLabelstringCanonical parent name without the TLD suffix e.g bob or child.bob. At most StringUtils.MAX_NAME_PATH_OCTETS octets. The bound is on the path in octets, so it limits nesting only through how much of that budget each level spends.
resolveraddressResolver contract address (zero clears).