Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,12 @@ WHITELIST_OPERATOR=0xd908e5a6c88e9263f8fd0756bd0b77916008bb72
# RPC_URL=https://rpc.example.org
RPC_URL=paseo_local

# Required. Bare TLD label the protocol registry is initialised with, without the
# leading dot (for example `dot`, `paseo`, or `test`). Distinct networks can share
# a chain id, so the TLD is set explicitly per deploy rather than derived. The
# deploy aborts if this is unset or empty, and no setter can correct it afterwards.
DOTNS_TLD=dot

# Optional. Manifest subdirectory under `deployments/`. Set it when two chains
# share a chain id (for example a previewnet and a next environment both reach
# 420420417 through the local adapter) so each keeps its own manifest. Defaults
Expand Down
4 changes: 4 additions & 0 deletions .github/workflows/deploy-contracts.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ jobs:
env:
ACCOUNT_NAME: anvil-default-7
ACCOUNT_PASSWORD: anvil-default-password
# Required by the deploy scripts (BaseDeployer.tldLabel). The TLD is passed
# to DotnsProtocolRegistry.initialize as init calldata, which does not affect
# any CREATE3 address, so the reproduction still lands the canonical set.
DOTNS_TLD: dot
WHITELIST_OPERATOR: "0x14dC79964da2C08b23698B3D3cc7Ca32193d9955"
FACTORY_DEPLOYER: "0xd498F7BC5bB3cBdd0068c3deEbbd814b69C3F164"
PINNED_FACTORY: "0x8533c79E058c5a6489CAFeCA86dc600E029D75f5"
Expand Down
4 changes: 2 additions & 2 deletions contracts/escrow/DotnsNameEscrow.sol
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ import {IDotnsProtocolRegistry} from "../registry/IDotnsProtocolRegistry.sol";
import {DotnsConstants} from "../utils/DotnsConstants.sol";

/// @title Dotns Name Escrow
/// @notice Holds refundable deposits for .dot names and manages the release/reclaim lifecycle.
/// @custom:security-contact admin@parity.io
/// @notice Holds refundable deposits for registered names and manages the release/reclaim
/// lifecycle. @custom:security-contact admin@parity.io
contract DotnsNameEscrow is
Initializable,
UUPSUpgradeable,
Expand Down
2 changes: 1 addition & 1 deletion contracts/escrow/IDotnsNameEscrow.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
pragma solidity ^0.8.34;

