Skip to content

Commit

Permalink
Merge pull request #5 from ensdomains/rootregistry
Browse files Browse the repository at this point in the history
Root registry & ETH registry improvements
  • Loading branch information
Arachnid authored Aug 30, 2024
2 parents 9d02b90 + 75606e8 commit d6f5f7d
Show file tree
Hide file tree
Showing 8 changed files with 233 additions and 72 deletions.
5 changes: 0 additions & 5 deletions contracts/src/registry/BaseRegistry.sol
Original file line number Diff line number Diff line change
Expand Up @@ -62,11 +62,6 @@ abstract contract BaseRegistry is IRegistry, ERC1155Singleton {
|| interfaceId == type(IRegistry).interfaceId || super.supportsInterface(interfaceId);
}

function _mint(uint256 tokenId, address owner, IRegistry registry, uint96 flags) internal {
_mint(owner, tokenId, 1, "");
datastore.setSubregistry(tokenId, address(registry), flags);
}

/**
*
* IRegistry functions *
Expand Down
56 changes: 17 additions & 39 deletions contracts/src/registry/ETHRegistry.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,16 @@ import {IERC1155Singleton} from "./IERC1155Singleton.sol";
import {IRegistry} from "./IRegistry.sol";
import {IRegistryDatastore} from "./IRegistryDatastore.sol";
import {BaseRegistry} from "./BaseRegistry.sol";
import {LockableRegistry} from "./LockableRegistry.sol";

contract ETHRegistry is BaseRegistry, AccessControl {
contract ETHRegistry is LockableRegistry, AccessControl {
bytes32 public constant REGISTRAR_ROLE = keccak256("REGISTRAR_ROLE");
uint32 public constant FLAGS_MASK = 0x7;
uint32 public constant FLAG_SUBREGISTRY_LOCKED = 0x1;
uint32 public constant FLAG_RESOLVER_LOCKED = 0x2;
uint32 public constant FLAG_FLAGS_LOCKED = 0x4;

error NameAlreadyRegistered(string label);
error NameExpired(uint256 tokenId);
error CannotReduceExpiration(uint64 oldExpiration, uint64 newExpiration);

constructor(IRegistryDatastore _datastore) BaseRegistry(_datastore) {
constructor(IRegistryDatastore _datastore) LockableRegistry(_datastore) {
_grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
}

Expand All @@ -43,21 +40,22 @@ contract ETHRegistry is BaseRegistry, AccessControl {
return super.ownerOf(tokenId);
}

function register(string calldata label, address owner, IRegistry registry, uint32 flags, uint64 expires)
function register(string calldata label, address owner, IRegistry registry, uint96 flags, uint64 expires)
public
onlyRole(REGISTRAR_ROLE)
returns (uint256 tokenId)
{
flags &= FLAGS_MASK;
tokenId = (uint256(keccak256(bytes(label))) & ~uint256(FLAGS_MASK)) | flags;
flags = (flags & FLAGS_MASK) | (uint96(expires) << 32);

(, uint96 oldFlags) = datastore.getSubregistry(tokenId);
uint64 oldExpiry = uint64(oldFlags >> 32);
if (oldExpiry >= block.timestamp) {
revert NameAlreadyRegistered(label);
}

_mint(tokenId, owner, registry, uint96(flags) | (uint96(expires) << 32));
_mint(owner, tokenId, 1, "");
datastore.setSubregistry(tokenId, address(registry), flags);
emit NewSubname(label);
return tokenId;
}
Expand All @@ -79,61 +77,41 @@ contract ETHRegistry is BaseRegistry, AccessControl {
return (uint64(_flags >> 32), uint32(_flags));
}

function lock(uint256 tokenId, uint32 flags)
function lock(uint256 tokenId, uint96 flags)
external
onlyTokenOwner(tokenId)
withSubregistryFlags(tokenId, FLAG_FLAGS_LOCKED, 0)
returns (uint256 newTokenId)
{
(address subregistry, uint96 oldFlags) = datastore.getSubregistry(tokenId);
uint96 newFlags = oldFlags | (flags & FLAGS_MASK);
if (newFlags != oldFlags) {
uint96 newFlags = _lock(tokenId, flags);
newTokenId = (tokenId & ~uint256(FLAGS_MASK)) | (newFlags & FLAGS_MASK);
if (tokenId != newTokenId) {
address owner = ownerOf(tokenId);
_burn(owner, tokenId, 1);
newTokenId = (tokenId & ~uint256(FLAGS_MASK)) | (newFlags & FLAGS_MASK);
_mint(newTokenId, owner, IRegistry(subregistry), newFlags);
} else {
newTokenId = tokenId;
_mint(owner, newTokenId, 1, "");
}
}

function setSubregistry(uint256 tokenId, IRegistry registry)
external
onlyTokenOwner(tokenId)
withSubregistryFlags(tokenId, FLAG_SUBREGISTRY_LOCKED, 0)
{
(, uint96 flags) = datastore.getSubregistry(tokenId);
datastore.setSubregistry(tokenId, address(registry), flags);
}

function setResolver(uint256 tokenId, address resolver)
external
onlyTokenOwner(tokenId)
withSubregistryFlags(tokenId, FLAG_RESOLVER_LOCKED, 0)
{
(, uint96 flags) = datastore.getResolver(tokenId);
datastore.setResolver(tokenId, resolver, flags);
}

function supportsInterface(bytes4 interfaceId) public view override(BaseRegistry, AccessControl) returns (bool) {
return interfaceId == type(IRegistry).interfaceId || super.supportsInterface(interfaceId);
}

function getSubregistry(string calldata label) external view virtual override returns (IRegistry) {
(address subregistry, uint96 flags) = datastore.getSubregistry(uint256(keccak256(bytes(label))));
uint64 expires = uint64(flags);
if (expires >= block.timestamp) {
if (expires <= block.timestamp) {
return IRegistry(address(0));
}
return IRegistry(subregistry);
}

function getResolver(string calldata label) external view virtual override returns (address) {
(address resolver, uint96 flags) = datastore.getResolver(uint256(keccak256(bytes(label))));
(address subregistry, uint96 flags) = datastore.getSubregistry(uint256(keccak256(bytes(label))));
uint64 expires = uint64(flags);
if (expires >= block.timestamp) {
if (expires <= block.timestamp) {
return address(0);
}

(address resolver, ) = datastore.getResolver(uint256(keccak256(bytes(label))));
return resolver;
}
}
55 changes: 55 additions & 0 deletions contracts/src/registry/LockableRegistry.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.13;

import {AccessControl} from "@openzeppelin/contracts/access/AccessControl.sol";

import {ERC1155Singleton} from "./ERC1155Singleton.sol";
import {IERC1155Singleton} from "./IERC1155Singleton.sol";
import {IRegistry} from "./IRegistry.sol";
import {IRegistryDatastore} from "./IRegistryDatastore.sol";
import {BaseRegistry} from "./BaseRegistry.sol";

abstract contract LockableRegistry is BaseRegistry {
uint96 public constant FLAGS_MASK = 0x7;
uint96 public constant FLAG_SUBREGISTRY_LOCKED = 0x1;
uint96 public constant FLAG_RESOLVER_LOCKED = 0x2;
uint96 public constant FLAG_FLAGS_LOCKED = 0x4;

constructor(IRegistryDatastore _datastore) BaseRegistry(_datastore) {
}

function _lock(uint256 tokenId, uint96 _flags)
internal
withSubregistryFlags(tokenId, FLAG_FLAGS_LOCKED, 0)
returns(uint96 newFlags)
{
(address subregistry, uint96 oldFlags) = datastore.getSubregistry(tokenId);
newFlags = oldFlags | (_flags & FLAGS_MASK);
if (newFlags != oldFlags) {
datastore.setSubregistry(tokenId, subregistry, newFlags);
}
}

function setSubregistry(uint256 tokenId, IRegistry registry)
external
onlyTokenOwner(tokenId)
withSubregistryFlags(tokenId, FLAG_SUBREGISTRY_LOCKED, 0)
{
(, uint96 _flags) = datastore.getSubregistry(tokenId);
datastore.setSubregistry(tokenId, address(registry), _flags);
}

function setResolver(uint256 tokenId, address resolver)
external
onlyTokenOwner(tokenId)
withSubregistryFlags(tokenId, FLAG_RESOLVER_LOCKED, 0)
{
(, uint96 _flags) = datastore.getResolver(tokenId);
datastore.setResolver(tokenId, resolver, _flags);
}

function flags(uint256 tokenId) external view returns(uint96) {
(, uint96 _flags) = datastore.getSubregistry(tokenId);
return _flags;
}
}
47 changes: 27 additions & 20 deletions contracts/src/registry/RootRegistry.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,52 +7,59 @@ import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol";

import {IRegistry} from "./IRegistry.sol";
import {IRegistryDatastore} from "./IRegistryDatastore.sol";
import {LockableRegistry} from "./LockableRegistry.sol";
import {BaseRegistry} from "./BaseRegistry.sol";

contract RootRegistry is BaseRegistry, AccessControl {
bytes32 public constant SUBDOMAIN_ISSUER_ROLE = keccak256("SUBDOMAIN_ISSUER_ROLE");
uint96 public constant SUBREGISTRY_FLAGS_MASK = 0x1;
uint96 public constant SUBREGISTRY_FLAG_LOCKED = 0x1;
contract RootRegistry is LockableRegistry, AccessControl {
bytes32 public constant TLD_ISSUER_ROLE = keccak256("TLD_ISSUER_ROLE");

constructor(IRegistryDatastore _datastore) BaseRegistry(_datastore) {
constructor(IRegistryDatastore _datastore) LockableRegistry(_datastore) {
_grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
}

function uri(uint256 /*id*/ ) public pure override returns (string memory) {
return "";
}

