DotnsRegistry

Git Source

Inherits: Initializable, UUPSUpgradeable, OwnableUpgradeable, IDotnsRegistry

Title: dotNS Registry

Author: Parity

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

Tokenised second-level nodes store owner == address(0) as a sentinel and defer to IDotnsRegistrar.ownerOf; subnodes carry an explicit owner address in records.

Note: security-contact: admin@parity.io

Constants

MAX_SUBNAME_DEPTH

Deepest a new subname may sit below its second-level name: a subname directly under alice is at depth 1, one under child.alice at depth 2.

Bounds the read cost, not the clear cost. Clearing is constant at any depth, but deciding whether a subname still resolves walks every level above it, and every read of a subname (owner, authority, each resolver record) pays that walk. Checked only when a subname is created, so a deeper subname that already exists keeps working.

uint256 public constant MAX_SUBNAME_DEPTH = 8

State Variables

records

Mapping of node identifiers to records.

mapping(bytes32 node => Record record) private records

protocolRegistry

Protocol-level address registry for all dotNS contracts.

IDotnsProtocolRegistry public protocolRegistry

_versions

Node => its versions.

mapping(bytes32 node => NodeVersions versions) private _versions

_parentOf

Subname => its parent. Zero for tokenised names, for deleted subnames, and for a subname whose parent was never recorded until it is linked (@custom:function linkSubname).

mapping(bytes32 node => bytes32 parent) private _parentOf

__gap

uint256[48] private __gap

Functions

authorised

Restricts access to the current owner of node.

modifier authorised(bytes32 node) ;

onlyRegistrarController

Restricts access to the configured registrar controller.

modifier onlyRegistrarController() ;

constructor

Note: oz-upgrades-unsafe-allow: constructor

constructor() ;

initialize

Initialises the registry.

Callable exactly once via Initializable, otherwise

Notes:

  • reverts: InvalidInitialization. registry must be non-zero, otherwise

  • reverts: NotAllowed.

function initialize(
    address initialOwner,
    IDotnsProtocolRegistry registry
)
    external
    initializer;

Parameters

NameTypeDescription
initialOwneraddressAddress that owns the contract once initialised.
registryIDotnsProtocolRegistryProtocol-level address registry used to resolve sibling contracts.

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

  • 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
    override
    authorised(record.parentNode)
    returns (bytes32 subnode);

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

function setOwner(bytes32 node, address newOwner) external override onlyRegistrarController;

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 newResolver) external override authorised(node);

Parameters

NameTypeDescription
nodebytes32
newResolveraddress

setSubnodeResolver

Sets the resolver for an existing subnode.

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

Note: 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

function setSubnodeResolver(SubnodeResolverRecord calldata record)
    external
    override
    authorised(record.parentNode);

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 override authorised(node);

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 override authorised(node);

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 override authorised(node);

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

  • emits: SubnamesCleared for the subname.

function deleteSubname(
    bytes32 parentNode,
    bytes32 labelhash
)
    external
    override
    authorised(parentNode);

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

function revokeName(bytes32 node) external override;

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.

Reads no msg.sender (see SystemUtils.originIsRoot).

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

function revokeSubname(bytes32 parentNode, bytes32 labelhash) external override;

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 override;

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
    override
    returns (bool live, uint64 recordsVersion);

Parameters

NameTypeDescription
nodebytes32Node identifier.

Returns

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

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 override 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 override returns (address);

_owner

Body of @custom:function owner.

function _owner(bytes32 node) internal view returns (address);

Parameters

NameTypeDescription
nodebytes32Node identifier.

Returns

NameTypeDescription
<none>addressThe address that owns node, or zero when it does not resolve.

resolver

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

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

recordExists

Returns whether a node exists and resolves.

function recordExists(bytes32 node) external view override 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
    override
    returns (bool authorisedFlag);

Parameters

NameTypeDescription
nodebytes32Node identifier.
accountaddressAddress whose authority is being checked.

Returns

NameTypeDescription
authorisedFlagboolTrue when account may manage node.

_writeSubnodeToStore

Writes subnode registration to the owner's LabelStore.

Keys the entry by node (full namehash) rather than labelhash so a single store lookup yields the canonical full name without re-walking the parent chain.

function _writeSubnodeToStore(
    address storeOwner,
    bytes32 node,
    string memory fullName
)
    internal;

_parentNamehash

Computes the namehash of parentLabel rooted at the network's TLD node.

Walks the label right-to-left in calldata using memory-safe assembly to avoid the cost of slicing into intermediate bytes and to keep gas linear in the label depth. Reads the TLD node from the protocol registry, so it is a view rather than pure.

function _parentNamehash(string calldata parentLabel) internal view returns (bytes32 node);

_authorised

Internal authorisation check for node ownership.

