// File: contracts/StoryProtocol.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 { Multicall } from "@openzeppelin/contracts/utils/Multicall.sol";

import { IIPOrgController } from "contracts/interfaces/ip-org/IIPOrgController.sol";
import { IIPOrg } from "contracts/interfaces/ip-org/IIPOrg.sol";

import { Errors } from "contracts/lib/Errors.sol";
import { LibRelationship } from "contracts/lib/modules/LibRelationship.sol";
import { Licensing } from "contracts/lib/modules/Licensing.sol";
import { ModuleRegistry } from "contracts/modules/ModuleRegistry.sol";
import { Registration } from "contracts/lib/modules/Registration.sol";
import { RELATIONSHIP_MODULE, LICENSING_MODULE, REGISTRATION_MODULE } from "contracts/lib/modules/Module.sol";

/// @title Story Protocol Gateway Contract
/// @notice The Story Protocol contract acts as a global gateway for calling
///         protocol-standardized IP actions (based on their enrolled modules).
///         Most functions can be solely executed through this contract, as it will
///         be actively maintained and upgraded to support all standardized modules.
///         In the future, for more customized logic, IP Orgs may choose to create
///         their own frontend contracts (gateways) for IP interaction.
contract StoryProtocol is Multicall {
    /// @notice The IP Org Controller administers creation of new IP Orgs.
    IIPOrgController public immutable IP_ORG_CONTROLLER;

    /// @notice The module registry is used to authorize calls to modules.
    ModuleRegistry public immutable MODULE_REGISTRY;

    /// @notice Initializes a new Story Protocol gateway contract.
    /// @param ipOrgController_ IP Org Controller contract, used for IP Org creation.
    /// @param moduleRegistry_ Protocol-wide module registry used for module bookkeeping.
    constructor(IIPOrgController ipOrgController_, ModuleRegistry moduleRegistry_) {
        if (address(ipOrgController_) == address(0) || address(moduleRegistry_) == address(0)) {
            revert Errors.ZeroAddress();
        }
        IP_ORG_CONTROLLER = ipOrgController_;
        MODULE_REGISTRY = moduleRegistry_;
    }

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

    /// @notice Sets the metadata for an IP Org.
    /// @param ipOrg_ The address of the IP Org being configured.
    /// @param baseURI_ The base token metadata URI for the IP Org.
    /// @param contractURI_ The contract URI associated with the IP Org.
    function setMetadata(address ipOrg_, string calldata baseURI_, string calldata contractURI_) public {
        bytes memory encodedParams = abi.encode(Registration.SET_IP_ORG_METADATA, abi.encode(baseURI_, contractURI_));
        MODULE_REGISTRY.configure(IIPOrg(ipOrg_), msg.sender, REGISTRATION_MODULE, encodedParams);
    }

    /// @notice Adds additional IP asset types for an IP Org.
    /// @param ipOrg_ The address of the IP Org being configured.
    /// @param ipAssetTypes_ The IP asset type descriptors to add for the IPOrg.
    function addIPAssetTypes(address ipOrg_, string[] calldata ipAssetTypes_) public {
        bytes memory encodedParams = abi.encode(Registration.SET_IP_ORG_ASSET_TYPES, abi.encode(ipAssetTypes_));
        MODULE_REGISTRY.configure(IIPOrg(ipOrg_), msg.sender, REGISTRATION_MODULE, encodedParams);
    }

    /// @notice Registers a new IP Org
    /// @param owner_ The address of the IP Org to be registered.
    /// @param name_ A name to associate with the IP Org.
    /// @param symbol_ A symbol to associate with the IP Org.
    function registerIpOrg(
        address owner_,
        string calldata name_,
        string calldata symbol_,
        string[] calldata ipAssetTypes_
    ) external returns (address ipOrg_) {
        return IP_ORG_CONTROLLER.registerIpOrg(owner_, name_, symbol_, ipAssetTypes_);
    }

    /// @notice Registers an IP Asset.
    /// @param ipOrg_ The governing IP Org under which the IP asset is registered.
    /// @param params_ The registration params, including owner, name, hash.
    /// @param licenseId_ Optional: The license id to associate with the IP asset, 0 if none.
    /// @param preHooksData_ Hooks to embed with the registration pre-call.
    /// @param postHooksData_ Hooks to embed with the registration post-call.
    /// @return The global IP asset and local IP Org asset id.
    function registerIPAsset(
        address ipOrg_,
        Registration.RegisterIPAssetParams calldata params_,
        uint256 licenseId_,
        bytes[] calldata preHooksData_,
        bytes[] calldata postHooksData_
    ) public returns (uint256, uint256) {
        bytes memory encodedParams = abi.encode(Registration.REGISTER_IP_ASSET, abi.encode(params_));
        bytes memory result = MODULE_REGISTRY.execute(
            IIPOrg(ipOrg_),
            msg.sender,
            REGISTRATION_MODULE,
            encodedParams,
            preHooksData_,
            postHooksData_
        );
        // An empty result indicates that an async hook call is pending execution.
        if (result.length == 0) {
            return (0, 0);
        }
        (uint256 globalId, uint256 localId) = abi.decode(result, (uint256, uint256));
        if (licenseId_ != 0) {
            _linkLnftToIpa(ipOrg_, licenseId_, globalId, msg.sender);
        }
        return (globalId, localId);
    }

    /// @notice Transfers an IP asset to another owner.
    /// @param ipOrg_ The IP Org which the IP asset is associated with.
    /// @param from_ The address of the current owner of the IP asset.
    /// @param to_ The address of the new owner of the IP asset.
    /// @param ipAssetId_ The global id of the IP asset being transferred.
    function transferIPAsset(
        address ipOrg_,
        address from_,
        address to_,
        uint256 ipAssetId_,
        bytes[] calldata preHooksData_,
        bytes[] calldata postHooksData_
    ) public {
        bytes memory encodedParams = abi.encode(Registration.TRANSFER_IP_ASSET, abi.encode(from_, to_, ipAssetId_));
        MODULE_REGISTRY.execute(
            IIPOrg(ipOrg_),
            msg.sender,
            REGISTRATION_MODULE,
            encodedParams,
            preHooksData_,
            postHooksData_
        );
    }

    ////////////////////////////////////////////////////////////////////////////
    //                            Relationships                               //
    ////////////////////////////////////////////////////////////////////////////

    /// @notice Adds a new custom relationship type for an IP Org.
    /// @param params_ Relationship configs including sources, destinations, and relationship type.
    function addRelationshipType(LibRelationship.AddRelationshipTypeParams calldata params_) external {
        MODULE_REGISTRY.configure(
            IIPOrg(params_.ipOrg),
            msg.sender,
            RELATIONSHIP_MODULE,
            abi.encode(LibRelationship.ADD_REL_TYPE_CONFIG, abi.encode(params_))
        );
    }

    /// @notice Removes a relationship type for an IP Org.
    /// @param ipOrg_ The IP Org under which the relationship type is defined.
    /// @param relType_ The relationship type being removed from the IP Org.
    function removeRelationshipType(address ipOrg_, string calldata relType_) external {
        MODULE_REGISTRY.configure(
            IIPOrg(ipOrg_),
            msg.sender,
            RELATIONSHIP_MODULE,
            abi.encode(LibRelationship.REMOVE_REL_TYPE_CONFIG, abi.encode(relType_))
        );
    }

    /// @notice Creates a new relationship for an IP Org.
    /// @param ipOrg_ The address of the IP Org creating the relationship.
    /// @param params_ Params for relationship creation, including type, source, and destination.
    /// @param preHooksData_ Data to be processed by any enrolled pre-hook actions.
    /// @param postHooksData_ Data to be processed by any enrolled post-hook actions.
    function createRelationship(
        address ipOrg_,
        LibRelationship.CreateRelationshipParams calldata params_,
        bytes[] calldata preHooksData_,
        bytes[] calldata postHooksData_
    ) external returns (uint256 relId) {
        bytes memory result = MODULE_REGISTRY.execute(
            IIPOrg(ipOrg_),
            msg.sender,
            RELATIONSHIP_MODULE,
            abi.encode(params_),
            preHooksData_,
            postHooksData_
        );
        return abi.decode(result, (uint256));
    }

    ////////////////////////////////////////////////////////////////////////////
    //                            Licensing                                   //
    ////////////////////////////////////////////////////////////////////////////

    /// @notice Configures a licensing framework for an IP Org, including licensing terms.
    /// @param ipOrg_ The address of the IP Org configuring the licensing.
    /// @param config_ Licensing configuration, including framework and licensor.
    function configureIpOrgLicensing(address ipOrg_, Licensing.LicensingConfig calldata config_) external {
        MODULE_REGISTRY.configure(
            IIPOrg(ipOrg_),
            msg.sender,
            LICENSING_MODULE,
            abi.encode(Licensing.LICENSING_FRAMEWORK_CONFIG, abi.encode(config_))
        );
    }

    /// Creates a tradeable License NFT in the License Registry.
    /// @param ipOrg_ The address of the IP Org creating the license.
    /// @param params_ Params around licensing creation, including IP asset id and terms.
    /// @param preHooksData_ Data to be processed by any enrolled pre-hook actions.
    /// @param postHooksData_ Data to be processed by any enrolled post-hook actions.
    /// @return The id of the created license.
    function createLicense(
        address ipOrg_,
        Licensing.LicenseCreation calldata params_,
        bytes[] calldata preHooksData_,
        bytes[] calldata postHooksData_
    ) external returns (uint256) {
        bytes memory params = abi.encode(params_);
        bytes memory result = MODULE_REGISTRY.execute(
            IIPOrg(ipOrg_),
            msg.sender,
            LICENSING_MODULE,
            abi.encode(Licensing.CREATE_LICENSE, params),
            preHooksData_,
            postHooksData_
        );
        return abi.decode(result, (uint256));
    }

    /// Activates a license that is pending approval
    /// @param ipOrg_ Address of the IP Org under which the license is contained.
    /// @param licenseId_ The identifier of the license.
    function activateLicense(address ipOrg_, uint256 licenseId_) external {
        MODULE_REGISTRY.execute(
            IIPOrg(ipOrg_),
            msg.sender,
            LICENSING_MODULE,
            abi.encode(Licensing.ACTIVATE_LICENSE, abi.encode(licenseId_)),
            new bytes[](0),
            new bytes[](0)
        );
    }

    /// Associates a license with an IPA
    /// @param ipOrg_ the ipOrg address
    /// @param licenseId_ the license id
    /// @param ipaId_ the ipa id
    function linkLnftToIpa(address ipOrg_, uint256 licenseId_, uint256 ipaId_) public {
        _linkLnftToIpa(ipOrg_, licenseId_, ipaId_, msg.sender);
    }

    function _linkLnftToIpa(address ipOrg_, uint256 licenseId_, uint256 ipaId_, address caller_) private {
        MODULE_REGISTRY.execute(
            IIPOrg(ipOrg_),
            caller_,
            LICENSING_MODULE,
            abi.encode(Licensing.LINK_LNFT_TO_IPA, abi.encode(licenseId_, ipaId_)),
            new bytes[](0),
            new bytes[](0)
        );
    }
}

