// File: contracts/modules/ModuleRegistry.sol



// SPDX-License-Identifier: UNLICENSED
// See https://github.com/storyprotocol/protocol-contracts/blob/main/StoryProtocol-AlphaTestingAgreement-17942166.3.pdf
pragma solidity ^0.8.19;

import { IModuleRegistry } from "contracts/interfaces/modules/IModuleRegistry.sol";
import { AccessControlled } from "contracts/access-control/AccessControlled.sol";
import { AccessControl } from "contracts/lib/AccessControl.sol";
import { IModule } from "contracts/interfaces/modules/base/IModule.sol";
import { IGateway } from "contracts/interfaces/modules/IGateway.sol";
import { Errors } from "contracts/lib/Errors.sol";
import { IIPOrg } from "contracts/interfaces/ip-org/IIPOrg.sol";
import { Multicall } from "@openzeppelin/contracts/utils/Multicall.sol";
import { IHook } from "contracts/interfaces/hooks/base/IHook.sol";
import { ModuleKey, ModuleDependencies, toModuleKey } from "contracts/lib/modules/Module.sol";

/// @title ModuleRegistry
/// @notice The module registry serves as the global repository for all modules
///         registered under Story Protocol, and acts as the central authorization
///         mechanism for configuring which frontends may call which modules.
contract ModuleRegistry is IModuleRegistry, AccessControlled, Multicall {
    /// @notice Identifying protocol-wide modules (opposed to those bound to specific IP Orgs).
    address public constant PROTOCOL_LEVEL = address(0);

    /// @dev Maps protocol hook string keys to their respective hooks.
    mapping(string => IHook) internal _protocolHooks;

    /// @dev Maps hook contracts to their respective hook key names.
    mapping(IHook => string) internal _hookKeys;

    /// @notice Maps module keys to their respective modules.
    mapping(ModuleKey => address) internal _modules;

    /// @notice Tracks whether a gateway can call a specific module function.
    mapping(ModuleKey => mapping(IGateway => mapping(bytes4 => bool))) internal _isAuthorized;

    constructor(address accessControl_) AccessControlled(accessControl_) {}

    /// @notice Gets the protocol-wide module associated with a module key.
    /// @param key_ The unique module key used to identify the module.
    function protocolModule(string calldata key_) public view returns (address) {
        return _modules[toModuleKey(key_)];
    }

    /// @notice Gets the protocol-wide module associated with a module key.
    /// @param key_ The unique module key used to identify the module.
    function protocolModule(ModuleKey key_) public view returns (address) {
        return _modules[key_];
    }

    /// @notice Checks whether a gateway is authorized to call a module function.
    /// @param key_ The type of the module being checked.
    /// @param gateway_ The gateway which has the module function as a dependency.
    /// @param fn_ The module function whose access is being checked for.
    function isAuthorized(ModuleKey key_, IGateway gateway_, bytes4 fn_) public view returns (bool) {
        if (_modules[key_] == address(0)) {
            revert Errors.ModuleRegistry_ModuleNotYetRegistered();
        }
        return _isAuthorized[key_][gateway_][fn_];
    }

    /// @notice Registers a new gateway to the protocol with its declared dependencies.
    /// @dev This is only callable by entities with the MODULE_REGISTRAR_ROLE role.
    /// @param gateway_ The gateway being registered into the protocol.
    function registerProtocolGateway(IGateway gateway_) external onlyRole(AccessControl.MODULE_REGISTRAR_ROLE) {
        ModuleDependencies memory dependencies = gateway_.updateDependencies();
        uint256 numModules = dependencies.keys.length;
        if (numModules != dependencies.fns.length) {
            revert Errors.ModuleRegistry_InvalidGateway();
        }

        for (uint256 i = 0; i < numModules; ++i) {
            ModuleKey moduleKey = dependencies.keys[i];
            bytes4[] memory fns = dependencies.fns[i];

            if (_modules[moduleKey] == address(0)) {
                revert Errors.ModuleRegistry_ModuleNotYetRegistered();
            }

            // Authorize all module function dependencies for the gateway.
            for (uint256 j = 0; j < fns.length; j++) {
                if (_isAuthorized[moduleKey][gateway_][fns[j]]) {
                    revert Errors.ModuleRegistry_DependencyAlreadyRegistered();
                }
                _isAuthorized[moduleKey][gateway_][fns[j]] = true;
                emit ModuleAuthorizationGranted(moduleKey, fns[j], address(gateway_), true);
            }
        }
    }

    /// @notice Removes a gatway as an authorized caller of the protocol.
    /// @dev This is only callable by entities with the MODULE_REGISTRAR_ROLE role.
    /// @param gateway_ The gateway being removed from the protocol.
    function removeProtocolGateway(IGateway gateway_) external onlyRole(AccessControl.MODULE_REGISTRAR_ROLE) {
        ModuleDependencies memory dependencies = gateway_.getDependencies();
        uint256 numModules = dependencies.keys.length;
        if (numModules != dependencies.fns.length) {
            revert Errors.ModuleRegistry_InvalidGateway();
        }

        for (uint256 i = 0; i < numModules; ++i) {
            ModuleKey moduleKey = dependencies.keys[i];
            bytes4[] memory fns = dependencies.fns[i];

            // Revoke authorizations made previously.
            // TODO: Change logic to track dependencies through the registry itself.
            for (uint256 j = 0; j < fns.length; j++) {
                if (!_isAuthorized[moduleKey][gateway_][fns[j]]) {
                    revert Errors.ModuleRegistry_DependencyNotYetRegistered();
                }
                _isAuthorized[moduleKey][gateway_][fns[j]] = false;
                emit ModuleAuthorizationGranted(moduleKey, fns[j], address(gateway_), false);
            }
        }
    }

    /// @notice Adds a new module to the protocol.
    /// @dev This is only callable by entities with the MODULE_REGISTRAR_ROLE role.
    /// @param key_ The identifier for the type of module being enrolled.
    /// @param module_ The module that will be registered into the protocol.
    function registerProtocolModule(
        ModuleKey key_,
        IModule module_
    ) external onlyRole(AccessControl.MODULE_REGISTRAR_ROLE) {
        if (address(module_) == address(0)) {
            revert Errors.ZeroAddress();
        }

        if (_modules[key_] != address(0)) {
            revert Errors.ModuleRegistry_ModuleAlreadyRegistered();
        }

        if (module_.moduleKey() != key_) {
            revert Errors.ModuleRegistry_ModuleKeyMismatch();
        }

        _modules[key_] = address(module_);

        emit ModuleAdded(PROTOCOL_LEVEL, string(abi.encodePacked(key_)), address(module_));
    }

    /// @notice Registers a new protocol hook.
    /// @param hookKey The unique identifier for the hook.
    /// @param hookAddress The address of the hook contract.
    /// @dev This function can only be called by an account with the MODULE_REGISTRAR_ROLE.
    function registerProtocolHook(
        string calldata hookKey,
        IHook hookAddress
    ) external onlyRole(AccessControl.MODULE_REGISTRAR_ROLE) {
        if (address(hookAddress) == address(0)) {
            revert Errors.ZeroAddress();
        }
        _protocolHooks[hookKey] = hookAddress;
        _hookKeys[hookAddress] = hookKey;
        emit HookAdded(PROTOCOL_LEVEL, hookKey, address(hookAddress));
    }

    /// @notice Removes a protocol hook.
    /// @param hookKey The unique identifier for the hook.
    /// @dev This function can only be called by an account with the MODULE_REGISTRAR_ROLE.
    /// If the hook is not registered, it reverts with an error.
    function removeProtocolHook(string calldata hookKey) external onlyRole(AccessControl.MODULE_REGISTRAR_ROLE) {
        if (address(_protocolHooks[hookKey]) == address(0)) {
            revert Errors.ModuleRegistry_HookNotRegistered(hookKey);
        }
        IHook hookAddress = _protocolHooks[hookKey];
        delete _protocolHooks[hookKey];
        delete _hookKeys[hookAddress];
        emit HookRemoved(PROTOCOL_LEVEL, hookKey, address(hookAddress));
    }

    /// Removes the current module configured for a module key.
    /// This is only callable by MODULE_REGISTRAR_ROLE holders.
    /// @param key_ The identifier for the type of module being removed.
    function removeProtocolModule(ModuleKey key_) external onlyRole(AccessControl.MODULE_REGISTRAR_ROLE) {
        if (_modules[key_] == address(0)) {
            revert Errors.ModuleRegistry_ModuleNotYetRegistered();
        }

        address removedModule = _modules[key_];
        delete _modules[key_];

        emit ModuleRemoved(key_, removedModule);
    }

    /// @notice Returns the protocol hook associated with a given hook key.
    /// @param hookKey The unique identifier for the hook.
    /// @return The protocol hook associated with the given hook key.
    function hookForKey(string calldata hookKey) external view returns (IHook) {
        return _protocolHooks[hookKey];
    }

    /// @notice Checks if a hook is registered in the protocol.
    /// @param hook_ The hook to check.
    /// @return True if the hook is registered, false otherwise.
    function isRegisteredHook(IHook hook_) external view returns (bool) {
        return address(_protocolHooks[_hookKeys[hook_]]) == address(hook_);
    }

    /// Execution entrypoint, callable by any address on its own behalf.
    /// @param ipOrg_ address of the IPOrg, or address(0) for protocol-level stuff
    /// @param moduleKey_ short module descriptor
    /// @param moduleParams_ encoded params for module action
    /// @param preHookParams_ encoded params for pre action hooks
    /// @param postHookParams_ encoded params for post action hooks
    /// @return encoded result of the module execution
    function execute(
        IIPOrg ipOrg_,
        string calldata moduleKey_,
        bytes memory moduleParams_,
        bytes[] memory preHookParams_,
        bytes[] memory postHookParams_
    ) external returns (bytes memory) {
        return _execute(ipOrg_, msg.sender, moduleKey_, moduleParams_, preHookParams_, postHookParams_);
    }

    /// Execution entrypoint, callable by any MODULE_EXECUTOR_ROLE holder on behalf of any address.
    /// @param ipOrg_ address of the IPOrg, or address(0) for protocol-level stuff
    /// @param caller_ address requesting the execution
    /// @param moduleKey_ short module descriptor
    /// @param moduleParams_ encoded params for module action
    /// @param preHookParams_ encoded params for pre action hooks
    /// @param postHookParams_ encoded params for post action hooks
    /// @return encoded result of the module execution
    function execute(
        IIPOrg ipOrg_,
        address caller_,
        string calldata moduleKey_,
        bytes calldata moduleParams_,
        bytes[] calldata preHookParams_,
        bytes[] calldata postHookParams_
    ) external onlyRole(AccessControl.MODULE_EXECUTOR_ROLE) returns (bytes memory) {
        return _execute(ipOrg_, caller_, moduleKey_, moduleParams_, preHookParams_, postHookParams_);
    }

    /// Configuration entrypoint, callable by any address on its own behalf.
    /// @param ipOrg_ address of the IPOrg, or address(0) for protocol-level stuff
    /// @param moduleKey_ short module descriptor
    /// @param params_ encoded params for module configuration
    function configure(IIPOrg ipOrg_, string calldata moduleKey_, bytes calldata params_) external {
        _configure(ipOrg_, msg.sender, moduleKey_, params_);
    }

    /// Configuration entrypoint, callable by any MODULE_EXECUTOR_ROLE holder on behalf of any address.
    /// @param ipOrg_ address of the IPOrg, or address(0) for protocol-level stuff
    /// @param caller_ address requesting the execution
    /// @param moduleKey_ short module descriptor
    /// @param params_ encoded params for module configuration
    function configure(
        IIPOrg ipOrg_,
        address caller_,
        string calldata moduleKey_,
        bytes calldata params_
    ) external onlyRole(AccessControl.MODULE_EXECUTOR_ROLE) returns (bytes memory) {
        return _configure(ipOrg_, caller_, moduleKey_, params_);
    }

    /// @dev Executes an action for a specific module.
    /// @param ipOrg_ The IP Org under which the execution is performed.
    /// @param caller_ The address of the original calling entity.
    /// @param moduleKey_ The identifier of the module being executed.
    /// @param moduleParams_ Encoded data to be passed to the module.
    /// @param preHookParams_ Set of data to be used for any registered pre-hooks.
    /// @param postHookParams_ Set of data to be used for any registered post-hooks.
    function _execute(
        IIPOrg ipOrg_,
        address caller_,
        string calldata moduleKey_,
        bytes memory moduleParams_,
        bytes[] memory preHookParams_,
        bytes[] memory postHookParams_
    ) private returns (bytes memory result) {
        IModule module = IModule(_modules[toModuleKey(moduleKey_)]);
        if (address(module) == address(0)) {
            revert Errors.ModuleRegistry_ModuleNotYetRegistered();
        }
        result = module.execute(ipOrg_, caller_, moduleParams_, preHookParams_, postHookParams_);
        emit ModuleExecuted(address(ipOrg_), moduleKey_, caller_, moduleParams_, preHookParams_, postHookParams_);
        return result;
    }

    /// @dev Configures a specific module for an IP Org.
    /// @param ipOrg_ The IP Org making the relevant configurations.
    /// @param caller_ The address of the calling entity performing the configuration.
    /// @param moduleKey_ The identifier for the module being configured.
    /// @param params_ Module-specific data used for the configuration.
    function _configure(
        IIPOrg ipOrg_,
        address caller_,
        string calldata moduleKey_,
        bytes calldata params_
    ) private returns (bytes memory result) {
        IModule module = IModule(_modules[toModuleKey(moduleKey_)]);
        if (address(module) == address(0)) {
            revert Errors.ModuleRegistry_ModuleNotYetRegistered();
        }
        result = module.configure(ipOrg_, caller_, params_);
        emit ModuleConfigured(address(ipOrg_), moduleKey_, caller_, params_);
        return result;
    }
}

