Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Root registry & ETH registry improvements #5

Merged
merged 3 commits into from
Aug 30, 2024
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
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