// 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/ip-org/IIPOrgController.sol



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

/// @notice IP Org Controller Interface
interface IIPOrgController {
    /// @notice Emits when a new IP Org is registered.
    /// @param owner The address of the IP Org owner.
    /// @param ipAssetOrg The address of the new IP Org contract.
    /// @param name Descriptive name for the new IP Org contract.
    /// @param symbol A describe symbol for the new IP Org contract.
    /// @param ipAssetTypes String descriptors of the IP asset types available.
    event IPOrgRegistered(address owner, address ipAssetOrg, string name, string symbol, string[] ipAssetTypes);

    /// @notice Emits when an IP Org is transferred to a new owner.
    /// @param ipOrg The address of the IP Org.
    /// @param prevOwner The address of the previous owner of the IP Org.
    /// @param newOwner The address of the new owner of the IP Org.
    event IPOrgTransferred(address ipOrg, address prevOwner, address newOwner);

    /// @notice Emits when an ownership transfer is initialized for a new owner.
    /// @param ipOrg The address of the IP Org.
    /// @param pendingOwner The pending owner to set for the IP Org.
    event IPOrgPendingOwnerSet(address ipOrg, address pendingOwner);

    /// @notice Registers a new IP Org.
    /// @param owner_ The address of the IP Org owner.
    /// @param name_ Metadata name to attach to the IP Org.
    /// @param symbol_ Metadata symbol to attach to the IP Org.
    /// @param ipAssetTypes_ String descriptors of the IP asset types available.
    function registerIpOrg(
        address owner_,
        string calldata name_,
        string calldata symbol_,
        string[] calldata ipAssetTypes_
    ) external returns (address);