// File: contracts/interfaces/modules/IModuleRegistry.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

import { ModuleKey } from "contracts/lib/modules/Module.sol";
import { IGateway } from "contracts/interfaces/modules/IGateway.sol";
import { IModule } from "contracts/interfaces/modules/base/IModule.sol";

/// @title IModuleRegistry
/// @notice Module Registry Interface
interface IModuleRegistry {
    /// @notice Emits when a gateway was successfully registered by the protocol
    ///         for a specific dependency (module type + module function).
    /// @param key The identifier of the dependent module type.
    /// @param fn The function identifier of the dependent module type.
    /// @param gateway The gateway address granted permission to use the dependency.
    /// @param grant Whether the gateway was authorized to use the dependency.
    event ModuleAuthorizationGranted(ModuleKey indexed key, bytes4 fn, address indexed gateway, bool grant);

    /// @notice Emits when a brand new module is enrolled to the protocol.
    /// @param ipOrg The IP Org to which the module belongs.
    /// @param moduleKey The string identifier of the module type that was added.
    /// @param module The address of the module.
    event ModuleAdded(address indexed ipOrg, string moduleKey, address indexed module);

    /// @notice Emits when the protocol module for a module type is removed.
    /// @param key The identifier of the module type that was added.
    /// @param module The address of the removed module
    event ModuleRemoved(ModuleKey indexed key, address indexed module);

    /// @notice Emits when a module is executed for an IP Org.
    event ModuleExecuted(
        address indexed ipOrg,
        string moduleKey,
        address indexed caller,
        bytes selfParams,
        bytes[] preHookParams,
        bytes[] postHookParams
    );

    /// @notice Emits when a module is configured for an IP Org.
    event ModuleConfigured(address indexed ipOrg, string moduleKey, address indexed caller, bytes params);

    /// @notice Emits when a new hook is added for a specific IP Org.
    event HookAdded(address indexed ipOrg, string hookKey, address indexed hook);

