Skip to content

Commit

Permalink
feat: add RONRegistrarController & NameChecker
Browse files Browse the repository at this point in the history
  • Loading branch information
TuDo1403 committed Oct 12, 2023
1 parent 57803a6 commit a395c34
Show file tree
Hide file tree
Showing 32 changed files with 2,338 additions and 3 deletions.
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,6 @@
[submodule "lib/contract-template"]
path = lib/contract-template
url = https://github.com/axieinfinity/contract-template
[submodule "lib/pyth-sdk-solidity"]
path = lib/pyth-sdk-solidity
url = https://github.com/pyth-network/pyth-sdk-solidity
1 change: 1 addition & 0 deletions lib/buffer
Submodule buffer added at 688aa0
1 change: 1 addition & 0 deletions lib/ens-contracts
Submodule ens-contracts added at 0c75ba
1 change: 1 addition & 0 deletions lib/pyth-sdk-solidity
Submodule pyth-sdk-solidity added at 11d6bc
1 change: 1 addition & 0 deletions lib/solady
Submodule solady added at 2ba1cc
3 changes: 2 additions & 1 deletion remappings.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@
ds-test/=lib/forge-std/lib/ds-test/src/
forge-std/=lib/forge-std/src/
@openzeppelin/=lib/openzeppelin-contracts/
contract-template/=lib/contract-template/src/
contract-template/=lib/contract-template/src/
@pythnetwork/=lib/pyth-sdk-solidity/
209 changes: 209 additions & 0 deletions src/NameChecker.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

import { AccessControlEnumerable } from "@openzeppelin/contracts/access/AccessControlEnumerable.sol";
import { Initializable } from "@openzeppelin/contracts/proxy/utils/Initializable.sol";
import { BitMaps } from "@openzeppelin/contracts/utils/structs/BitMaps.sol";
import { Math, LibSubString } from "./libraries/LibSubString.sol";
import { INameChecker } from "./interfaces/INameChecker.sol";