    /// @notice Checks whether an IP Org exists.
    function isIpOrg(address ipOrg_) external view returns (bool);
}

// 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: 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/lib/modules/LibRelationship.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 Relationship Module Library
library LibRelationship {
    /// @notice defines the elements that can be related in a relationship type
    enum Relatables {
        /// Unset value
        Undefined,
        /// The relationship type can be used to relate IPAs
        Ipa,
        /// The relationship type can be used to relate IPOrg token ids
        IpOrgEntry,
        /// The relationship type can be used to relate License token ids
        License,
        /// The relationship type can be used to relate Addresses
        Address,
        /// The relationship type can be used to relate External NFTs (e.g. ERC721)
        ExternalNft
    }

    /// @notice defines the elements that can be related in a relationship type
    struct RelatedElements {
        /// Source type
        Relatables src;
        /// Destination type
        Relatables dst;
    }

    /// @notice defines 2 related elements under a certain type
    struct Relationship {
        /// Relationship type name id
        string relType;
        /// Source address
        address srcAddress;
        /// Destination address
        address dstAddress;
        /// Source id (or zero if not applicable)
        uint256 srcId;
        /// Destination id (or zero if not applicable)
        uint256 dstId;
    }

    /// @notice defines the parameters for adding a relationship type
    struct AddRelationshipTypeParams {
        /// Relationship type name id
        string relType;
        /// IP Org address or zero address for protocol level rel types
        address ipOrg;
        /// Source and destination types
        RelatedElements allowedElements;
        /// Source ipOrgs types allowed or empty array if not applicable
        uint8[] allowedSrcs;
        /// Destination ipOrgs types allowed or empty array if not applicable
        uint8[] allowedDsts;
    }

    /// @notice defines the parameters to define a relationship type
    struct RelationshipType {
        /// Allowed source address
        address src;
        /// Allowd source subtypes (bitmask verion of allowedSrcs)
        uint256 srcSubtypesMask;
        /// Allowed destination address
        address dst;
        /// Allowed destination subtypes (bitmask verion of allowedDsts)
        uint256 dstSubtypesMask;
    }

    /// @notice defines the parameters for creating a relationship
    struct CreateRelationshipParams {
        /// Relationship type name id
        string relType;
        /// Source address
        address srcAddress;
        /// Source id or zero if not applicable
        uint256 srcId;
        /// Destination address
        address dstAddress;
        /// Destination id or zero if not applicable
        uint256 dstId;
    }

    /// Constant for protocol level relationship types
    address public constant PROTOCOL_LEVEL_RELATIONSHIP = address(0);
    /// Any address goes
    address public constant NO_ADDRESS_RESTRICTIONS = 0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF;

    /// Action to configure a relationship type in Relationship Module
    bytes32 public constant ADD_REL_TYPE_CONFIG = keccak256("ADD_REL_TYPE");
    /// Action to remove a relationship type in Relationship Module
    bytes32 public constant REMOVE_REL_TYPE_CONFIG = keccak256("REMOVE_REL_TYPE");
}

// File: contracts/lib/modules/Licensing.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 { BitMask } from "contracts/lib/BitMask.sol";
import { Strings } from "@openzeppelin/contracts/utils/Strings.sol";
import { ShortStrings, ShortString } from "@openzeppelin/contracts/utils/ShortStrings.sol";