    /// @notice Emits when a hook is removed for an IP Org.
    event HookRemoved(address indexed ipOrg, string hookKey, address indexed hook);

    /// @notice Registers a new module of a provided type to Story Protocol.
    /// @param key_ The bytes32 type of the module being registered.
    /// @param module_ The actual module being registered.
    function registerProtocolModule(ModuleKey key_, IModule module_) external;

    /// @notice Fetches the protocol module by its string identifier.
    /// @param key_ The string module type.
    /// @return The module associated with the module key.
    function protocolModule(string calldata key_) external view returns (address);

    /// @notice Fetches the protocol module bound to a module type.
    /// @param key_ The bytes32 module type.
    /// @return The module associated with the module key.
    function protocolModule(ModuleKey key_) external view returns (address);

    /// @notice Checks whether a gateway has permission to call a module function.
    /// @param key_ The module type.
    /// @param gateway_ The gateway which has the module as a dependency.
    /// @param fn_ The module function whose access is being checked for.
    function isAuthorized(ModuleKey key_, IGateway gateway_, bytes4 fn_) external view returns (bool);
}

// File: contracts/access-control/AccessControlled.sol



// SPDX-License-Identifier: UNLICENSED
// See https://github.com/storyprotocol/protocol-contracts/blob/main/StoryProtocol-AlphaTestingAgreement-17942166.3.pdf
pragma solidity ^0.8.19;

// solhint-disable-next-line max-line-length
import { ERC165CheckerUpgradeable } from "@openzeppelin/contracts-upgradeable/utils/introspection/ERC165CheckerUpgradeable.sol";
import { IAccessControl } from "@openzeppelin/contracts/access/IAccessControl.sol";

import { AccessControl } from "contracts/lib/AccessControl.sol";
import { IAccessControlled } from "contracts/interfaces/access-control/IAccessControlled.sol";
import { Errors } from "contracts/lib/Errors.sol";

/// @title Access Controlled Contract
/// @notice This contract is to be inherited by any protocol components that require granular
///         roles for execution, as defined by the Access Control Singleton contract. Later on,
///         this contract will be deprecated in favor of authorization through te module registry.
abstract contract AccessControlled is IAccessControlled {
    using ERC165CheckerUpgradeable for address;

    /// @notice Pointer to the global Access Control Singleton for protocol auth.
    IAccessControl private _accessControl;

    /// @notice Checks if msg.sender has role `role`, reverts otherwise.
    /// @param role_ The role being checked for, set by the Access Control Singleton.
    modifier onlyRole(bytes32 role_) {
        if (!_hasRole(role_, msg.sender)) {
            revert Errors.MissingRole(role_, msg.sender);
        }
        _;
    }

    /// @notice Instantiates a new Access Controlled contract.
    /// @param accessControl_ The global Access Control Singleton contract address.
    constructor(address accessControl_) {
        if (!accessControl_.supportsInterface(type(IAccessControl).interfaceId))
            revert Errors.UnsupportedInterface("IAccessControl");
        _accessControl = IAccessControl(accessControl_);
        emit AccessControlUpdated(accessControl_);
    }

    /// @notice Sets the Access Control Singleton used for authorization.
    /// @param accessControl_ The address of the new Access Control Singleton.
    function setAccessControl(address accessControl_) public onlyRole(AccessControl.PROTOCOL_ADMIN_ROLE) {
        if (!accessControl_.supportsInterface(type(IAccessControl).interfaceId))
            revert Errors.UnsupportedInterface("IAccessControl");
        _accessControl = IAccessControl(accessControl_);
        emit AccessControlUpdated(accessControl_);
    }

    /// @dev Checks if an account `account_` has role `role_` assigned.
    /// @param role_ The role being checked for as defined by the Access Control Singlton.
    /// @param account_ The address whose role permissions are being checked for.
    /// @return return True if the account has the role, False otherwise.
    function _hasRole(bytes32 role_, address account_) internal view returns (bool) {
        return _accessControl.hasRole(role_, account_);
    }
}

// File: contracts/lib/AccessControl.sol



// SPDX-License-Identifier: UNLICENSED
// See https://github.com/storyprotocol/protocol-contracts/blob/main/StoryProtocol-AlphaTestingAgreement-17942166.3.pdf
pragma solidity ^0.8.19;

/// @title Access Control Library
/// @notice Library for access control helpers and protocol role definitions.
///         These roles are used by the AccessControlSingleton, accessed by AccessControlled contracts.
library AccessControl {
    // Default admin role as per OZ AccessControl system. All other roles stem from this.
    bytes32 public constant PROTOCOL_ADMIN_ROLE = bytes32(0);

    // Role that can upgrade UUPS contracts or Beacon Proxies
    bytes32 public constant UPGRADER_ROLE = keccak256("UPGRADER_ROLE");

    // Role for managing protocol-wide and IP Org localized relationships.
    bytes32 public constant RELATIONSHIP_MANAGER_ROLE = keccak256("RELATIONSHIP_MANAGER_ROLE");

    // Role that can perform admin tasks on the Licensing Module contracts (setNonCommercialLicenseURI)
    bytes32 public constant LICENSING_MANAGER_ROLE = keccak256("LICENSING_MANAGER_ROLE");

    // Role that can add new modules to the Module Registry
    bytes32 public constant MODULE_REGISTRAR_ROLE = keccak256("MODULE_REGISTRAR_ROLE");

    // Role that can execute modules
    bytes32 public constant MODULE_EXECUTOR_ROLE = keccak256("MODULE_EXECUTOR_ROLE");

    // Role that can execute Hooks
    bytes32 public constant HOOK_CALLER_ROLE = keccak256("HOOK_CALLER_ROLE");

}

// File: contracts/interfaces/modules/base/IModule.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

import { IIPOrg } from "contracts/interfaces/ip-org/IIPOrg.sol";
import { ModuleKey } from "contracts/lib/modules/Module.sol";

/// @title Story Protocol Module Interface.
/// @notice This interface must be implemented by all protocol modules in Story Protocol,
///         providing the base functionality needed for authorization and execution
///         logic centered around IP assets.
interface IModule {
    /// The execution of the module is pending, and will need to be executed again.
    event RequestPending(address indexed sender);
    /// Module execution completed successfully.
    event RequestCompleted(address indexed sender);
    /// Module execution failed.
    event RequestFailed(address indexed sender, string reason);

    /// @notice Gets the protocol-wide key associated with the module.
    /// @return The bytes32 identifier of the module.
    function moduleKey() external pure returns (ModuleKey);

    /// @notice Main execution entrypoint.
    /// @dev This function verifies encoded module params, executes any pre-action hooks,
    ///      performs the main module logic, and then executes any post-action hooks.
    ///      Modules must decide themselves how parameters are encoded and decoded.
    /// @param ipOrg_ Address of the IP Org or the zero address (for protocol-wide modules).
    /// @param caller_ Address of the caller.
    /// @param moduleParams_ Encoded params to be decoded for module execution.
    /// @param preHookParams_ Encoded params used for pre-hook execution logic.
    /// @param postHookParams_ Encoded params used for post-hook execution logic.
    /// @return result of the module action
    function execute(
        IIPOrg ipOrg_,
        address caller_,
        bytes calldata moduleParams_,
        bytes[] calldata preHookParams_,
        bytes[] calldata postHookParams_
    ) external returns (bytes memory result);

    /// @notice Module configuration entrypoint.
    /// @dev Note that it is up to the module on how the parameters should be
    ///      encoded, unpacked, and used for configuration.
    /// @param ipOrg_ Address of the IP Org or the zero address (for protocol-wide modules).
    /// @param caller_ Address of configuration caller.
    /// @param params_ ABI-encoded parameters used for module configuration.
    /// @return result Result of the module configuration expressed as a bytes array.
    function configure(IIPOrg ipOrg_, address caller_, bytes calldata params_) external returns (bytes memory result);
}

// File: contracts/interfaces/modules/IGateway.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

import { ModuleDependencies } from "contracts/lib/modules/Module.sol";

