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

Rebase/reverse-registrar #12

Closed
wants to merge 25 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
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
166 changes: 166 additions & 0 deletions src/RNSReverseRegistrar.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import { Initializable } from "@openzeppelin/contracts/proxy/utils/Initializable.sol";
import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
import { INameResolver } from "@rns-contracts/interfaces/resolvers/INameResolver.sol";
import { IERC165, IERC181, IReverseRegistrar } from "@rns-contracts/interfaces/IReverseRegistrar.sol";
import { INSUnified } from "@rns-contracts/interfaces/INSUnified.sol";
import { LibStrAddrConvert } from "@rns-contracts/libraries/LibStrAddrConvert.sol";

/**
* @notice Customized version of ReverseRegistrar: https://github.com/ensdomains/ens-contracts/blob/0c75ba23fae76165d51c9c80d76d22261e06179d/contracts/reverseRegistrar/ReverseRegistrar.sol
* @dev The reverse registrar provides functions to claim a reverse record, as well as a convenience function to
* configure the record as it's most commonly used, as a way of specifying a canonical name for an address.
* The reverse registrar is specified in EIP 181 https://eips.ethereum.org/EIPS/eip-181.
*/
contract RNSReverseRegistrar is Initializable, Ownable, IReverseRegistrar {
/// @dev This controller must equal to IReverseRegistrar.CONTROLLER_ROLE()
bytes32 public constant CONTROLLER_ROLE = keccak256("CONTROLLER_ROLE");
/// @dev Value equals to namehash('addr.reverse')
bytes32 public constant ADDR_REVERSE_NODE = 0x91d1777781884d03a6757a803996e38de2a42967fb37eeaca72729271025a9e2;

/// @dev Gap for upgradeability.
uint256[50] private ____gap;
/// @dev The rns unified contract.
INSUnified internal _rnsUnified;
/// @dev The default resolver.
INameResolver internal _defaultResolver;

modifier live() {
_requireLive();
_;
}

modifier onlyAuthorized(address addr) {
_requireAuthorized(addr);
_;
}

constructor() payable {
_disableInitializers();
}

function initialize(address admin, INSUnified rnsUnified) external initializer {
_rnsUnified = rnsUnified;
_transferOwnership(admin);
}

/**
* @inheritdoc IReverseRegistrar
*/
function getDefaultResolver() external view returns (INameResolver) {
return _defaultResolver;
}

/**
* @inheritdoc IReverseRegistrar
*/
function getRNSUnified() external view returns (INSUnified) {
return _rnsUnified;
}

/**
* @inheritdoc IERC165
*/
function supportsInterface(bytes4 interfaceId) external pure returns (bool) {
return interfaceId == type(IReverseRegistrar).interfaceId || interfaceId == type(IERC165).interfaceId
|| interfaceId == type(IERC181).interfaceId;
}

/**
* @inheritdoc IReverseRegistrar
*/
function setDefaultResolver(INameResolver resolver) external onlyOwner {
if (address(resolver) == address(0)) revert NullAssignment();
_defaultResolver = resolver;
emit DefaultResolverChanged(resolver);
}

/**
* @inheritdoc IERC181
*/
function claim(address addr) external returns (bytes32) {
return claimWithResolver(addr, address(_defaultResolver));
}

/**
* @inheritdoc IERC181
*/
function setName(string memory name) external returns (bytes32 node) {
return setNameForAddr(_msgSender(), name);
}

/**
* @inheritdoc IReverseRegistrar
*/
function getAddress(bytes32 node) external view returns (address) {
INSUnified.Record memory record = _rnsUnified.getRecord(uint256(node));
if (record.immut.parentId != uint256(ADDR_REVERSE_NODE)) revert InvalidNode();
return LibStrAddrConvert.parseAddr(record.immut.label);
}

/**
* @inheritdoc IERC181
*/
function claimWithResolver(address addr, address resolver) public live onlyAuthorized(addr) returns (bytes32 node) {
node = _claimWithResolver(addr, resolver);
}

/**
* @inheritdoc IReverseRegistrar
*/
function setNameForAddr(address addr, string memory name)
public
live
onlyAuthorized(addr)
returns (bytes32 node)
{
node = computeNode(addr);
INSUnified rnsUnified = _rnsUnified;
if (rnsUnified.ownerOf(uint256(node)) != address(this)) {
bytes32 claimedNode = _claimWithResolver(addr, address(_defaultResolver));
if (claimedNode != node) revert InvalidNode();
}

INSUnified.Record memory record = rnsUnified.getRecord(uint256(node));
INameResolver(record.mut.resolver).setName(node, name);
}

/**
* @inheritdoc IReverseRegistrar
*/
function computeNode(address addr) public pure returns (bytes32) {
return keccak256(abi.encodePacked(ADDR_REVERSE_NODE, keccak256(bytes(LibStrAddrConvert.toString(addr)))));
}

/**
* @dev Helper method to claim domain hex(addr) + '.addr.reverse' for addr.
* Emits an event {ReverseClaimed}.
*/
function _claimWithResolver(address addr, address resolver) internal returns (bytes32 node) {
string memory stringifiedAddr = LibStrAddrConvert.toString(addr);
(, uint256 id) =
_rnsUnified.mint(uint256(ADDR_REVERSE_NODE), stringifiedAddr, resolver, address(this), type(uint64).max);
node = bytes32(id);
emit ReverseClaimed(addr, node);
}

/**
* @dev Helper method to ensure the contract can mint or modify domain hex(addr) + '.addr.reverse' for addr.
*/
function _requireLive() internal view {
if (_rnsUnified.ownerOf(uint256(ADDR_REVERSE_NODE)) == address(this)) revert InvalidConfig();
}

/**
* @dev Helper method to ensure addr is authorized for claiming domain hex(addr) + '.addr.reverse' for addr.
*/
function _requireAuthorized(address addr) internal view {
address sender = _msgSender();
INSUnified rnsUnified = _rnsUnified;
if (!(addr == sender || rnsUnified.hasRole(CONTROLLER_ROLE, sender) || rnsUnified.isApprovedForAll(addr, sender))) {
revert Unauthorized();
}
}
}
68 changes: 43 additions & 25 deletions src/RNSUnified.sol
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ contract RNSUnified is Initializable, RNSToken {
_setGracePeriod(gracePeriod);

_mint(admin, 0x00);
Record memory record;
_recordOf[0x00].mut.expiry = record.mut.expiry = MAX_EXPIRY;
emit RecordUpdated(0x00, ModifyingField.Expiry.indicator(), record);
}