/// @title Licensing Module Library
library Licensing {
    using ShortStrings for *;
    /// @notice Enum for the status of a license.
    enum LicenseStatus {
        /// No status has been set.
        Unset,
        /// The license is active. It can be linked with IPAs.
        Active,
        /// The license is pending licensor approval. It can't be linked with IPAs until approved
        /// by the licensor using the LicensingModule (activateLicense)
        PendingLicensorApproval,
        /// The license has been revoked. It can't be linked with IPAs or sublicensed.
        /// Sub-licenses will be considered non active.
        Revoked
    }

    /// @notice Defines the types to encode/decode the parameters of a licensing framework.
    enum ParameterType {
        /// Boolean value
        Bool,
        /// Number value (18 decimals)
        Number,
        /// Address value
        Address,
        /// String value
        String,
        /// Array of ShortString values
        ShortStringArray,
        // uint256 bitmask representing indexes in choices array. ParamDefinition will have the available choices array.
        MultipleChoice
    }

    /// @notice Defines the configuration of the licensor for an IP org.
    enum LicensorConfig {
        /// Null value.
        Unset,
        /// Licensor is the IP org owner, for all licenses.
        IpOrgOwnerAlways,
        /// Licensor will be:
        /// - If parentLicense is provided, the licensee of the parent license.
        /// - If parentLicense is not provided, the Owner of the linked IPA.
        /// - If no parentLicense and no linked IPA, the IP org owner.
        Source
    }

    /// @notice Data struct representing a license agreement minted according to an IP Org Licensing Framework
    struct LicenseData {
        /// License status.
        LicenseStatus status;
        /// If true, other licenses can set this one as parent license, false will prevent it.
        bool derivativesAllowed;
        /// If true, sublicenses can be created by anyone, and the terms of this license will be
        /// applied to the sublicenses. If false, sublicenses can only be created by the licensor.
        bool isReciprocal;
        /// If true, sublicenses will be minted with LicenseStatus.PendingLicensorApproval.
        bool derivativeNeedsApproval;
        /// Address with permission to revoke the license.
        address revoker;
        /// address granting the license
        address licensor;
        /// address of the ip org that produced the terms
        address ipOrg;
        /// The id of the license framework used to mint this license
        ShortString frameworkId;
        /// If the licensee is linked to an IPA, this is the IPA id. 0 otherwise
        uint256 ipaId;
        /// The id of the parent license, if any. 0 otherwise
        uint256 parentLicenseId;
    }

    /// @notice Parameters to mint a license
    struct LicenseCreation {
        /// Array of (tag, value) pairs for the parameters, corresponding to the tags in the
        /// licensing framework.
        ParamValue[] params;
        /// Parent license id, if any. 0 otherwise.
        uint256 parentLicenseId;
        /// Linked IPA id, if any. 0 otherwise.
        uint256 ipaId;
    }

    /// @notice Corresponds to a parameter (licensing term) of a licensing framework.
    struct ParamDefinition {
        /// The parameter id, used to identify the parameter in the license agreement text.
        ShortString tag;
        /// The type of the parameter, used to encode/decode the value
        ParameterType paramType;
        /// Encoded according to paramType, might be empty.
        bytes defaultValue;
        /// If MultipleChoice, String[] of the available choices. Empty bytes otherwise.
        bytes availableChoices;
    }

    /// @notice Corresponds to a value for parameter (licensing term) of a licensing framework.
    struct ParamValue {
        /// The parameter id, used to identify the parameter in the license agreement text.
        ShortString tag;
        /// Encoded according to paramType, might be empty.
        bytes value;
    }

    /// @notice Parameters to set a licensing framework in LicensingFrameworkRepo
    struct SetFramework {
        /// The id of the licensing framework.
        string id;
        /// The URL to the license agreement text.
        string textUrl;
        /// Array of parameter definitions, corresponding to the parameters in
        /// the license agreement text.
        ParamDefinition[] paramDefs;
    }

    /// @notice Parameters to set a licensing framework in an IP org
    struct LicensingConfig {
        /// The id of the licensing framework.
        string frameworkId;
        /// Array of (tag, value) pairs for the parameters, corresponding to the tags in the
        /// licensing framework.
        ParamValue[] params;
        /// Enum with the rules to determine the licensor for this IP org's licenses
        LicensorConfig licensor;
    }

    /// @notice Maximum number of parameters allowed in a licensing framework.
    uint256 public constant MAX_PARAM_TAGS = 255;

    /// Input for IpOrg legal terms configuration in LicensingModule (for now, the only option)
    bytes32 public constant LICENSING_FRAMEWORK_CONFIG = keccak256("LICENSING_FRAMEWORK_CONFIG");
    /// Create license action in LicensingModule
    bytes32 public constant CREATE_LICENSE = keccak256("CREATE_LICENSE");
    /// Activate license action in LicensingModule
    bytes32 public constant ACTIVATE_LICENSE = keccak256("ACTIVATE_LICENSE");
    /// Revoke license action in LicensingModule
    bytes32 public constant LINK_LNFT_TO_IPA = keccak256("LINK_LNFT_TO_IPA");

    /// @notice Returns the string representation of a license status.
    function statusToString(LicenseStatus status_) internal pure returns (string memory) {
        if (status_ == LicenseStatus.Unset) {
            return "Unset";
        } else if (status_ == LicenseStatus.Active) {
            return "Active";
        } else if (status_ == LicenseStatus.PendingLicensorApproval) {
            return "Pending Licensor Approval";
        } else if (status_ == LicenseStatus.Revoked) {
            return "Revoked";
        }
        return "Unknown";
    }

    /// @notice decodes value from a MultipleChoice parameter into the corresponding ShortString[]
    /// with the choices
    /// @param value the encoded value
    /// @param availableChoices the encoded available choices for the parameter, defined in parameter
    /// definition
    function decodeMultipleChoice(
        bytes memory value,
        bytes memory availableChoices
    ) internal pure returns (ShortString[] memory) {
        uint256 mask = abi.decode(value, (uint256));
        uint8[] memory indexes = BitMask.getSetIndexes(mask);
        ShortString[] memory choices = abi.decode(availableChoices, (ShortString[]));
        ShortString[] memory result = new ShortString[](indexes.length);
        for (uint256 i = 0; i < indexes.length; i++) {
            result[i] = choices[indexes[i]];
        }
        return result;
    }

    /// @notice Sets the indexes for the chosen options in a MultipleChoice parameter,
    /// and encodes it into bytes
    /// @param choiceIndexes_ the indexes of the chosen options
    /// @return value the encoded value
    function encodeMultipleChoice(uint8[] memory choiceIndexes_) internal pure returns (bytes memory value) {
        uint256 mask = BitMask.convertToMask(choiceIndexes_);
        return abi.encode(mask);
    }

    /// @notice Validates the value for a parameter, according to its type, meaning it will
    /// decode the value and try to revert if it's not valid.
    /// @dev WARNING: Some validations are not possible to do on chain (like string validation),
    /// so they should be done off chain. Also, Boolean decoded as a Number will be valid.
    /// @param paramDef_ the parameter definition
    /// @param value_ the encoded value
    function validateParamValue(ParamDefinition memory paramDef_, bytes memory value_) internal pure returns (bool) {
        // An empty value signals the parameter is untagged, to trigger default values in the
        // license agreement text, but that's valid
        if (keccak256(value_) == keccak256("")) {
            return true;
        }
        if (paramDef_.paramType == Licensing.ParameterType.Bool) {
            abi.decode(value_, (bool));
            return true;
        } else if (paramDef_.paramType == Licensing.ParameterType.Number && abi.decode(value_, (uint256)) == 0) {
            return false;
        } else if (
            paramDef_.paramType == Licensing.ParameterType.Address &&
            // Not supporting address(0) as a valid value
            abi.decode(value_, (address)) == address(0)
        ) {
            return false;
        } else if (
            paramDef_.paramType == Licensing.ParameterType.String &&
            (keccak256(abi.encodePacked(abi.decode(value_, (string)))) == keccak256(abi.encode(" ")) ||
                keccak256(abi.encodePacked(abi.decode(value_, (string)))) == keccak256(abi.encode("")))
        ) {
            return false;
        } else if (
            paramDef_.paramType == Licensing.ParameterType.ShortStringArray &&
            abi.decode(value_, (ShortString[])).length == 0
        ) {
            return false;
        } else if (
            paramDef_.paramType == Licensing.ParameterType.MultipleChoice &&
            abi.decode(paramDef_.availableChoices, (ShortString[])).length == 0
        ) {
            return false;
        }
        return true;
    }

    /// @notice converts a ShortString[] to a string representation of a JSON array. Used for LicenseRegistry
    /// metadata rendering.
    /// @param ss the ShortString[]
    /// @return the string representation of a JSON array
    function shortStringArrayToJsonArray(ShortString[] memory ss) internal pure returns (string memory) {
        string memory result = "[";
        uint256 len = ss.length;
        for (uint256 i = 0; i < len; i++) {
            ShortString s = ss[i];
            result = string(abi.encodePacked(result, "\"", s.toString(), "\"")); // solhint-disable-line
            if (i != len - 1) {
                result = string(abi.encodePacked(result, ","));
            }
        }
        return string(abi.encodePacked(result, "]"));
    }

    /// @notice decodes value from a parameter into a string representation that can be used
    /// in LicenseRegistry metadata rendering.
    /// @param paramDef_ the parameter definition
    /// @param value_ the encoded value
    /// @return the string representation of the value
    function getDecodedParamString(
        Licensing.ParamDefinition memory paramDef_,
        bytes memory value_
    ) internal pure returns (string memory) {
        if (paramDef_.paramType == Licensing.ParameterType.Bool) {
            return abi.decode(value_, (bool)) ? "true" : "false";
        } else if (paramDef_.paramType == Licensing.ParameterType.Number) {
            return Strings.toString(abi.decode(value_, (uint256)));
        } else if (paramDef_.paramType == Licensing.ParameterType.String) {
            return abi.decode(value_, (string));
        } else if (paramDef_.paramType == Licensing.ParameterType.Address) {
            address addr = abi.decode(value_, (address));
            return Strings.toHexString(uint160(addr), 20);
        } else if (paramDef_.paramType == Licensing.ParameterType.ShortStringArray) {
            ShortString[] memory choices = abi.decode(value_, (ShortString[]));
            return shortStringArrayToJsonArray(choices);
        } else if (paramDef_.paramType == Licensing.ParameterType.MultipleChoice) {
            ShortString[] memory choices = decodeMultipleChoice(value_, paramDef_.availableChoices);
            return shortStringArrayToJsonArray(choices);
        }
        return "";
    }
}

