SemverResolver

Description:

Multi-signature wallet contract requiring multiple confirmations for transaction execution.

Blockchain: Ethereum

Source Code: View Code On The Blockchain

Solidity Source Code:

{{
  "language": "Solidity",
  "sources": {
    "src/SemverResolver.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

// Using IERC165 from forge-std to avoid OpenZeppelin version conflicts
import {IERC165} from "forge-std/interfaces/IERC165.sol";
import {Strings} from "@openzeppelin/contracts/utils/Strings.sol";
import {ENS} from "ens-contracts/registry/ENS.sol";
import {IExtendedResolver} from "ens-contracts/resolvers/profiles/IExtendedResolver.sol";
import {IContentHashResolver} from "ens-contracts/resolvers/profiles/IContentHashResolver.sol";
import {ITextResolver} from "ens-contracts/resolvers/profiles/ITextResolver.sol";
import {INameWrapper} from "ens-contracts/wrapper/INameWrapper.sol";
import {NameCoder} from "ens-contracts/utils/NameCoder.sol";
import {BytesUtils} from "ens-contracts/utils/BytesUtils.sol";
import {VersionRegistry} from "./VersionRegistry.sol";

/// @title SemverResolver
/// @notice ENS resolver with semantic versioning support and wildcard resolution
/// @dev Implements IExtendedResolver for wildcard queries (e.g., "1-2.myapp.eth" resolves to highest 1.2.x version)
/// @dev Supports contenthash and text("version") resolution for versioned content
contract SemverResolver is VersionRegistry, IExtendedResolver, IContentHashResolver, ITextResolver, IERC165 {
    // ABI encoding constants
    uint256 private constant SELECTOR_SIZE = 4;

    // DNS encoding constants
    uint256 private constant DNS_LABEL_LENGTH_OFFSET = 0; // Position of length byte in DNS label
    uint256 private constant DNS_LABEL_DATA_OFFSET = 1; // Position where label data starts

    // Array indexing constants
    uint256 private constant FIRST_ELEMENT_INDEX = 0;

    // Version label constant
    string private constant VERSION_LABEL = "version";

    // Precomputed hash for "version" key to save gas
    bytes32 private constant VERSION_KEY_HASH = keccak256(bytes(VERSION_LABEL));

    // IPFS CIDv1 dag-pb contenthash with multihash prefix for ENS (EIP-1577)
    // Format: <protocol><cid-version><multicodec><hash-function><hash-length>
    // 0xe3 = IPFS protocol, 0x01 = CIDv1, 0x01 = raw, 0x70 = dag-pb, 0x12 = sha2-256, 0x20 = 32 bytes
    bytes6 private constant IPFS_CONTENTHASH_PREFIX = hex"e30101701220";

    ENS public immutable ENS_REGISTRY;
    INameWrapper public immutable NAME_WRAPPER;

    // Standard ENS errors (same signatures as defined in ENS ecosystem)
    // These match the errors defined in:
    // - Unauthorised: ens-contracts/wrapper/NameWrapper.sol:19
    // - UnsupportedResolverProfile: ens-contracts/universalResolver/IUniversalResolver.sol:17
    error Unauthorised(bytes32 node, address addr);
    error UnsupportedResolverProfile(bytes4 selector);

    /// @dev Gets the actual owner of an ENS name, handling wrapped names
    /// @param node The namehash of the ENS name
    /// @return The actual owner address (unwrapped if necessary)
    function _getActualOwner(bytes32 node) internal view returns (address) {
        address owner = ENS_REGISTRY.owner(node);

        // If the owner is the NameWrapper contract, get the actual owner from the wrapper
        if (owner == address(NAME_WRAPPER)) {
            try NAME_WRAPPER.ownerOf(uint256(node)) returns (address actualOwner) {
                return actualOwner;
            } catch {
                // If the call fails, fall back to the registry owner
                return owner;
            }
        }

        return owner;
    }

    /// @dev Checks if the caller is authorized for the given node
    /// @param node The namehash of the ENS name
    /// @param caller The address to check authorization for
    /// @return True if authorized, false otherwise
    function _isAuthorised(bytes32 node, address caller) internal view returns (bool) {
        address actualOwner = _getActualOwner(node);

        // Check if caller is the owner or approved by the owner
        return caller == actualOwner || ENS_REGISTRY.isApprovedForAll(actualOwner, caller);
    }

    /// @dev Restricts access to ENS name owner or approved operators
    /// @dev Now properly handles wrapped ENS names via NameWrapper contract
    modifier authorised(bytes32 node) {
        if (!_isAuthorised(node, msg.sender)) {
            revert Unauthorised(node, msg.sender);
        }
        _;
    }

    /// @notice Creates a new SemverResolver that enables version-aware ENS resolution
    /// @param _ens The ENS registry contract address
    /// @param _nameWrapper The NameWrapper contract address
    constructor(ENS _ens, INameWrapper _nameWrapper) {
        ENS_REGISTRY = _ens;
        NAME_WRAPPER = _nameWrapper;
    }

    /// @dev Encodes a raw IPFS hash for ENS contenthash (EIP-1577 compliance)
    /// @param rawHash Raw 32-byte IPFS hash (sha256 digest only, not full CID)
    /// @return Properly encoded contenthash with IPFS CIDv1 dag-pb multihash prefix
    /// @dev Encoding format: 0xe3 (IPFS) + 0x01 (CIDv1) + 0x70 (dag-pb) + 0x12 (sha2-256) + 0x20 (32 bytes)
    /// @dev Null safety: Returns empty bytes for zero hash (indicates no content)
    /// @dev Examples:
    ///   - _encodeIpfsContenthash(0x0) → "" (empty)
    ///   - _encodeIpfsContenthash(sha256("content")) → 0xe30101701220{32-byte-hash}
    function _encodeIpfsContenthash(bytes32 rawHash) internal pure returns (bytes memory) {
        if (rawHash == bytes32(0)) {
            return "";
        }
        // Encode IPFS hash with proper multihash prefix for ENS contenthash (EIP-1577)
        return abi.encodePacked(IPFS_CONTENTHASH_PREFIX, rawHash);
    }

    /// @notice Checks if this resolver supports a specific interface like contenthash or text resolution
    /// @param interfaceId The interface identifier to check (ERC-165)
    /// @return True if the interface is supported, false otherwise
    /// @dev Supports IExtendedResolver, IContentHashResolver, ITextResolver, and ERC165
    function supportsInterface(bytes4 interfaceId) public pure override returns (bool) {
        return interfaceId == type(IExtendedResolver).interfaceId
            || interfaceId == type(IContentHashResolver).interfaceId || interfaceId == 0xbc1c58d1 // ENSIP-7 contenthash
            || interfaceId == type(ITextResolver).interfaceId || interfaceId == 0x01ffc9a7; // ERC165
    }

    /// @notice Resolves version-aware ENS queries like "1-2.myapp.eth" to find the highest matching 1.2.x version
    /// @param name DNS-encoded name (e.g., "\x031-2\x06myapp\x03eth\x00" for "1-2.myapp.eth")
    /// @param data ABI-encoded function call (selector + arguments)
    /// @return ABI-encoded return value from the resolved function
    /// @dev Supports two resolution profiles:
    ///   1. IContentHashResolver.contenthash → returns IPFS content hash for version
    ///   2. ITextResolver.text → returns version string for key="version"
    /// @dev Resolution strategy:
    ///   - First attempts direct resolution (exact name match)
    ///   - Falls back to wildcard resolution if no direct match found
    /// @dev Complexity: O(log n) where n is number of versions for the base name
    /// @dev Examples:
    ///   - resolve("1-2.myapp.eth", contenthash.selector) → content hash for highest 1.2.x version
    ///   - resolve("1.myapp.eth", text.selector + "version") → version string for highest 1.x.x
    function resolve(bytes memory name, bytes memory data) external view override returns (bytes memory) {
        require(data.length >= SELECTOR_SIZE, "Invalid data length");
        bytes4 selector = bytes4(data);

        if (selector == IContentHashResolver.contenthash.selector) {
            bytes32 node = NameCoder.namehash(name, 0);
            bytes memory hash = this.contenthash(node);
            // If no direct match, try wildcard resolution
            if (hash.length == 0) {
                hash = _resolveWildcardContenthash(name, data);
            }
            return abi.encode(hash);
        }

        if (selector == ITextResolver.text.selector) {
            // Strip the selector to get the arguments
            assert(data.length >= SELECTOR_SIZE); // SMTChecker: ensure valid data length
            (, string memory key) =
                abi.decode(BytesUtils.substring(data, SELECTOR_SIZE, data.length - SELECTOR_SIZE), (bytes32, string));
            string memory value = _resolveWildcardText(name, key);

            return abi.encode(value);
        }

        revert UnsupportedResolverProfile(selector);
    }

    /// @notice Gets the IPFS content hash for the latest version of an ENS name
    /// @param node The ENS namehash to query
    /// @return The content hash of the latest version as bytes, or empty if no versions exist
    /// @dev Implements IContentHashResolver interface for direct (non-wildcard) queries
    function contenthash(bytes32 node) external view override returns (bytes memory) {
        bytes32 hash = getLatestContentHash(node);
        return _encodeIpfsContenthash(hash);
    }

    /// @notice Gets text data for an ENS name, currently only supports the "version" key
    /// @param node The ENS namehash to query
    /// @param key The text record key (only "version" is supported)
    /// @return The text value for the key, or empty string if not found or unsupported
    /// @dev Only supports key "version" which returns the latest version as a string (e.g., "1.2.3")
    /// @dev All other keys return empty string as manual text records are not supported
    function text(bytes32 node, string calldata key) external view override returns (string memory) {
        // Special handling for "version" key - return latest version as string
        if (keccak256(bytes(key)) == VERSION_KEY_HASH) {
            Version memory latestVersion = getLatestVersion(node);

            // If no versions exist, return empty string
            if (latestVersion.major == 0 && latestVersion.minor == 0 && latestVersion.patch == 0) {
                return "";
            }

            return _versionToString(latestVersion);
        }

        // For all other keys, return empty string (no manual text setting allowed)
        return "";
    }

    /// @dev Core wildcard version resolution logic
    /// @param name DNS-encoded name where first label is version (e.g., "\x031-2\x06myapp\x03eth\x00")
    /// @return Version record with the highest matching version, or zero version if not found
    /// @notice Supports three query types:
    ///   - Major-only: "1" → finds highest 1.x.x version
    ///   - Major.minor: "1-2" → finds highest 1.2.x version
    ///   - Exact: "1-2-3" → finds exact 1.2.3 version
    /// @notice Uses hyphen separators instead of dots to avoid DNS label conflicts
    /// @notice Returns zero version (0.0.0) if no matching version exists
    /// @dev Complexity: Refactored into smaller functions for better readability
    function _resolveWildcardVersion(bytes memory name) internal view returns (VersionRecord memory) {
        // Parse the DNS-encoded name to extract version and base components
        (string memory versionLabel, bytes32 baseNode) = _extractVersionAndBaseName(name);

        // Parse the version label to determine query parameters
        ParsedVersion memory parsedVersion = _parseVersionFromLabel(versionLabel);

        // Execute the appropriate version query based on parsed components
        return _executeVersionQuery(baseNode, parsedVersion);
    }

    /// @dev Extracts version label and base name from DNS-encoded name
    /// @param name DNS-encoded name (e.g., "\x031-2\x06myapp\x03eth\x00")
    /// @return versionLabel The version string from first label (e.g., "1-2")
    /// @return baseNode The namehash of the base name (e.g., namehash("myapp.eth"))
    /// @dev Example: "\x031-2\x06myapp\x03eth\x00" → ("1-2", namehash("myapp.eth"))
    function _extractVersionAndBaseName(bytes memory name)
        private
        pure
        returns (string memory versionLabel, bytes32 baseNode)
    {
        // Extract the first label length from DNS encoding
        // Note: DNS name validation is handled upstream by NameCoder.namehash()
        uint256 labelLength = uint256(uint8(name[DNS_LABEL_LENGTH_OFFSET]));

        // Extract version label (first label after length byte)
        bytes memory versionBytes = BytesUtils.substring(name, DNS_LABEL_DATA_OFFSET, labelLength);
        versionLabel = string(versionBytes);

        // Extract base name (remainder after version label)
        bytes memory baseName = BytesUtils.substring(
            name, labelLength + DNS_LABEL_DATA_OFFSET, name.length - labelLength - DNS_LABEL_DATA_OFFSET
        );
        baseNode = NameCoder.namehash(baseName, FIRST_ELEMENT_INDEX);

        return (versionLabel, baseNode);
    }

    /// @dev Executes the appropriate version query based on parsed version components
    /// @param baseNode The namehash of the base ENS name
    /// @param parsedVersion The parsed version with component flags
    /// @return The matching version record or zero version if not found
    /// @dev Query types:
    ///   - Major only: hasMinor=false → getHighestVersionForMajor()
    ///   - Major.minor: hasMinor=true, hasPatch=false → getHighestVersionForMajorMinor()
    ///   - Exact: hasPatch=true → getExactVersion()
    function _executeVersionQuery(bytes32 baseNode, ParsedVersion memory parsedVersion)
        private
        view
        returns (VersionRecord memory)
    {
        Version memory version = parsedVersion.version;

        if (!parsedVersion.hasMinor) {
            // Major-only query: find highest version with matching major (e.g., "1" matches 1.x.x)
            return getHighestVersionForMajor(baseNode, version.major);
        } else if (!parsedVersion.hasPatch) {
            // Major.minor query: find highest version with matching major.minor (e.g., "1-2" matches 1.2.x)
            return getHighestVersionForMajorMinor(baseNode, version.major, version.minor);
        } else {
            // Exact version query: find exact match (e.g., "1-2-3" matches 1.2.3 only)
            return getExactVersion(baseNode, version.major, version.minor, version.patch);
        }
    }

    /// @dev Resolves contenthash for wildcard version queries
    /// @param name DNS-encoded name with version prefix (e.g., "\x031-2\x06myapp\x03eth\x00")
    /// @return ABI-encoded content hash of the matched version, or empty bytes if no match
    /// @notice This function is called by resolve() when direct contenthash lookup fails
    function _resolveWildcardContenthash(bytes memory name, bytes memory /* data */ )
        internal
        view
        returns (bytes memory)
    {
        VersionRecord memory result = _resolveWildcardVersion(name);

        // If no matching version found, return empty
        if (result.contentHash == bytes32(0)) {
            return "";
        }

        return _encodeIpfsContenthash(result.contentHash);
    }

    /// @dev Resolves text record (version string) for wildcard version queries
    /// @param name DNS-encoded name with version prefix (e.g., "\x031-2\x06myapp\x03eth\x00")
    /// @return Version string of the matched version (e.g., "1.2.3"), or empty if no match
    /// @notice This function is called by resolve() for text("version") wildcard queries
    function _resolveWildcardText(bytes memory name, string memory /* key */ ) internal view returns (string memory) {
        VersionRecord memory result = _resolveWildcardVersion(name);

        // If no matching version found, return empty
        if (result.contentHash == bytes32(0)) {
            return "";
        }

        // Return the version as a string
        return _versionToString(result.version);
    }

    /// @notice Publishes a new version of content for your ENS name (e.g., version `major`.`minor`.`patch of your hash `contentHash`).
    /// @param namehash The ENS namehash to publish content for
    /// @param major The major version number (0-255)
    /// @param minor The minor version number (0-255)
    /// @param patch The patch version number (0-65535)
    /// @param contentHash Raw IPFS hash (32 bytes, sha256 digest only)
    /// @dev contentHash should be the raw sha256 hash from IPFS CID, not the full CID
    /// @dev For JavaScript: use `ipfs.add()` then extract hash from CID using libraries like:
    /// @dev - multiformats: `CID.parse(cid).multihash.digest`
    /// @dev - ipfs-http-client: built-in hash extraction utilities
    /// @dev The resolver automatically encodes this as EIP-1577 contenthash for ENS compatibility
    /// @dev Only callable by the ENS name owner or approved operators
    /// @dev Version must be strictly greater than all existing versions (enforced by addVersion)
    /// @dev Emits ContenthashChanged and TextChanged events for the exact version and parent versions that resolve to it
    function publishContent(bytes32 namehash, uint8 major, uint8 minor, uint16 patch, bytes32 contentHash)
        external
        authorised(namehash)
    {
        addVersion(namehash, major, minor, patch, contentHash);

        bytes memory encodedContenthash = _encodeIpfsContenthash(contentHash);
        string memory newVersionString = _versionToString(_createVersion(major, minor, patch));

        // Emit ContenthashChanged event for the exact version (base namehash)
        emit ContenthashChanged(namehash, encodedContenthash);

        // Emit TextChanged event for the "version" key for the exact version
        emit TextChanged(namehash, VERSION_LABEL, VERSION_LABEL, newVersionString);

        // Check if this new version is now the highest patch for this major.minor
        // If so, emit events for the major.minor version queries (e.g., "1-2.ebooks.thomasoncrypto.eth")
        VersionRecord memory currentHighestForMajorMinor = getHighestVersionForMajorMinor(namehash, major, minor);
        if (currentHighestForMajorMinor.version.patch == patch) {
            // This new version is the highest for its major.minor - emit events for major.minor queries
            bytes32 majorMinorNamehash = _computeVersionNamehash(namehash, major, minor, false);
            emit ContenthashChanged(majorMinorNamehash, encodedContenthash);
            emit TextChanged(majorMinorNamehash, VERSION_LABEL, VERSION_LABEL, newVersionString);
        }

        // Check if this new version is now the highest overall for this major
        // If so, emit events for the major version queries (e.g., "1.ebooks.thomasoncrypto.eth")
        VersionRecord memory currentHighestForMajor = getHighestVersionForMajor(namehash, major);
        if (
            currentHighestForMajor.version.major == major && currentHighestForMajor.version.minor == minor
                && currentHighestForMajor.version.patch == patch
        ) {
            // This new version is the highest for its major - emit events for major queries
            bytes32 majorNamehash = _computeVersionNamehash(namehash, major, 0, true);
            emit ContenthashChanged(majorNamehash, encodedContenthash);
            emit TextChanged(majorNamehash, VERSION_LABEL, VERSION_LABEL, newVersionString);
        }
    }

    /// @dev Computes the namehash for version-specific subdomains
    /// @param baseNamehash The base ENS namehash (e.g., for "ebooks.thomasoncrypto.eth")
    /// @param major The major version number
    /// @param minor The minor version number (ignored if majorOnly is true)
    /// @param majorOnly If true, computes hash for "1.base", if false for "1-2.base"
    /// @return The computed namehash for the version subdomain
    function _computeVersionNamehash(bytes32 baseNamehash, uint8 major, uint8 minor, bool majorOnly)
        private
        pure
        returns (bytes32)
    {
        string memory versionLabel;
        if (majorOnly) {
            versionLabel = Strings.toString(major);
        } else {
            versionLabel = string(abi.encodePacked(Strings.toString(major), "-", Strings.toString(minor)));
        }

        // Use ENS NameCoder to properly compute the namehash for version subdomain
        bytes32 labelHash = keccak256(bytes(versionLabel));
        return NameCoder.namehash(baseNamehash, labelHash);
    }
}
"
    },
    "lib/forge-std/src/interfaces/IERC165.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity >=0.6.2;

interface IERC165 {
    /// @notice Query if a contract implements an interface
    /// @param interfaceID The interface identifier, as specified in ERC-165
    /// @dev Interface identification is specified in ERC-165. This function
    /// uses less than 30,000 gas.
    /// @return `true` if the contract implements `interfaceID` and
    /// `interfaceID` is not 0xffffffff, `false` otherwise
    function supportsInterface(bytes4 interfaceID) external view returns (bool);
}
"
    },
    "lib/openzeppelin-contracts/contracts/utils/Strings.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (utils/Strings.sol)

pragma solidity ^0.8.20;

import {Math} from "./math/Math.sol";
import {SafeCast} from "./math/SafeCast.sol";
import {SignedMath} from "./math/SignedMath.sol";

/**
 * @dev String operations.
 */
library Strings {
    using SafeCast for *;

    bytes16 private constant HEX_DIGITS = "0123456789abcdef";
    uint8 private constant ADDRESS_LENGTH = 20;
    uint256 private constant SPECIAL_CHARS_LOOKUP =
        (1 << 0x08) | // backspace
            (1 << 0x09) | // tab
            (1 << 0x0a) | // newline
            (1 << 0x0c) | // form feed
            (1 << 0x0d) | // carriage return
            (1 << 0x22) | // double quote
            (1 << 0x5c); // backslash

    /**
     * @dev The `value` string doesn't fit in the specified `length`.
     */
    error StringsInsufficientHexLength(uint256 value, uint256 length);

    /**
     * @dev The string being parsed contains characters that are not in scope of the given base.
     */
    error StringsInvalidChar();

    /**
     * @dev The string being parsed is not a properly formatted address.
     */
    error StringsInvalidAddressFormat();

    /**
     * @dev Converts a `uint256` to its ASCII `string` decimal representation.
     */
    function toString(uint256 value) internal pure returns (string memory) {
        unchecked {
            uint256 length = Math.log10(value) + 1;
            string memory buffer = new string(length);
            uint256 ptr;
            assembly ("memory-safe") {
                ptr := add(add(buffer, 0x20), length)
            }
            while (true) {
                ptr--;
                assembly ("memory-safe") {
                    mstore8(ptr, byte(mod(value, 10), HEX_DIGITS))
                }
                value /= 10;
                if (value == 0) break;
            }
            return buffer;
        }
    }

    /**
     * @dev Converts a `int256` to its ASCII `string` decimal representation.
     */
    function toStringSigned(int256 value) internal pure returns (string memory) {
        return string.concat(value < 0 ? "-" : "", toString(SignedMath.abs(value)));
    }

    /**
     * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.
     */
    function toHexString(uint256 value) internal pure returns (string memory) {
        unchecked {
            return toHexString(value, Math.log256(value) + 1);
        }
    }

    /**
     * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.
     */
    function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {
        uint256 localValue = value;
        bytes memory buffer = new bytes(2 * length + 2);
        buffer[0] = "0";
        buffer[1] = "x";
        for (uint256 i = 2 * length + 1; i > 1; --i) {
            buffer[i] = HEX_DIGITS[localValue & 0xf];
            localValue >>= 4;
        }
        if (localValue != 0) {
            revert StringsInsufficientHexLength(value, length);
        }
        return string(buffer);
    }

    /**
     * @dev Converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal
     * representation.
     */
    function toHexString(address addr) internal pure returns (string memory) {
        return toHexString(uint256(uint160(addr)), ADDRESS_LENGTH);
    }

    /**
     * @dev Converts an `address` with fixed length of 20 bytes to its checksummed ASCII `string` hexadecimal
     * representation, according to EIP-55.
     */
    function toChecksumHexString(address addr) internal pure returns (string memory) {
        bytes memory buffer = bytes(toHexString(addr));

        // hash the hex part of buffer (skip length + 2 bytes, length 40)
        uint256 hashValue;
        assembly ("memory-safe") {
            hashValue := shr(96, keccak256(add(buffer, 0x22), 40))
        }

        for (uint256 i = 41; i > 1; --i) {
            // possible values for buffer[i] are 48 (0) to 57 (9) and 97 (a) to 102 (f)
            if (hashValue & 0xf > 7 && uint8(buffer[i]) > 96) {
                // case shift by xoring with 0x20
                buffer[i] ^= 0x20;
            }
            hashValue >>= 4;
        }
        return string(buffer);
    }

    /**
     * @dev Returns true if the two strings are equal.
     */
    function equal(string memory a, string memory b) internal pure returns (bool) {
        return bytes(a).length == bytes(b).length && keccak256(bytes(a)) == keccak256(bytes(b));
    }

    /**
     * @dev Parse a decimal string and returns the value as a `uint256`.
     *
     * Requirements:
     * - The string must be formatted as `[0-9]*`
     * - The result must fit into an `uint256` type
     */
    function parseUint(string memory input) internal pure returns (uint256) {
        return parseUint(input, 0, bytes(input).length);
    }

    /**
     * @dev Variant of {parseUint-string} that parses a substring of `input` located between position `begin` (included) and
     * `end` (excluded).
     *
     * Requirements:
     * - The substring must be formatted as `[0-9]*`
     * - The result must fit into an `uint256` type
     */
    function parseUint(string memory input, uint256 begin, uint256 end) internal pure returns (uint256) {
        (bool success, uint256 value) = tryParseUint(input, begin, end);
        if (!success) revert StringsInvalidChar();
        return value;
    }

    /**
     * @dev Variant of {parseUint-string} that returns false if the parsing fails because of an invalid character.
     *
     * NOTE: This function will revert if the result does not fit in a `uint256`.
     */
    function tryParseUint(string memory input) internal pure returns (bool success, uint256 value) {
        return _tryParseUintUncheckedBounds(input, 0, bytes(input).length);
    }

    /**
     * @dev Variant of {parseUint-string-uint256-uint256} that returns false if the parsing fails because of an invalid
     * character.
     *
     * NOTE: This function will revert if the result does not fit in a `uint256`.
     */
    function tryParseUint(
        string memory input,
        uint256 begin,
        uint256 end
    ) internal pure returns (bool success, uint256 value) {
        if (end > bytes(input).length || begin > end) return (false, 0);
        return _tryParseUintUncheckedBounds(input, begin, end);
    }

    /**
     * @dev Implementation of {tryParseUint-string-uint256-uint256} that does not check bounds. Caller should make sure that
     * `begin <= end <= input.length`. Other inputs would result in undefined behavior.
     */
    function _tryParseUintUncheckedBounds(
        string memory input,
        uint256 begin,
        uint256 end
    ) private pure returns (bool success, uint256 value) {
        bytes memory buffer = bytes(input);

        uint256 result = 0;
        for (uint256 i = begin; i < end; ++i) {
            uint8 chr = _tryParseChr(bytes1(_unsafeReadBytesOffset(buffer, i)));
            if (chr > 9) return (false, 0);
            result *= 10;
            result += chr;
        }
        return (true, result);
    }

    /**
     * @dev Parse a decimal string and returns the value as a `int256`.
     *
     * Requirements:
     * - The string must be formatted as `[-+]?[0-9]*`
     * - The result must fit in an `int256` type.
     */
    function parseInt(string memory input) internal pure returns (int256) {
        return parseInt(input, 0, bytes(input).length);
    }

    /**
     * @dev Variant of {parseInt-string} that parses a substring of `input` located between position `begin` (included) and
     * `end` (excluded).
     *
     * Requirements:
     * - The substring must be formatted as `[-+]?[0-9]*`
     * - The result must fit in an `int256` type.
     */
    function parseInt(string memory input, uint256 begin, uint256 end) internal pure returns (int256) {
        (bool success, int256 value) = tryParseInt(input, begin, end);
        if (!success) revert StringsInvalidChar();
        return value;
    }

    /**
     * @dev Variant of {parseInt-string} that returns false if the parsing fails because of an invalid character or if
     * the result does not fit in a `int256`.
     *
     * NOTE: This function will revert if the absolute value of the result does not fit in a `uint256`.
     */
    function tryParseInt(string memory input) internal pure returns (bool success, int256 value) {
        return _tryParseIntUncheckedBounds(input, 0, bytes(input).length);
    }

    uint256 private constant ABS_MIN_INT256 = 2 ** 255;

    /**
     * @dev Variant of {parseInt-string-uint256-uint256} that returns false if the parsing fails because of an invalid
     * character or if the result does not fit in a `int256`.
     *
     * NOTE: This function will revert if the absolute value of the result does not fit in a `uint256`.
     */
    function tryParseInt(
        string memory input,
        uint256 begin,
        uint256 end
    ) internal pure returns (bool success, int256 value) {
        if (end > bytes(input).length || begin > end) return (false, 0);
        return _tryParseIntUncheckedBounds(input, begin, end);
    }

    /**
     * @dev Implementation of {tryParseInt-string-uint256-uint256} that does not check bounds. Caller should make sure that
     * `begin <= end <= input.length`. Other inputs would result in undefined behavior.
     */
    function _tryParseIntUncheckedBounds(
        string memory input,
        uint256 begin,
        uint256 end
    ) private pure returns (bool success, int256 value) {
        bytes memory buffer = bytes(input);

        // Check presence of a negative sign.
        bytes1 sign = begin == end ? bytes1(0) : bytes1(_unsafeReadBytesOffset(buffer, begin)); // don't do out-of-bound (possibly unsafe) read if sub-string is empty
        bool positiveSign = sign == bytes1("+");
        bool negativeSign = sign == bytes1("-");
        uint256 offset = (positiveSign || negativeSign).toUint();

        (bool absSuccess, uint256 absValue) = tryParseUint(input, begin + offset, end);

        if (absSuccess && absValue < ABS_MIN_INT256) {
            return (true, negativeSign ? -int256(absValue) : int256(absValue));
        } else if (absSuccess && negativeSign && absValue == ABS_MIN_INT256) {
            return (true, type(int256).min);
        } else return (false, 0);
    }

    /**
     * @dev Parse a hexadecimal string (with or without "0x" prefix), and returns the value as a `uint256`.
     *
     * Requirements:
     * - The string must be formatted as `(0x)?[0-9a-fA-F]*`
     * - The result must fit in an `uint256` type.
     */
    function parseHexUint(string memory input) internal pure returns (uint256) {
        return parseHexUint(input, 0, bytes(input).length);
    }

    /**
     * @dev Variant of {parseHexUint-string} that parses a substring of `input` located between position `begin` (included) and
     * `end` (excluded).
     *
     * Requirements:
     * - The substring must be formatted as `(0x)?[0-9a-fA-F]*`
     * - The result must fit in an `uint256` type.
     */
    function parseHexUint(string memory input, uint256 begin, uint256 end) internal pure returns (uint256) {
        (bool success, uint256 value) = tryParseHexUint(input, begin, end);
        if (!success) revert StringsInvalidChar();
        return value;
    }

    /**
     * @dev Variant of {parseHexUint-string} that returns false if the parsing fails because of an invalid character.
     *
     * NOTE: This function will revert if the result does not fit in a `uint256`.
     */
    function tryParseHexUint(string memory input) internal pure returns (bool success, uint256 value) {
        return _tryParseHexUintUncheckedBounds(input, 0, bytes(input).length);
    }

    /**
     * @dev Variant of {parseHexUint-string-uint256-uint256} that returns false if the parsing fails because of an
     * invalid character.
     *
     * NOTE: This function will revert if the result does not fit in a `uint256`.
     */
    function tryParseHexUint(
        string memory input,
        uint256 begin,
        uint256 end
    ) internal pure returns (bool success, uint256 value) {
        if (end > bytes(input).length || begin > end) return (false, 0);
        return _tryParseHexUintUncheckedBounds(input, begin, end);
    }

    /**
     * @dev Implementation of {tryParseHexUint-string-uint256-uint256} that does not check bounds. Caller should make sure that
     * `begin <= end <= input.length`. Other inputs would result in undefined behavior.
     */
    function _tryParseHexUintUncheckedBounds(
        string memory input,
        uint256 begin,
        uint256 end
    ) private pure returns (bool success, uint256 value) {
        bytes memory buffer = bytes(input);

        // skip 0x prefix if present
        bool hasPrefix = (end > begin + 1) && bytes2(_unsafeReadBytesOffset(buffer, begin)) == bytes2("0x"); // don't do out-of-bound (possibly unsafe) read if sub-string is empty
        uint256 offset = hasPrefix.toUint() * 2;

        uint256 result = 0;
        for (uint256 i = begin + offset; i < end; ++i) {
            uint8 chr = _tryParseChr(bytes1(_unsafeReadBytesOffset(buffer, i)));
            if (chr > 15) return (false, 0);
            result *= 16;
            unchecked {
                // Multiplying by 16 is equivalent to a shift of 4 bits (with additional overflow check).
                // This guarantees that adding a value < 16 will not cause an overflow, hence the unchecked.
                result += chr;
            }
        }
        return (true, result);
    }

    /**
     * @dev Parse a hexadecimal string (with or without "0x" prefix), and returns the value as an `address`.
     *
     * Requirements:
     * - The string must be formatted as `(0x)?[0-9a-fA-F]{40}`
     */
    function parseAddress(string memory input) internal pure returns (address) {
        return parseAddress(input, 0, bytes(input).length);
    }

    /**
     * @dev Variant of {parseAddress-string} that parses a substring of `input` located between position `begin` (included) and
     * `end` (excluded).
     *
     * Requirements:
     * - The substring must be formatted as `(0x)?[0-9a-fA-F]{40}`
     */
    function parseAddress(string memory input, uint256 begin, uint256 end) internal pure returns (address) {
        (bool success, address value) = tryParseAddress(input, begin, end);
        if (!success) revert StringsInvalidAddressFormat();
        return value;
    }

    /**
     * @dev Variant of {parseAddress-string} that returns false if the parsing fails because the input is not a properly
     * formatted address. See {parseAddress-string} requirements.
     */
    function tryParseAddress(string memory input) internal pure returns (bool success, address value) {
        return tryParseAddress(input, 0, bytes(input).length);
    }

    /**
     * @dev Variant of {parseAddress-string-uint256-uint256} that returns false if the parsing fails because input is not a properly
     * formatted address. See {parseAddress-string-uint256-uint256} requirements.
     */
    function tryParseAddress(
        string memory input,
        uint256 begin,
        uint256 end
    ) internal pure returns (bool success, address value) {
        if (end > bytes(input).length || begin > end) return (false, address(0));

        bool hasPrefix = (end > begin + 1) && bytes2(_unsafeReadBytesOffset(bytes(input), begin)) == bytes2("0x"); // don't do out-of-bound (possibly unsafe) read if sub-string is empty
        uint256 expectedLength = 40 + hasPrefix.toUint() * 2;

        // check that input is the correct length
        if (end - begin == expectedLength) {
            // length guarantees that this does not overflow, and value is at most type(uint160).max
            (bool s, uint256 v) = _tryParseHexUintUncheckedBounds(input, begin, end);
            return (s, address(uint160(v)));
        } else {
            return (false, address(0));
        }
    }

    function _tryParseChr(bytes1 chr) private pure returns (uint8) {
        uint8 value = uint8(chr);

        // Try to parse `chr`:
        // - Case 1: [0-9]
        // - Case 2: [a-f]
        // - Case 3: [A-F]
        // - otherwise not supported
        unchecked {
            if (value > 47 && value < 58) value -= 48;
            else if (value > 96 && value < 103) value -= 87;
            else if (value > 64 && value < 71) value -= 55;
            else return type(uint8).max;
        }

        return value;
    }

    /**
     * @dev Escape special characters in JSON strings. This can be useful to prevent JSON injection in NFT metadata.
     *
     * WARNING: This function should only be used in double quoted JSON strings. Single quotes are not escaped.
     *
     * NOTE: This function escapes all unicode characters, and not just the ones in ranges defined in section 2.5 of
     * RFC-4627 (U+0000 to U+001F, U+0022 and U+005C). ECMAScript's `JSON.parse` does recover escaped unicode
     * characters that are not in this range, but other tooling may provide different results.
     */
    function escapeJSON(string memory input) internal pure returns (string memory) {
        bytes memory buffer = bytes(input);
        bytes memory output = new bytes(2 * buffer.length); // worst case scenario
        uint256 outputLength = 0;

        for (uint256 i; i < buffer.length; ++i) {
            bytes1 char = bytes1(_unsafeReadBytesOffset(buffer, i));
            if (((SPECIAL_CHARS_LOOKUP & (1 << uint8(char))) != 0)) {
                output[outputLength++] = "\\";
                if (char == 0x08) output[outputLength++] = "b";
                else if (char == 0x09) output[outputLength++] = "t";
                else if (char == 0x0a) output[outputLength++] = "n";
                else if (char == 0x0c) output[outputLength++] = "f";
                else if (char == 0x0d) output[outputLength++] = "r";
                else if (char == 0x5c) output[outputLength++] = "\\";
                else if (char == 0x22) {
                    // solhint-disable-next-line quotes
                    output[outputLength++] = '"';
                }
            } else {
                output[outputLength++] = char;
            }
        }
        // write the actual length and deallocate unused memory
        assembly ("memory-safe") {
            mstore(output, outputLength)
            mstore(0x40, add(output, shl(5, shr(5, add(outputLength, 63)))))
        }

        return string(output);
    }

    /**
     * @dev Reads a bytes32 from a bytes array without bounds checking.
     *
     * NOTE: making this function internal would mean it could be used with memory unsafe offset, and marking the
     * assembly block as such would prevent some optimizations.
     */
    function _unsafeReadBytesOffset(bytes memory buffer, uint256 offset) private pure returns (bytes32 value) {
        // This is not memory safe in the general case, but all calls to this private function are within bounds.
        assembly ("memory-safe") {
            value := mload(add(add(buffer, 0x20), offset))
        }
    }
}
"
    },
    "lib/ens-contracts/contracts/registry/ENS.sol": {
      "content": "//SPDX-License-Identifier: MIT
pragma solidity >=0.8.4;

interface ENS {
    // Logged when the owner of a node assigns a new owner to a subnode.
    event NewOwner(bytes32 indexed node, bytes32 indexed label, address owner);

    // Logged when the owner of a node transfers ownership to a new account.
    event Transfer(bytes32 indexed node, address owner);

    // Logged when the resolver for a node changes.
    event NewResolver(bytes32 indexed node, address resolver);

    // Logged when the TTL of a node changes
    event NewTTL(bytes32 indexed node, uint64 ttl);

    // Logged when an operator is added or removed.
    event ApprovalForAll(
        address indexed owner,
        address indexed operator,
        bool approved
    );

    function setRecord(
        bytes32 node,
        address owner,
        address resolver,
        uint64 ttl
    ) external;

    function setSubnodeRecord(
        bytes32 node,
        bytes32 label,
        address owner,
        address resolver,
        uint64 ttl
    ) external;

    function setSubnodeOwner(
        bytes32 node,
        bytes32 label,
        address owner
    ) external returns (bytes32);

    function setResolver(bytes32 node, address resolver) external;

    function setOwner(bytes32 node, address owner) external;

    function setTTL(bytes32 node, uint64 ttl) external;

    function setApprovalForAll(address operator, bool approved) external;

    function owner(bytes32 node) external view returns (address);

    function resolver(bytes32 node) external view returns (address);

    function ttl(bytes32 node) external view returns (uint64);

    function recordExists(bytes32 node) external view returns (bool);

    function isApprovedForAll(
        address owner,
        address operator
    ) external view returns (bool);
}
"
    },
    "lib/ens-contracts/contracts/resolvers/profiles/IExtendedResolver.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