/// @title Dotns Name Escrow Interface
/// @notice Escrows refundable deposits for .dot registrations and manages the release lifecycle.
/// @notice Escrows refundable deposits for registered names and manages the release lifecycle.
/// @custom:security-contact admin@parity.io
interface IDotnsNameEscrow {
/// @notice Parameters for recording a deposit position.
Expand Down
12 changes: 6 additions & 6 deletions contracts/registrars/DotnsPopController.sol
Original file line number Diff line number Diff line change
Expand Up @@ -430,7 +430,7 @@ contract DotnsPopController is
returns (address)
{
bytes32 labelhash = LabelUtils.labelhashMemory(label);
bytes32 node = LabelUtils.namehash(labelhash);
bytes32 node = LabelUtils.namehashUnder(protocolRegistry.tldNode(), labelhash);
if (store == address(0)) {
store = factory.deployLabelStoreFor(user);
}
Expand Down Expand Up @@ -651,7 +651,7 @@ contract DotnsPopController is
/// @param label Bare DNS label (no TLD); the TLD is appended on write.
function _writeRecord(address store, bytes32 node, string memory label) internal {
if (ILabelStore(store).isLocked(node)) return;
ILabelStore(store).storeLabel(node, string.concat(label, DotnsConstants.TLD));
ILabelStore(store).storeLabel(node, string.concat(label, protocolRegistry.tld()));
}

/// @notice Appends a deferred binding for `user` and adds them to the enumeration set.
Expand Down Expand Up @@ -796,22 +796,22 @@ contract DotnsPopController is
/// @notice Validates a lite-person `NAMEXX` label and derives `(labelhash, node)`.
function _validateLiteLabel(string memory liteLabel)
internal
pure
view
returns (bytes32 labelhash, bytes32 node)
{
require(liteLabel.isLitePersonLabelMemory(), InvalidLiteLabel());
labelhash = LabelUtils.labelhashMemory(liteLabel);
node = LabelUtils.namehash(labelhash);
node = LabelUtils.namehashUnder(protocolRegistry.tldNode(), labelhash);
}

/// @notice Validates a base (full-person) DNS label and derives `(labelhash, node)`.
function _validateBaseLabel(string calldata baseLabel)
internal
pure
view
returns (bytes32 labelhash, bytes32 node)
{
require(baseLabel.isSingleLabel(), InvalidBaseLabel());
(labelhash, node) = LabelUtils.deriveNode(baseLabel);
(labelhash, node) = LabelUtils.deriveNode(protocolRegistry.tldNode(), baseLabel);
}

/// @notice Reverts when a non-empty chat key is not exactly `CHAT_KEY_LENGTH` bytes.
Expand Down
11 changes: 7 additions & 4 deletions contracts/registrars/DotnsRegistrar.sol
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ contract DotnsRegistrar is
function labelOf(uint256 tokenId) external view override returns (string memory) {
address holder = _ownerOf(tokenId);
if (holder == address(0)) return "";
return LabelUtils.stripDotTld(_readLabel(tokenId, holder));
return LabelUtils.stripTld(protocolRegistry.tld(), _readLabel(tokenId, holder));
}

/// @inheritdoc IDotnsRegistrar
Expand Down Expand Up @@ -352,7 +352,7 @@ contract DotnsRegistrar is
/// the registry would have already broken every other call site).
function _writeOwnerLabel(address owner, uint256 tokenId, string calldata label) private {
_storeFactory()
.writeLabel(owner, bytes32(tokenId), string.concat(label, DotnsConstants.TLD));
.writeLabel(owner, bytes32(tokenId), string.concat(label, protocolRegistry.tld()));
}

/// @notice Quotes the friction fee required for a transfer.
Expand Down Expand Up @@ -404,8 +404,11 @@ contract DotnsRegistrar is
// No label means there is no label-derived price to charge against; treat as a zero-fee
// move (typical of gateway-cold PoP mints that have not yet claimed a `LabelStore`).
if (bytes(fullName).length == 0) return (0, 0);
string memory label = LabelUtils.stripDotTld(fullName);
if (bytes(label).length == 0) return (0, 0);
// A stored full name always carries the registry TLD suffix, so an empty strip means the
// name is malformed for this registry (a wrong or missing suffix); fail loudly rather than
// mis-pricing the move as zero-fee.
string memory label = LabelUtils.stripTld(registry.tld(), fullName);
require(bytes(label).length != 0, InvalidLabel());

reachFloor =
IPopRules(registry.get(DotnsConstants.POP_RULES)).transferFloor(label, from, to);
Expand Down
12 changes: 6 additions & 6 deletions contracts/registrars/DotnsRegistrarController.sol
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,12 @@ import {RegistrationUtils} from "../utils/RegistrationUtils.sol";
import {StoreUtils} from "../utils/StoreUtils.sol";

/// @title Dotns Registrar Controller
/// @notice Allocates .dot labels using a commit reveal scheme.
/// @notice Allocates top-level labels using a commit reveal scheme.
/// @dev Orchestrates allocation, PoP validation, pricing enforcement, forward registry
/// wiring, default reverse resolution, and immutable store writing.
///
/// Tokenisation: the minted ERC721 tokenId is `uint256(node)`, where
/// `node = namehash(DOT_NODE, labelhash)`. The registry stores a sentinel owner
/// `node = namehash(tldNode, labelhash)`. The registry stores a sentinel owner
/// (`address(0)`) for tokenised nodes and derives ownership from the ERC721 registrar for
/// authorisation.
/// @custom:security-contact admin@parity.io
Expand Down Expand Up @@ -324,12 +324,12 @@ contract DotnsRegistrarController is
/// the policy minimum" from "shape-valid but already minted".
function _validatedLabelNode(string calldata label)
internal
pure
view
returns (bytes32 labelhash, bytes32 node)
{
require(label.isSingleLabel(), InvalidLabel());
require(bytes(label).length >= 3, LabelTooShort(label));
(labelhash, node) = LabelUtils.deriveNode(label);
(labelhash, node) = LabelUtils.deriveNode(protocolRegistry.tldNode(), label);
}

function _requireAvailableLabel(string calldata label)
Expand Down Expand Up @@ -395,13 +395,13 @@ contract DotnsRegistrarController is
// @custom:function register) so the registry's `ownerOf` check sees the new holder.
IStoreFactory factory =
IStoreFactory(protocolRegistry.get(DotnsConstants.STORE_FACTORY));
string memory fullName = string.concat(registration.label, DotnsConstants.TLD);
string memory fullName = string.concat(registration.label, protocolRegistry.tld());
labelStore = factory.writeLabel(registration.owner, node, fullName);
}

if (setReverseRecord) {
reverse.setReverseName(
registration.owner, string.concat(registration.label, DotnsConstants.TLD)
registration.owner, string.concat(registration.label, protocolRegistry.tld())
);
}

Expand Down
2 changes: 1 addition & 1 deletion contracts/registrars/IDotnsRegistrarController.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {IDotnsController} from "./IDotnsController.sol";
import {IPopRules} from "../pop/IPopRules.sol";

/// @title Dotns Registrar Controller
/// @notice Interface for registering .dot labels using a commit reveal scheme.
/// @notice Interface for registering top-level labels using a commit reveal scheme.
/// @dev Defines allocation only; forward resolution, reverse lookup, pricing mechanics, PoP
/// validation, and store writing are handled by external contracts. Users commit a hash of
/// registration parameters and, after a minimum delay, reveal the same parameters to register.
Expand Down
41 changes: 36 additions & 5 deletions contracts/registry/DotnsProtocolRegistry.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,27 @@ import {
} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";

import {IDotnsProtocolRegistry} from "./IDotnsProtocolRegistry.sol";
import {LabelUtils} from "../utils/LabelUtils.sol";
import {StringUtils} from "../utils/StringUtils.sol";

/// @title Dotns Protocol Registry
/// @author Parity
/// @notice Upgradeable address registry for all DotNS protocol contracts.
/// @notice Upgradeable address registry for all DotNS protocol contracts, and the authority for
/// the network's top-level domain.
/// @dev Single source of truth for sibling-contract lookups. All siblings resolve each other via
/// well-known `bytes32` constants in `DotnsConstants` rather than holding direct addresses,
/// so an upgrade or rewire only mutates this contract.
/// so an upgrade or rewire only mutates this contract. The TLD node and suffix are set once
/// at initialisation and read live by every consumer, so a network runs one TLD without
/// recompiling its contracts.
/// @custom:security-contact admin@parity.io
contract DotnsProtocolRegistry is
Initializable,
UUPSUpgradeable,
OwnableUpgradeable,
IDotnsProtocolRegistry
{
using StringUtils for string;

/// @notice Address stored for each well-known protocol key.
mapping(bytes32 key => address addr) private _addresses;

Expand All @@ -30,18 +37,32 @@ contract DotnsProtocolRegistry is
/// multiple keys without being treated as deregistered when only one key is rewired.
mapping(address addr => uint256 refcount) private _registeredRefcount;

/// @notice Namehash of the TLD node, `namehash(0, keccak256(bytes(tldLabel)))`.
bytes32 private _tldNode;

/// @notice TLD suffix including the leading dot, e.g. `.dot`.
string private _tld;

uint256[50] private __gap;

/// @custom:oz-upgrades-unsafe-allow constructor
constructor() {
_disableInitializers();
}

/// @notice Initialises the protocol registry.
/// @notice Initialises the protocol registry and fixes the network's TLD.
/// @dev Callable exactly once via `Initializable`, otherwise
/// @custom:reverts InvalidInitialization. Sets the deployer as owner.
function initialize() external initializer {
/// @custom:reverts InvalidInitialization. Sets the deployer as owner. `tldLabel` is the
/// bare label without a dot (e.g. `dot`, `paseo`); it must be a single DNS label,
/// otherwise @custom:reverts InvalidTld. The TLD is fixed here because changing it after
/// names exist would reroot every node.
/// @param tldLabel Bare TLD label, without the leading dot.
function initialize(string calldata tldLabel) external initializer {
__Ownable_init(msg.sender);

require(tldLabel.isSingleLabel(), InvalidTld());
_tldNode = LabelUtils.namehashUnder(bytes32(0), LabelUtils.labelhash(tldLabel));
_tld = string.concat(".", tldLabel);
}

/// @inheritdoc IDotnsProtocolRegistry
Expand Down Expand Up @@ -70,6 +91,16 @@ contract DotnsProtocolRegistry is
return addr != address(0) && _registeredRefcount[addr] > 0;
}

/// @inheritdoc IDotnsProtocolRegistry
function tldNode() external view override returns (bytes32 node) {
return _tldNode;
}

/// @inheritdoc IDotnsProtocolRegistry
function tld() external view override returns (string memory suffix) {
return _tld;
}

/// @notice Returns implementation version.
/// @return versionString Current version string.
function version() external pure virtual returns (string memory versionString) {
Expand Down
12 changes: 7 additions & 5 deletions contracts/registry/DotnsRegistry.sol
Original file line number Diff line number Diff line change
Expand Up @@ -101,12 +101,13 @@ contract DotnsRegistry is Initializable, UUPSUpgradeable, OwnableUpgradeable, ID

if (newOwner != previousOwner) {
string memory fullName =
string.concat(subLabel, ".", parentLabel, DotnsConstants.TLD);
string.concat(subLabel, ".", parentLabel, protocolRegistry.tld());
_writeSubnodeToStore(newOwner, subnode, fullName);
}
} else {
records[subnode] = Record({owner: newOwner, resolver: reverseResolver, exists: true});
string memory fullName = string.concat(subLabel, ".", parentLabel, DotnsConstants.TLD);
string memory fullName =
string.concat(subLabel, ".", parentLabel, protocolRegistry.tld());
_writeSubnodeToStore(newOwner, subnode, fullName);
}

Expand Down Expand Up @@ -209,15 +210,16 @@ contract DotnsRegistry is Initializable, UUPSUpgradeable, OwnableUpgradeable, ID
factory.writeLabel(storeOwner, node, fullName);
}

/// @notice Computes the namehash of `parentLabel` rooted at the configured TLD.
/// @notice Computes the namehash of `parentLabel` rooted at the network's TLD node.
/// @dev 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.
function _parentNamehash(string calldata parentLabel) internal pure returns (bytes32 node) {
/// 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) {
bytes calldata labels = bytes(parentLabel);
uint256 end = labels.length;
require(end != 0, ParentLabelMismatch());

node = DotnsConstants.DOT_NODE;
node = protocolRegistry.tldNode();

while (true) {
uint256 start = end;
Expand Down
15 changes: 14 additions & 1 deletion contracts/registry/IDotnsProtocolRegistry.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ pragma solidity ^0.8.34;
/// @notice Interface for the DotNS protocol-level address registry.
/// @dev Single source of truth for sibling lookups. Contracts resolve each other via well-known
/// `bytes32` constants in `DotnsConstants` so an upgrade or rewire only mutates the
/// registry, never the consumers.
/// registry, never the consumers. The registry also holds the network's top-level domain,
/// so every consumer reads one TLD rather than compiling its own.
/// @custom:security-contact admin@parity.io
interface IDotnsProtocolRegistry {
/// @notice Emitted when a protocol address is set or updated.
Expand All @@ -15,6 +16,9 @@ interface IDotnsProtocolRegistry {
/// @notice Thrown when a zero address is provided where one is not allowed.
error ZeroAddress();

/// @notice Thrown when the TLD label supplied at initialisation is not a single DNS label.
error InvalidTld();

/// @notice Returns the address stored for a given key.
/// @dev Returns `address(0)` when the key is unset; callers must validate when non-zero is
/// required.
Expand All @@ -35,4 +39,13 @@ interface IDotnsProtocolRegistry {
/// registered return true. Treats `address(0)` as never registered regardless of
/// refcount.
function isRegisteredAddress(address addr) external view returns (bool registered);

/// @notice Returns the namehash of the network's TLD node.
/// @dev `namehash(0, keccak256(bytes(tldLabel)))`, fixed at initialisation. Consumers use it
/// as the root parent when deriving a name's node.
function tldNode() external view returns (bytes32 node);

/// @notice Returns the network's TLD suffix, including the leading dot (e.g. `.dot`).
/// @dev Fixed at initialisation. Consumers append it when rendering a label as a full name.
function tld() external view returns (string memory suffix);
}
4 changes: 2 additions & 2 deletions contracts/registry/IDotnsRegistry.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ pragma solidity ^0.8.34;
interface IDotnsRegistry {
/// @notice Record describing a subnode creation request.
/// @param subLabel Human readable subnode label e.g "alice".
/// @param parentLabel Canonical parent name without the `.dot` suffix e.g. bob or child.bob.
/// @param parentLabel Canonical parent name without the TLD suffix e.g. bob or child.bob.
/// @param owner Address to assign as owner of the created subnode.
struct SubnodeRecord {
bytes32 parentNode;
Expand Down Expand Up @@ -60,7 +60,7 @@ interface IDotnsRegistry {

/// @notice Record describing a subnode resolver update request.
/// @param subLabel Human-readable subnode label e.g "alice".
/// @param parentLabel Canonical parent name without `.dot` suffix e.g bob or child.bob.
/// @param parentLabel Canonical parent name without the TLD suffix e.g bob or child.bob.
/// @param resolver Resolver contract address (zero clears).
struct SubnodeResolverRecord {
bytes32 parentNode;
Expand Down
12 changes: 6 additions & 6 deletions contracts/resolvers/DotnsReverseResolver.sol
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import {DotnsConstants} from "../utils/DotnsConstants.sol";
import {LabelUtils} from "../utils/LabelUtils.sol";

/// @title Dotns Reverse Resolver
/// @notice Resolves an address to its associated .dot name.
/// @notice Resolves an address to its associated name under the network TLD.
/// @dev Writes are gated on a fixed writer address resolved from the protocol
/// registry (the registrar or its controller), not on node ownership.
/// Reverse records bind to an EOA rather than a registry node, so authority
Expand Down Expand Up @@ -72,12 +72,12 @@ contract DotnsReverseResolver is
/// @inheritdoc IDotnsReverseResolver
function claimReverseRecord(string calldata label) external override {
bytes32 labelhash = LabelUtils.labelhash(label);
uint256 tokenId = uint256(LabelUtils.namehash(labelhash));
uint256 tokenId = uint256(LabelUtils.namehashUnder(protocolRegistry.tldNode(), labelhash));

IERC721 registrar = IERC721(protocolRegistry.get(DotnsConstants.REGISTRAR));
require(registrar.ownerOf(tokenId) == msg.sender, NotNameOwner(msg.sender, tokenId));

string memory fullName = string.concat(label, DotnsConstants.TLD);
string memory fullName = string.concat(label, protocolRegistry.tld());
reverseNames[msg.sender] = fullName;
emit ReverseNameSet(msg.sender, fullName);
}
Expand All @@ -87,13 +87,13 @@ contract DotnsReverseResolver is
string memory stored = reverseNames[addr];
if (bytes(stored).length == 0) return "";

// Strip the `.dot` suffix and validate against current ownership so a transferred-away
// Strip the TLD suffix and validate against current ownership so a transferred-away
// name never resolves under a stale reverse record.
string memory label = LabelUtils.stripDotTld(stored);
string memory label = LabelUtils.stripTld(protocolRegistry.tld(), stored);
if (bytes(label).length == 0) return "";

bytes32 labelhash = LabelUtils.labelhashMemory(label);
uint256 tokenId = uint256(LabelUtils.namehash(labelhash));
uint256 tokenId = uint256(LabelUtils.namehashUnder(protocolRegistry.tldNode(), labelhash));

IERC721 registrar = IERC721(protocolRegistry.get(DotnsConstants.REGISTRAR));
try registrar.ownerOf(tokenId) returns (address currentOwner) {
Expand Down
Loading
Loading