// 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/lib/modules/Registration.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 Relationship Module Library
library Registration {
    /// @notice IPOrg configuration settings.
    struct IPOrgConfig {
        string baseURI;
        string contractURI;
        string[] assetTypes;
    }

    /// @notice Struct used for IP asset registration.
    struct RegisterIPAssetParams {
        address owner;
        uint8 ipOrgAssetType;
        string name;
        bytes32 hash;
        string mediaUrl;
    }

    // TODO(leeren): Change in favor of granular function-selector based auth.

    // Constants used for determining module configuration logic.
    bytes32 public constant SET_IP_ORG_METADATA = keccak256("SET_IP_ORG_METADATA");
    bytes32 public constant SET_IP_ORG_ASSET_TYPES = keccak256("SET_IP_ORG_ASSET_TYPES");

    // Constants used for determining module execution logic.
    bytes32 public constant REGISTER_IP_ASSET = keccak256("REGISTER_IP_ASSET");
    bytes32 public constant TRANSFER_IP_ASSET = keccak256("TRANSFER_IP_ASSET");
}

// 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/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: contracts/lib/BitMask.sol



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

/**
 * @notice Based on OpenZeppelin's BitMap, this library is used to encode a set of indexes in a compact way.
 * Instead of using a storage type like OZ, where they use a mapping(uint256 => uint256) for large numbers of values,
 * this library limts it to a 256 values in a single uint256.
 */
library BitMask {
    /// Returns whether the bit at `index` is set.
    function isSet(uint256 mask_, uint8 index_) internal pure returns (bool) {
        uint256 indexMask = 1 << (index_ & 0xff);
        return mask_ & indexMask != 0;
    }

    /// Sets the bit at `index` to the boolean `value`.
    function setTo(uint256 mask_, uint256 index_, bool value_) internal pure returns (uint256) {
        if (value_) {
            return set(mask_, index_);
        } else {
            return unset(mask_, index_);
        }
    }

    /// Sets the bit at `index`.
    function set(uint256 mask_, uint256 index_) internal pure returns (uint256) {
        uint256 indexMask = 1 << (index_ & 0xff);
        return mask_ |= indexMask;
    }

    /// Unsets the bit at `index`.
    function unset(uint256 mask_, uint256 index_) internal pure returns (uint256) {
        uint256 indexMask = 1 << (index_ & 0xff);
        return mask_ &= ~indexMask;
    }

    /// Gets the uint8 from the bitmask as an array
    function getSetIndexes(uint256 mask_) internal pure returns (uint8[] memory) {
        // Count the number of set bits to allocate the array size
        uint256 count;
        for (uint8 i = 0; i < 255; ++i) {
            if (isSet(mask_, i)) {
                ++count;
            }
        }
        uint8[] memory setBitIndexes = new uint8[](count);
        // Fill the array with indices of set bits
        uint256 index = 0;
        for (uint8 i = 0; i < 255; ++i) {
            if (isSet(mask_, i)) {
                setBitIndexes[index] = i;
                ++index;
            }
        }
        return setBitIndexes;
    }

    /// Converts an array of uint8 to a bit mask
    function convertToMask(uint8[] memory indexes_) internal pure returns (uint256) {
        uint256 mask = 0;
        for (uint256 i = 0; i < indexes_.length; ) {
            mask |= 1 << (uint256(indexes_[i]) & 0xff);
            unchecked {
                i++;
            }
        }
        return mask;
    }
}

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



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

pragma solidity ^0.8.0;

import "./math/Math.sol";
import "./math/SignedMath.sol";

/**
 * @dev String operations.
 */