/// @title Module Gateway Interface
/// @notice Interface for a Story Protocol module gateway, which is a contract
///         that may be granted access by the module registry to call module
///         functions declared by the gateway's module dependency set.
interface IGateway {
    /// @notice Synchronizes all downstream dependencies via the module registry.
    /// @dev This function may only be called by the module registry.
    /// @return dependencies The freshly updated dependencies needed by the gateway.
    function updateDependencies() external returns (ModuleDependencies memory dependencies);

    /// @notice Fetches all module dependencies required by the gateway contract.
    /// @return dependencies The dependencies that the gateway requires from the protocol.
    function getDependencies() external view returns (ModuleDependencies memory dependencies);
}

// File: contracts/lib/Errors.sol



// SPDX-License-Identifier: UNLICENSED
// See https://github.com/storyprotocol/protocol-contracts/blob/main/StoryProtocol-AlphaTestingAgreement-17942166.3.pdf
pragma solidity ^0.8.19;

/// @title Errors Library
/// @notice Library for all Story Protocol contract errors.
library Errors {
    ////////////////////////////////////////////////////////////////////////////
    //                                  Globals                               //
    ////////////////////////////////////////////////////////////////////////////

    /// @notice The provided array may not be empty.
    error EmptyArray();

    /// @notice The arrays may not have a mismatch in length.
    error LengthMismatch();

    /// @notice The provided role does not exist for the given account.
    error MissingRole(bytes32 role, address account);

    /// @notice The caller is not authorized to perform the call.
    error Unauthorized();

    /// @notice The provided interface is not supported.
    error UnsupportedInterface(string name);

    /// @notice The zero address may not be used as input.
    error ZeroAddress();

    /// @notice The amount specified may not be zero.
    error ZeroAmount();

    ////////////////////////////////////////////////////////////////////////////
    //                            BaseModule                                  //
    ////////////////////////////////////////////////////////////////////////////

    error BaseModule_HooksParamsLengthMismatch(uint8 hookType);
    error BaseModule_ZeroIpaRegistry();
    error BaseModule_ZeroLicenseRegistry();
    error BaseModule_OnlyModuleRegistry();

    /// @notice The caller is not authorized to perform this operation.
    error BaseModule_Unauthorized();

    ////////////////////////////////////////////////////////////////////////////
    //                            HookRegistry                                //
    ////////////////////////////////////////////////////////////////////////////

    /// @notice The hook is already registered.
    error HookRegistry_RegisteringDuplicatedHook();

    /// @notice This error is thrown when trying to register a hook with the address 0.
    error HookRegistry_RegisteringZeroAddressHook();

    /// @notice This error is thrown when the caller is not IP Org owner.
    error HookRegistry_CallerNotIPOrgOwner();

    /// @notice This error is thrown when trying to register more than the maximum allowed number of hooks.
    error HookRegistry_MaxHooksExceeded();

    /// @notice Hooks configuration array length does not match that of the hooks array.
    error HookRegistry_HooksConfigLengthMismatch();

    /// @notice This error is thrown when the provided index is out of bounds of the hooks array.
    error HookRegistry_IndexOutOfBounds(uint256 hooksIndex);

    /// @notice The module may not be the zero address.
    error HookRegistry_ZeroModuleRegistry();

    /// @notice The provided hook has not been whitelisted.
    error HookRegistry_RegisteringNonWhitelistedHook(address hookAddress);

    ////////////////////////////////////////////////////////////////////////////
    //                      BaseRelationshipProcessor                         //
    ////////////////////////////////////////////////////////////////////////////

    /// @notice Call may only be processed by the relationship module.
    error BaseRelationshipProcessor_OnlyRelationshipModule();

    ////////////////////////////////////////////////////////////////////////////
    //                           ModuleRegistry                               //
    ////////////////////////////////////////////////////////////////////////////

    /// @notice The selected module has yet to been registered.
    error ModuleRegistry_ModuleNotYetRegistered();

    /// @notice The module depenedency has not yet been registered for the gatway.
    error ModuleRegistry_DependencyNotYetRegistered();

    /// @notice The module depenedency was already registered for the gateway.
    error ModuleRegistry_DependencyAlreadyRegistered();

    /// @notice The caller is not the org owner.
    error ModuleRegistry_CallerNotOrgOwner();

    /// @notice Hook has yet to be registered.
    error ModuleRegistry_HookNotRegistered(string hookKey);

    /// @notice The selected module was already registered.
    error ModuleRegistry_ModuleAlreadyRegistered();

    /// @notice The key of the targeted module does not match the provided key.
    error ModuleRegistry_ModuleKeyMismatch();

    /// @notice The caller is not authorized to call the module dependency.
    error ModuleRegistry_Unauthorized();

    /// @notice The gateway is not valid for registration.
    error ModuleRegistry_InvalidGateway();

    ////////////////////////////////////////////////////////////////////////////
    //                                 CollectModule                          //
    ////////////////////////////////////////////////////////////////////////////

    /// @notice Collect module caller is unauthorized.
    error CollectModule_CallerUnauthorized();

    /// @notice Collect NFT has already been initialized.
    error CollectModule_CollectNotYetInitialized();

    /// @notice Collect action is not authorized for the collect module.
    error CollectModule_CollectUnauthorized();

    /// @notice Collect module IP asset is already initialized.
    error CollectModule_IPAssetAlreadyInitialized();

    /// @notice Collect module IP asset does not exist.
    error CollectModule_IPAssetNonExistent();

    /// @notice Collect module provided IP asset registry does not exist.
    error CollectModule_IPOrgNonExistent();

    ////////////////////////////////////////////////////////////////////////////
    //                           CollectPaymentModule                         //
    ////////////////////////////////////////////////////////////////////////////

    /// @notice The configured collect module payment amount is invalid.
    error CollectPaymentModule_AmountInvalid();

    /// @notice The ERC-20 transfer failed when processing the payment collect.
    error CollectPaymentModule_ERC20TransferFailed();

    /// @notice The collect ERC-20 transfer was not properly ABI-encoded.
    error CollectPaymentModule_ERC20TransferInvalidABIEncoding();

    /// @notice The collect ERC-20 transfer returned a non-successful value.
    error CollectPaymentModule_ERC20TransferInvalidReturnValue();

    /// @notice Invalid settings were configured for the collect payment module.
    error CollectPaymentModule_InvalidSettings();

    /// @notice Native tokens are not allowed for the configured payment module.
    error CollectPaymentModule_NativeTokenNotAllowed();

    /// @notice Native tokens failed to transfer for the payment collect.
    error CollectPaymentModule_NativeTransferFailed();

    /// @notice Invalid parameters were passed in to the payment collect.
    error CollectPaymentModule_PaymentParamsInvalid();

    /// @notice Insufficient funds were provided for the payment collect.
    error CollectPaymentModule_PaymentInsufficient();

    /// @notice The token provided for the payment collect is invalid.
    error CollectPaymentModule_TokenInvalid();

    ////////////////////////////////////////////////////////////////////////////
    //                                  CollectNFT                            //
    ////////////////////////////////////////////////////////////////////////////

    /// @notice Collect NFT has already been initialized.
    error CollectNFT_AlreadyInitialized();

    /// @notice Caller of the Collect NFT is not authorized.
    error CollectNFT_CallerUnauthorized();

    /// @notice Collector address is not valid.
    error CollectNFT_CollectorInvalid();

    /// @notice IP asset bound to the Collect NFT does not exist.
    error CollectNFT_IPAssetNonExistent();

    ////////////////////////////////////////////////////////////////////////////
    //                                   ERC721                               //
    ////////////////////////////////////////////////////////////////////////////

    /// @notice Originating address does not own the NFT.
    error ERC721_OwnerInvalid();

    /// @notice Receiving address cannot be the zero address.
    error ERC721_ReceiverInvalid();

    /// @notice Receiving contract does not implement the ERC-721 wallet interface.
    error ERC721_SafeTransferUnsupported();

    /// @notice Sender is not NFT owner, approved address, or owner operator.
    error ERC721_SenderUnauthorized();

    /// @notice Token has already been minted.
    error ERC721_TokenAlreadyMinted();

    /// @notice NFT does not exist.
    error ERC721_TokenNonExistent();

    ////////////////////////////////////////////////////////////////////////////
    //                                 IPAccountImpl                          //
    ////////////////////////////////////////////////////////////////////////////

    /// @notice IP account caller is not the owner.
    error IPAccountImpl_CallerNotOwner();

    ////////////////////////////////////////////////////////////////////////////
    //                               IPAccountRegistry                        //
    ////////////////////////////////////////////////////////////////////////////

    /// @notice IP account implementation does not exist.
    error IPAccountRegistry_NonExistentIpAccountImpl();

    /// @notice IP account initialization failed.
    error IPAccountRegistry_InitializationFailed();

    ////////////////////////////////////////////////////////////////////////////
    //                         UintArrayMask                               //
    ////////////////////////////////////////////////////////////////////////////

    error UintArrayMask_EmptyArray();

    ////////////////////////////////////////////////////////////////////////////
    //                               IPOrg                                    //
    ////////////////////////////////////////////////////////////////////////////

    /// @notice IP identifier is over bounds.
    error IPOrg_IdOverBounds();

    /// @notice Licensing is not configured.
    error IPOrg_LicensingNotConfigured();

    /// @notice IP Org wrapper id does not exist.
    error IPOrg_IdDoesNotExist();

    ////////////////////////////////////////////////////////////////////////////
    //                             IPOrgController                            //
    ////////////////////////////////////////////////////////////////////////////

    /// @notice The caller is not the owner of the IP Org Controller.
    error IPOrgController_InvalidOwner();

    /// @notice IP Org does not exist.
    error IPOrgController_IPOrgNonExistent();

    /// @notice The caller is not the authorized IP Org owner.
    error IPOrgController_InvalidIPOrgOwner();

    /// @notice The new owner for an IP Org may not be the zero address.
    error IPOrgController_InvalidNewIPOrgOwner();

    /// @notice The owner transfer has not yet been initialized.
    error IPOrgController_OwnerTransferUninitialized();

    ////////////////////////////////////////////////////////////////////////////
    //                                LibDuration                             //
    ////////////////////////////////////////////////////////////////////////////

    /// @notice The caller is not the designated renwer.
    error LibDuration_CallerNotRenewer();

    /// @notice The start time is not valid.
    error LibDuration_InvalidStartTime();

    /// @notice The proposed license is not renewable.
    error LibDuration_NotRenewable();

    /// @notice A zero TTL may not be used for configuration.
    error LibDuration_ZeroTTL();

    ////////////////////////////////////////////////////////////////////////////
    //                       LicensingFrameworkRepo                           //
    ////////////////////////////////////////////////////////////////////////////
    error LicensingFrameworkRepo_FrameworkAlreadyAdded();
    error LicensingFrameworkRepo_DuplicateParamType();
    error LicensingFrameworkRepo_TooManyParams();

    ////////////////////////////////////////////////////////////////////////////
    //                        LicensingModule                                 //
    ////////////////////////////////////////////////////////////////////////////

    /// @notice The franchise does not exist.
    error LicensingModule_CallerNotIpOrgOwner();
    error LicensingModule_InvalidConfigType();
    error LicensingModule_InvalidTermCommercialStatus();
    error LicensingModule_IpOrgFrameworkAlreadySet();
    error LicensingModule_DuplicateTermId();
    error LicensingModule_CommercialLicenseNotAllowed();
    error LicensingModule_NonCommercialTermsRequired();
    error LicensingModule_IpOrgNotConfigured();
    error LicensingModule_IpOrgAlreadyConfigured();
    error LicensingModule_ipOrgTermNotFound();
    error LicensingModule_ShareAlikeDisabled();
    error LicensingModule_InvalidAction();
    error LicensingModule_CallerNotLicensor();
    error LicensingModule_ParentLicenseNotActive();
    error LicensingModule_DerivativeNotAllowed();
    error LicensingModule_InvalidIpa();
    error LicensingModule_CallerNotLicenseOwner();
    error LicensingModule_CantFindParentLicenseOrRelatedIpa();
    error LicensingModule_InvalidLicenseeType();
    error LicensingModule_InvalidLicensorType();
    error LicensingModule_InvalidLicensorConfig();
    error LicensingModule_InvalidParamValue();
    error LicensingModule_InvalidParamsLength();
    error LicensingModule_DuplicateParam();
    error LicensingModule_ReciprocalCannotSetParams();
    error LicensingModule_ParamSetByIpOrg();
    error LicensingModule_InvalidInputValue();
    error LicensingModule_IpOrgFrameworkNotSet();

    ////////////////////////////////////////////////////////////////////////////
    //                            LicenseRegistry                             //
    ////////////////////////////////////////////////////////////////////////////

    error LicenseRegistry_UnknownLicenseId();
    error LicenseRegistry_CallerNotLicensingModule();
    error LicenseRegistry_CallerNotRevoker();
    error LicenseRegistry_CallerNotLicensingModuleOrLicensee();
    error LicenseRegistry_CallerNotLicensor();
    error LicenseRegistry_LicenseNotPendingApproval();
    error LicenseRegistry_InvalidLicenseStatus();
    error LicenseRegistry_ParentLicenseNotActive();
    error LicenseRegistry_IPANotActive();
    error LicenseRegistry_LicenseNotActive();
    error LicenseRegistry_LicenseAlreadyLinkedToIpa();

    ////////////////////////////////////////////////////////////////////////////
    //                            RegistrationModule                          //
    ////////////////////////////////////////////////////////////////////////////

    /// @notice The caller is not authorized to perform registration.
    error RegistrationModule_CallerNotAuthorized();

    /// @notice The configured caller is invalid.
    error RegistrationModule_InvalidCaller();

    /// @notice The IP asset does not exist.
    error RegistrationModule_IPAssetNonExistent();

    /// @notice The registration module for the IP Org was not yet configured.
    error RegistrationModule_IPOrgNotConfigured();

    /// @notice The registration configuration action is not valid.
    error RegistrationModule_InvalidConfigOperation();

    /// @notice The registration execution action is not valid.
    error RegistrationModule_InvalidExecutionOperation();

    /// @notice IP asset type is not in the list of supported types for
    /// the IP Org.
    error RegistrationModule_InvalidIPAssetType();

    /// @notice IPAsset types provided are more than the maximum allowed.
    error RegistrationModule_TooManyAssetTypes();

    ////////////////////////////////////////////////////////////////////////////
    //                            RelationshipModule                          //
    ////////////////////////////////////////////////////////////////////////////

    /// @notice Unable to relate to another franchise.
    error RelationshipModule_CannotRelateToOtherIPOrg();

    /// @notice The intent has already been registered.
    error RelationshipModule_IntentAlreadyRegistered();

    /// @notice The selected TTL is not valid.
    error RelationshipModule_InvalidTTL();

    /// @notice The selected end timestamp is not valid.
    error RelationshipModule_InvalidEndTimestamp();

    /// @notice Relationship does not exist.
    error RelationshipModule_NonExistingRelationship();

    /// @notice The relationship source IP type is not supported.
    error RelationshipModule_UnsupportedRelationshipSrc();

    /// @notice The relationship destination IP type is not supported.
    error RelationshipModule_UnsupportedRelationshipDst();

    /// @notice Trying an unsupported config action
    error RelationshipModule_InvalidConfigOperation();

    /// @notice Unauthorized caller
    error RelationshipModule_CallerNotIpOrgOwner();

    /// @notice Value not on Relatable enum
    error RelationshipModule_InvalidRelatable();

    /// @notice Getting an invalid relationship type
    error RelationshipModule_RelTypeNotSet(string relType);

    /// @notice Relating invalid src addresss
    error RelationshipModule_InvalidSrcAddress();

    /// @notice Relating invalid dst addresss
    error RelationshipModule_InvalidDstAddress();

    /// @notice Relating unsupported src ipOrg asset type
    error RelationshipModule_InvalidSrcId();

    /// @notice Relating unsupported dst ipOrg asset type
    error RelationshipModule_InvalidDstId();

    /// @notice For IPORG_ENTRY - IPORG_ENTRY relationships,
    /// ipOrg address must be set
    error RelationshipModule_IpOrgRelatableCannotBeProtocolLevel();

    /// @notice Index is not found for the asset types of that IP Org.
    error RelationshipModule_UnsupportedIpOrgIndexType();

    ////////////////////////////////////////////////////////////////////////////
    //                                RoyaltyNFT                              //
    ////////////////////////////////////////////////////////////////////////////

    /// @notice Mismatch between parity of accounts and their respective allocations.
    error RoyaltyNFT_AccountsAndAllocationsMismatch(uint256 accountsLength, uint256 allocationsLength);

    /// @notice Invalid summation for royalty NFT allocations.
    error RoyaltyNFT_InvalidAllocationsSum(uint32 allocationsSum);

    ////////////////////////////////////////////////////////////////////////////
    //                                  Hook                                  //
    ////////////////////////////////////////////////////////////////////////////

    /// @notice The hook request was not found.
    error Hook_RequestedNotFound();

    /// @notice The sync operation is not supported in Async hooks.
    error Hook_UnsupportedSyncOperation();

    /// @notice The async operation is not supported in Sync hooks.
    error Hook_UnsupportedAsyncOperation();

    /// @notice The callback function can only called by designated callback caller.
    error Hook_OnlyCallbackCallerCanCallback(address current, address expected);

    /// @notice Invalid async request ID.
    error Hook_InvalidAsyncRequestId(bytes32 invalidRequestId);

    /// @notice The address is not the owner of the token.
    error TokenGatedHook_NotTokenOwner(address tokenAddress, address ownerAddress);

    error Hook_AsyncHookError(bytes32 requestId, string reason);

    /// @notice Invalid Hook configuration.
    error Hook_InvalidHookConfig(string reason);

    ////////////////////////////////////////////////////////////////////////////
    //                       LicensorApprovalHook                             //
    ////////////////////////////////////////////////////////////////////////////

    error LicensorApprovalHook_ApprovalAlreadyRequested();
    error LicensorApprovalHook_InvalidLicensor();
    error LicensorApprovalHook_InvalidLicenseId();
    error LicensorApprovalHook_NoApprovalRequested();
    error LicensorApprovalHook_InvalidResponseStatus();
}