interface IExtendedResolver {
    function resolve(
        bytes memory name,
        bytes memory data
    ) external view returns (bytes memory);
}
"
    },
    "lib/ens-contracts/contracts/resolvers/profiles/IContentHashResolver.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity >=0.8.4;

interface IContentHashResolver {
    event ContenthashChanged(bytes32 indexed node, bytes hash);

    /// Returns the contenthash associated with an ENS node.
    /// @param node The ENS node to query.
    /// @return The associated contenthash.
    function contenthash(bytes32 node) external view returns (bytes memory);
}
"
    },
    "lib/ens-contracts/contracts/resolvers/profiles/ITextResolver.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity >=0.8.4;

interface ITextResolver {
    event TextChanged(
        bytes32 indexed node,
        string indexed indexedKey,
        string key,
        string value
    );

    /// Returns the text data associated with an ENS node and key.
    /// @param node The ENS 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);
}
"
    },
    "lib/ens-contracts/contracts/wrapper/INameWrapper.sol": {
      "content": "//SPDX-License-Identifier: MIT
pragma solidity ~0.8.17;

import "../registry/ENS.sol";
import "../ethregistrar/IBaseRegistrar.sol";
import "@openzeppelin/contracts/token/ERC1155/IERC1155.sol";
import "./IMetadataService.sol";
import "./INameWrapperUpgrade.sol";

uint32 constant CANNOT_UNWRAP = 1;
uint32 constant CANNOT_BURN_FUSES = 2;
uint32 constant CANNOT_TRANSFER = 4;
uint32 constant CANNOT_SET_RESOLVER = 8;
uint32 constant CANNOT_SET_TTL = 16;
uint32 constant CANNOT_CREATE_SUBDOMAIN = 32;
uint32 constant CANNOT_APPROVE = 64;
//uint16 reserved for parent controlled fuses from bit 17 to bit 32
uint32 constant PARENT_CANNOT_CONTROL = 1 << 16;
uint32 constant IS_DOT_ETH = 1 << 17;
uint32 constant CAN_EXTEND_EXPIRY = 1 << 18;
uint32 constant CAN_DO_EVERYTHING = 0;
uint32 constant PARENT_CONTROLLED_FUSES = 0xFFFF0000;
// all fuses apart from IS_DOT_ETH
uint32 constant USER_SETTABLE_FUSES = 0xFFFDFFFF;

interface INameWrapper is IERC1155 {
    event NameWrapped(
        bytes32 indexed node,
        bytes name,
        address owner,
        uint32 fuses,
        uint64 expiry
    );

    event NameUnwrapped(bytes32 indexed node, address owner);

    event FusesSet(bytes32 indexed node, uint32 fuses);
    event ExpiryExtended(bytes32 indexed node, uint64 expiry);

    function ens() external view returns (ENS);

    function registrar() external view returns (IBaseRegistrar);

    function metadataService() external view returns (IMetadataService);

    function names(bytes32) external view returns (bytes memory);

    function name() external view returns (string memory);

    function upgradeContract() external view returns (INameWrapperUpgrade);

    function supportsInterface(bytes4 interfaceID) external view returns (bool);

    function wrap(
        bytes calldata name,
        address wrappedOwner,
        address resolver
    ) external;

    function wrapETH2LD(
        string calldata label,
        address wrappedOwner,
        uint16 ownerControlledFuses,
        address resolver
    ) external returns (uint64 expires);

    function registerAndWrapETH2LD(
        string calldata label,
        address wrappedOwner,
        uint256 duration,
        address resolver,
        uint16 ownerControlledFuses
    ) external returns (uint256 registrarExpiry);

    function renew(
        uint256 labelHash,
        uint256 duration
    ) external returns (uint256 expires);

    function unwrap(bytes32 node, bytes32 label, address owner) external;

    function unwrapETH2LD(
        bytes32 label,
        address newRegistrant,
        address newController
    ) external;

    function upgrade(bytes calldata name, bytes calldata extraData) external;

    function setFuses(
        bytes32 node,
        uint16 ownerControlledFuses
    ) external returns (uint32 newFuses);

    function setChildFuses(
        bytes32 parentNode,
        bytes32 labelhash,
        uint32 fuses,
        uint64 expiry
    ) external;

    function setSubnodeRecord(
        bytes32 node,
        string calldata label,
        address owner,
        address resolver,
        uint64 ttl,
        uint32 fuses,
        uint64 expiry
    ) external returns (bytes32);

    function setRecord(
        bytes32 node,
        address owner,
        address resolver,
        uint64 ttl
    ) external;

    function setSubnodeOwner(
        bytes32 node,
        string calldata label,
        address newOwner,
        uint32 fuses,
        uint64 expiry
    ) external returns (bytes32);

    function extendExpiry(
        bytes32 node,
        bytes32 labelhash,
        uint64 expiry
    ) external returns (uint64);

    function canModifyName(
        bytes32 node,
        address addr
    ) external view returns (bool);

    function setResolver(bytes32 node, address resolver) external;

    function setTTL(bytes32 node, uint64 ttl) external;

    function ownerOf(uint256 id) external view returns (address owner);

    function approve(address to, uint256 tokenId) external;

    function getApproved(uint256 tokenId) external view returns (address);

    function getData(
        uint256 id
    ) external view returns (address, uint32, uint64);

    function setMetadataService(IMetadataService _metadataService) external;

    function uri(uint256 tokenId) external view returns (string memory);

    function setUpgradeContract(INameWrapperUpgrade _upgradeAddress) external;

    function allFusesBurned(
        bytes32 node,
        uint32 fuseMask
    ) external view returns (bool);

    function isWrapped(bytes32) external view returns (bool);

    function isWrapped(bytes32, bytes32) external view returns (bool);
}
"
    },
    "lib/ens-contracts/contracts/utils/NameCoder.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {HexUtils} from "../utils/HexUtils.sol";