library Strings {
    bytes16 private constant _SYMBOLS = "0123456789abcdef";
    uint8 private constant _ADDRESS_LENGTH = 20;

    /**
     * @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;
            /// @solidity memory-safe-assembly
            assembly {
                ptr := add(buffer, add(32, length))
            }
            while (true) {
                ptr--;
                /// @solidity memory-safe-assembly
                assembly {
                    mstore8(ptr, byte(mod(value, 10), _SYMBOLS))
                }
                value /= 10;
                if (value == 0) break;
            }
            return buffer;
        }
    }

    /**
     * @dev Converts a `int256` to its ASCII `string` decimal representation.
     */
    function toString(int256 value) internal pure returns (string memory) {
        return string(abi.encodePacked(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) {
        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] = _SYMBOLS[value & 0xf];
            value >>= 4;
        }
        require(value == 0, "Strings: hex length insufficient");
        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 Returns true if the two strings are equal.
     */
    function equal(string memory a, string memory b) internal pure returns (bool) {
        return keccak256(bytes(a)) == keccak256(bytes(b));
    }
}

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



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

pragma solidity ^0.8.8;

import "./StorageSlot.sol";

// | string  | 0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA   |
// | length  | 0x                                                              BB |
type ShortString is bytes32;

/**
 * @dev This library provides functions to convert short memory strings
 * into a `ShortString` type that can be used as an immutable variable.
 *
 * Strings of arbitrary length can be optimized using this library if
 * they are short enough (up to 31 bytes) by packing them with their
 * length (1 byte) in a single EVM word (32 bytes). Additionally, a
 * fallback mechanism can be used for every other case.
 *
 * Usage example:
 *
 * ```solidity
 * contract Named {
 *     using ShortStrings for *;
 *
 *     ShortString private immutable _name;
 *     string private _nameFallback;
 *
 *     constructor(string memory contractName) {
 *         _name = contractName.toShortStringWithFallback(_nameFallback);
 *     }
 *
 *     function name() external view returns (string memory) {
 *         return _name.toStringWithFallback(_nameFallback);
 *     }
 * }
 * ```
 */
library ShortStrings {
    // Used as an identifier for strings longer than 31 bytes.
    bytes32 private constant _FALLBACK_SENTINEL = 0x00000000000000000000000000000000000000000000000000000000000000FF;

    error StringTooLong(string str);
    error InvalidShortString();

    /**
     * @dev Encode a string of at most 31 chars into a `ShortString`.
     *
     * This will trigger a `StringTooLong` error is the input string is too long.
     */
    function toShortString(string memory str) internal pure returns (ShortString) {
        bytes memory bstr = bytes(str);
        if (bstr.length > 31) {
            revert StringTooLong(str);
        }
        return ShortString.wrap(bytes32(uint256(bytes32(bstr)) | bstr.length));
    }

    /**
     * @dev Decode a `ShortString` back to a "normal" string.
     */
    function toString(ShortString sstr) internal pure returns (string memory) {
        uint256 len = byteLength(sstr);
        // using `new string(len)` would work locally but is not memory safe.
        string memory str = new string(32);
        /// @solidity memory-safe-assembly
        assembly {
            mstore(str, len)
            mstore(add(str, 0x20), sstr)
        }
        return str;
    }

    /**
     * @dev Return the length of a `ShortString`.
     */
    function byteLength(ShortString sstr) internal pure returns (uint256) {
        uint256 result = uint256(ShortString.unwrap(sstr)) & 0xFF;
        if (result > 31) {
            revert InvalidShortString();
        }
        return result;
    }

    /**
     * @dev Encode a string into a `ShortString`, or write it to storage if it is too long.
     */
    function toShortStringWithFallback(string memory value, string storage store) internal returns (ShortString) {
        if (bytes(value).length < 32) {
            return toShortString(value);
        } else {
            StorageSlot.getStringSlot(store).value = value;
            return ShortString.wrap(_FALLBACK_SENTINEL);
        }
    }

    /**
     * @dev Decode a string that was encoded to `ShortString` or written to storage using {setWithFallback}.
     */
    function toStringWithFallback(ShortString value, string storage store) internal pure returns (string memory) {
        if (ShortString.unwrap(value) != _FALLBACK_SENTINEL) {
            return toString(value);
        } else {
            return store;
        }
    }

    /**
     * @dev Return the length of a string that was encoded to `ShortString` or written to storage using {setWithFallback}.
     *
     * WARNING: This will return the "byte length" of the string. This may not reflect the actual length in terms of
     * actual characters as the UTF-8 encoding of a single character can span over multiple bytes.
     */
    function byteLengthWithFallback(ShortString value, string storage store) internal view returns (uint256) {
        if (ShortString.unwrap(value) != _FALLBACK_SENTINEL) {
            return byteLength(value);
        } else {
            return bytes(store).length;
        }
    }
}

// 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/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: node_modules/@openzeppelin/contracts/utils/math/Math.sol



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

pragma solidity ^0.8.0;

/**
 * @dev Standard math utilities missing in the Solidity language.
 */
library Math {
    enum Rounding {
        Down, // Toward negative infinity
        Up, // Toward infinity
        Zero // Toward zero
    }

    /**
     * @dev Returns the largest of two numbers.
     */
    function max(uint256 a, uint256 b) internal pure returns (uint256) {
        return a > b ? a : b;
    }

    /**
     * @dev Returns the smallest of two numbers.
     */
    function min(uint256 a, uint256 b) internal pure returns (uint256) {
        return a < b ? a : b;
    }

    /**
     * @dev Returns the average of two numbers. The result is rounded towards
     * zero.
     */
    function average(uint256 a, uint256 b) internal pure returns (uint256) {
        // (a + b) / 2 can overflow.
        return (a & b) + (a ^ b) / 2;
    }

    /**
     * @dev Returns the ceiling of the division of two numbers.
     *
     * This differs from standard division with `/` in that it rounds up instead
     * of rounding down.
     */
    function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {
        // (a + b - 1) / b can overflow on addition, so we distribute.
        return a == 0 ? 0 : (a - 1) / b + 1;
    }

    /**
     * @notice Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or denominator == 0
     * @dev Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv)
     * with further edits by Uniswap Labs also under MIT license.
     */
    function mulDiv(uint256 x, uint256 y, uint256 denominator) internal pure returns (uint256 result) {
        unchecked {
            // 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2^256 and mod 2^256 - 1, then use
            // use the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256
            // variables such that product = prod1 * 2^256 + prod0.
            uint256 prod0; // Least significant 256 bits of the product
            uint256 prod1; // Most significant 256 bits of the product
            assembly {
                let mm := mulmod(x, y, not(0))
                prod0 := mul(x, y)
                prod1 := sub(sub(mm, prod0), lt(mm, prod0))
            }

            // Handle non-overflow cases, 256 by 256 division.
            if (prod1 == 0) {
                // Solidity will revert if denominator == 0, unlike the div opcode on its own.
                // The surrounding unchecked block does not change this fact.
                // See https://docs.soliditylang.org/en/latest/control-structures.html#checked-or-unchecked-arithmetic.
                return prod0 / denominator;
            }

            // Make sure the result is less than 2^256. Also prevents denominator == 0.
            require(denominator > prod1, "Math: mulDiv overflow");

            ///////////////////////////////////////////////
            // 512 by 256 division.
            ///////////////////////////////////////////////

            // Make division exact by subtracting the remainder from [prod1 prod0].
            uint256 remainder;
            assembly {
                // Compute remainder using mulmod.
                remainder := mulmod(x, y, denominator)

                // Subtract 256 bit number from 512 bit number.
                prod1 := sub(prod1, gt(remainder, prod0))
                prod0 := sub(prod0, remainder)
            }

            // Factor powers of two out of denominator and compute largest power of two divisor of denominator. Always >= 1.
            // See https://cs.stackexchange.com/q/138556/92363.

            // Does not overflow because the denominator cannot be zero at this stage in the function.
            uint256 twos = denominator & (~denominator + 1);
            assembly {
                // Divide denominator by twos.
                denominator := div(denominator, twos)

                // Divide [prod1 prod0] by twos.
                prod0 := div(prod0, twos)

                // Flip twos such that it is 2^256 / twos. If twos is zero, then it becomes one.
                twos := add(div(sub(0, twos), twos), 1)
            }

            // Shift in bits from prod1 into prod0.
            prod0 |= prod1 * twos;

            // Invert denominator mod 2^256. Now that denominator is an odd number, it has an inverse modulo 2^256 such
            // that denominator * inv = 1 mod 2^256. Compute the inverse by starting with a seed that is correct for
            // four bits. That is, denominator * inv = 1 mod 2^4.
            uint256 inverse = (3 * denominator) ^ 2;

            // Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also works
            // in modular arithmetic, doubling the correct bits in each step.
            inverse *= 2 - denominator * inverse; // inverse mod 2^8
            inverse *= 2 - denominator * inverse; // inverse mod 2^16
            inverse *= 2 - denominator * inverse; // inverse mod 2^32
            inverse *= 2 - denominator * inverse; // inverse mod 2^64
            inverse *= 2 - denominator * inverse; // inverse mod 2^128
            inverse *= 2 - denominator * inverse; // inverse mod 2^256

            // Because the division is now exact we can divide by multiplying with the modular inverse of denominator.
            // This will give us the correct result modulo 2^256. Since the preconditions guarantee that the outcome is
            // less than 2^256, this is the final result. We don't need to compute the high bits of the result and prod1
            // is no longer required.
            result = prod0 * inverse;
            return result;
        }
    }

    /**
     * @notice Calculates x * y / denominator with full precision, following the selected rounding direction.
     */
    function mulDiv(uint256 x, uint256 y, uint256 denominator, Rounding rounding) internal pure returns (uint256) {
        uint256 result = mulDiv(x, y, denominator);
        if (rounding == Rounding.Up && mulmod(x, y, denominator) > 0) {
            result += 1;
        }
        return result;
    }

    /**
     * @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded down.
     *
     * Inspired by Henry S. Warren, Jr.'s "Hacker's Delight" (Chapter 11).
     */
    function sqrt(uint256 a) internal pure returns (uint256) {
        if (a == 0) {
            return 0;
        }

        // For our first guess, we get the biggest power of 2 which is smaller than the square root of the target.
        //
        // We know that the "msb" (most significant bit) of our target number `a` is a power of 2 such that we have
        // `msb(a) <= a < 2*msb(a)`. This value can be written `msb(a)=2**k` with `k=log2(a)`.
        //
        // This can be rewritten `2**log2(a) <= a < 2**(log2(a) + 1)`
        // → `sqrt(2**k) <= sqrt(a) < sqrt(2**(k+1))`
        // → `2**(k/2) <= sqrt(a) < 2**((k+1)/2) <= 2**(k/2 + 1)`
        //
        // Consequently, `2**(log2(a) / 2)` is a good first approximation of `sqrt(a)` with at least 1 correct bit.
        uint256 result = 1 << (log2(a) >> 1);

        // At this point `result` is an estimation with one bit of precision. We know the true value is a uint128,
        // since it is the square root of a uint256. Newton's method converges quadratically (precision doubles at
        // every iteration). We thus need at most 7 iteration to turn our partial result with one bit of precision
        // into the expected uint128 result.
        unchecked {
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            return min(result, a / result);
        }
    }

    /**
     * @notice Calculates sqrt(a), following the selected rounding direction.
     */
    function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) {
        unchecked {
            uint256 result = sqrt(a);
            return result + (rounding == Rounding.Up && result * result < a ? 1 : 0);
        }
    }

    /**
     * @dev Return the log in base 2, rounded down, of a positive value.
     * Returns 0 if given 0.
     */
    function log2(uint256 value) internal pure returns (uint256) {
        uint256 result = 0;
        unchecked {
            if (value >> 128 > 0) {
                value >>= 128;
                result += 128;
            }
            if (value >> 64 > 0) {
                value >>= 64;
                result += 64;
            }
            if (value >> 32 > 0) {
                value >>= 32;
                result += 32;
            }
            if (value >> 16 > 0) {
                value >>= 16;
                result += 16;
            }
            if (value >> 8 > 0) {
                value >>= 8;
                result += 8;
            }
            if (value >> 4 > 0) {
                value >>= 4;
                result += 4;
            }
            if (value >> 2 > 0) {
                value >>= 2;
                result += 2;
            }
            if (value >> 1 > 0) {
                result += 1;
            }
        }
        return result;
    }

    /**
     * @dev Return the log in base 2, following the selected rounding direction, of a positive value.
     * Returns 0 if given 0.
     */
    function log2(uint256 value, Rounding rounding) internal pure returns (uint256) {
        unchecked {
            uint256 result = log2(value);
            return result + (rounding == Rounding.Up && 1 << result < value ? 1 : 0);
        }
    }

    /**
     * @dev Return the log in base 10, rounded down, of a positive value.
     * Returns 0 if given 0.
     */
    function log10(uint256 value) internal pure returns (uint256) {
        uint256 result = 0;
        unchecked {
            if (value >= 10 ** 64) {
                value /= 10 ** 64;
                result += 64;
            }
            if (value >= 10 ** 32) {
                value /= 10 ** 32;
                result += 32;
            }
            if (value >= 10 ** 16) {
                value /= 10 ** 16;
                result += 16;
            }
            if (value >= 10 ** 8) {
                value /= 10 ** 8;
                result += 8;
            }
            if (value >= 10 ** 4) {
                value /= 10 ** 4;
                result += 4;
            }
            if (value >= 10 ** 2) {
                value /= 10 ** 2;
                result += 2;
            }
            if (value >= 10 ** 1) {
                result += 1;
            }
        }
        return result;
    }

    /**
     * @dev Return the log in base 10, following the selected rounding direction, of a positive value.
     * Returns 0 if given 0.
     */
    function log10(uint256 value, Rounding rounding) internal pure returns (uint256) {
        unchecked {
            uint256 result = log10(value);
            return result + (rounding == Rounding.Up && 10 ** result < value ? 1 : 0);
        }
    }

    /**
     * @dev Return the log in base 256, rounded down, of a positive value.
     * Returns 0 if given 0.
     *
     * Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string.
     */
    function log256(uint256 value) internal pure returns (uint256) {
        uint256 result = 0;
        unchecked {
            if (value >> 128 > 0) {
                value >>= 128;
                result += 16;
            }
            if (value >> 64 > 0) {
                value >>= 64;
                result += 8;
            }
            if (value >> 32 > 0) {
                value >>= 32;
                result += 4;
            }
            if (value >> 16 > 0) {
                value >>= 16;
                result += 2;
            }
            if (value >> 8 > 0) {
                result += 1;
            }
        }
        return result;
    }

    /**
     * @dev Return the log in base 256, following the selected rounding direction, of a positive value.
     * Returns 0 if given 0.
     */
    function log256(uint256 value, Rounding rounding) internal pure returns (uint256) {
        unchecked {
            uint256 result = log256(value);
            return result + (rounding == Rounding.Up && 1 << (result << 3) < value ? 1 : 0);
        }
    }
}