// File: contracts/interfaces/ip-org/IIPOrg.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

/// @notice IP Org Interface
interface IIPOrg {
    /// @notice Returns the current owner of the IP asset within the IP Org.
    /// @return The address of the owner of the IP asset.
    function ownerOf(uint256 id) external view returns (address);

    /// @notice Transfers ownership of the IP asset wrapper within an IP Org.
    /// @param from The previous owner of the wrapped IP asset.
    /// @param to The new owner of the wrapped IP asset.
    /// @param id The identifier of the IP Org asset.
    function transferFrom(address from, address to, uint256 id) external;

    /// @notice Burns an IP asset wrapper within the IP Org.
    /// @dev This function is only callable by the IP Org registration module.
    /// @param id The local identifier of the IP asset within the IP Org.
    function burn(uint256 id) external;

    /// @notice Mints an IP Asset wrapper for the IP Org.
    /// @dev This function is only callable by the IP Org registration module.
    /// @param owner Address of the current owner of the local IP Org asset.
    /// @param assetType The IP Org asset type.
    /// @return id The local identifier of the minted IP Org wrapped asset.
    function mint(address owner, uint8 assetType) external returns (uint256 id);

    /// @notice Gets the current owner of the IP Org.
    /// @return The address of the IP Org owner.
    function owner() external view returns (address);