/// @dev Library for encoding/decoding names.
///
/// An ENS name is stop-separated labels, eg. "aaa.bb.c".
///
/// A DNS-encoded name is composed of byte length-prefixed labels with a terminator byte.
/// eg. "\x03aaa\x02bb\x01c\x00".
/// - maximum label length is 255 bytes.
/// - length = 0 is reserved for the terminator (root).
///
/// To encode a label larger than 255 bytes, use a hashed label.
/// A label of any length can be converted to a hashed label.
///
/// A hashed label is encoded as "[" + toHex(keccak256(label)) + "]".
/// eg. [af2caa1c2ca1d027f1ac823b529d0a67cd144264b2789fa2ea4d63a67c7103cc] = "vitalik".
/// - always 66 bytes.
/// - matches: `/^\[[0-9a-f]{64}\]$/`.
///
/// w/o hashed labels: `dns.length == 2 + ens.length` and the mapping is injective.
///  w/ hashed labels: `dns.length == 2 + ens.split('.').map(x => x.utf8Length).sum(n => n > 255 ? 66 : n)`.
///
library NameCoder {
    /// @dev The DNS-encoded name is malformed.
    ///      Error selector: `0xba4adc23`
    error DNSDecodingFailed(bytes dns);

    /// @dev A label of the ENS name has an invalid size.
    ///      Error selector: `0x9a4c3e3b`
    error DNSEncodingFailed(string ens);

    /// @dev Read the `size` of the label at `offset`.
    ///      If `size = 0`, it must be the end of `name` (no junk at end).
    ///      Reverts `DNSDecodingFailed`.
    /// @param name The DNS-encoded name.
    /// @param offset The offset into `name` to start reading.
    /// @return size The size of the label in bytes.
    /// @return nextOffset The offset into `name` of the next label.
    function nextLabel(
        bytes memory name,
        uint256 offset
    ) internal pure returns (uint8 size, uint256 nextOffset) {
        assembly {
            size := byte(0, mload(add(add(name, 32), offset))) // uint8(name[offset])
            nextOffset := add(offset, add(1, size)) // offset + 1 + size
        }
        if (size > 0 ? nextOffset >= name.length : nextOffset != name.length) {
            revert DNSDecodingFailed(name);
        }
    }

    /// @dev Find the offset of the label before `offset` in `name`.
    ///      * `prevOffset(name, 0)` reverts.
    ///      * `prevOffset(name, name.length + 1)` reverts.
    ///      * `prevOffset(name, name.length) = name.length - 1`.
    ///      * `prevOffset(name, name.length - 1) = <tld>`.
    ///      Reverts `DNSDecodingFailed`.
    /// @param name The DNS-encoded name.
    /// @param offset The offset into `name` to start reading backwards.
    /// @return prevOffset The offset into `name` of the previous label.
    function prevLabel(
        bytes memory name,
        uint256 offset
    ) internal pure returns (uint256 prevOffset) {
        while (true) {
            (, uint256 nextOffset) = nextLabel(name, prevOffset);
            if (nextOffset == offset) break;
            if (nextOffset > offset) {
                revert DNSDecodingFailed(name);
            }
            prevOffset = nextOffset;
        }
    }

    /// @dev Compute the ENS labelhash of the label at `offset` and the offset for the next label.
    ///      Disallows hashed label of zero (eg. `[0..0]`) to prevent confusion with terminator.
    ///      Reverts `DNSDecodingFailed`.
    /// @param name The DNS-encoded name.
    /// @param offset The offset into `name` to start reading.
    /// @param parseHashed If true, supports hashed labels.
    /// @return labelHash The resulting labelhash.
    /// @return nextOffset The offset into `name` of the next label.
    /// @return size The size of the label in bytes.
    /// @return wasHashed If true, the label was interpreted as a hashed label.
    function readLabel(
        bytes memory name,
        uint256 offset,
        bool parseHashed
    )
        internal
        pure
        returns (
            bytes32 labelHash,
            uint256 nextOffset,
            uint8 size,
            bool wasHashed
        )
    {
        (size, nextOffset) = nextLabel(name, offset);
        if (
            parseHashed &&
            size == 66 &&
            name[offset + 1] == "[" &&
            name[nextOffset - 1] == "]"
        ) {
            (labelHash, wasHashed) = HexUtils.hexStringToBytes32(
                name,
                offset + 2,
                nextOffset - 1
            ); // will not revert
            if (!wasHashed || labelHash == bytes32(0)) {
                revert DNSDecodingFailed(name); // "readLabel: malformed" or null literal
            }
        } else if (size > 0) {
            assembly {
                labelHash := keccak256(add(add(name, offset), 33), size)
            }
        }
    }

    /// @dev Same as `BytesUtils.namehash()` but supports hashed labels.
    function readLabel(
        bytes memory name,
        uint256 offset
    ) internal pure returns (bytes32 labelHash, uint256 nextOffset) {
        (labelHash, nextOffset, , ) = readLabel(name, offset, true);
    }

    /// @dev Compute the ENS namehash of `name[:offset]`.
    ///      Supports hashed labels.
    ///      Reverts `DNSDecodingFailed`.
    /// @param name The DNS-encoded name.
    /// @param offset The offset into name start hashing.
    /// @return hash The namehash of `name[:offset]`.
    function namehash(
        bytes memory name,
        uint256 offset
    ) internal pure returns (bytes32 hash) {
        (hash, offset) = readLabel(name, offset);
        if (hash != bytes32(0)) {
            hash = namehash(namehash(name, offset), hash);
        }
    }

    /// @dev Compute a child namehash from a parent namehash.
    /// @param parentNode The namehash of the parent.
    /// @param labelHash The labelhash of the child.
    /// @return node The namehash of the child.
    function namehash(
        bytes32 parentNode,
        bytes32 labelHash
    ) internal pure returns (bytes32 node) {
        // ~100 gas less than: keccak256(abi.encode(parentNode, labelHash))
        assembly {
            mstore(0, parentNode)
            mstore(32, labelHash)
            node := keccak256(0, 64)
        }
    }

    /// @dev Convert DNS-encoded name to ENS name.
    ///      Reverts `DNSDecodingFailed`.
    /// @param dns The DNS-encoded name to convert, eg. `\x03aaa\x02bb\x01c\x00`.
    /// @return ens The equivalent ENS name, eg. `aaa.bb.c`.
    function decode(
        bytes memory dns
    ) internal pure returns (string memory ens) {
        unchecked {
            uint256 n = dns.length;
            if (n == 1 && dns[0] == 0) return ""; // only valid answer is root
            if (n < 3) revert DNSDecodingFailed(dns);
            bytes memory v = new bytes(n - 2); // always 2-shorter
            uint256 src;
            uint256 dst;
            while (src < n) {
                uint8 len = uint8(dns[src++]);
                if (len == 0) break;
                uint256 end = src + len;
                if (end > dns.length) revert DNSDecodingFailed(dns); // overflow
                if (dst > 0) v[dst++] = "."; // skip first stop
                while (src < end) {
                    bytes1 x = dns[src++]; // read byte
                    if (x == ".") revert DNSDecodingFailed(dns); // malicious label
                    v[dst++] = x; // write byte
                }
            }
            if (src != dns.length) revert DNSDecodingFailed(dns); // junk at end
            return string(v);
        }
    }

    /// @dev Convert ENS name to DNS-encoded name.
    ///      Hashes labels longer than 255 bytes.
    ///      Reverts `DNSEncodingFailed`.
    /// @param ens The ENS name to convert, eg. `aaa.bb.c`.
    /// @return dns The corresponding DNS-encoded name, eg. `\x03aaa\x02bb\x01c\x00`.
    function encode(
        string memory ens
    ) internal pure returns (bytes memory dns) {
        unchecked {
            uint256 n = bytes(ens).length;
            if (n == 0) return hex"00"; // root
            dns = new bytes(n + 2);
            uint256 start;
            assembly {
                start := add(dns, 32) // first byte of output
            }
            uint256 end = start; // remember position to write length
            for (uint256 i; i < n; i++) {
                bytes1 x = bytes(ens)[i]; // read byte
                if (x == ".") {
                    start = _createHashedLabel(start, end);
                    if (start == 0) revert DNSEncodingFailed(ens);
                    end = start; // jump to next position
                } else {
                    assembly {
                        end := add(end, 1) // increase length
                        mstore(end, x) // write byte
                    }
                }
            }
            start = _createHashedLabel(start, end);
            if (start == 0) revert DNSEncodingFailed(ens);
            assembly {
                mstore8(start, 0) // terminal byte
                mstore(dns, sub(start, add(dns, 31))) // truncate length
            }
        }
    }

    /// @dev Write the label length.
    ///      If longer than 255, writes a hashed label instead.
    /// @param start The memory offset of the length-prefixed label.
    /// @param end The memory offset at the end of the label.
    /// @return next The memory offset for the next label.
    ///              Returns 0 if label is empty (handled by caller).
    function _createHashedLabel(
        uint256 start,
        uint256 end
    ) internal pure returns (uint256 next) {
        uint256 size = end - start; // length of label
        if (size > 255) {
            assembly {
                mstore(0, keccak256(add(start, 1), size)) // compute hash of label
            }
            HexUtils.unsafeHex(0, start + 2, 64); // override label with hex(hash)
            assembly {
                mstore8(add(start, 1), 0x5B) // "["
                mstore8(add(start, 66), 0x5D) // "]"
            }
            size = 66;
        }
        if (size > 0) {
            assembly {
                mstore8(start, size) // update length
            }
            next = start + 1 + size; // advance
        }
    }

    /// @dev Find the offset of `name` that namehashes to `nodeSuffix`.
    /// @param name The name to search.
    /// @param nodeSuffix The node to match.
    /// @return matched True if `name` ends with the suffix.
    /// @return node The namehash of `name[offset:]`.
    /// @return prevOffset The offset into `name` of the label before the suffix, or `matchOffset` if no match or prior label.
    /// @return matchOffset The offset into `name` that namehashes to the `nodeSuffix`, or 0 if no match.
    function matchSuffix(
        bytes memory name,
        uint256 offset,
        bytes32 nodeSuffix
    )
        internal
        pure
        returns (
            bool matched,
            bytes32 node,
            uint256 prevOffset,
            uint256 matchOffset
        )
    {
        (bytes32 labelHash, uint256 next) = readLabel(name, offset);
        if (labelHash != bytes32(0)) {
            (matched, node, prevOffset, matchOffset) = matchSuffix(
                name,
                next,
                nodeSuffix
            );
            if (node == nodeSuffix) {
                matched = true;
                prevOffset = offset;
                matchOffset = next;
            }
            node = namehash(node, labelHash);
        }
        if (node == nodeSuffix) {
            matched = true;
            prevOffset = matchOffset = offset;
        }
    }
}
"
    },
    "lib/ens-contracts/contracts/utils/BytesUtils.sol": {
      "content": "//SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

library BytesUtils {
    /// @dev `offset` was beyond `length`.
    ///       Error selector: `0x8a3c1cfb`
    error OffsetOutOfBoundsError(uint256 offset, uint256 length);

    /// @dev Assert `end` is not beyond the length of `v`.
    function _checkBound(bytes memory v, uint256 end) internal pure {
        if (end > v.length) {
            revert OffsetOutOfBoundsError(end, v.length);
        }
    }

    /// @dev Compute `keccak256(v[off:off+len])`.
    /// @param v The source bytes.
    /// @param off The offset into the source.
    /// @param len The number of bytes to hash.
    /// @return ret The corresponding hash.
    function keccak(
        bytes memory v,
        uint256 off,
        uint256 len
    ) internal pure returns (bytes32 ret) {
        _checkBound(v, off + len);
        assembly {
            ret := keccak256(add(add(v, 32), off), len)
        }
    }

    /// @dev Lexicographically compare two byte strings.
    /// @param vA The first bytes to compare.
    /// @param vB The second bytes to compare.
    /// @return Positive number if `A > B`, negative number if `A < B`, or zero if `A == B`.
    function compare(
        bytes memory vA,
        bytes memory vB
    ) internal pure returns (int256) {
        return compare(vA, 0, vA.length, vB, 0, vB.length);
    }

    /// @dev Lexicographically compare two byte ranges: `A = vA[offA:offA+lenA]` and `B = vB[offB:offB+lenB]`.
    /// @param vA The first bytes.
    /// @param offA The offset of the first bytes.
    /// @param lenA The length of the first bytes.
    /// @param vB The second bytes.
    /// @param offB The offset of the second bytes.
    /// @param lenB The length of the second bytes.
    /// @return Positive number if `A > B`, negative number if `A < B`, or zero if `A == B`.
    function compare(
        bytes memory vA,
        uint256 offA,
        uint256 lenA,
        bytes memory vB,
        uint256 offB,
        uint256 lenB
    ) internal pure returns (int256) {
        _checkBound(vA, offA + lenA);
        _checkBound(vB, offB + lenB);
        uint256 ptrA;
        uint256 ptrB;
        assembly {
            ptrA := add(vA, offA)
            ptrB := add(vB, offB)
        }
        uint256 shortest = lenA < lenB ? lenA : lenB;
        for (uint256 i; i < shortest; i += 32) {
            uint256 a;
            uint256 b;
            assembly {
                ptrA := add(ptrA, 32)
                ptrB := add(ptrB, 32)
                a := mload(ptrA)
                b := mload(ptrB)
            }
            if (a != b) {
                uint256 rest = shortest - i;
                if (rest < 32) {
                    rest = (32 - rest) << 3; // bits to drop
                    a >>= rest; // shift out the
                    b >>= rest; // irrelevant bits
                }
                if (a < b) {
                    return -1;
                } else if (a > b) {
                    return 1;
                }
            }
        }
        return int256(lenA) - int256(lenB);
    }

    /// @dev Determine if `a[offA:offA+len] == b[offB:offB+len]`.
    /// @param vA The first bytes.
    /// @param offA The offset into the first bytes.
    /// @param vB The second bytes.
    /// @param offB The offset into the second bytes.
    /// @param len The number of bytes to compare.
    /// @return True if the byte ranges are equal.
    function equals(
        bytes memory vA,
        uint256 offA,
        bytes memory vB,
        uint256 offB,
        uint256 len
    ) internal pure returns (bool) {
       

Tags:
ERC721, ERC1155, ERC165, Multisig, Non-Fungible, Upgradeable, Multi-Signature, Factory|addr:0x63d7d19579976a988e8cdcb08101ca569bfbbabd|verified:true|block:23533189|tx:0xf8a22d4a503b3ce6987a62f16b8b4489055b1dd75d1b6cee8678865b34e90d83|first_check:1759932056

Submitted on: 2025-10-08 16:00:56

Comments

Log in to comment.

No comments yet.