Reverts with NotAuthorised when msg.sender is not authorised for node.

function _authorised(bytes32 node) internal view;

_isAuthorised

Canonical authorisation rule for a node, parameterised by account.

Honours the sentinel-zero pattern: if the registry has no explicit owner, fall back to the registrar's ERC-721 owner / approved / operator-for-all chain. This is the single source of truth _authorised and isAuthorised both delegate to.

function _isAuthorised(bytes32 node, address account) internal view returns (bool);

_isLive

Returns whether node currently resolves.

A tokenised name, or a node that was never created or was deleted, carries a zero stored owner, and resolves exactly when its record exists. A subname resolves while the subname version it was created under is still its parent's, and its parent resolves in turn, so the walk ends at the tokenised name above it. Clearing therefore costs the same at any depth, and the depth is paid on reads instead. A subname with no recorded parent ends the walk as resolving, because nothing above it can be checked, until

Note: function: linkSubname or a write through its parent links it.

function _isLive(bytes32 node) internal view returns (bool live);

Parameters

NameTypeDescription
nodebytes32Node identifier.

Returns

NameTypeDescription
liveboolTrue when node resolves.

_labelCount

Returns the number of labels in a dotted name path, which is the depth below its second-level name of a subname created under it.

path has already passed isNamePath, so it has no empty segment and no leading or trailing dot, and the count is one more than the number of dots.

function _labelCount(string calldata path) internal pure returns (uint256 count);

Parameters

NameTypeDescription
pathstringDotted parent path without the TLD.

Returns

NameTypeDescription
countuint256Number of labels in path.

_deleteSubname

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

Shared by @custom:function deleteSubname and @custom:function revokeSubname, which differ only in who may call them. Reads no msg.sender, so it also runs under a Root origin (see SystemUtils.originIsRoot).

function _deleteSubname(bytes32 parentNode, bytes32 labelhash) internal;

Parameters

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

_linkIfUnlinked

Links subnode to parentNode when it is a subname with no recorded parent.

The caller derives subnode from parentNode, which is what proves the parent. The subname is linked under its parent's first subname version, which is the version it was created under: every path that creates a subname records its parent, so one with no recorded parent was created while its parent's subname version was still zero.

function _linkIfUnlinked(bytes32 subnode, bytes32 parentNode) internal;

Parameters

NameTypeDescription
subnodebytes32The subname.
parentNodebytes32Its parent.

_clearRecords

Clears every record of node and returns its resolver pointer to the default.

function _clearRecords(bytes32 node) internal;

Parameters

NameTypeDescription
nodebytes32Node to clear.

_clearRecordVersion

Bumps the record version of node.

function _clearRecordVersion(bytes32 node) internal;

Parameters

NameTypeDescription
nodebytes32Node identifier.

_clearSubnameVersion

Bumps the subname version of node.

function _clearSubnameVersion(bytes32 node) internal;

Parameters

NameTypeDescription
nodebytes32Node identifier.

_onlyRegistrarController

Internal check for registrar-authorised controller privileges.

The registry trusts every controller the registrar trusts. Routing controller authorisation through the registrar's controllers mapping keeps the trust list in one place and lets commit-reveal and PoP controllers coexist without registry reconfiguration on each addition.

function _onlyRegistrarController() internal view;

version

Returns the release this network declares it runs, read live from the protocol registry so every dotNS contract reports one synchronised value.

Mirror of IDotnsProtocolRegistry.protocolVersion, kept under the historical version() selector for ABI compatibility. It reports the network's declaration, not this contract's build; per-contract identity is the codehash declared on the registry.

function version() external view virtual returns (string memory versionString);

Returns

NameTypeDescription
versionStringstringDeclared release as bare semver, empty when never declared.

_authorizeUpgrade

Function that should revert when msg.sender is not authorized to upgrade the contract. Called by {upgradeToAndCall}. Normally, this function will use an xref:access.adoc[access control] modifier such as {Ownable-onlyOwner}.

function _authorizeUpgrade(address) internal onlyOwner {}
function _authorizeUpgrade(address newImplementation) internal override onlyOwner;

Structs

NodeVersions

Versions that let a node, or everything beneath it, be cleared at constant cost.

Neither records nor subnames can be listed, so clearing never visits them. It bumps a version instead, and readers compare against it:

  • records is the version a node's resolver records are stamped with. Bumping it makes every record of the node read as unset.
  • subnames is the version a new child is created under. Bumping it makes every child, and through them every deeper subname, stop resolving.
  • parentSubnames is the parent's subnames at the moment this subname was created. Kept apart from records because @custom:function setOwner rewrites a whole Record, and a version reset to zero would bring back every record written under it.
struct NodeVersions {
    uint64 records;
    uint64 subnames;
    uint64 parentSubnames;
}