function mint(string calldata label, address owner, IRegistry registry, bool locked)
/**
* @dev Mints a new TLD.
* @param label The plaintext label for the TLD.
* @param owner The new owner of the TLD token.
* @param registry The address of the registry to use.
* @param flags Flags to set.
*/
function mint(string calldata label, address owner, IRegistry registry, uint96 flags)
external
onlyRole(SUBDOMAIN_ISSUER_ROLE)
onlyRole(TLD_ISSUER_ROLE)
returns(uint256 tokenId)
{
uint256 tokenId = uint256(keccak256(bytes(label)));
_mint(tokenId, owner, registry, locked ? SUBREGISTRY_FLAG_LOCKED : 0);
tokenId = uint256(keccak256(bytes(label)));
_mint(owner, tokenId, 1, "");
datastore.setSubregistry(tokenId, address(registry), flags);
emit NewSubname(label);
}

/**
* @dev Burns a TLD.
* TLDs cannot be burned if any of their flags are set.
* @param tokenId The tokenID of the TLD to burn.
*/
function burn(uint256 tokenId)
external
onlyRole(SUBDOMAIN_ISSUER_ROLE)
withSubregistryFlags(tokenId, SUBREGISTRY_FLAGS_MASK, 0)
onlyTokenOwner(tokenId)
withSubregistryFlags(tokenId, FLAGS_MASK, 0)
{
address owner = ownerOf(tokenId);
_burn(owner, tokenId, 1);
datastore.setSubregistry(tokenId, address(0), 0);
}