/// @inheritdoc INSUnified
Expand Down Expand Up @@ -126,12 +129,17 @@ contract RNSUnified is Initializable, RNSToken {

/// @inheritdoc INSUnified
function renew(uint256 id, uint64 duration) external whenNotPaused onlyRole(CONTROLLER_ROLE) {
_setExpiry(id, uint64(LibSafeRange.addWithUpperbound(_recordOf[id].mut.expiry, duration, MAX_EXPIRY)));
Record memory record;
record.mut.expiry = uint64(LibSafeRange.addWithUpperbound(_recordOf[id].mut.expiry, duration, MAX_EXPIRY));
_setExpiry(id, record.mut.expiry);
emit RecordUpdated(id, ModifyingField.Expiry.indicator(), record);
}

/// @inheritdoc INSUnified
function setExpiry(uint256 id, uint64 expiry) external whenNotPaused onlyRole(CONTROLLER_ROLE) {
_setExpiry(id, expiry);
Record memory record;
_setExpiry(id, record.mut.expiry = expiry);
emit RecordUpdated(id, ModifyingField.Expiry.indicator(), record);
}

/// @inheritdoc INSUnified
Expand All @@ -143,6 +151,7 @@ contract RNSUnified is Initializable, RNSToken {

for (uint256 i; i < ids.length;) {
id = ids[i];
if (!_exists(id)) revert Unexists();
if (_recordOf[id].mut.protected != protected) {
_recordOf[id].mut.protected = protected;
emit RecordUpdated(id, indicator, record);
Expand All @@ -161,8 +170,23 @@ contract RNSUnified is Initializable, RNSToken {
onlyAuthorized(id, indicator)
{
Record memory record;
_recordOf[id].mut = record.mut = mutRecord;
MutableRecord storage sMutRecord = _recordOf[id].mut;

if (indicator.hasAny(ModifyingField.Protected.indicator())) {
sMutRecord.protected = record.mut.protected = mutRecord.protected;
}
if (indicator.hasAny(ModifyingField.Expiry.indicator())) {
_setExpiry(id, record.mut.expiry = mutRecord.expiry);
}
if (indicator.hasAny(ModifyingField.Resolver.indicator())) {
sMutRecord.resolver = record.mut.resolver = mutRecord.resolver;
}
emit RecordUpdated(id, indicator, record);

// Updating owner might emit more {RecordUpdated} events. See method {_transfer}.
if (indicator.hasAny(ModifyingField.Owner.indicator())) {
_safeTransfer(_recordOf[id].mut.owner, mutRecord.owner, id, "");
}
}

/**
Expand All @@ -181,6 +205,7 @@ contract RNSUnified is Initializable, RNSToken {
if (indicator.hasAny(IMMUTABLE_FIELDS_INDICATOR)) {
return (false, CannotSetImmutableField.selector);
}
if (!_exists(id)) return (false, Unexists.selector);
if (indicator.hasAny(ModifyingField.Protected.indicator()) && !hasRole(PROTECTED_SETTLER_ROLE, requester)) {
return (false, MissingProtectedSettlerRole.selector);
}
Expand Down Expand Up @@ -253,7 +278,7 @@ contract RNSUnified is Initializable, RNSToken {
if (!allowed) {
assembly ("memory-safe") {
mstore(0x00, errorCode)
revert(0x1c, 0x04)
revert(0x00, 0x04)
}
}
}
Expand Down Expand Up @@ -281,7 +306,6 @@ contract RNSUnified is Initializable, RNSToken {

Record memory record;
_recordOf[id].mut.expiry = record.mut.expiry = expiry;
emit RecordUpdated(id, ModifyingField.Expiry.indicator(), record);
}

/**
Expand All @@ -294,30 +318,24 @@ contract RNSUnified is Initializable, RNSToken {
emit GracePeriodUpdated(_msgSender(), gracePeriod);
}

/// @dev Override {ERC721-_afterTokenTransfer}.
function _afterTokenTransfer(address from, address to, uint256 firstTokenId, uint256 batchSize)
internal
virtual
override
{
super._afterTokenTransfer(from, to, firstTokenId, batchSize);
/// @dev Override {ERC721-_transfer}.
function _transfer(address from, address to, uint256 id) internal override {
super._transfer(from, to, id);

Record memory record;
record.mut.owner = to;
ModifyingIndicator indicator = ModifyingField.Owner.indicator();
bool shouldUpdateProtected = !hasRole(PROTECTED_SETTLER_ROLE, _msgSender());
if (shouldUpdateProtected) indicator = indicator | ModifyingField.Protected.indicator();

for (uint256 id = firstTokenId; id < firstTokenId + batchSize;) {
_recordOf[id].mut.owner = to;
if (shouldUpdateProtected) {
_recordOf[id].mut.protected = false;
emit RecordUpdated(id, indicator, record);
}

unchecked {
id++;
}
_recordOf[id].mut.owner = record.mut.owner = to;
if (!hasRole(PROTECTED_SETTLER_ROLE, _msgSender()) && _recordOf[id].mut.protected) {
_recordOf[id].mut.protected = false;
indicator = indicator | ModifyingField.Protected.indicator();
}
emit RecordUpdated(id, indicator, record);
}

/// @dev Override {ERC721-_burn}.
function _burn(uint256 id) internal override {
super._burn(id);
delete _recordOf[id].mut;
}
}
10 changes: 9 additions & 1 deletion src/interfaces/INSUnified.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,14 @@
pragma solidity ^0.8.19;

import { IERC721Metadata } from "@openzeppelin/contracts/token/ERC721/extensions/IERC721Metadata.sol";
import { IAccessControlEnumerable } from "@openzeppelin/contracts/access/IAccessControlEnumerable.sol";
import { ModifyingIndicator } from "../types/ModifyingIndicator.sol";

interface INSUnified is IERC721Metadata {
interface INSUnified is IAccessControlEnumerable, IERC721Metadata {
/// @dev Error: The provided token id is expired.
error Expired();
/// @dev Error: The provided token id is unexists.
error Unexists();
/// @dev Error: The provided id expiry is greater than parent id expiry.
error ExceedParentExpiry();
/// @dev Error: The provided name is unavailable for registration.
Expand Down Expand Up @@ -96,6 +99,11 @@ interface INSUnified is IERC721Metadata {
*/
function RESERVATION_ROLE() external pure returns (bytes32);

/**
* @dev Returns the max expiry value.
*/
function MAX_EXPIRY() external pure returns (uint64);

/**
* @dev Returns true if the specified name is available for registration.
* Note: Only available after passing the grace period.
Expand Down
Loading