From 317fc9653bc26f119e1307e54df729fe18be7558 Mon Sep 17 00:00:00 2001 From: Tu Do <18521578@gm.uit.edu.vn> Date: Thu, 12 Oct 2023 13:57:19 +0700 Subject: [PATCH] feat: add Public Resolvers into testnet v0.2.0 (#17) * forge install: ens-contracts v0.1 * forge install: buffer 688aa09e9ad241a94609e6af539e65f229912b16 * chore: migrate from private repo * feat: add PublicResolver & dependency * fix: outdated name and address in Public Resolver (#5) fix: outdated name and address in Public Resolver * fix: resolve merge conflicts * fix: resolve merge conflicts --------- Co-authored-by: Duc Tho Tran --- .gitmodules | 6 + lib/buffer | 1 + lib/ens-contracts | 1 + remappings.txt | 4 +- src/RNSUnified.sol | 5 + src/extensions/Multicallable.sol | 52 +++++ src/interfaces/IMulticallable.sol | 37 ++++ src/interfaces/INSUnified.sol | 5 + src/interfaces/IReverseRegistrar.sol | 2 +- src/interfaces/resolvers/IABIResolver.sol | 38 ++++ src/interfaces/resolvers/IAddressResolver.sol | 28 +++ .../resolvers/IContentHashResolver.sol | 28 +++ .../resolvers/IDNSRecordResolver.sol | 41 ++++ src/interfaces/resolvers/IDNSZoneResolver.sol | 28 +++ .../resolvers/IInterfaceResolver.sol | 34 ++++ .../resolvers/IPublicKeyResolver.sol | 37 ++++ src/interfaces/resolvers/IPublicResolver.sol | 60 ++++++ src/interfaces/resolvers/ITextResolver.sol | 30 +++ src/interfaces/resolvers/IVersionResolver.sol | 25 +++ src/libraries/ErrorHandler.sol | 22 ++ src/resolvers/ABIResolvable.sol | 45 +++++ src/resolvers/AddressResolvable.sol | 36 ++++ src/resolvers/BaseVersion.sol | 36 ++++ src/resolvers/ContentHashResolvable.sol | 36 ++++ src/resolvers/DNSResolvable.sol | 141 +++++++++++++ src/resolvers/InterfaceResolvable.sol | 68 +++++++ src/resolvers/NameResolvable.sol | 35 ++++ src/resolvers/PublicKeyResolvable.sol | 36 ++++ src/resolvers/PublicResolver.sol | 190 ++++++++++++++++++ src/resolvers/TextResolvable.sol | 34 ++++ 30 files changed, 1139 insertions(+), 2 deletions(-) create mode 160000 lib/buffer create mode 160000 lib/ens-contracts create mode 100644 src/extensions/Multicallable.sol create mode 100644 src/interfaces/IMulticallable.sol create mode 100644 src/interfaces/resolvers/IABIResolver.sol create mode 100644 src/interfaces/resolvers/IAddressResolver.sol create mode 100644 src/interfaces/resolvers/IContentHashResolver.sol create mode 100644 src/interfaces/resolvers/IDNSRecordResolver.sol create mode 100644 src/interfaces/resolvers/IDNSZoneResolver.sol create mode 100644 src/interfaces/resolvers/IInterfaceResolver.sol create mode 100644 src/interfaces/resolvers/IPublicKeyResolver.sol create mode 100644 src/interfaces/resolvers/IPublicResolver.sol create mode 100644 src/interfaces/resolvers/ITextResolver.sol create mode 100644 src/interfaces/resolvers/IVersionResolver.sol create mode 100644 src/libraries/ErrorHandler.sol create mode 100644 src/resolvers/ABIResolvable.sol create mode 100644 src/resolvers/AddressResolvable.sol create mode 100644 src/resolvers/BaseVersion.sol create mode 100644 src/resolvers/ContentHashResolvable.sol create mode 100644 src/resolvers/DNSResolvable.sol create mode 100644 src/resolvers/InterfaceResolvable.sol create mode 100644 src/resolvers/NameResolvable.sol create mode 100644 src/resolvers/PublicKeyResolvable.sol create mode 100644 src/resolvers/PublicResolver.sol create mode 100644 src/resolvers/TextResolvable.sol diff --git a/.gitmodules b/.gitmodules index 544667fe..8fa5be48 100644 --- a/.gitmodules +++ b/.gitmodules @@ -7,3 +7,9 @@ [submodule "lib/contract-template"] path = lib/contract-template url = https://github.com/axieinfinity/contract-template +[submodule "lib/ens-contracts"] + path = lib/ens-contracts + url = https://github.com/ensdomains/ens-contracts +[submodule "lib/buffer"] + path = lib/buffer + url = https://github.com/ensdomains/buffer diff --git a/lib/buffer b/lib/buffer new file mode 160000 index 00000000..688aa09e --- /dev/null +++ b/lib/buffer @@ -0,0 +1 @@ +Subproject commit 688aa09e9ad241a94609e6af539e65f229912b16 diff --git a/lib/ens-contracts b/lib/ens-contracts new file mode 160000 index 00000000..0c75ba23 --- /dev/null +++ b/lib/ens-contracts @@ -0,0 +1 @@ +Subproject commit 0c75ba23fae76165d51c9c80d76d22261e06179d diff --git a/remappings.txt b/remappings.txt index e2c6633c..41209dd1 100644 --- a/remappings.txt +++ b/remappings.txt @@ -2,4 +2,6 @@ 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/ \ No newline at end of file +contract-template/=lib/contract-template/src/ +@ensdomains/ens-contracts/=lib/ens-contracts/contracts/ +@ensdomains/buffer/=lib/buffer/ \ No newline at end of file diff --git a/src/RNSUnified.sol b/src/RNSUnified.sol index 134c1cba..65b60bd9 100644 --- a/src/RNSUnified.sol +++ b/src/RNSUnified.sol @@ -58,6 +58,11 @@ contract RNSUnified is Initializable, RNSToken { emit RecordUpdated(0x0, ModifyingField.Expiry.indicator(), record); } + /// @inheritdoc INSUnified + function namehash(string memory) external pure returns (bytes32 node) { + revert("TODO"); + } + /// @inheritdoc INSUnified function available(uint256 id) public view returns (bool) { return block.timestamp > LibSafeRange.add(_expiry(id), _gracePeriod); diff --git a/src/extensions/Multicallable.sol b/src/extensions/Multicallable.sol new file mode 100644 index 00000000..fc6f52b6 --- /dev/null +++ b/src/extensions/Multicallable.sol @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import { ERC165 } from "@openzeppelin/contracts/utils/introspection/ERC165.sol"; +import { IMulticallable } from "@rns-contracts/interfaces/IMulticallable.sol"; +import { ErrorHandler } from "@rns-contracts/libraries/ErrorHandler.sol"; + +abstract contract Multicallable is ERC165, IMulticallable { + using ErrorHandler for bool; + + /** + * @dev Override {IERC165-supportsInterface}. + */ + function supportsInterface(bytes4 interfaceID) public view virtual override returns (bool) { + return interfaceID == type(IMulticallable).interfaceId || super.supportsInterface(interfaceID); + } + + /** + * @inheritdoc IMulticallable + */ + function multicall(bytes[] calldata data) public override returns (bytes[] memory results) { + return _tryMulticall(true, data); + } + + /** + * @inheritdoc IMulticallable + */ + function tryMulticall(bool requireSuccess, bytes[] calldata data) public override returns (bytes[] memory results) { + return _tryMulticall(requireSuccess, data); + } + + /** + * @dev See {IMulticallable-tryMulticall}. + */ + function _tryMulticall(bool requireSuccess, bytes[] calldata data) internal returns (bytes[] memory results) { + uint256 length = data.length; + results = new bytes[](length); + + bool success; + bytes memory result; + + for (uint256 i; i < length;) { + (success, result) = address(this).delegatecall(data[i]); + if (requireSuccess) success.handleRevert(result); + results[i] = result; + + unchecked { + ++i; + } + } + } +} diff --git a/src/interfaces/IMulticallable.sol b/src/interfaces/IMulticallable.sol new file mode 100644 index 00000000..5e536433 --- /dev/null +++ b/src/interfaces/IMulticallable.sol @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.0; + +/** + * @notice To multi-call to a specified contract which has multicall interface: + * + * ```solidity + * interface IMock is IMulticallable { + * function foo() external; + * function bar() external; + * } + * + * bytes[] memory calldatas = new bytes[](2); + * calldatas[0] = abi.encodeCall(IMock.foo,()); + * calldatas[1] = abi.encodeCall(IMock.bar,()); + * IMock(target).multicall(calldatas); + * ``` + */ +interface IMulticallable { + /** + * @dev Executes bulk action to the original contract. + * Reverts if there is a single call failed. + * + * @param data The calldata to original contract. + * + */ + function multicall(bytes[] calldata data) external returns (bytes[] memory results); + + /** + * @dev Executes bulk action to the original contract. + * + * @param requireSuccess Flag to indicating whether the contract reverts if there is a single call failed. + * @param data The calldata to original contract. + * + */ + function tryMulticall(bool requireSuccess, bytes[] calldata data) external returns (bytes[] memory results); +} diff --git a/src/interfaces/INSUnified.sol b/src/interfaces/INSUnified.sol index 0863335f..9742ebab 100644 --- a/src/interfaces/INSUnified.sol +++ b/src/interfaces/INSUnified.sol @@ -104,6 +104,11 @@ interface INSUnified is IAccessControlEnumerable, IERC721Metadata { */ function MAX_EXPIRY() external pure returns (uint64); + /** + * @dev Returns the name hash output of a domain. + */ + function namehash(string memory domain) external pure returns (bytes32 node); + /** * @dev Returns true if the specified name is available for registration. * Note: Only available after passing the grace period. diff --git a/src/interfaces/IReverseRegistrar.sol b/src/interfaces/IReverseRegistrar.sol index f3fe2d1e..c40e25b3 100644 --- a/src/interfaces/IReverseRegistrar.sol +++ b/src/interfaces/IReverseRegistrar.sol @@ -1,4 +1,4 @@ -// SPDX-LicINSe-Identifier: UNLICINSED +// SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.0; import { IERC165 } from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; diff --git a/src/interfaces/resolvers/IABIResolver.sol b/src/interfaces/resolvers/IABIResolver.sol new file mode 100644 index 00000000..678ef5f1 --- /dev/null +++ b/src/interfaces/resolvers/IABIResolver.sol @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.0; + +interface IABIResolver { + /// Thrown when the input content type is invalid. + error InvalidContentType(); + + /// @dev Emitted when the ABI is changed. + event ABIChanged(bytes32 indexed node, uint256 indexed contentType); + + /** + * @dev Sets the ABI associated with an INS node. Nodes may have one ABI of each content type. To remove an ABI, set it + * to the empty string. + * + * Requirements: + * - The method caller must be authorized to change user fields of RNS Token `node`. See indicator + * {ModifyingIndicator.USER_FIELDS_INDICATOR}. + * - The content type must be powers of 2. + * + * Emitted an event {ABIChanged}. + * + * @param node The node to update. + * @param contentType The content type of the ABI + * @param data The ABI data. + */ + function setABI(bytes32 node, uint256 contentType, bytes calldata data) external; + + /** + * @dev Returns the ABI associated with an INS node. + * Defined in EIP-205, see more at https://eips.ethereum.org/EIPS/eip-205 + * + * @param node The INS node to query + * @param contentTypes A bitwise OR of the ABI formats accepted by the caller. + * @return contentType The content type of the return value + * @return data The ABI data + */ + function ABI(bytes32 node, uint256 contentTypes) external view returns (uint256 contentType, bytes memory data); +} diff --git a/src/interfaces/resolvers/IAddressResolver.sol b/src/interfaces/resolvers/IAddressResolver.sol new file mode 100644 index 00000000..84c986fa --- /dev/null +++ b/src/interfaces/resolvers/IAddressResolver.sol @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.0; + +interface IAddressResolver { + /// @dev Emitted when an address of a node is changed. + event AddrChanged(bytes32 indexed node, address addr); + + /** + * @dev Sets the address associated with an INS node. + * + * Requirement: + * - The method caller must be authorized to change user fields of RNS Token `node`. See indicator + * {ModifyingIndicator.USER_FIELDS_INDICATOR}. + * + * Emits an event {AddrChanged}. + * + * @param node The node to update. + * @param addr The address to set. + */ + function setAddr(bytes32 node, address addr) external; + + /** + * @dev Returns the address associated with an INS node. + * @param node The INS node to query. + * @return The associated address. + */ + function addr(bytes32 node) external view returns (address payable); +} diff --git a/src/interfaces/resolvers/IContentHashResolver.sol b/src/interfaces/resolvers/IContentHashResolver.sol new file mode 100644 index 00000000..7db60259 --- /dev/null +++ b/src/interfaces/resolvers/IContentHashResolver.sol @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +interface IContentHashResolver { + /// @dev Emitted when the content hash of a node is changed. + event ContentHashChanged(bytes32 indexed node, bytes hash); + + /** + * @dev Sets the content hash associated with an INS node. + * + * Requirements: + * - The method caller must be authorized to change user fields of RNS Token `node`. See indicator + * {ModifyingIndicator.USER_FIELDS_INDICATOR}. + * + * Emits an event {ContentHashChanged}. + * + * @param node The node to update. + * @param hash The content hash to set + */ + function setContentHash(bytes32 node, bytes calldata hash) external; + + /** + * @dev Returns the content hash associated with an INS node. + * @param node The INS node to query. + * @return The associated content hash. + */ + function contentHash(bytes32 node) external view returns (bytes memory); +} diff --git a/src/interfaces/resolvers/IDNSRecordResolver.sol b/src/interfaces/resolvers/IDNSRecordResolver.sol new file mode 100644 index 00000000..97e5434e --- /dev/null +++ b/src/interfaces/resolvers/IDNSRecordResolver.sol @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.4; + +interface IDNSRecordResolver { + /// @dev Emitted whenever a given node/name/resource's RRSET is updated. + event DNSRecordChanged(bytes32 indexed node, bytes name, uint16 resource, bytes record); + /// @dev Emitted whenever a given node/name/resource's RRSET is deleted. + event DNSRecordDeleted(bytes32 indexed node, bytes name, uint16 resource); + + /** + * @dev Set one or more DNS records. Records are supplied in wire-format. Records with the same node/name/resource + * must be supplied one after the other to ensure the data is updated correctly. For example, if the data was + * supplied: + * a.example.com IN A 1.2.3.4 + * a.example.com IN A 5.6.7.8 + * www.example.com IN CNAME a.example.com. + * then this would store the two A records for a.example.com correctly as a single RRSET, however if the data was + * supplied: + * a.example.com IN A 1.2.3.4 + * www.example.com IN CNAME a.example.com. + * a.example.com IN A 5.6.7.8 + * then this would store the first A record, the CNAME, then the second A record which would overwrite the first. + * + * Requirements: + * - The method caller must be authorized to change user fields of RNS Token `node`. See indicator + * {ModifyingIndicator.USER_FIELDS_INDICATOR}. + * + * @param node the namehash of the node for which to set the records + * @param data the DNS wire format records to set + */ + function setDNSRecords(bytes32 node, bytes calldata data) external; + + /** + * @dev Obtain a DNS record. + * @param node the namehash of the node for which to fetch the record + * @param name the keccak-256 hash of the fully-qualified name for which to fetch the record + * @param resource the ID of the resource as per https://en.wikipedia.org/wiki/List_of_DNS_record_types + * @return the DNS record in wire format if present, otherwise empty + */ + function dnsRecord(bytes32 node, bytes32 name, uint16 resource) external view returns (bytes memory); +} diff --git a/src/interfaces/resolvers/IDNSZoneResolver.sol b/src/interfaces/resolvers/IDNSZoneResolver.sol new file mode 100644 index 00000000..ea74e062 --- /dev/null +++ b/src/interfaces/resolvers/IDNSZoneResolver.sol @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.4; + +interface IDNSZoneResolver { + /// @dev Emitted whenever a given node's zone hash is updated. + event DNSZonehashChanged(bytes32 indexed node, bytes lastzonehash, bytes zonehash); + + /** + * @dev Sets the hash for the zone. + * + * Requirements: + * - The method caller must be authorized to change user fields of RNS Token `node`. See indicator + * {ModifyingIndicator.USER_FIELDS_INDICATOR}. + * + * Emits an event {DNSZonehashChanged}. + * + * @param node The node to update. + * @param hash The zonehash to set + */ + function setZonehash(bytes32 node, bytes calldata hash) external; + + /** + * @dev Obtains the hash for the zone. + * @param node The INS node to query. + * @return The associated contenthash. + */ + function zonehash(bytes32 node) external view returns (bytes memory); +} diff --git a/src/interfaces/resolvers/IInterfaceResolver.sol b/src/interfaces/resolvers/IInterfaceResolver.sol new file mode 100644 index 00000000..e6f6018a --- /dev/null +++ b/src/interfaces/resolvers/IInterfaceResolver.sol @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +interface IInterfaceResolver { + /// @dev Emitted when the interface of node is changed. + event InterfaceChanged(bytes32 indexed node, bytes4 indexed interfaceID, address implementer); + + /** + * @dev Sets an interface associated with a name. + * Setting the address to 0 restores the default behaviour of querying the contract at `addr()` for interface support. + * + * Requirements: + * - The method caller must be authorized to change user fields of RNS Token `node`. See indicator + * {ModifyingIndicator.USER_FIELDS_INDICATOR}. + * + * @param node The node to update. + * @param interfaceID The EIP 165 interface ID. + * @param implementer The address of a contract that implements this interface for this node. + */ + function setInterface(bytes32 node, bytes4 interfaceID, address implementer) external; + + /** + * @dev Returns the address of a contract that implements the specified interface for this name. + * + * If an implementer has not been set for this interfaceID and name, the resolver will query the contract at `addr()`. + * If `addr()` is set, a contract exists at that address, and that contract implements EIP165 and returns `true` for + * the specified interfaceID, its address will be returned. + * + * @param node The INS node to query. + * @param interfaceID The EIP 165 interface ID to check for. + * @return The address that implements this interface, or 0 if the interface is unsupported. + */ + function interfaceImplementer(bytes32 node, bytes4 interfaceID) external view returns (address); +} diff --git a/src/interfaces/resolvers/IPublicKeyResolver.sol b/src/interfaces/resolvers/IPublicKeyResolver.sol new file mode 100644 index 00000000..760aa454 --- /dev/null +++ b/src/interfaces/resolvers/IPublicKeyResolver.sol @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +interface IPublicKeyResolver { + struct PublicKey { + bytes32 x; + bytes32 y; + } + + /// @dev Emitted when a node public key is changed. + event PubkeyChanged(bytes32 indexed node, bytes32 x, bytes32 y); + + /** + * @dev Sets the SECP256k1 public key associated with an INS node. + * + * Requirements: + * - The method caller must be authorized to change user fields of RNS Token `node`. See indicator + * {ModifyingIndicator.USER_FIELDS_INDICATOR}. + * + * Emits an event {PubkeyChanged}. + * + * @param node The INS node to query + * @param x the X coordinate of the curve point for the public key. + * @param y the Y coordinate of the curve point for the public key. + */ + function setPubkey(bytes32 node, bytes32 x, bytes32 y) external; + + /** + * @dev Returns the SECP256k1 public key associated with an INS node. + * Defined in EIP 619. + * + * @param node The INS node to query + * @return x The X coordinate of the curve point for the public key. + * @return y The Y coordinate of the curve point for the public key. + */ + function pubkey(bytes32 node) external view returns (bytes32 x, bytes32 y); +} diff --git a/src/interfaces/resolvers/IPublicResolver.sol b/src/interfaces/resolvers/IPublicResolver.sol new file mode 100644 index 00000000..a2e0d67f --- /dev/null +++ b/src/interfaces/resolvers/IPublicResolver.sol @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import { INSUnified } from "@rns-contracts/interfaces/INSUnified.sol"; +import { IReverseRegistrar } from "@rns-contracts/interfaces/IReverseRegistrar.sol"; +import { IABIResolver } from "./IABIResolver.sol"; +import { IAddressResolver } from "./IAddressResolver.sol"; +import { IContentHashResolver } from "./IContentHashResolver.sol"; +import { IDNSRecordResolver } from "./IDNSRecordResolver.sol"; +import { IDNSZoneResolver } from "./IDNSZoneResolver.sol"; +import { IInterfaceResolver } from "./IInterfaceResolver.sol"; +import { INameResolver } from "./INameResolver.sol"; +import { IPublicKeyResolver } from "./IPublicKeyResolver.sol"; +import { ITextResolver } from "./ITextResolver.sol"; +import { IMulticallable } from "../IMulticallable.sol"; + +interface IPublicResolver is + IABIResolver, + IAddressResolver, + IContentHashResolver, + IDNSRecordResolver, + IDNSZoneResolver, + IInterfaceResolver, + INameResolver, + IPublicKeyResolver, + ITextResolver, + IMulticallable +{ + /// @dev See {IERC1155-ApprovalForAll}. Logged when an operator is added or removed. + event ApprovalForAll(address indexed owner, address indexed operator, bool approved); + + /// @dev Logged when a delegate is approved or an approval is revoked. + event Approved(address owner, bytes32 indexed node, address indexed delegate, bool indexed approved); + + /** + * @dev Checks if an account is authorized to manage the resolution of a specific RNS node. + * @param node The RNS node. + * @param account The account address being checked for authorization. + * @return A boolean indicating whether the account is authorized. + */ + function isAuthorized(bytes32 node, address account) external view returns (bool); + + /** + * @dev Retrieves the RNSUnified associated with this resolver. + */ + function getRNSUnified() external view returns (INSUnified); + + /** + * @dev Retrieves the reverse registrar associated with this resolver. + */ + function getReverseRegistrar() external view returns (IReverseRegistrar); + + /** + * @dev This function provides an extra security check when called from privileged contracts (such as + * RONRegistrarController) that can set records on behalf of the node owners. + * + * Reverts if the node is not null but calldata is mismatched. + */ + function multicallWithNodeCheck(bytes32 node, bytes[] calldata data) external returns (bytes[] memory results); +} diff --git a/src/interfaces/resolvers/ITextResolver.sol b/src/interfaces/resolvers/ITextResolver.sol new file mode 100644 index 00000000..1408b4e4 --- /dev/null +++ b/src/interfaces/resolvers/ITextResolver.sol @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +interface ITextResolver { + /// @dev Emitted when a node text is changed. + event TextChanged(bytes32 indexed node, string indexed indexedKey, string key, string value); + + /** + * @dev Sets the text data associated with an INS node and key. + * + * Requirements: + * - The method caller must be authorized to change user fields of RNS Token `node`. See indicator + * {ModifyingIndicator.USER_FIELDS_INDICATOR}. + * + * Emits an event {TextChanged}. + * + * @param node The node to update. + * @param key The key to set. + * @param value The text data value to set. + */ + function setText(bytes32 node, string calldata key, string calldata value) external; + + /** + * Returns the text data associated with an INS node and key. + * @param node The INS node to query. + * @param key The text data key to query. + * @return The associated text data. + */ + function text(bytes32 node, string calldata key) external view returns (string memory); +} diff --git a/src/interfaces/resolvers/IVersionResolver.sol b/src/interfaces/resolvers/IVersionResolver.sol new file mode 100644 index 00000000..b88a3ebb --- /dev/null +++ b/src/interfaces/resolvers/IVersionResolver.sol @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +interface IVersionResolver { + /// @dev Emitted when the version of a node is changed. + event VersionChanged(bytes32 indexed node, uint64 newVersion); + + /** + * @dev Increments the record version associated with an INS node. + * + * Requirements: + * - The method caller must be authorized to change user fields of RNS Token `node`. See indicator + * {ModifyingIndicator.USER_FIELDS_INDICATOR}. + * + * Emits an event {VersionChanged}. + * + * @param node The node to update. + */ + function clearRecords(bytes32 node) external; + + /** + * @dev Returns the latest version of a node. + */ + function recordVersions(bytes32 node) external view returns (uint64); +} diff --git a/src/libraries/ErrorHandler.sol b/src/libraries/ErrorHandler.sol new file mode 100644 index 00000000..5ea118d6 --- /dev/null +++ b/src/libraries/ErrorHandler.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +library ErrorHandler { + error ExternalCallFailed(); + + function handleRevert(bool status, bytes memory returnOrRevertData) internal pure { + assembly { + if iszero(status) { + let revertLength := mload(returnOrRevertData) + if iszero(iszero(revertLength)) { + // Start of revert data bytes. The 0x20 offset is always the same. + revert(add(returnOrRevertData, 0x20), revertLength) + } + + // revert ExternalCallFailed() + mstore(0x00, 0x350c20f1) + revert(0x1c, 0x04) + } + } + } +} diff --git a/src/resolvers/ABIResolvable.sol b/src/resolvers/ABIResolvable.sol new file mode 100644 index 00000000..5d9c6e05 --- /dev/null +++ b/src/resolvers/ABIResolvable.sol @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import "@openzeppelin/contracts/utils/introspection/ERC165.sol"; +import "@rns-contracts/interfaces/resolvers/IABIResolver.sol"; +import "./BaseVersion.sol"; + +abstract contract ABIResolvable is IABIResolver, ERC165, BaseVersion { + /// @dev Gap for upgradeability. + uint256[50] private ____gap; + + /// @dev Mapping from version => node => content type => abi + mapping(uint64 version => mapping(bytes32 node => mapping(uint256 contentType => bytes abi))) internal _versionalAbi; + + /** + * @dev Override {IERC165-supportsInterface}. + */ + function supportsInterface(bytes4 interfaceID) public view virtual override(BaseVersion, ERC165) returns (bool) { + return interfaceID == type(IABIResolver).interfaceId || super.supportsInterface(interfaceID); + } + + /** + * @inheritdoc IABIResolver + */ + function ABI(bytes32 node, uint256 contentTypes) external view virtual override returns (uint256, bytes memory) { + mapping(uint256 contentType => bytes abi) storage abiSet = _versionalAbi[_recordVersion[node]][node]; + + for (uint256 contentType = 1; contentType <= contentTypes; contentType <<= 1) { + if ((contentType & contentTypes) != 0 && abiSet[contentType].length > 0) { + return (contentType, abiSet[contentType]); + } + } + + return (0, ""); + } + + /** + * @dev See {IABIResolver-setABI}. + */ + function _setABI(bytes32 node, uint256 contentType, bytes calldata data) internal { + if (((contentType - 1) & contentType) != 0) revert InvalidContentType(); + _versionalAbi[_recordVersion[node]][node][contentType] = data; + emit ABIChanged(node, contentType); + } +} diff --git a/src/resolvers/AddressResolvable.sol b/src/resolvers/AddressResolvable.sol new file mode 100644 index 00000000..65a46513 --- /dev/null +++ b/src/resolvers/AddressResolvable.sol @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import "@openzeppelin/contracts/utils/introspection/ERC165.sol"; +import "@rns-contracts/interfaces/resolvers/IAddressResolver.sol"; +import "./BaseVersion.sol"; + +abstract contract AddressResolvable is IAddressResolver, ERC165, BaseVersion { + /// @dev Gap for upgradeability. + uint256[50] private ____gap; + + /// @dev Mapping from version => node => address + mapping(uint64 version => mapping(bytes32 node => address addr)) internal _versionAddress; + + /** + * @dev Override {IERC165-supportsInterface}. + */ + function supportsInterface(bytes4 interfaceID) public view virtual override(BaseVersion, ERC165) returns (bool) { + return interfaceID == type(IAddressResolver).interfaceId || super.supportsInterface(interfaceID); + } + + /** + * @inheritdoc IAddressResolver + */ + function addr(bytes32 node) public view virtual override returns (address payable) { + return payable(_versionAddress[_recordVersion[node]][node]); + } + + /** + * @dev See {IAddressResolver-setAddr}. + */ + function _setAddr(bytes32 node, address addr_) internal { + emit AddrChanged(node, addr_); + _versionAddress[_recordVersion[node]][node] = addr_; + } +} diff --git a/src/resolvers/BaseVersion.sol b/src/resolvers/BaseVersion.sol new file mode 100644 index 00000000..4d8ba90e --- /dev/null +++ b/src/resolvers/BaseVersion.sol @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import "@openzeppelin/contracts/utils/introspection/ERC165.sol"; +import "@rns-contracts/interfaces/resolvers/IVersionResolver.sol"; + +abstract contract BaseVersion is IVersionResolver, ERC165 { + /// @dev Gap for upgradeability. + uint256[50] private ____gap; + + /// @dev Mapping from node => version + mapping(bytes32 node => uint64 version) internal _recordVersion; + + /** + * @dev Override {IERC165-supportsInterface}. + */ + function supportsInterface(bytes4 interfaceID) public view virtual override returns (bool) { + return interfaceID == type(IVersionResolver).interfaceId || super.supportsInterface(interfaceID); + } + + /** + * @inheritdoc IVersionResolver + */ + function recordVersions(bytes32 node) external view returns (uint64) { + return _recordVersion[node]; + } + + /** + * @dev See {IVersionResolver-clearRecords}. + */ + function _clearRecords(bytes32 node) internal { + unchecked { + emit VersionChanged(node, ++_recordVersion[node]); + } + } +} diff --git a/src/resolvers/ContentHashResolvable.sol b/src/resolvers/ContentHashResolvable.sol new file mode 100644 index 00000000..28a6f9be --- /dev/null +++ b/src/resolvers/ContentHashResolvable.sol @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import "@openzeppelin/contracts/utils/introspection/ERC165.sol"; +import "@rns-contracts/interfaces/resolvers/IContentHashResolver.sol"; +import "./BaseVersion.sol"; + +abstract contract ContentHashResolvable is IContentHashResolver, ERC165, BaseVersion { + /// @dev Gap for upgradeability. + uint256[50] private ____gap; + + /// @dev Mapping from version => node => content hash + mapping(uint64 version => mapping(bytes32 node => bytes contentHash)) internal _versionContentHash; + + /** + * @dev Override {IERC165-supportsInterface}. + */ + function supportsInterface(bytes4 interfaceID) public view virtual override(BaseVersion, ERC165) returns (bool) { + return interfaceID == type(IContentHashResolver).interfaceId || super.supportsInterface(interfaceID); + } + + /** + * @inheritdoc IContentHashResolver + */ + function contentHash(bytes32 node) external view virtual override returns (bytes memory) { + return _versionContentHash[_recordVersion[node]][node]; + } + + /** + * @dev See {IContentHashResolver-setContentHash}. + */ + function _setContentHash(bytes32 node, bytes calldata hash) internal { + _versionContentHash[_recordVersion[node]][node] = hash; + emit ContentHashChanged(node, hash); + } +} diff --git a/src/resolvers/DNSResolvable.sol b/src/resolvers/DNSResolvable.sol new file mode 100644 index 00000000..a8d59ca1 --- /dev/null +++ b/src/resolvers/DNSResolvable.sol @@ -0,0 +1,141 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import "@openzeppelin/contracts/utils/introspection/ERC165.sol"; +import "@ensdomains/ens-contracts/dnssec-oracle/RRUtils.sol"; +import "@rns-contracts/interfaces/resolvers/IDNSRecordResolver.sol"; +import "@rns-contracts/interfaces/resolvers/IDNSZoneResolver.sol"; +import "./BaseVersion.sol"; + +abstract contract DNSResolvable is IDNSRecordResolver, IDNSZoneResolver, ERC165, BaseVersion { + using RRUtils for *; + using BytesUtils for bytes; + + /// @dev Gap for upgradeability. + uint256[50] private ____gap; + + /// @dev The records themselves. Stored as binary RRSETs. + mapping( + uint64 version => mapping(bytes32 node => mapping(bytes32 nameHash => mapping(uint16 resource => bytes data))) + ) private _versionRecord; + + /// @dev Count of number of entries for a given name. Required for DNS resolvers when resolving wildcards. + mapping(uint64 version => mapping(bytes32 node => mapping(bytes32 nameHash => uint16 count))) private + _versionNameEntriesCount; + + /** + * @dev Zone hashes for the domains. A zone hash is an EIP-1577 content hash in binary format that should point to a + * resource containing a single zonefile. + */ + mapping(uint64 version => mapping(bytes32 node => bytes data)) private _versionZonehash; + + /** + * @dev Override {IERC165-supportsInterface}. + */ + function supportsInterface(bytes4 interfaceID) public view virtual override(BaseVersion, ERC165) returns (bool) { + return interfaceID == type(IDNSRecordResolver).interfaceId || interfaceID == type(IDNSZoneResolver).interfaceId + || super.supportsInterface(interfaceID); + } + + /** + * @dev Checks whether a given node has records. + * @param node the namehash of the node for which to check the records + * @param name the namehash of the node for which to check the records + */ + function hasDNSRecords(bytes32 node, bytes32 name) public view virtual returns (bool) { + return (_versionNameEntriesCount[_recordVersion[node]][node][name] != 0); + } + + /** + * @inheritdoc IDNSRecordResolver + */ + function dnsRecord(bytes32 node, bytes32 name, uint16 resource) public view virtual override returns (bytes memory) { + return _versionRecord[_recordVersion[node]][node][name][resource]; + } + + /** + * @inheritdoc IDNSZoneResolver + */ + function zonehash(bytes32 node) external view virtual override returns (bytes memory) { + return _versionZonehash[_recordVersion[node]][node]; + } + + /** + * @dev See {IDNSRecordResolver-setDNSRecords}. + */ + function _setDNSRecords(bytes32 node, bytes calldata data) internal { + uint16 resource = 0; + uint256 offset = 0; + bytes memory name; + bytes memory value; + bytes32 nameHash; + uint64 version = _recordVersion[node]; + // Iterate over the data to add the resource records + for (RRUtils.RRIterator memory iter = data.iterateRRs(0); !iter.done(); iter.next()) { + if (resource == 0) { + resource = iter.dnstype; + name = iter.name(); + nameHash = keccak256(abi.encodePacked(name)); + value = bytes(iter.rdata()); + } else { + bytes memory newName = iter.name(); + if (resource != iter.dnstype || !name.equals(newName)) { + _setDNSRRSet(node, name, resource, data, offset, iter.offset - offset, value.length == 0, version); + resource = iter.dnstype; + offset = iter.offset; + name = newName; + nameHash = keccak256(name); + value = bytes(iter.rdata()); + } + } + } + + if (name.length > 0) { + _setDNSRRSet(node, name, resource, data, offset, data.length - offset, value.length == 0, version); + } + } + + /** + * @dev See {IDNSZoneResolver-setZonehash}. + */ + function _setZonehash(bytes32 node, bytes calldata hash) internal { + uint64 currentRecordVersion = _recordVersion[node]; + bytes memory oldhash = _versionZonehash[currentRecordVersion][node]; + _versionZonehash[currentRecordVersion][node] = hash; + emit DNSZonehashChanged(node, oldhash, hash); + } + + /** + * @dev Helper method to set DNS config. + * + * May emit an event {DNSRecordDeleted}. + * May emit an event {DNSRecordChanged}. + * + */ + function _setDNSRRSet( + bytes32 node, + bytes memory name, + uint16 resource, + bytes memory data, + uint256 offset, + uint256 size, + bool deleteRecord, + uint64 version + ) private { + bytes32 nameHash = keccak256(name); + bytes memory rrData = data.substring(offset, size); + if (deleteRecord) { + if (_versionRecord[version][node][nameHash][resource].length != 0) { + _versionNameEntriesCount[version][node][nameHash]--; + } + delete (_versionRecord[version][node][nameHash][resource]); + emit DNSRecordDeleted(node, name, resource); + } else { + if (_versionRecord[version][node][nameHash][resource].length == 0) { + _versionNameEntriesCount[version][node][nameHash]++; + } + _versionRecord[version][node][nameHash][resource] = rrData; + emit DNSRecordChanged(node, name, resource, rrData); + } + } +} diff --git a/src/resolvers/InterfaceResolvable.sol b/src/resolvers/InterfaceResolvable.sol new file mode 100644 index 00000000..84671603 --- /dev/null +++ b/src/resolvers/InterfaceResolvable.sol @@ -0,0 +1,68 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import "@openzeppelin/contracts/utils/introspection/ERC165.sol"; +import { BaseVersion } from "./BaseVersion.sol"; +import { IInterfaceResolver } from "@rns-contracts/interfaces/resolvers/IInterfaceResolver.sol"; + +abstract contract InterfaceResolvable is IInterfaceResolver, ERC165, BaseVersion { + /// @dev Gap for upgradeability. + uint256[50] private ____gap; + + /// @dev Mapping from version => node => interfaceID => address + mapping(uint64 version => mapping(bytes32 node => mapping(bytes4 interfaceID => address addr))) internal + _versionInterface; + + /** + * @dev Override {IERC165-supportsInterface}. + */ + function supportsInterface(bytes4 interfaceID) public view virtual override(BaseVersion, ERC165) returns (bool) { + return interfaceID == type(IInterfaceResolver).interfaceId || super.supportsInterface(interfaceID); + } + + /** + * @inheritdoc IInterfaceResolver + */ + function interfaceImplementer(bytes32 node, bytes4 interfaceID) external view virtual override returns (address) { + address implementer = _versionInterface[_recordVersion[node]][node][interfaceID]; + if (implementer != address(0)) return implementer; + + address addrOfNode = addr(node); + if (addrOfNode == address(0)) return address(0); + + bool success; + bytes memory returnData; + + (success, returnData) = + addrOfNode.staticcall(abi.encodeCall(IERC165.supportsInterface, (type(IERC165).interfaceId))); + + // EIP 165 not supported by target + if (!_isValidReturnData(success, returnData)) return address(0); + + (success, returnData) = addrOfNode.staticcall(abi.encodeCall(IERC165.supportsInterface, (interfaceID))); + // Specified interface not supported by target + if (!_isValidReturnData(success, returnData)) return address(0); + + return addrOfNode; + } + + /** + * @dev See {IAddressResolver-addr}. + */ + function addr(bytes32 node) public view virtual returns (address payable); + + /** + * @dev Checks whether the return data is valid. + */ + function _isValidReturnData(bool success, bytes memory returnData) internal pure returns (bool) { + return success || returnData.length < 32 || returnData[31] == 0; + } + + /** + * @dev See {InterfaceResolver-setInterface}. + */ + function _setInterface(bytes32 node, bytes4 interfaceID, address implementer) internal virtual { + _versionInterface[_recordVersion[node]][node][interfaceID] = implementer; + emit InterfaceChanged(node, interfaceID, implementer); + } +} diff --git a/src/resolvers/NameResolvable.sol b/src/resolvers/NameResolvable.sol new file mode 100644 index 00000000..11b50824 --- /dev/null +++ b/src/resolvers/NameResolvable.sol @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import { BaseVersion } from "./BaseVersion.sol"; +import { INameResolver } from "@rns-contracts/interfaces/resolvers/INameResolver.sol"; + +abstract contract NameResolvable is INameResolver, BaseVersion { + /// @dev Gap for upgradeability. + uint256[50] private ____gap; + + /// @dev mapping from version => node => name + mapping(uint64 version => mapping(bytes32 node => string name)) internal _versionName; + + /** + * @dev Override {IERC165-supportsInterface}. + */ + function supportsInterface(bytes4 interfaceID) public view virtual override returns (bool) { + return interfaceID == type(INameResolver).interfaceId || super.supportsInterface(interfaceID); + } + + /** + * @inheritdoc INameResolver + */ + function name(bytes32 node) public view virtual override returns (string memory) { + return _versionName[_recordVersion[node]][node]; + } + + /** + * @dev See {INameResolver-setName}. + */ + function _setName(bytes32 node, string memory newName) internal virtual { + _versionName[_recordVersion[node]][node] = newName; + emit NameChanged(node, newName); + } +} diff --git a/src/resolvers/PublicKeyResolvable.sol b/src/resolvers/PublicKeyResolvable.sol new file mode 100644 index 00000000..e2e26b64 --- /dev/null +++ b/src/resolvers/PublicKeyResolvable.sol @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import { BaseVersion } from "./BaseVersion.sol"; +import { IPublicKeyResolver } from "@rns-contracts/interfaces/resolvers/IPublicKeyResolver.sol"; + +abstract contract PublicKeyResolvable is BaseVersion, IPublicKeyResolver { + /// @dev Gap for upgradeability. + uint256[50] private ____gap; + + /// @dev Mapping from version => node => public key + mapping(uint64 version => mapping(bytes32 node => PublicKey publicKey)) internal _versionPublicKey; + + /** + * @dev Override {IERC165-supportsInterface}. + */ + function supportsInterface(bytes4 interfaceID) public view virtual override returns (bool) { + return interfaceID == type(IPublicKeyResolver).interfaceId || super.supportsInterface(interfaceID); + } + + /** + * @dev See {IPublicKeyResolver-pubkey}. + */ + function pubkey(bytes32 node) external view virtual override returns (bytes32 x, bytes32 y) { + uint64 currentRecordVersion = _recordVersion[node]; + return (_versionPublicKey[currentRecordVersion][node].x, _versionPublicKey[currentRecordVersion][node].y); + } + + /** + * @dev See {IPublicKeyResolver-setPubkey}. + */ + function _setPubkey(bytes32 node, bytes32 x, bytes32 y) internal virtual { + _versionPublicKey[_recordVersion[node]][node] = PublicKey(x, y); + emit PubkeyChanged(node, x, y); + } +} diff --git a/src/resolvers/PublicResolver.sol b/src/resolvers/PublicResolver.sol new file mode 100644 index 00000000..6f653816 --- /dev/null +++ b/src/resolvers/PublicResolver.sol @@ -0,0 +1,190 @@ +//SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import { Initializable } from "@openzeppelin/contracts/proxy/utils/Initializable.sol"; +import { IVersionResolver } from "@rns-contracts/interfaces/resolvers/IVersionResolver.sol"; +import { Multicallable } from "@rns-contracts/extensions/Multicallable.sol"; +import { USER_FIELDS_INDICATOR } from "../types/ModifyingIndicator.sol"; +import { ABIResolvable } from "./ABIResolvable.sol"; +import { AddressResolvable } from "./AddressResolvable.sol"; +import { ContentHashResolvable } from "./ContentHashResolvable.sol"; +import { DNSResolvable } from "./DNSResolvable.sol"; +import { InterfaceResolvable } from "./InterfaceResolvable.sol"; +import { NameResolvable } from "./NameResolvable.sol"; +import { PublicKeyResolvable } from "./PublicKeyResolvable.sol"; +import { TextResolvable } from "./TextResolvable.sol"; +import "@rns-contracts/interfaces/resolvers/IPublicResolver.sol"; + +/** + * @title Public Resolver + * @notice Customized version of PublicResolver: https://github.com/ensdomains/ens-contracts/blob/0c75ba23fae76165d51c9c80d76d22261e06179d/contracts/resolvers/PublicResolver.sol + * @dev A simple resolver anyone can use, only allows the owner of a node to set its address. + */ +contract PublicResolver is + IPublicResolver, + ABIResolvable, + AddressResolvable, + ContentHashResolvable, + DNSResolvable, + InterfaceResolvable, + NameResolvable, + PublicKeyResolvable, + TextResolvable, + Multicallable, + Initializable +{ + /// @dev Gap for upgradeability. + uint256[50] private ____gap; + + /// @dev The RNS Unified contract + INSUnified internal _rnsUnified; + + /// @dev The reverse registrar contract + IReverseRegistrar internal _reverseRegistrar; + + modifier onlyAuthorized(bytes32 node) { + _requireAuthorized(node, msg.sender); + _; + } + + constructor() payable { + _disableInitializers(); + } + + function initialize(INSUnified rnsUnified, IReverseRegistrar reverseRegistrar) external initializer { + _rnsUnified = rnsUnified; + _reverseRegistrar = reverseRegistrar; + } + + /** + * @dev Override {IERC165-supportsInterface}. + */ + function supportsInterface(bytes4 interfaceID) + public + view + override( + ABIResolvable, + AddressResolvable, + ContentHashResolvable, + DNSResolvable, + InterfaceResolvable, + NameResolvable, + PublicKeyResolvable, + TextResolvable, + Multicallable + ) + returns (bool) + { + return super.supportsInterface(interfaceID); + } + + /// @inheritdoc IPublicResolver + function getRNSUnified() external view returns (INSUnified) { + return _rnsUnified; + } + + /// @inheritdoc IPublicResolver + function getReverseRegistrar() external view returns (IReverseRegistrar) { + return _reverseRegistrar; + } + + /// @inheritdoc IPublicResolver + function multicallWithNodeCheck(bytes32 node, bytes[] calldata data) + external + override + returns (bytes[] memory results) + { + if (node != 0) { + for (uint256 i; i < data.length;) { + require(node == bytes32(data[i][4:36]), "PublicResolver: All records must have a matching namehash"); + unchecked { + ++i; + } + } + } + + return _tryMulticall(true, data); + } + + /// @inheritdoc IVersionResolver + function clearRecords(bytes32 node) external onlyAuthorized(node) { + _clearRecords(node); + } + + /// @inheritdoc IABIResolver + function setABI(bytes32 node, uint256 contentType, bytes calldata data) external onlyAuthorized(node) { + _setABI(node, contentType, data); + } + + /// @inheritdoc IAddressResolver + function setAddr(bytes32 node, address addr_) external onlyAuthorized(node) { + revert("PublicResolver: Cannot set address"); + _setAddr(node, addr_); + } + + /// @inheritdoc IContentHashResolver + function setContentHash(bytes32 node, bytes calldata hash) external onlyAuthorized(node) { + _setContentHash(node, hash); + } + + /// @inheritdoc IDNSRecordResolver + function setDNSRecords(bytes32 node, bytes calldata data) external onlyAuthorized(node) { + _setDNSRecords(node, data); + } + + /// @inheritdoc IDNSZoneResolver + function setZonehash(bytes32 node, bytes calldata hash) external onlyAuthorized(node) { + _setZonehash(node, hash); + } + + /// @inheritdoc IInterfaceResolver + function setInterface(bytes32 node, bytes4 interfaceID, address implementer) external onlyAuthorized(node) { + _setInterface(node, interfaceID, implementer); + } + + /// @inheritdoc INameResolver + function setName(bytes32 node, string calldata newName) external onlyAuthorized(node) { + _setName(node, newName); + } + + /// @inheritdoc IPublicKeyResolver + function setPubkey(bytes32 node, bytes32 x, bytes32 y) external onlyAuthorized(node) { + _setPubkey(node, x, y); + } + + /// @inheritdoc ITextResolver + function setText(bytes32 node, string calldata key, string calldata value) external onlyAuthorized(node) { + _setText(node, key, value); + } + + /// @inheritdoc IPublicResolver + function isAuthorized(bytes32 node, address account) public view returns (bool authorized) { + (authorized,) = _rnsUnified.canSetRecord(account, uint256(node), USER_FIELDS_INDICATOR); + } + + /// @dev Override {IAddressResolvable-addr}. + function addr(bytes32 node) + public + view + virtual + override(AddressResolvable, IAddressResolver, InterfaceResolvable) + returns (address payable) + { + return payable(_rnsUnified.ownerOf(uint256(node))); + } + + /// @dev Override {INameResolver-name}. + function name(bytes32 node) public view virtual override(INameResolver, NameResolvable) returns (string memory) { + address reversedAddress = _reverseRegistrar.getAddress(node); + string memory domainName = super.name(node); + uint256 tokenId = uint256(_rnsUnified.namehash(domainName)); + return _rnsUnified.ownerOf(tokenId) == reversedAddress ? domainName : ""; + } + + /** + * @dev Reverts if the msg sender is not authorized. + */ + function _requireAuthorized(bytes32 node, address account) internal view { + require(isAuthorized(node, account), "PublicResolver: unauthorized caller"); + } +} diff --git a/src/resolvers/TextResolvable.sol b/src/resolvers/TextResolvable.sol new file mode 100644 index 00000000..92b0290c --- /dev/null +++ b/src/resolvers/TextResolvable.sol @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import { BaseVersion } from "./BaseVersion.sol"; +import { ITextResolver } from "@rns-contracts/interfaces/resolvers/ITextResolver.sol"; + +abstract contract TextResolvable is BaseVersion, ITextResolver { + /// @dev Gap for upgradeability. + uint256[50] private ____gap; + /// @dev Mapping from version => node => key => text + mapping(uint64 version => mapping(bytes32 node => mapping(string key => string text))) internal _versionText; + + /** + * @dev Override {IERC165-supportsInterface}. + */ + function supportsInterface(bytes4 interfaceID) public view virtual override returns (bool) { + return interfaceID == type(ITextResolver).interfaceId || super.supportsInterface(interfaceID); + } + + /** + * @inheritdoc ITextResolver + */ + function text(bytes32 node, string calldata key) external view virtual override returns (string memory) { + return _versionText[_recordVersion[node]][node][key]; + } + + /** + * @dev See {ITextResolver-setText}. + */ + function _setText(bytes32 node, string calldata key, string calldata value) internal virtual { + _versionText[_recordVersion[node]][node][key] = value; + emit TextChanged(node, key, key, value); + } +}