function lock(uint256 tokenId) external onlyRole(SUBDOMAIN_ISSUER_ROLE) {
(address subregistry, uint96 flags) = datastore.getSubregistry(tokenId);
datastore.setSubregistry(tokenId, subregistry, flags & SUBREGISTRY_FLAG_LOCKED);
}

function setSubregistry(uint256 tokenId, IRegistry registry)
function lock(uint256 tokenId, uint96 flags)
external
onlyTokenOwner(tokenId)
withSubregistryFlags(tokenId, SUBREGISTRY_FLAGS_MASK, 0)
returns(uint96)
{
(, uint96 flags) = datastore.getSubregistry(tokenId);
datastore.setSubregistry(tokenId, address(registry), flags);
return _lock(tokenId, flags);
}

function supportsInterface(bytes4 interfaceId) public view override(BaseRegistry, AccessControl) returns (bool) {
Expand Down
3 changes: 2 additions & 1 deletion contracts/src/registry/UserRegistry.sol
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@ contract UserRegistry is BaseRegistry {

function mint(string calldata _label, address owner, IRegistry registry, uint96 flags) external onlyNameOwner {
uint256 tokenId = uint256(keccak256(bytes(_label)));
_mint(tokenId, owner, registry, flags);
_mint(owner, tokenId, 1, "");
datastore.setSubregistry(tokenId, address(registry), flags);
emit NewSubname(label);
}

Expand Down
38 changes: 34 additions & 4 deletions contracts/test/ETHRegistry.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ contract TestETHRegistry is Test, ERC1155Holder {
}

function test_register_locked() public {
uint32 flags = registry.FLAG_SUBREGISTRY_LOCKED() | registry.FLAG_RESOLVER_LOCKED();
uint96 flags = registry.FLAG_SUBREGISTRY_LOCKED() | registry.FLAG_RESOLVER_LOCKED();
uint256 expectedId =
uint256(keccak256("test2") & 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8) | flags;
vm.expectEmit(true, true, true, true);
Expand All @@ -43,7 +43,7 @@ contract TestETHRegistry is Test, ERC1155Holder {
}

function test_lock_name() public {
uint32 flags = registry.FLAG_SUBREGISTRY_LOCKED() | registry.FLAG_RESOLVER_LOCKED();
uint96 flags = registry.FLAG_SUBREGISTRY_LOCKED() | registry.FLAG_RESOLVER_LOCKED();
uint256 oldTokenId = registry.register("test2", address(this), registry, 0, uint64(block.timestamp) + 86400);

vm.expectEmit(true, true, true, true);
Expand All @@ -57,17 +57,47 @@ contract TestETHRegistry is Test, ERC1155Holder {
}

function test_cannot_unlock_name() public {
uint32 flags = registry.FLAG_SUBREGISTRY_LOCKED() | registry.FLAG_RESOLVER_LOCKED();
uint96 flags = registry.FLAG_SUBREGISTRY_LOCKED() | registry.FLAG_RESOLVER_LOCKED();

uint256 oldTokenId = registry.register("test2", address(this), registry, flags, uint64(block.timestamp) + 86400);
uint256 newTokenId = registry.lock(oldTokenId, 0);
vm.assertEq(oldTokenId, newTokenId);
}

function testFail_cannot_mint_duplicates() public {
uint32 flags = registry.FLAG_SUBREGISTRY_LOCKED() | registry.FLAG_RESOLVER_LOCKED();
uint96 flags = registry.FLAG_SUBREGISTRY_LOCKED() | registry.FLAG_RESOLVER_LOCKED();

registry.register("test2", address(this), registry, flags, uint64(block.timestamp) + 86400);
registry.register("test2", address(this), registry, 0, uint64(block.timestamp) + 86400);
}

function test_set_subregistry() public {
uint256 tokenId = registry.register("test2", address(this), registry, 0, uint64(block.timestamp) + 86400);
registry.setSubregistry(tokenId, IRegistry(address(this)));
vm.assertEq(address(registry.getSubregistry("test2")), address(this));
}

function testFail_cannot_set_locked_subregistry() public {
uint96 flags = registry.FLAG_SUBREGISTRY_LOCKED();
uint256 tokenId = registry.register("test2", address(this), registry, flags, uint64(block.timestamp) + 86400);
registry.setSubregistry(tokenId, IRegistry(address(this)));
}

function test_set_resolver() public {
uint256 tokenId = registry.register("test2", address(this), registry, 0, uint64(block.timestamp) + 86400);
registry.setResolver(tokenId, address(this));
vm.assertEq(address(registry.getResolver("test2")), address(this));
}

function testFail_cannot_set_locked_resolver() public {
uint96 flags = registry.FLAG_RESOLVER_LOCKED();
uint256 tokenId = registry.register("test2", address(this), registry, flags, uint64(block.timestamp) + 86400);
registry.setResolver(tokenId, address(this));
}

function testFail_cannot_set_locked_flags() public {
uint96 flags = registry.FLAG_FLAGS_LOCKED();
uint256 tokenId = registry.register("test2", address(this), registry, flags, uint64(block.timestamp) + 86400);
registry.lock(tokenId, registry.FLAG_RESOLVER_LOCKED());
}
}
Loading

0 comments on commit d6f5f7d

Please sign in to comment.