// File: node_modules/@openzeppelin/contracts/utils/math/SignedMath.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.8.0) (utils/math/SignedMath.sol)

pragma solidity ^0.8.0;

/**
 * @dev Standard signed math utilities missing in the Solidity language.
 */
library SignedMath {
    /**
     * @dev Returns the largest of two signed numbers.
     */
    function max(int256 a, int256 b) internal pure returns (int256) {
        return a > b ? a : b;
    }

    /**
     * @dev Returns the smallest of two signed numbers.
     */
    function min(int256 a, int256 b) internal pure returns (int256) {
        return a < b ? a : b;
    }

    /**
     * @dev Returns the average of two signed numbers without overflow.
     * The result is rounded towards zero.
     */
    function average(int256 a, int256 b) internal pure returns (int256) {
        // Formula from the book "Hacker's Delight"
        int256 x = (a & b) + ((a ^ b) >> 1);
        return x + (int256(uint256(x) >> 255) & (a ^ b));
    }

    /**
     * @dev Returns the absolute unsigned value of a signed value.
     */
    function abs(int256 n) internal pure returns (uint256) {
        unchecked {
            // must be unchecked in order to support `n = type(int256).min`
            return uint256(n >= 0 ? n : -n);
        }
    }
}

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



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (utils/StorageSlot.sol)
// This file was procedurally generated from scripts/generate/templates/StorageSlot.js.