    /// @notice Returns contract-level metadata for the IP Org.
    /// @return The contract-wide URI associated with the IP Org.
    function contractURI() external view returns (string memory);

    /// @notice Returns the IP Org asset type for a given IP Org asset.
    /// @return The id associated with the IP Org asset type.
    function ipOrgAssetType(uint256 id_) external view returns (uint8);

    /// @notice Gets the global IP asset id associated with this IP Org asset.
    /// @param id_ The local id of the IP Org wrapped IP asset.
    /// @return The global identifier of the IP asset.
    function ipAssetId(uint256 id_) external returns (uint256);
}

// File: node_modules/@openzeppelin/contracts/utils/Multicall.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (utils/Multicall.sol)

pragma solidity ^0.8.0;

import "./Address.sol";

/**
 * @dev Provides a function to batch together multiple calls in a single external call.
 *
 * _Available since v4.1._
 */
abstract contract Multicall {
    /**
     * @dev Receives and executes a batch of function calls on this contract.
     * @custom:oz-upgrades-unsafe-allow-reachable delegatecall
     */
    function multicall(bytes[] calldata data) external virtual returns (bytes[] memory results) {
        results = new bytes[](data.length);
        for (uint256 i = 0; i < data.length; i++) {
            results[i] = Address.functionDelegateCall(address(this), data[i]);
        }
        return results;
    }
}

// File: contracts/interfaces/hooks/base/IHook.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

/// @notice Enum for representing various states of an async hook.
enum HookResult {
    Pending, // Indicates the hook is ongoing execution.
    Completed // Indicates the hook has successfully completed.
}

/// @title Hook Interface.
/// @notice This interface defines methods for synchronous and asynchronous hooks.
/// @dev Hooks are used to execute custom logic in response to certain events or conditions.
interface IHook {
    /// @notice Executes a synchronous hook.
    /// @param hookContext_ The context of an executing hook. It is an encoded version of Hook.ExecutionContext
    /// @return result The result of the hook execution.
    /// @return hookData The data returned by the hook.
    function executeSync(bytes calldata hookContext_) external returns (HookResult result, bytes memory hookData);

    /// @notice Executes an asynchronous hook.
    /// @param hookContext_ The context of an executing hook. It is an encoded version of Hook.ExecutionContext
    /// @param callbackHandler_ The address of the callback handler.
    /// @return result The result of the hook execution.
    /// @return hookData The data returned by the hook.
    /// @return requestId The id of the request.
    function executeAsync(
        bytes calldata hookContext_,
        address callbackHandler_
    ) external returns (HookResult result, bytes memory hookData, bytes32 requestId);

    /// @notice Validates the configuration for the hook.
    /// @dev This should be overridden by hook implementations to provide custom validation logic.
    /// @param hookConfig_ The configuration data for the hook.
    function validateConfig(bytes calldata hookConfig_) external view;
}

// File: contracts/lib/modules/Module.sol



// SPDX-License-Identifier: UNLICENSED
// See https://github.com/storyprotocol/protocol-contracts/blob/main/StoryProtocol-AlphaTestingAgreement-17942166.3.pdf
pragma solidity ^0.8.19;

// This file contains module structures and constants used throughout Story Protocol.

// A module key is identified by its keccak-256 encoded string identifier.
type ModuleKey is bytes32;

using { moduleKeyEquals as == } for ModuleKey global;
using { moduleKeyNotEquals as != } for ModuleKey global;

// A gateway's module dependencies are composed of a list of module keys
// and a list of function selectors dependend on for each of these modules.
struct ModuleDependencies {
    ModuleKey[] keys;
    bytes4[][] fns;
}

// Helper function for comparing equality between two keys.
function moduleKeyEquals(ModuleKey k1, ModuleKey k2) pure returns (bool) {
    return ModuleKey.unwrap(k1) == ModuleKey.unwrap(k2);
}

// Helper function for comparing inequality between two keys.
function moduleKeyNotEquals(ModuleKey k1, ModuleKey k2) pure returns (bool) {
    return ModuleKey.unwrap(k1) != ModuleKey.unwrap(k2);
}

// Transforms a string to its designated module key.
function toModuleKey(string calldata moduleKey_) pure returns (ModuleKey) {
    return ModuleKey.wrap(keccak256(abi.encodePacked(moduleKey_)));
}