contract NameChecker is Initializable, AccessControlEnumerable, INameChecker {
using LibSubString for *;
using BitMaps for BitMaps.BitMap;

/// @dev Gap for upgradeability.
uint256[50] private ____gap;

LibSubString.WordRange internal _wordRange;
BitMaps.BitMap internal _forbiddenWordMap;

constructor() payable {
_disableInitializers();
}

function initialize(address admin, uint8 min, uint8 max) external initializer {
_setupRole(DEFAULT_ADMIN_ROLE, admin);
_setWordRange(min, max);
}

/**
* @inheritdoc INameChecker
*/
function getWordRange() external view returns (uint8 min, uint8 max) {
LibSubString.WordRange memory wordRange = _wordRange;
return (wordRange.min, wordRange.max);
}

/**
* @inheritdoc INameChecker
*/
function setWordRange(uint8 min, uint8 max) external onlyRole(DEFAULT_ADMIN_ROLE) {
_setWordRange(min, max);
}

/**
* @inheritdoc INameChecker
*/
function forbidden(string calldata name) public view returns (bool) {
return containsInvalidCharacter(name) || containsBlacklistedWord(name);
}

/**
* @inheritdoc INameChecker
*/
function containsBlacklistedWord(string calldata name) public view returns (bool) {
string[] memory sstrs = getAllSubStrings(name);
uint256 length = sstrs.length;

for (uint256 i; i < length;) {
if (_forbiddenWordMap.get(pack(sstrs[i]))) return true;

unchecked {
++i;
}
}

return false;
}

/**
* @inheritdoc INameChecker
*/
function containsInvalidCharacter(string calldata name) public pure returns (bool) {
unchecked {
bytes1 char;
bytes memory bName = bytes(name);
uint256 length = bName.length;

uint256 tail = length - 1;
// Check if the name is empty or starts or ends with a hyphen (-)
if (length == 0 || bName[0] == 0x2d || bName[tail] == 0x2d) return true;

// [0x30, 0x39] => [0-9]
// [0x61, 0x7a] => [a-z]
for (uint256 i; i < length; ++i) {
char = bName[i];
if (char == 0x2d) {
// Check consecutive hyphens
if (i != tail && bName[i + 1] == 0x2d) return true;
}
// Check for invalid character (not (-) || [0-9] || [a-z])
else if (!((char >= 0x30 && char <= 0x39) || (char >= 0x61 && char <= 0x7a))) {
return true;
}
}

return false;
}
}

/**
* @inheritdoc INameChecker
*/
function pack(string memory str) public pure returns (uint256 packed) {
assembly ("memory-safe") {
// We don't need to zero right pad the string,
// since this is our own custom non-standard packing scheme.
packed :=
mul(
// Load the length and the bytes.
mload(add(str, 0x1f)),
// `length != 0 && length < 32`. Abuses underflow.
// Assumes that the length is valid and within the block gas limit.
lt(sub(mload(str), 1), 0x1f)
)
}
}

/**
* @inheritdoc INameChecker
*/
function packBulk(string[] memory strs) public pure returns (uint256[] memory packeds) {
uint256 length = strs.length;
packeds = new uint256[](length);

for (uint256 i; i < length;) {
packeds[i] = pack(strs[i]);

unchecked {
++i;
}
}
}

/**
* @inheritdoc INameChecker
*/
function setForbiddenWords(string[] calldata words, bool shouldForbid) external onlyRole(DEFAULT_ADMIN_ROLE) {
uint256[] memory packedWords = packBulk(words);
_setForbiddenWords(packedWords, shouldForbid);
}

/**
* @inheritdoc INameChecker
*/
function setForbiddenWords(uint256[] calldata packedWords, bool shouldForbid) external onlyRole(DEFAULT_ADMIN_ROLE) {
_setForbiddenWords(packedWords, shouldForbid);
}

/**
* @inheritdoc INameChecker
*/
function totalSubString(uint256 strlen) public view returns (uint256 total, uint256 min, uint256 max) {
(total, min, max) = strlen.totalSubString(_wordRange);
}

/**
* @inheritdoc INameChecker
*/
function getAllSubStrings(string calldata str) public view returns (string[] memory subStrings) {
subStrings = str.getAllSubStrings(_wordRange);
}

/**
* @dev Set the forbidden status of packed words.
* @param packedWords An array of packed word representations.
* @param shouldForbid A boolean flag indicating whether to forbid or unforbid the words.
* @notice It ensures that packed words are not zero, indicating their validity.
* @notice Emits a `ForbiddenWordsUpdated` event upon successful execution.
*/
function _setForbiddenWords(uint256[] memory packedWords, bool shouldForbid) internal {
uint256 length = packedWords.length;
uint256 strlen;
uint256 max;
uint256 min = type(uint256).max;

for (uint256 i; i < length;) {
require(packedWords[i] != 0, "NameChecker: invalid packed word");
strlen = packedWords[i] >> 0xf8;
min = Math.min(min, strlen);
max = Math.max(max, strlen);
_forbiddenWordMap.setTo(packedWords[i], shouldForbid);

unchecked {
++i;
}
}

if (shouldForbid) {
LibSubString.WordRange memory wordRange = _wordRange;
min = Math.min(min, wordRange.min);
max = Math.max(max, wordRange.max);
if (!(min == wordRange.min && max == wordRange.max)) _setWordRange(uint8(min), uint8(max));
}

emit ForbiddenWordsUpdated(_msgSender(), length, shouldForbid);
}

/**
* @dev Set the allowed word length range.
* @param min The minimum word length allowed.
* @param max The maximum word length allowed.
* @notice The minimum word length must be greater than 0, and it must not exceed the maximum word length.
*/
function _setWordRange(uint8 min, uint8 max) internal {
require(min != 0 && min <= max, "NameChecker: min word length > max word length");
_wordRange = LibSubString.WordRange(min, max);
emit WordRangeUpdated(_msgSender(), min, max);
}
}
3 changes: 2 additions & 1 deletion src/RNSUnified.sol
Original file line number Diff line number Diff line change
Expand Up @@ -128,10 +128,11 @@ contract RNSUnified is Initializable, RNSToken {
}

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

Expand Down
Loading

0 comments on commit a395c34

Please sign in to comment.