pragma solidity ^0.8.0;

/**
 * @dev Library for reading and writing primitive types to specific storage slots.
 *
 * Storage slots are often used to avoid storage conflict when dealing with upgradeable contracts.
 * This library helps with reading and writing to such slots without the need for inline assembly.
 *
 * The functions in this library return Slot structs that contain a `value` member that can be used to read or write.
 *
 * Example usage to set ERC1967 implementation slot:
 * ```solidity
 * contract ERC1967 {
 *     bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
 *
 *     function _getImplementation() internal view returns (address) {
 *         return StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value;
 *     }
 *
 *     function _setImplementation(address newImplementation) internal {
 *         require(Address.isContract(newImplementation), "ERC1967: new implementation is not a contract");
 *         StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation;
 *     }
 * }
 * ```
 *
 * _Available since v4.1 for `address`, `bool`, `bytes32`, `uint256`._
 * _Available since v4.9 for `string`, `bytes`._
 */
library StorageSlot {
    struct AddressSlot {
        address value;
    }

    struct BooleanSlot {
        bool value;
    }

    struct Bytes32Slot {
        bytes32 value;
    }

    struct Uint256Slot {
        uint256 value;
    }

    struct StringSlot {
        string value;
    }

    struct BytesSlot {
        bytes value;
    }

    /**
     * @dev Returns an `AddressSlot` with member `value` located at `slot`.
     */
    function getAddressSlot(bytes32 slot) internal pure returns (AddressSlot storage r) {
        /// @solidity memory-safe-assembly
        assembly {
            r.slot := slot
        }
    }

    /**
     * @dev Returns an `BooleanSlot` with member `value` located at `slot`.
     */
    function getBooleanSlot(bytes32 slot) internal pure returns (BooleanSlot storage r) {
        /// @solidity memory-safe-assembly
        assembly {
            r.slot := slot
        }
    }

    /**
     * @dev Returns an `Bytes32Slot` with member `value` located at `slot`.
     */
    function getBytes32Slot(bytes32 slot) internal pure returns (Bytes32Slot storage r) {
        /// @solidity memory-safe-assembly
        assembly {
            r.slot := slot
        }
    }

    /**
     * @dev Returns an `Uint256Slot` with member `value` located at `slot`.
     */
    function getUint256Slot(bytes32 slot) internal pure returns (Uint256Slot storage r) {
        /// @solidity memory-safe-assembly
        assembly {
            r.slot := slot
        }
    }

    /**
     * @dev Returns an `StringSlot` with member `value` located at `slot`.
     */
    function getStringSlot(bytes32 slot) internal pure returns (StringSlot storage r) {
        /// @solidity memory-safe-assembly
        assembly {
            r.slot := slot
        }
    }

    /**
     * @dev Returns an `StringSlot` representation of the string storage pointer `store`.
     */
    function getStringSlot(string storage store) internal pure returns (StringSlot storage r) {
        /// @solidity memory-safe-assembly
        assembly {
            r.slot := store.slot
        }
    }

    /**
     * @dev Returns an `BytesSlot` with member `value` located at `slot`.
     */
    function getBytesSlot(bytes32 slot) internal pure returns (BytesSlot storage r) {
        /// @solidity memory-safe-assembly
        assembly {
            r.slot := slot
        }
    }

    /**
     * @dev Returns an `BytesSlot` representation of the bytes storage pointer `store`.
     */
    function getBytesSlot(bytes storage store) internal pure returns (BytesSlot storage r) {
        /// @solidity memory-safe-assembly
        assembly {
            r.slot := store.slot
        }
    }
}

// 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-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);
}