// String values for core protocol modules.
string constant RELATIONSHIP_MODULE = "RELATIONSHIP_MODULE";
string constant LICENSING_MODULE = "LICENSING_MODULE";
string constant REGISTRATION_MODULE = "REGISTRATION_MODULE";

// Module key values for core protocol modules.
ModuleKey constant RELATIONSHIP_MODULE_KEY = ModuleKey.wrap(keccak256(abi.encodePacked(RELATIONSHIP_MODULE)));
ModuleKey constant LICENSING_MODULE_KEY = ModuleKey.wrap(keccak256(abi.encodePacked(LICENSING_MODULE)));
ModuleKey constant REGISTRATION_MODULE_KEY = ModuleKey.wrap(keccak256(abi.encodePacked(REGISTRATION_MODULE)));

// File: node_modules/@openzeppelin/contracts-upgradeable/utils/introspection/ERC165CheckerUpgradeable.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (utils/introspection/ERC165Checker.sol)

pragma solidity ^0.8.0;

import "./IERC165Upgradeable.sol";

/**
 * @dev Library used to query support of an interface declared via {IERC165}.
 *
 * Note that these functions return the actual result of the query: they do not
 * `revert` if an interface is not supported. It is up to the caller to decide
 * what to do in these cases.
 */
library ERC165CheckerUpgradeable {
    // As per the EIP-165 spec, no interface should ever match 0xffffffff
    bytes4 private constant _INTERFACE_ID_INVALID = 0xffffffff;

    /**
     * @dev Returns true if `account` supports the {IERC165} interface.
     */
    function supportsERC165(address account) internal view returns (bool) {
        // Any contract that implements ERC165 must explicitly indicate support of
        // InterfaceId_ERC165 and explicitly indicate non-support of InterfaceId_Invalid
        return
            supportsERC165InterfaceUnchecked(account, type(IERC165Upgradeable).interfaceId) &&
            !supportsERC165InterfaceUnchecked(account, _INTERFACE_ID_INVALID);
    }

    /**
     * @dev Returns true if `account` supports the interface defined by
     * `interfaceId`. Support for {IERC165} itself is queried automatically.
     *
     * See {IERC165-supportsInterface}.
     */
    function supportsInterface(address account, bytes4 interfaceId) internal view returns (bool) {
        // query support of both ERC165 as per the spec and support of _interfaceId
        return supportsERC165(account) && supportsERC165InterfaceUnchecked(account, interfaceId);
    }

    /**
     * @dev Returns a boolean array where each value corresponds to the
     * interfaces passed in and whether they're supported or not. This allows
     * you to batch check interfaces for a contract where your expectation
     * is that some interfaces may not be supported.
     *
     * See {IERC165-supportsInterface}.
     *
     * _Available since v3.4._
     */
    function getSupportedInterfaces(
        address account,
        bytes4[] memory interfaceIds
    ) internal view returns (bool[] memory) {
        // an array of booleans corresponding to interfaceIds and whether they're supported or not
        bool[] memory interfaceIdsSupported = new bool[](interfaceIds.length);

        // query support of ERC165 itself
        if (supportsERC165(account)) {
            // query support of each interface in interfaceIds
            for (uint256 i = 0; i < interfaceIds.length; i++) {
                interfaceIdsSupported[i] = supportsERC165InterfaceUnchecked(account, interfaceIds[i]);
            }
        }

        return interfaceIdsSupported;
    }

    /**
     * @dev Returns true if `account` supports all the interfaces defined in
     * `interfaceIds`. Support for {IERC165} itself is queried automatically.
     *
     * Batch-querying can lead to gas savings by skipping repeated checks for
     * {IERC165} support.
     *
     * See {IERC165-supportsInterface}.
     */
    function supportsAllInterfaces(address account, bytes4[] memory interfaceIds) internal view returns (bool) {
        // query support of ERC165 itself
        if (!supportsERC165(account)) {
            return false;
        }

        // query support of each interface in interfaceIds
        for (uint256 i = 0; i < interfaceIds.length; i++) {
            if (!supportsERC165InterfaceUnchecked(account, interfaceIds[i])) {
                return false;
            }
        }

        // all interfaces supported
        return true;
    }

    /**
     * @notice Query if a contract implements an interface, does not check ERC165 support
     * @param account The address of the contract to query for support of an interface
     * @param interfaceId The interface identifier, as specified in ERC-165
     * @return true if the contract at account indicates support of the interface with
     * identifier interfaceId, false otherwise
     * @dev Assumes that account contains a contract that supports ERC165, otherwise
     * the behavior of this method is undefined. This precondition can be checked
     * with {supportsERC165}.
     *
     * Some precompiled contracts will falsely indicate support for a given interface, so caution
     * should be exercised when using this function.
     *
     * Interface identification is specified in ERC-165.
     */
    function supportsERC165InterfaceUnchecked(address account, bytes4 interfaceId) internal view returns (bool) {
        // prepare call
        bytes memory encodedParams = abi.encodeWithSelector(IERC165Upgradeable.supportsInterface.selector, interfaceId);

        // perform static call
        bool success;
        uint256 returnSize;
        uint256 returnValue;
        assembly {
            success := staticcall(30000, account, add(encodedParams, 0x20), mload(encodedParams), 0x00, 0x20)
            returnSize := returndatasize()
            returnValue := mload(0x00)
        }

        return success && returnSize >= 0x20 && returnValue > 0;
    }
}

// File: node_modules/@openzeppelin/contracts/access/IAccessControl.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (access/IAccessControl.sol)

pragma solidity ^0.8.0;

/**
 * @dev External interface of AccessControl declared to support ERC165 detection.
 */
interface IAccessControl {
    /**
     * @dev Emitted when `newAdminRole` is set as ``role``'s admin role, replacing `previousAdminRole`
     *
     * `DEFAULT_ADMIN_ROLE` is the starting admin for all roles, despite
     * {RoleAdminChanged} not being emitted signaling this.
     *
     * _Available since v3.1._
     */
    event RoleAdminChanged(bytes32 indexed role, bytes32 indexed previousAdminRole, bytes32 indexed newAdminRole);

    /**
     * @dev Emitted when `account` is granted `role`.
     *
     * `sender` is the account that originated the contract call, an admin role
     * bearer except when using {AccessControl-_setupRole}.
     */
    event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender);

    /**
     * @dev Emitted when `account` is revoked `role`.
     *
     * `sender` is the account that originated the contract call:
     *   - if using `revokeRole`, it is the admin role bearer
     *   - if using `renounceRole`, it is the role bearer (i.e. `account`)
     */
    event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender);

    /**
     * @dev Returns `true` if `account` has been granted `role`.
     */
    function hasRole(bytes32 role, address account) external view returns (bool);

    /**
     * @dev Returns the admin role that controls `role`. See {grantRole} and
     * {revokeRole}.
     *
     * To change a role's admin, use {AccessControl-_setRoleAdmin}.
     */
    function getRoleAdmin(bytes32 role) external view returns (bytes32);

    /**
     * @dev Grants `role` to `account`.
     *
     * If `account` had not been already granted `role`, emits a {RoleGranted}
     * event.
     *
     * Requirements:
     *
     * - the caller must have ``role``'s admin role.
     */
    function grantRole(bytes32 role, address account) external;

    /**
     * @dev Revokes `role` from `account`.
     *
     * If `account` had been granted `role`, emits a {RoleRevoked} event.
     *
     * Requirements:
     *
     * - the caller must have ``role``'s admin role.
     */
    function revokeRole(bytes32 role, address account) external;

    /**
     * @dev Revokes `role` from the calling account.
     *
     * Roles are often managed via {grantRole} and {revokeRole}: this function's
     * purpose is to provide a mechanism for accounts to lose their privileges
     * if they are compromised (such as when a trusted device is misplaced).
     *
     * If the calling account had been granted `role`, emits a {RoleRevoked}
     * event.
     *
     * Requirements:
     *
     * - the caller must be `account`.
     */
    function renounceRole(bytes32 role, address account) external;
}

// File: contracts/interfaces/access-control/IAccessControlled.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

/// @title Access Controlled Interface
/// @notice This interface must be implemented by all protocol components that require
///         to be authorized via the global Access Control Singleton contract. This
///         initially includes all modules and hooks contracts, but will later be
///         sunset in favor of central authorization via the module registry.
interface IAccessControlled {
    /// @notice Emits when the global Access Control Singleton contract is updated.
    /// @param accessControl Address of the protocol-wide Access Control singleton contract.
    event AccessControlUpdated(address indexed accessControl);

    /// @notice Sets the Access Control Singleton instance.
    /// @param accessControl_ address of the new instance of the Access Control Singleton.
    function setAccessControl(address accessControl_) external;
}

// File: node_modules/@openzeppelin/contracts/utils/Address.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (utils/Address.sol)

pragma solidity ^0.8.1;

/**
 * @dev Collection of functions related to the address type
 */
library Address {
    /**
     * @dev Returns true if `account` is a contract.
     *
     * [IMPORTANT]
     * ====
     * It is unsafe to assume that an address for which this function returns
     * false is an externally-owned account (EOA) and not a contract.
     *
     * Among others, `isContract` will return false for the following
     * types of addresses:
     *
     *  - an externally-owned account
     *  - a contract in construction
     *  - an address where a contract will be created
     *  - an address where a contract lived, but was destroyed
     *
     * Furthermore, `isContract` will also return true if the target contract within
     * the same transaction is already scheduled for destruction by `SELFDESTRUCT`,
     * which only has an effect at the end of a transaction.
     * ====
     *
     * [IMPORTANT]
     * ====
     * You shouldn't rely on `isContract` to protect against flash loan attacks!
     *
     * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets
     * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract
     * constructor.
     * ====
     */
    function isContract(address account) internal view returns (bool) {
        // This method relies on extcodesize/address.code.length, which returns 0
        // for contracts in construction, since the code is only stored at the end
        // of the constructor execution.

        return account.code.length > 0;
    }

    /**
     * @dev Replacement for Solidity's `transfer`: sends `amount` wei to
     * `recipient`, forwarding all available gas and reverting on errors.
     *
     * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
     * of certain opcodes, possibly making contracts go over the 2300 gas limit
     * imposed by `transfer`, making them unable to receive funds via
     * `transfer`. {sendValue} removes this limitation.
     *
     * https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more].
     *
     * IMPORTANT: because control is transferred to `recipient`, care must be
     * taken to not create reentrancy vulnerabilities. Consider using
     * {ReentrancyGuard} or the
     * https://solidity.readthedocs.io/en/v0.8.0/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
     */
    function sendValue(address payable recipient, uint256 amount) internal {
        require(address(this).balance >= amount, "Address: insufficient balance");

        (bool success, ) = recipient.call{value: amount}("");
        require(success, "Address: unable to send value, recipient may have reverted");
    }

    /**
     * @dev Performs a Solidity function call using a low level `call`. A
     * plain `call` is an unsafe replacement for a function call: use this
     * function instead.
     *
     * If `target` reverts with a revert reason, it is bubbled up by this
     * function (like regular Solidity function calls).
     *
     * Returns the raw returned data. To convert to the expected return value,
     * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
     *
     * Requirements:
     *
     * - `target` must be a contract.
     * - calling `target` with `data` must not revert.
     *
     * _Available since v3.1._
     */
    function functionCall(address target, bytes memory data) internal returns (bytes memory) {
        return functionCallWithValue(target, data, 0, "Address: low-level call failed");
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
     * `errorMessage` as a fallback revert reason when `target` reverts.
     *
     * _Available since v3.1._
     */
    function functionCall(
        address target,
        bytes memory data,
        string memory errorMessage
    ) internal returns (bytes memory) {
        return functionCallWithValue(target, data, 0, errorMessage);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but also transferring `value` wei to `target`.
     *
     * Requirements:
     *
     * - the calling contract must have an ETH balance of at least `value`.
     * - the called Solidity function must be `payable`.
     *
     * _Available since v3.1._
     */
    function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
        return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
    }

    /**
     * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
     * with `errorMessage` as a fallback revert reason when `target` reverts.
     *
     * _Available since v3.1._
     */
    function functionCallWithValue(
        address target,
        bytes memory data,
        uint256 value,
        string memory errorMessage
    ) internal returns (bytes memory) {
        require(address(this).balance >= value, "Address: insufficient balance for call");
        (bool success, bytes memory returndata) = target.call{value: value}(data);
        return verifyCallResultFromTarget(target, success, returndata, errorMessage);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but performing a static call.
     *
     * _Available since v3.3._
     */
    function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
        return functionStaticCall(target, data, "Address: low-level static call failed");
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
     * but performing a static call.
     *
     * _Available since v3.3._
     */
    function functionStaticCall(
        address target,
        bytes memory data,
        string memory errorMessage
    ) internal view returns (bytes memory) {
        (bool success, bytes memory returndata) = target.staticcall(data);
        return verifyCallResultFromTarget(target, success, returndata, errorMessage);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but performing a delegate call.
     *
     * _Available since v3.4._
     */
    function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
        return functionDelegateCall(target, data, "Address: low-level delegate call failed");
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
     * but performing a delegate call.
     *
     * _Available since v3.4._
     */
    function functionDelegateCall(
        address target,
        bytes memory data,
        string memory errorMessage
    ) internal returns (bytes memory) {
        (bool success, bytes memory returndata) = target.delegatecall(data);
        return verifyCallResultFromTarget(target, success, returndata, errorMessage);
    }

    /**
     * @dev Tool to verify that a low level call to smart-contract was successful, and revert (either by bubbling
     * the revert reason or using the provided one) in case of unsuccessful call or if target was not a contract.
     *
     * _Available since v4.8._
     */
    function verifyCallResultFromTarget(
        address target,
        bool success,
        bytes memory returndata,
        string memory errorMessage
    ) internal view returns (bytes memory) {
        if (success) {
            if (returndata.length == 0) {
                // only check isContract if the call was successful and the return data is empty
                // otherwise we already know that it was a contract
                require(isContract(target), "Address: call to non-contract");
            }
            return returndata;
        } else {
            _revert(returndata, errorMessage);
        }
    }

    /**
     * @dev Tool to verify that a low level call was successful, and revert if it wasn't, either by bubbling the
     * revert reason or using the provided one.
     *
     * _Available since v4.3._
     */
    function verifyCallResult(
        bool success,
        bytes memory returndata,
        string memory errorMessage
    ) internal pure returns (bytes memory) {
        if (success) {
            return returndata;
        } else {
            _revert(returndata, errorMessage);
        }
    }

    function _revert(bytes memory returndata, string memory errorMessage) private pure {
        // Look for revert reason and bubble it up if present
        if (returndata.length > 0) {
            // The easiest way to bubble the revert reason is using memory via assembly
            /// @solidity memory-safe-assembly
            assembly {
                let returndata_size := mload(returndata)
                revert(add(32, returndata), returndata_size)
            }
        } else {
            revert(errorMessage);
        }
    }
}

// File: node_modules/@openzeppelin/contracts-upgradeable/utils/introspection/IERC165Upgradeable.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/introspection/IERC165.sol)

pragma solidity ^0.8.0;

/**
 * @dev Interface of the ERC165 standard, as defined in the
 * https://eips.ethereum.org/EIPS/eip-165[EIP].
 *
 * Implementers can declare support of contract interfaces, which can then be
 * queried by others ({ERC165Checker}).
 *
 * For an implementation, see {ERC165}.
 */
interface IERC165Upgradeable {
    /**
     * @dev Returns true if this contract implements the interface defined by
     * `interfaceId`. See the corresponding
     * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]
     * to learn more about how these ids are created.
     *
     * This function call must use less than 30 000 gas.
     */
    function supportsInterface(bytes4 interfaceId) external view returns (bool);
}