diff --git a/contracts/IPAssetRegistry.sol b/contracts/IPAssetRegistry.sol index 5779665a..18db2faa 100644 --- a/contracts/IPAssetRegistry.sol +++ b/contracts/IPAssetRegistry.sol @@ -7,6 +7,7 @@ import { IRegistrationModule } from "contracts/interfaces/modules/registration/I import { IModuleRegistry } from "contracts/interfaces/modules/IModuleRegistry.sol"; import { IIPOrg } from "contracts/interfaces/ip-org/IIPOrg.sol"; import { ModuleRegistryKeys } from "contracts/lib/modules/ModuleRegistryKeys.sol"; +import { REGISTRATION_MODULE_KEY } from "contracts/lib/modules/Module.sol"; import { Errors } from "contracts/lib/Errors.sol"; /// @title Global IP Asset Registry @@ -41,7 +42,7 @@ contract IPAssetRegistry is IIPAssetRegistry { /// @notice Restricts calls to the registration module of the IP Asset. /// TODO(ramarti): Enable IPOrg-specific registration modules to be authorized. modifier onlyRegistrationModule() { - if (MODULE_REGISTRY.protocolModule(ModuleRegistryKeys.REGISTRATION_MODULE) != msg.sender) { + if (address(MODULE_REGISTRY.protocolModule(REGISTRATION_MODULE_KEY)) != msg.sender) { revert Errors.Unauthorized(); } _; @@ -70,10 +71,6 @@ contract IPAssetRegistry is IIPAssetRegistry { bytes32 hash_ ) public onlyRegistrationModule returns (uint256 ipAssetId) { - if (MODULE_REGISTRY.protocolModule(ModuleRegistryKeys.REGISTRATION_MODULE) != msg.sender) { - revert Errors.Unauthorized(); - } - // Crate a new IP asset with the provided IP attributes. ipAssetId = ++totalSupply; uint64 registrationDate = uint64(block.timestamp); @@ -129,7 +126,7 @@ contract IPAssetRegistry is IIPAssetRegistry { /// @notice Returns the current owner of an IP asset. /// @param ipAssetId_ The id of the IP asset being queried. function ipAssetOwner(uint256 ipAssetId_) public view returns (address) { - address registrationModule = MODULE_REGISTRY.protocolModule(ModuleRegistryKeys.REGISTRATION_MODULE); + address registrationModule = address(MODULE_REGISTRY.protocolModule(REGISTRATION_MODULE_KEY)); return IRegistrationModule(registrationModule).ownerOf(ipAssetId_); } diff --git a/contracts/StoryProtocol.sol b/contracts/StoryProtocol.sol index 8a3dd2d5..5eced094 100644 --- a/contracts/StoryProtocol.sol +++ b/contracts/StoryProtocol.sol @@ -4,6 +4,7 @@ pragma solidity ^0.8.19; import { IIPOrgController } from "contracts/interfaces/ip-org/IIPOrgController.sol"; import { IIPOrg } from "contracts/interfaces/ip-org/IIPOrg.sol"; +import { Gateway } from "contracts/modules/Gateway.sol"; import { IPOrgParams } from "contracts/lib/IPOrgParams.sol"; import { Errors } from "contracts/lib/Errors.sol"; import { IPOrgParams } from "contracts/lib/IPOrgParams.sol"; @@ -14,6 +15,11 @@ import { ModuleRegistryKeys } from "contracts/lib/modules/ModuleRegistryKeys.sol import { Licensing } from "contracts/lib/modules/Licensing.sol"; import { FixedSet } from "contracts/utils/FixedSet.sol"; import { Multicall } from "@openzeppelin/contracts/utils/Multicall.sol"; +import { ILicensingModule } from "contracts/interfaces/modules/licensing/ILicensingModule.sol"; +import { IRegistrationModule } from "contracts/interfaces/modules/registration/IRegistrationModule.sol"; +import { IRelationshipModule } from "contracts/interfaces/modules/relationships/IRelationshipModule.sol"; +import { RELATIONSHIP_MODULE, LICENSING_MODULE, REGISTRATION_MODULE } from "contracts/lib/modules/Module.sol"; +import { ModuleKey, REGISTRATION_MODULE_KEY, LICENSING_MODULE_KEY, RELATIONSHIP_MODULE_KEY, ModuleDependencies } from "contracts/lib/modules/Module.sol"; /// @title Story Protocol Gateway Contract /// @notice The Story Protocol contract acts as a global gateway for calling all @@ -24,6 +30,11 @@ import { Multicall } from "@openzeppelin/contracts/utils/Multicall.sol"; /// their own frontend contracts (gateways) for IP interaction. contract StoryProtocol is Multicall { + // Modules which the Story Protocol gateway depends on. + IRegistrationModule public registrationModule; + ILicensingModule public licensingModule; + IRelationshipModule public relationshipModule; + IIPOrgController public immutable IP_ORG_CONTROLLER; ModuleRegistry public immutable MODULE_REGISTRY; @@ -58,7 +69,7 @@ contract StoryProtocol is Multicall { MODULE_REGISTRY.configure( IIPOrg(ipOrg_), msg.sender, - ModuleRegistryKeys.REGISTRATION_MODULE, + REGISTRATION_MODULE, encodedParams ); } @@ -77,7 +88,7 @@ contract StoryProtocol is Multicall { MODULE_REGISTRY.configure( IIPOrg(ipOrg_), msg.sender, - ModuleRegistryKeys.REGISTRATION_MODULE, + REGISTRATION_MODULE, encodedParams ); } @@ -118,7 +129,7 @@ contract StoryProtocol is Multicall { bytes memory result = MODULE_REGISTRY.execute( IIPOrg(ipOrg_), msg.sender, - ModuleRegistryKeys.REGISTRATION_MODULE, + REGISTRATION_MODULE, encodedParams, preHooksData_, postHooksData_ @@ -150,7 +161,7 @@ contract StoryProtocol is Multicall { MODULE_REGISTRY.execute( IIPOrg(ipOrg_), msg.sender, - ModuleRegistryKeys.REGISTRATION_MODULE, + REGISTRATION_MODULE, encodedParams, preHooksData_, postHooksData_ @@ -168,7 +179,7 @@ contract StoryProtocol is Multicall { MODULE_REGISTRY.configure( IIPOrg(params_.ipOrg), msg.sender, - ModuleRegistryKeys.RELATIONSHIP_MODULE, + RELATIONSHIP_MODULE, abi.encode(LibRelationship.ADD_REL_TYPE_CONFIG, abi.encode(params_)) ); } @@ -180,7 +191,7 @@ contract StoryProtocol is Multicall { MODULE_REGISTRY.configure( IIPOrg(ipOrg_), msg.sender, - ModuleRegistryKeys.RELATIONSHIP_MODULE, + RELATIONSHIP_MODULE, abi.encode( LibRelationship.REMOVE_REL_TYPE_CONFIG, abi.encode(relType) @@ -197,7 +208,7 @@ contract StoryProtocol is Multicall { bytes memory result = MODULE_REGISTRY.execute( IIPOrg(ipOrg_), msg.sender, - ModuleRegistryKeys.RELATIONSHIP_MODULE, + RELATIONSHIP_MODULE, abi.encode(params_), preHooksData_, postHooksData_ @@ -219,7 +230,7 @@ contract StoryProtocol is Multicall { MODULE_REGISTRY.configure( IIPOrg(ipOrg_), msg.sender, - ModuleRegistryKeys.LICENSING_MODULE, + LICENSING_MODULE, abi.encode(Licensing.LICENSING_FRAMEWORK_CONFIG, abi.encode(config_)) ); } @@ -240,7 +251,7 @@ contract StoryProtocol is Multicall { bytes memory result = MODULE_REGISTRY.execute( IIPOrg(ipOrg_), msg.sender, - ModuleRegistryKeys.LICENSING_MODULE, + LICENSING_MODULE, abi.encode( Licensing.CREATE_LICENSE, params @@ -261,7 +272,7 @@ contract StoryProtocol is Multicall { MODULE_REGISTRY.execute( IIPOrg(ipOrg_), msg.sender, - ModuleRegistryKeys.LICENSING_MODULE, + LICENSING_MODULE, abi.encode( Licensing.ACTIVATE_LICENSE, abi.encode(licenseId_) @@ -283,7 +294,7 @@ contract StoryProtocol is Multicall { MODULE_REGISTRY.execute( IIPOrg(ipOrg_), msg.sender, - ModuleRegistryKeys.LICENSING_MODULE, + LICENSING_MODULE, abi.encode( Licensing.LINK_LNFT_TO_IPA, abi.encode(licenseId_, ipaId_) diff --git a/contracts/access-control/AccessControlSingleton.sol b/contracts/access-control/AccessControlSingleton.sol index 2f9bc70a..9b745075 100644 --- a/contracts/access-control/AccessControlSingleton.sol +++ b/contracts/access-control/AccessControlSingleton.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: UNLICENSED // See Story Protocol Alpha Agreement: https://github.com/storyprotocol/protocol-contracts/blob/main/StoryProtocol-AlphaTestingAgreement-17942166.3.pdf -pragma solidity ^0.8.13; +pragma solidity ^0.8.19; import { Errors } from "contracts/lib/Errors.sol"; import { AccessControl } from "contracts/lib/AccessControl.sol"; diff --git a/contracts/interfaces/modules/IGateway.sol b/contracts/interfaces/modules/IGateway.sol new file mode 100644 index 00000000..317518b3 --- /dev/null +++ b/contracts/interfaces/modules/IGateway.sol @@ -0,0 +1,21 @@ +// 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); + +} diff --git a/contracts/interfaces/modules/IModuleRegistry.sol b/contracts/interfaces/modules/IModuleRegistry.sol index 66590d30..e7421594 100644 --- a/contracts/interfaces/modules/IModuleRegistry.sol +++ b/contracts/interfaces/modules/IModuleRegistry.sol @@ -1,21 +1,43 @@ // 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"; +import { IIPOrg } from "contracts/interfaces/ip-org/IIPOrg.sol"; + /// @title IModuleRegistry /// @notice Module Registry Interface interface IModuleRegistry { - /// @notice Emits when a new module is added for a specific IP Org. + /// @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 a module is removed for an IP Org. + /// @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( - address indexed ipOrg, - string moduleKey, + ModuleKey indexed key, address indexed module ); @@ -51,6 +73,25 @@ interface IModuleRegistry { address indexed hook ); - /// @notice Fetches the latest protocol module bound to a specific key. - function protocolModule(string calldata moduleKey) external view returns (address); + /// @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); + } diff --git a/contracts/interfaces/modules/base/IModule.sol b/contracts/interfaces/modules/base/IModule.sol index 98c4fec4..9b76d9f5 100644 --- a/contracts/interfaces/modules/base/IModule.sol +++ b/contracts/interfaces/modules/base/IModule.sol @@ -1,8 +1,8 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.13; -import { IModule } from "./IModule.sol"; import { IIPOrg } from "contracts/interfaces/ip-org/IIPOrg.sol"; +import { ModuleKey } from "contracts/lib/modules/Module.sol"; /// @title IModule /// @notice Interface for a Story Protocol Module, building block of the protocol functionality. @@ -14,6 +14,10 @@ interface IModule { /// 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 It will verify params, execute pre action hooks, perform the action, /// execute post action hooks and emit the RequestCompleted event, plus returning the result. diff --git a/contracts/interfaces/modules/licensing/ILicensingModule.sol b/contracts/interfaces/modules/licensing/ILicensingModule.sol new file mode 100644 index 00000000..764fe49d --- /dev/null +++ b/contracts/interfaces/modules/licensing/ILicensingModule.sol @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import { IModule } from "contracts/interfaces/modules/base/IModule.sol"; + +/// @title Licensing Module Interface +/// @notice Interface for the licensing module +/// TODO(ramarti): Fill this in. +interface ILicensingModule is IModule {} diff --git a/contracts/interfaces/modules/registration/IRegistrationModule.sol b/contracts/interfaces/modules/registration/IRegistrationModule.sol index e859644f..1d8380e5 100644 --- a/contracts/interfaces/modules/registration/IRegistrationModule.sol +++ b/contracts/interfaces/modules/registration/IRegistrationModule.sol @@ -2,9 +2,11 @@ pragma solidity ^0.8.19; import { Registration } from "contracts/lib/modules/Registration.sol"; +import { IIPOrg } from "contracts/interfaces/ip-org/IIPOrg.sol"; +import { IModule } from "contracts/interfaces/modules/base/IModule.sol"; /// @title IRegistrationModule -interface IRegistrationModule { +interface IRegistrationModule is IModule { /// @notice Emits when an IPOrg updates metadata associated with its IPA. /// @param ipOrg The address of the IP Org whose metadata was updated. @@ -76,4 +78,5 @@ interface IRegistrationModule { /// @notice Returns true if the index for an IP Org asset type is supported. function isValidIpOrgAssetType(address ipOrg_, uint8 index) external view returns (bool); + } diff --git a/contracts/interfaces/modules/relationships/IRelationshipModule.sol b/contracts/interfaces/modules/relationships/IRelationshipModule.sol index 8f72e135..cc22d4c6 100644 --- a/contracts/interfaces/modules/relationships/IRelationshipModule.sol +++ b/contracts/interfaces/modules/relationships/IRelationshipModule.sol @@ -2,10 +2,12 @@ pragma solidity ^0.8.13; import { LibRelationship } from "contracts/lib/modules/LibRelationship.sol"; +import { IModule } from "contracts/interfaces/modules/base/IModule.sol"; /// @title IRelationshipModule /// @notice Interface for the RelationshipModule. -interface IRelationshipModule { +interface IRelationshipModule is IModule { + /// Emitted with a new Relationship Type definitions is created event RelationshipTypeSet( // Short string naming the type diff --git a/contracts/ip-org/IPOrg.sol b/contracts/ip-org/IPOrg.sol index 19ca8d12..705ec3a6 100644 --- a/contracts/ip-org/IPOrg.sol +++ b/contracts/ip-org/IPOrg.sol @@ -11,7 +11,7 @@ import { IPOrgParams } from "contracts/lib/IPOrgParams.sol"; import { ERC721Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol"; import { IERC165Upgradeable } from "@openzeppelin/contracts-upgradeable/utils/introspection/IERC165Upgradeable.sol"; import { IPAssetRegistry } from "contracts/IPAssetRegistry.sol"; -import { ModuleRegistryKeys } from "contracts/lib/modules/ModuleRegistryKeys.sol"; +import { REGISTRATION_MODULE_KEY } from "contracts/lib/modules/Module.sol"; import { Errors } from "contracts/lib/Errors.sol"; /// @title IP Organization Contract @@ -42,7 +42,7 @@ contract IPOrg is /// @notice Restricts calls to being through the registration module. modifier onlyRegistrationModule() { - if (IModuleRegistry(MODULE_REGISTRY).protocolModule(ModuleRegistryKeys.REGISTRATION_MODULE) != msg.sender) { + if (address(MODULE_REGISTRY.protocolModule(REGISTRATION_MODULE_KEY)) != msg.sender) { revert Errors.Unauthorized(); } _; @@ -74,13 +74,13 @@ contract IPOrg is function tokenURI( uint256 tokenId_ ) public view override returns (string memory) { - address registrationModule = IModuleRegistry(MODULE_REGISTRY).protocolModule(ModuleRegistryKeys.REGISTRATION_MODULE); + address registrationModule = address(IModuleRegistry(MODULE_REGISTRY).protocolModule(REGISTRATION_MODULE_KEY)); return IRegistrationModule(registrationModule).tokenURI(address(this), tokenId_, ipOrgAssetType(tokenId_)); } /// @notice Retrieves the contract URI for the IP Org collection. function contractURI() public view override returns (string memory) { - address registrationModule = IModuleRegistry(MODULE_REGISTRY).protocolModule(ModuleRegistryKeys.REGISTRATION_MODULE); + address registrationModule = address(IModuleRegistry(MODULE_REGISTRY).protocolModule(REGISTRATION_MODULE_KEY)); return IRegistrationModule(registrationModule).contractURI(address(this)); } @@ -88,7 +88,7 @@ contract IPOrg is /// @param id The local id of the IP Org wrapped IP asset. /// @return The global identifier of the IP asset. function ipAssetId(uint256 id) public returns (uint256) { - address registrationModule = MODULE_REGISTRY.protocolModule(ModuleRegistryKeys.REGISTRATION_MODULE); + address registrationModule = address(MODULE_REGISTRY.protocolModule(REGISTRATION_MODULE_KEY)); return IRegistrationModule(registrationModule).ipAssetId(address(this), id); } diff --git a/contracts/ip-org/IPOrgController.sol b/contracts/ip-org/IPOrgController.sol index 6cbe1315..31744ec7 100644 --- a/contracts/ip-org/IPOrgController.sol +++ b/contracts/ip-org/IPOrgController.sol @@ -16,6 +16,7 @@ import { IIPOrgController } from "contracts/interfaces/ip-org/IIPOrgController.s import { IIPOrg } from "contracts/interfaces/ip-org/IIPOrg.sol"; import { IPOrg } from "contracts/ip-org/IPOrg.sol"; import { AccessControl } from "contracts/lib/AccessControl.sol"; +import { REGISTRATION_MODULE } from "contracts/lib/modules/Module.sol"; /// @title IP Org Controller Contract /// @notice The IP Org Controller is the protocol-wide factory contract for creating @@ -184,7 +185,7 @@ contract IPOrgController is ModuleRegistry(MODULE_REGISTRY).configure( IIPOrg(ipOrg_), address(this), - ModuleRegistryKeys.REGISTRATION_MODULE, + REGISTRATION_MODULE, encodedParams ); diff --git a/contracts/lib/Errors.sol b/contracts/lib/Errors.sol index 27ae353b..ed83c832 100644 --- a/contracts/lib/Errors.sol +++ b/contracts/lib/Errors.sol @@ -41,6 +41,9 @@ library Errors { error BaseModule_ZeroLicenseRegistry(); error BaseModule_OnlyModuleRegistry(); + /// @notice The caller is not authorized to perform this operation. + error BaseModule_Unauthorized(); + //////////////////////////////////////////////////////////////////////////// // HookRegistry // //////////////////////////////////////////////////////////////////////////// @@ -72,9 +75,32 @@ library Errors { // ModuleRegistry // //////////////////////////////////////////////////////////////////////////// - error ModuleRegistry_ModuleNotRegistered(string moduleName); + /// @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 // diff --git a/contracts/lib/modules/Module.sol b/contracts/lib/modules/Module.sol new file mode 100644 index 00000000..6c6fe5ef --- /dev/null +++ b/contracts/lib/modules/Module.sol @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: BUSL-1.1 +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))); diff --git a/contracts/modules/Gateway.sol b/contracts/modules/Gateway.sol new file mode 100644 index 00000000..c2e51597 --- /dev/null +++ b/contracts/modules/Gateway.sol @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.19; + +import { Errors } from "contracts/lib/Errors.sol"; +import { IGateway } from "contracts/interfaces/modules/IGateway.sol"; +import { ModuleDependencies } from "contracts/lib/modules/Module.sol"; +import { ModuleRegistry } from "contracts/modules/ModuleRegistry.sol"; + +/// @title Module Gateway +/// @notice This contract serves as the base layer all module "frontends" must +/// extend. Protocol admins enroll gateways through the module registry, +/// which give them access to call all module functions listed as part +/// of their dependency set. +abstract contract Gateway is IGateway { + + bool public registered; + + ModuleRegistry public immutable MODULE_REGISTRY; + + /// @notice Modifier that restricts the caller to only the module registry. + modifier onlyModuleRegistry() { + if (msg.sender != address(MODULE_REGISTRY)) { + revert Errors.BaseModule_OnlyModuleRegistry(); + } + _; + } + + constructor(ModuleRegistry moduleRegistry_) { + MODULE_REGISTRY = moduleRegistry_; + } + + /// @notice Synchronizes all downstream dependencies via the module registry. + function updateDependencies() external virtual override returns (ModuleDependencies memory dependencies); + + /// @notice Fetches all module dependencies required by the gateway contract. + function getDependencies() external view virtual override returns (ModuleDependencies memory dependencies); + +} diff --git a/contracts/modules/ModuleRegistry.sol b/contracts/modules/ModuleRegistry.sol index 4bde2285..a2b18253 100644 --- a/contracts/modules/ModuleRegistry.sol +++ b/contracts/modules/ModuleRegistry.sol @@ -5,11 +5,15 @@ 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 { BaseModule } from "./base/BaseModule.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"; +import { Gateway } from "./Gateway.sol"; /// @title ModuleRegistry /// @notice This contract is the source of truth for all modules that are registered in the protocol. @@ -19,46 +23,124 @@ contract ModuleRegistry is IModuleRegistry, AccessControlled, Multicall { address public constant PROTOCOL_LEVEL = address(0); - mapping(string => BaseModule) internal _protocolModules; mapping(string => IHook) internal _protocolHooks; 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 moduleKey_ The unique module key used to identify the module. - function protocolModule(string calldata moduleKey_) public view returns (address) { - return address(_protocolModules[moduleKey_]); + /// @param key_ The unique module key used to identify the module. + function protocolModule(string calldata key_) public view returns (address) { + return _modules[toModuleKey(key_)]; } - /// Add a module to the protocol, that will be available for all IPOrgs. - /// This is only callable by MODULE_REGISTRAR_ROLE holders. - /// @param moduleKey short module descriptor - /// @param moduleAddress address of the module - function registerProtocolModule( - string calldata moduleKey, - BaseModule moduleAddress + /// @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) { - // TODO: inteface check in the module - if (address(moduleAddress) == address(0)) { - revert Errors.ZeroAddress(); + 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); + } + } - _protocolModules[moduleKey] = moduleAddress; - emit ModuleAdded(PROTOCOL_LEVEL, moduleKey, address(moduleAddress)); } - /// Remove a module from the protocol (all IPOrgs) - /// This is only callable by MODULE_REGISTRAR_ROLE holders. - /// @param moduleKey short module descriptor - function removeProtocolModule( - string calldata moduleKey + /// @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(_protocolModules[moduleKey]) == address(0)) { - revert Errors.ModuleRegistry_ModuleNotRegistered(moduleKey); + 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(); } - address moduleAddress = address(_protocolModules[moduleKey]); - delete _protocolModules[moduleKey]; - emit ModuleRemoved(PROTOCOL_LEVEL, moduleKey, moduleAddress); + + _modules[key_] = address(module_); + + emit ModuleAdded(PROTOCOL_LEVEL, string(abi.encodePacked(key_)), address(module_)); } /// @notice Registers a new protocol hook. @@ -93,14 +175,20 @@ contract ModuleRegistry is IModuleRegistry, AccessControlled, Multicall { emit HookRemoved(PROTOCOL_LEVEL, hookKey, address(hookAddress)); } - /// Get a module from the protocol, by its key. - function moduleForKey(string calldata moduleKey) external view returns (BaseModule) { - return _protocolModules[moduleKey]; - } + /// 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_]; - // Returns true if the provided address is a module. - function isModule(string calldata moduleKey, address caller_) external view returns (bool) { - return address(_protocolModules[moduleKey]) == caller_; + emit ModuleRemoved(key_, removedModule); } /// @notice Returns the protocol hook associated with a given hook key. @@ -126,7 +214,7 @@ contract ModuleRegistry is IModuleRegistry, AccessControlled, Multicall { /// @return encoded result of the module execution function execute( IIPOrg ipOrg_, - string memory moduleKey_, + string calldata moduleKey_, bytes memory moduleParams_, bytes[] memory preHookParams_, bytes[] memory postHookParams_ @@ -182,14 +270,14 @@ contract ModuleRegistry is IModuleRegistry, AccessControlled, Multicall { function _execute( IIPOrg ipOrg_, address caller_, - string memory moduleKey_, + string calldata moduleKey_, bytes memory moduleParams_, bytes[] memory preHookParams_, bytes[] memory postHookParams_ ) private returns (bytes memory result) { - BaseModule module = _protocolModules[moduleKey_]; + IModule module = IModule(_modules[toModuleKey(moduleKey_)]); if (address(module) == address(0)) { - revert Errors.ModuleRegistry_ModuleNotRegistered(moduleKey_); + revert Errors.ModuleRegistry_ModuleNotYetRegistered(); } result = module.execute(ipOrg_, caller_, moduleParams_, preHookParams_, postHookParams_); emit ModuleExecuted(address(ipOrg_), moduleKey_, caller_, moduleParams_, preHookParams_, postHookParams_); @@ -202,9 +290,9 @@ contract ModuleRegistry is IModuleRegistry, AccessControlled, Multicall { string calldata moduleKey_, bytes calldata params_ ) private returns (bytes memory result) { - BaseModule module = _protocolModules[moduleKey_]; + IModule module = IModule(_modules[toModuleKey(moduleKey_)]); if (address(module) == address(0)) { - revert Errors.ModuleRegistry_ModuleNotRegistered(moduleKey_); + revert Errors.ModuleRegistry_ModuleNotYetRegistered(); } result = module.configure(ipOrg_, caller_, params_); emit ModuleConfigured(address(ipOrg_), moduleKey_, caller_, params_); diff --git a/contracts/modules/base/BaseModule.sol b/contracts/modules/base/BaseModule.sol index 44789cec..b820eede 100644 --- a/contracts/modules/base/BaseModule.sol +++ b/contracts/modules/base/BaseModule.sol @@ -9,10 +9,12 @@ import { Errors } from "contracts/lib/Errors.sol"; import { IIPOrg } from "contracts/interfaces/ip-org/IIPOrg.sol"; import { IPOrgController } from "contracts/ip-org/IPOrgController.sol"; import { ModuleRegistry } from "contracts/modules/ModuleRegistry.sol"; +import { Gateway } from "contracts/modules/Gateway.sol"; import { IPAssetRegistry } from "contracts/IPAssetRegistry.sol"; import { LicenseRegistry } from "contracts/modules/licensing/LicenseRegistry.sol"; import { ICallbackHandler } from "contracts/interfaces/hooks/base/ICallbackHandler.sol"; import { IERC165, ERC165 } from "@openzeppelin/contracts/utils/introspection/ERC165.sol"; +import { ModuleKey } from "contracts/lib/modules/Module.sol"; /// @title BaseModule /// @notice Base implementation for all modules in Story Protocol. This is meant to ensure @@ -54,6 +56,12 @@ abstract contract BaseModule is /// @dev The execution of the module is pending, and will need to be executed again. mapping(bytes32 => ModuleExecutionContext) private _asyncContexts; + /// @notice Modifier for authorizing the calling entity. + modifier onlyAuthorized() { + _authenticate(); + _; + } + modifier onlyModuleRegistry() { if (msg.sender != address(MODULE_REGISTRY)) { revert Errors.BaseModule_OnlyModuleRegistry(); @@ -75,6 +83,10 @@ abstract contract BaseModule is IP_ORG_CONTROLLER = params_.ipOrgController; } + /// @notice Gets the protocol-wide key associated with the module. + /// @return The string identifier of the module. + function moduleKey() public pure virtual override returns (ModuleKey); + /// Main execution entrypoint. It will verify params, execute pre action hooks, perform the action, /// execute post action hooks and emit the RequestCompleted event, plus returning the result. /// It's up to the module to decode and encode params appropriately. @@ -211,6 +223,13 @@ abstract contract BaseModule is bytes memory params_ ) internal virtual returns (bytes memory result) {} + /// @notice Authenticates the caller entity through the module registry. + function _authenticate() internal view { + if (!MODULE_REGISTRY.isAuthorized(moduleKey(), Gateway(msg.sender), msg.sig)) { + revert Errors.BaseModule_Unauthorized(); + } + } + /// @dev Generates a registry key based on module execution parameters. /// This function should be overridden in derived contracts to provide the actual logic for generating the registry key. /// @param ipOrg_ The address of the IPOrg. diff --git a/contracts/modules/licensing/LicenseRegistry.sol b/contracts/modules/licensing/LicenseRegistry.sol index 7c6b88ae..90636076 100644 --- a/contracts/modules/licensing/LicenseRegistry.sol +++ b/contracts/modules/licensing/LicenseRegistry.sol @@ -6,12 +6,12 @@ import { Licensing } from "contracts/lib/modules/Licensing.sol"; import { IPAssetRegistry } from "contracts/IPAssetRegistry.sol"; import { Errors } from "contracts/lib/Errors.sol"; import { ModuleRegistry } from "contracts/modules/ModuleRegistry.sol"; -import { ModuleRegistryKeys } from "contracts/lib/modules/ModuleRegistryKeys.sol"; import { LicensingFrameworkRepo } from "contracts/modules/licensing/LicensingFrameworkRepo.sol"; import { ERC721 } from "@openzeppelin/contracts/token/ERC721/ERC721.sol"; import { ShortString, ShortStrings } from "@openzeppelin/contracts/utils/ShortStrings.sol"; import { Strings } from "@openzeppelin/contracts/utils/Strings.sol"; import { Base64 } from "@openzeppelin/contracts/utils/Base64.sol"; +import { LICENSING_MODULE_KEY } from "contracts/lib/modules/Module.sol"; /// @title LicenseRegistry /// @notice This contract is the source of truth for all licenses that are registered in the protocol. @@ -43,24 +43,16 @@ contract LicenseRegistry is ERC721 { LicensingFrameworkRepo public immutable LICENSING_FRAMEWORK_REPO; modifier onlyLicensingModule() { - if ( - !MODULE_REGISTRY.isModule( - ModuleRegistryKeys.LICENSING_MODULE, - msg.sender - ) - ) { + address licensingModule = address(MODULE_REGISTRY.protocolModule(LICENSING_MODULE_KEY)); + if (licensingModule != msg.sender) { revert Errors.LicenseRegistry_CallerNotLicensingModule(); } _; } modifier onlyLicensingModuleOrLicensee(uint256 licenseId_) { - if ( - !MODULE_REGISTRY.isModule( - ModuleRegistryKeys.LICENSING_MODULE, - msg.sender - ) && msg.sender != ownerOf(licenseId_) - ) { + address licensingModule = address(MODULE_REGISTRY.protocolModule(LICENSING_MODULE_KEY)); + if (licensingModule != msg.sender && msg.sender != ownerOf(licenseId_)) { revert Errors.LicenseRegistry_CallerNotLicensingModuleOrLicensee(); } _; diff --git a/contracts/modules/licensing/LicensingModule.sol b/contracts/modules/licensing/LicensingModule.sol index 820d679b..c254602e 100644 --- a/contracts/modules/licensing/LicensingModule.sol +++ b/contracts/modules/licensing/LicensingModule.sol @@ -6,14 +6,17 @@ import { Licensing } from "contracts/lib/modules/Licensing.sol"; import { Errors } from "contracts/lib/Errors.sol"; import { ModuleRegistryKeys } from "contracts/lib/modules/ModuleRegistryKeys.sol"; import { BaseModule } from "contracts/modules/base/BaseModule.sol"; +import { IModule } from "contracts/interfaces/modules/base/IModule.sol"; import { IIPOrg } from "contracts/interfaces/ip-org/IIPOrg.sol"; import { LicensingFrameworkRepo } from "./LicensingFrameworkRepo.sol"; import { ShortString, ShortStrings } from "@openzeppelin/contracts/utils/ShortStrings.sol"; import { FixedSet } from "contracts/utils/FixedSet.sol"; import { IPAsset } from "contracts/lib/IPAsset.sol"; import { PIPLicensingTerms } from "contracts/lib/modules/PIPLicensingTerms.sol"; +import { ILicensingModule } from "contracts/interfaces/modules/licensing/ILicensingModule.sol"; import { ShortStringOps } from "contracts/utils/ShortStringOps.sol"; import { BitMask } from "contracts/lib/BitMask.sol"; +import { ModuleKey, LICENSING_MODULE_KEY } from "contracts/lib/modules/Module.sol"; /// @title Licensing module /// @notice Story Protocol module that: @@ -22,7 +25,7 @@ import { BitMask } from "contracts/lib/BitMask.sol"; /// - Enables license holders to create derivative licenses and sublicenses /// @dev The alpha version of this module is hardcoded to use the SPIPL-1 framework /// Thanks to the authors of ERC-5218 for the inspiration (see https://eips.ethereum.org/EIPS/eip-5218) -contract LicensingModule is BaseModule { +contract LicensingModule is BaseModule, ILicensingModule { using ShortStrings for *; using FixedSet for FixedSet.ShortStringSet; @@ -64,6 +67,11 @@ contract LicensingModule is BaseModule { DEFAULT_REVOKER = defaultRevoker_; } + /// @notice Gets the protocol-wide module key for the licensing module. + function moduleKey() public pure override(BaseModule, IModule) returns (ModuleKey) { + return LICENSING_MODULE_KEY; + } + function getIpOrgLicensorConfig( address ipOrg_ ) external view returns (Licensing.LicensorConfig) { diff --git a/contracts/modules/registration/RegistrationModule.sol b/contracts/modules/registration/RegistrationModule.sol index 26dbcb3c..93f25821 100644 --- a/contracts/modules/registration/RegistrationModule.sol +++ b/contracts/modules/registration/RegistrationModule.sol @@ -9,12 +9,13 @@ import { IPAssetRegistry } from "contracts/IPAssetRegistry.sol"; import { IRegistrationModule } from "contracts/interfaces/modules/registration/IRegistrationModule.sol"; import { IIPOrg } from "contracts/interfaces/ip-org/IIPOrg.sol"; import { AccessControlled } from "contracts/access-control/AccessControlled.sol"; +import { IModule } from "contracts/interfaces/modules/base/IModule.sol"; import { AccessControl } from "contracts/lib/AccessControl.sol"; import { LibRelationship } from "contracts/lib/modules/LibRelationship.sol"; import { Registration } from "contracts/lib/modules/Registration.sol"; import { Errors } from "contracts/lib/Errors.sol"; import { IPAsset } from "contracts/lib/IPAsset.sol"; - +import { ModuleKey, REGISTRATION_MODULE_KEY } from "contracts/lib/modules/Module.sol"; /// @title Registration Module /// @notice The registration module is responsible for registration, transferring, and @@ -52,6 +53,10 @@ contract RegistrationModule is BaseModule, IRegistrationModule, AccessControlled address accessControl_ ) BaseModule(params_) AccessControlled(accessControl_) {} + /// @notice Gets the protocol-wide module key for the registration module. + function moduleKey() public pure override(BaseModule, IModule) returns (ModuleKey) { + return REGISTRATION_MODULE_KEY; + } /// @notice Registers hooks for a specific type and IP Org. /// @dev This function can only be called by the IP Org owner. diff --git a/contracts/modules/relationships/RelationshipModule.sol b/contracts/modules/relationships/RelationshipModule.sol index fcb05fd6..018d814a 100644 --- a/contracts/modules/relationships/RelationshipModule.sol +++ b/contracts/modules/relationships/RelationshipModule.sol @@ -13,6 +13,8 @@ import { BitMask } from "contracts/lib/BitMask.sol"; import { Errors } from "contracts/lib/Errors.sol"; import { IRegistrationModule } from "contracts/interfaces/modules/registration/IRegistrationModule.sol"; import { ModuleRegistryKeys } from "contracts/lib/modules/ModuleRegistryKeys.sol"; +import { IModule } from "contracts/interfaces/modules/base/IModule.sol"; +import { ModuleKey, REGISTRATION_MODULE_KEY, RELATIONSHIP_MODULE_KEY } from "contracts/lib/modules/Module.sol"; /// @title Relationship Module /// @notice Contract that handles the creation and management of relationships between entities. @@ -43,6 +45,11 @@ contract RelationshipModule is BaseModule, IRelationshipModule, AccessControlled ) BaseModule(params_) AccessControlled(accessControl_) {} + /// @notice Gets the protocol-wide module key for the relationship module. + function moduleKey() public pure override(BaseModule, IModule) returns (ModuleKey) { + return RELATIONSHIP_MODULE_KEY; + } + /// @notice Registers hooks for a specific hook type, based on IP Org and relationship type. /// @dev This function can only be called by the IP Org owner. /// @param hType_ The type of the hooks to register. @@ -166,7 +173,7 @@ contract RelationshipModule is BaseModule, IRelationshipModule, AccessControlled uint8[] memory allowedTypes_ ) private view { IRegistrationModule regModule = IRegistrationModule( - MODULE_REGISTRY.protocolModule(ModuleRegistryKeys.REGISTRATION_MODULE) + address(MODULE_REGISTRY.protocolModule(REGISTRATION_MODULE_KEY)) ); uint256 length = allowedTypes_.length; for (uint256 i = 0; i < length; i++) { diff --git a/script/foundry/deployment/Main.s.sol b/script/foundry/deployment/Main.s.sol index d71f8c6f..ca3e8ed2 100644 --- a/script/foundry/deployment/Main.s.sol +++ b/script/foundry/deployment/Main.s.sol @@ -33,6 +33,7 @@ import { PolygonToken } from "contracts/lib/hooks/PolygonToken.sol"; import { Create2 } from "@openzeppelin/contracts/utils/Create2.sol"; import { Hook } from "contracts/lib/hooks/Hook.sol"; import "script/foundry/utils/HooksFactory.sol"; +import { ModuleKey, LICENSING_MODULE_KEY, REGISTRATION_MODULE_KEY, RELATIONSHIP_MODULE_KEY } from "contracts/lib/modules/Module.sol"; contract Main is Script, BroadcastManager, JsonDeploymentHandler, ProxyHelper { @@ -335,15 +336,15 @@ contract Main is Script, BroadcastManager, JsonDeploymentHandler, ProxyHelper { // REGISTER MODULES ModuleRegistry(moduleRegistry).registerProtocolModule( - ModuleRegistryKeys.REGISTRATION_MODULE, + REGISTRATION_MODULE_KEY, BaseModule(registrationModule) ); ModuleRegistry(moduleRegistry).registerProtocolModule( - ModuleRegistryKeys.RELATIONSHIP_MODULE, + RELATIONSHIP_MODULE_KEY, BaseModule(relationshipModule) ); ModuleRegistry(moduleRegistry).registerProtocolModule( - ModuleRegistryKeys.LICENSING_MODULE, + LICENSING_MODULE_KEY, BaseModule(licensingModule) ); string[] memory ipAssetTypes = new string[](2); diff --git a/test/foundry/IPOrgControllerTest.t.sol b/test/foundry/IPOrgControllerTest.t.sol index c2b6eea3..c5b3cc06 100644 --- a/test/foundry/IPOrgControllerTest.t.sol +++ b/test/foundry/IPOrgControllerTest.t.sol @@ -14,7 +14,7 @@ import { AccessControlHelper } from "./utils/AccessControlHelper.sol"; import { MockIPOrgController } from "./mocks/MockIPOrgController.sol"; import { BaseModule } from "contracts/modules/base/BaseModule.sol"; import { RegistrationModule } from "contracts/modules/registration/RegistrationModule.sol"; -import { ModuleRegistryKeys } from "contracts/lib/modules/ModuleRegistryKeys.sol"; +import { REGISTRATION_MODULE_KEY } from "contracts/lib/modules/Module.sol"; import { LicensingFrameworkRepo } from "contracts/modules/licensing/LicensingFrameworkRepo.sol"; import 'test/foundry/utils/ProxyHelper.sol'; import "forge-std/Test.sol"; @@ -86,7 +86,7 @@ contract IPOrgControllerTest is Test, ProxyHelper, AccessControlHelper { address(accessControl) ); _grantRole(vm, AccessControl.MODULE_REGISTRAR_ROLE, address(this)); - moduleRegistry.registerProtocolModule(ModuleRegistryKeys.REGISTRATION_MODULE, registrationModule); + moduleRegistry.registerProtocolModule(REGISTRATION_MODULE_KEY, registrationModule); vm.label(prevIpOrgOwner, "Prev IP Org Owner"); vm.label(newIpOrgOwner, "New IP Org Owner"); @@ -119,7 +119,7 @@ contract IPOrgControllerTest is Test, ProxyHelper, AccessControlHelper { ipAssetTypes[0] = "type1"; ipAssetTypes[1] = "type2"; ipOrg = IPOrg(ipOrgController.registerIpOrg(msg.sender, "name", "symbol", ipAssetTypes)); - moduleRegistry.registerProtocolModule(ModuleRegistryKeys.REGISTRATION_MODULE, BaseModule(address(this))); + vm.startPrank(address(registrationModule)); uint256 ipAssetId = ipOrg.mint(ipOrgOwner, 1); assertEq(ipOrg.ipOrgAssetType(ipAssetId), 1); assertEq(ipOrg.ownerOf(ipAssetId), ipOrgOwner); @@ -140,9 +140,10 @@ contract IPOrgControllerTest is Test, ProxyHelper, AccessControlHelper { ipAssetTypes[0] = "type1"; ipAssetTypes[1] = "type2"; ipOrg = IPOrg(ipOrgController.registerIpOrg(msg.sender, "name", "symbol", ipAssetTypes)); - moduleRegistry.registerProtocolModule(ModuleRegistryKeys.REGISTRATION_MODULE, BaseModule(address(this))); + vm.startPrank(address(registrationModule)); uint256 ipAssetId = ipOrg.mint(ipOrgOwner, 1); ipOrg.burn(ipAssetId); + vm.stopPrank(); vm.expectRevert(Errors.IPOrg_IdDoesNotExist.selector); ipOrg.ipOrgAssetType(ipAssetId); vm.expectRevert(); @@ -154,7 +155,7 @@ contract IPOrgControllerTest is Test, ProxyHelper, AccessControlHelper { ipAssetTypes[0] = "type1"; ipAssetTypes[1] = "type2"; ipOrg = IPOrg(ipOrgController.registerIpOrg(msg.sender, "name", "symbol", ipAssetTypes)); - moduleRegistry.registerProtocolModule(ModuleRegistryKeys.REGISTRATION_MODULE, BaseModule(address(this))); + vm.prank(address(registrationModule)); uint256 ipAssetId = ipOrg.mint(ipOrgOwner, 1); vm.prank(ipOrgOwner); vm.expectRevert(Errors.Unauthorized.selector); diff --git a/test/foundry/access-control/AccessControlSingleton.t.sol b/test/foundry/access-control/AccessControlSingleton.t.sol index a7a37f41..4a0b2872 100644 --- a/test/foundry/access-control/AccessControlSingleton.t.sol +++ b/test/foundry/access-control/AccessControlSingleton.t.sol @@ -9,6 +9,8 @@ import { AccessControlSingleton } from "contracts/access-control/AccessControlSi import { AccessControlUpgradeable } from "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol"; contract AccessControlSingletonTest is Test, AccessControlHelper { + + error TestError(); function setUp() public { _setupAccessControl(); } @@ -20,6 +22,11 @@ contract AccessControlSingletonTest is Test, AccessControlHelper { ); } + function test_AccessControlSingleton_revert_reinitialize() public { + vm.expectRevert("Initializable: contract is already initialized"); + accessControl.initialize(admin); + } + function test_AccessControlSingleton_revert_zeroAdmin() public { AccessControlSingleton ac2 = new AccessControlSingleton(); vm.expectRevert(Errors.ZeroAddress.selector); diff --git a/test/foundry/access-control/AccessControlledUpgradeable.t.sol b/test/foundry/access-control/AccessControlledUpgradeable.t.sol index 7654de39..824f49bd 100644 --- a/test/foundry/access-control/AccessControlledUpgradeable.t.sol +++ b/test/foundry/access-control/AccessControlledUpgradeable.t.sol @@ -29,6 +29,27 @@ contract AccessControlledUpgradeableTest is Test, AccessControlHelper { ); } + function test_AccessControlled_revert_invalidInterface() public { + MockAccessControlledUpgradeable invalidACL = new MockAccessControlledUpgradeable(); + invalidACL.setIsInterfaceValid(false); + address mockAddr = address(new MockAccessControlledUpgradeable()); + bytes memory encodedData = abi.encodeWithSelector( + bytes4(keccak256(bytes("initialize(address)"))), + address(invalidACL) + ); + vm.expectRevert( + abi.encodeWithSelector( + Errors.UnsupportedInterface.selector, + "IAccessControl" + ) + ); + _deployUUPSProxy( + mockAddr, + encodedData + ); + invalidACL.setIsInterfaceValid(false); + } + function test_AccessControlled_onlyRole() public { bytes32 role = keccak256("TEST_ROLE"); _grantRole(vm, role, address(this)); diff --git a/test/foundry/mocks/MockAccessControlledUpgradeable.sol b/test/foundry/mocks/MockAccessControlledUpgradeable.sol index 770d76fa..ea14cd83 100644 --- a/test/foundry/mocks/MockAccessControlledUpgradeable.sol +++ b/test/foundry/mocks/MockAccessControlledUpgradeable.sol @@ -2,14 +2,29 @@ pragma solidity ^0.8.13; import { AccessControlledUpgradeable } from "contracts/access-control/AccessControlledUpgradeable.sol"; +import {IAccessControl} from "@openzeppelin/contracts/access/IAccessControl.sol"; + contract MockAccessControlledUpgradeable is AccessControlledUpgradeable { + bool isInterfaceValid; + function initialize(address accessControl) public initializer { __AccessControlledUpgradeable_init(accessControl); } + function setIsInterfaceValid(bool isValid) public { + isInterfaceValid = isValid; + } + function exposeOnlyRole(bytes32 role) public onlyRole(role) {} function _authorizeUpgrade(address newImplementation) internal virtual override {} + + function supportsInterface(bytes4 interfaceId) public view returns (bool) { + if (isInterfaceValid) { + return false; + } + return interfaceId == type(IAccessControl).interfaceId; + } } diff --git a/test/foundry/mocks/MockBaseModule.sol b/test/foundry/mocks/MockBaseModule.sol index 9f4bd7c7..403bc955 100644 --- a/test/foundry/mocks/MockBaseModule.sol +++ b/test/foundry/mocks/MockBaseModule.sol @@ -4,6 +4,7 @@ pragma solidity ^0.8.18; import { BaseModule } from "contracts/modules/base/BaseModule.sol"; import { IIPOrg } from "contracts/interfaces/ip-org/IIPOrg.sol"; +import { ModuleKey } from "contracts/lib/modules/Module.sol"; /// @title Mock BaseModule /// @notice This mock contract is used for testing the base module flow @@ -31,6 +32,13 @@ contract MockBaseModule is BaseModule { _admin = admin_; } + // Stub for testing authorization via the module registry. + function test() external onlyAuthorized() {} + + function moduleKey() public pure override returns (ModuleKey) { + return ModuleKey.wrap(keccak256(abi.encodePacked("test"))); + } + function callStackAt( uint256 index_ ) external view returns (BaseModuleCall memory) { diff --git a/test/foundry/mocks/MockGateway.sol b/test/foundry/mocks/MockGateway.sol new file mode 100644 index 00000000..6f54d91a --- /dev/null +++ b/test/foundry/mocks/MockGateway.sol @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.18; + +import { MockBaseModule } from "./MockBaseModule.sol"; +import { Gateway } from "contracts/modules/Gateway.sol"; +import { ModuleKey, ModuleDependencies } from "contracts/lib/modules/Module.sol"; +import { ModuleRegistry } from "contracts/modules/ModuleRegistry.sol"; + +/// @title Mock Gateway +/// @notice This mock contract is used for testing the gateway. +contract MockGateway is Gateway { + + ModuleKey constant TEST_MODULE_KEY = ModuleKey.wrap(keccak256(abi.encodePacked("test"))); + + bool isValid; + MockBaseModule module; + + constructor(bool isValid_, ModuleRegistry moduleRegistry_) Gateway(moduleRegistry_) { + isValid = isValid_; + } + + function updateDependencies() + external + override + onlyModuleRegistry + returns (ModuleDependencies memory dependencies) + { + // Synchronize relevant modules with the registry. + module = MockBaseModule(MODULE_REGISTRY.protocolModule(TEST_MODULE_KEY)); + return getDependencies(); + } + + function getDependencies() public view override returns (ModuleDependencies memory dependencies) { + ModuleKey[] memory keys; + bytes4[][] memory fns; + if (!isValid) { + keys = new ModuleKey[](1); + fns = new bytes4[][](2); + + keys[0] = TEST_MODULE_KEY; + bytes4[] memory moduleFns = new bytes4[](2); + fns[0] = moduleFns; + + } else { + keys = new ModuleKey[](1); + fns = new bytes4[][](1); + keys[0] = TEST_MODULE_KEY; + bytes4[] memory moduleFns = new bytes4[](1); + moduleFns[0] = MockBaseModule.test.selector; + fns[0] = moduleFns; + } + return ModuleDependencies(keys, fns); + } + + function setIsValid(bool isValid_) public { + isValid = isValid_; + } + + function callModule() external { + return module.test(); + } +} diff --git a/test/foundry/modules/ModuleRegistry.t.sol b/test/foundry/modules/ModuleRegistry.t.sol index 924230d1..32f07458 100644 --- a/test/foundry/modules/ModuleRegistry.t.sol +++ b/test/foundry/modules/ModuleRegistry.t.sol @@ -10,24 +10,37 @@ import "test/foundry/mocks/MockBaseHook.sol"; import "contracts/ip-org/IPOrgController.sol"; import "contracts/lib/Errors.sol"; import "contracts/modules/ModuleRegistry.sol"; +import { MockGateway } from "test/foundry/mocks/MockGateway.sol"; +import { ModuleKey } from "contracts/lib/modules/Module.sol"; -// Todo: test events contract ModuleRegistryTest is Test, AccessControlHelper { + + ModuleKey constant UNREGISTERED_MODULE = ModuleKey.wrap(keccak256(abi.encodePacked("unregistered_module_key"))); + ModuleKey constant TEST_MODULE = ModuleKey.wrap(keccak256(abi.encodePacked("test"))); ModuleRegistry registry; + MockGateway gateway; event RequestPending(address indexed sender); event RequestCompleted(address indexed sender); + event ModuleAuthorizationGranted( + ModuleKey indexed key, + bytes4 fn, + address indexed gateway, + bool grant + ); + event ModuleAdded( address indexed ipOrg, string moduleKey, address indexed module ); + event ModuleRemoved( - address indexed ipOrg, - string moduleKey, + ModuleKey indexed key, address indexed module ); + event ModuleConfigured( address indexed ipOrg, string moduleKey, @@ -39,8 +52,94 @@ contract ModuleRegistryTest is Test, AccessControlHelper { _setupAccessControl(); _grantRole(vm, AccessControl.MODULE_REGISTRAR_ROLE, admin); registry = new ModuleRegistry(address(accessControl)); + gateway = new MockGateway(true, registry); + } + + function test_moduleRegistry_registerProtocolGateway() public { + BaseModule.ModuleConstruction memory moduleConstruction = BaseModule.ModuleConstruction( + IPAssetRegistry(address(0x123)), + ModuleRegistry(address(0x983)), + LicenseRegistry(address(0x123)), + IPOrgController(address(0x123)) + ); + MockBaseModule module = new MockBaseModule(admin, moduleConstruction); + vm.startPrank(admin); + registry.registerProtocolModule(TEST_MODULE, module); + vm.expectEmit(address(registry)); + emit ModuleAuthorizationGranted(TEST_MODULE, MockBaseModule.test.selector, address(gateway), true); + registry.registerProtocolGateway(gateway); + assertTrue(registry.isAuthorized(TEST_MODULE, gateway, MockBaseModule.test.selector)); + } + + function test_moduleRegistry_removeProtocolGateway() public { + BaseModule.ModuleConstruction memory moduleConstruction = BaseModule.ModuleConstruction( + IPAssetRegistry(address(0x123)), + ModuleRegistry(address(0x983)), + LicenseRegistry(address(0x123)), + IPOrgController(address(0x123)) + ); + MockBaseModule module = new MockBaseModule(admin, moduleConstruction); + vm.startPrank(admin); + registry.registerProtocolModule(TEST_MODULE, module); + registry.registerProtocolGateway(gateway); + vm.expectEmit(address(registry)); + emit ModuleAuthorizationGranted(TEST_MODULE, MockBaseModule.test.selector, address(gateway), false); + registry.removeProtocolGateway(gateway); + assertFalse(registry.isAuthorized(TEST_MODULE, gateway, MockBaseModule.test.selector)); + } + + function test_moduleRegistry_revert_registerInvalidGateway() public { + vm.startPrank(admin); + gateway.setIsValid(false); + vm.expectRevert(Errors.ModuleRegistry_InvalidGateway.selector); + registry.registerProtocolGateway(gateway); + } + + function test_moduleRegistry_revert_registerGatewayUnregisteredModule() public { + vm.startPrank(admin); + vm.expectRevert(Errors.ModuleRegistry_ModuleNotYetRegistered.selector); + registry.registerProtocolGateway(gateway); + } + + function test_moduleRegistry_revert_registerGatewayDuplicateDependency() public { + BaseModule.ModuleConstruction memory moduleConstruction = BaseModule.ModuleConstruction( + IPAssetRegistry(address(0x123)), + ModuleRegistry(address(0x983)), + LicenseRegistry(address(0x123)), + IPOrgController(address(0x123)) + ); + MockBaseModule module = new MockBaseModule(admin, moduleConstruction); + vm.startPrank(admin); + registry.registerProtocolModule(TEST_MODULE, module); + registry.registerProtocolGateway(gateway); + vm.expectRevert(Errors.ModuleRegistry_DependencyAlreadyRegistered.selector); + registry.registerProtocolGateway(gateway); } + function test_moduleRegistry_revert_removeUnregisteredGateway() public { + vm.startPrank(admin); + vm.expectRevert(Errors.ModuleRegistry_DependencyNotYetRegistered.selector); + registry.removeProtocolGateway(gateway); + } + + function test_moduleRegistry_revert_removeInvalidGateway() public { + BaseModule.ModuleConstruction memory moduleConstruction = BaseModule.ModuleConstruction( + IPAssetRegistry(address(0x123)), + ModuleRegistry(address(0x983)), + LicenseRegistry(address(0x123)), + IPOrgController(address(0x123)) + ); + MockBaseModule module = new MockBaseModule(admin, moduleConstruction); + vm.startPrank(admin); + registry.registerProtocolModule(TEST_MODULE, module); + registry.registerProtocolGateway(gateway); + gateway.setIsValid(false); + vm.expectRevert(Errors.ModuleRegistry_InvalidGateway.selector); + registry.removeProtocolGateway(gateway); + } + + + function test_moduleRegistry_addProtocolModule() public { BaseModule.ModuleConstruction memory moduleConstruction = BaseModule.ModuleConstruction( IPAssetRegistry(address(0x123)), @@ -51,19 +150,19 @@ contract ModuleRegistryTest is Test, AccessControlHelper { MockBaseModule module = new MockBaseModule(admin, moduleConstruction); vm.expectEmit(address(registry)); - emit ModuleAdded(registry.PROTOCOL_LEVEL(), "test", address(module)); + emit ModuleAdded(address(0), string(abi.encodePacked(TEST_MODULE)), address(module)); vm.prank(admin); - registry.registerProtocolModule("test", module); + registry.registerProtocolModule(TEST_MODULE, module); - assertEq(address(registry.moduleForKey("test")), address(module)); + assertEq(address(registry.protocolModule(TEST_MODULE)), address(module)); } function test_moduleRegistry_revert_addProtocolModuleZeroAddress() public { vm.expectRevert(Errors.ZeroAddress.selector); vm.prank(admin); - registry.registerProtocolModule("test", BaseModule(address(0))); + registry.registerProtocolModule(TEST_MODULE, BaseModule(address(0))); - assertEq(address(registry.moduleForKey("test")), address(0)); + assertEq(address(registry.protocolModule(TEST_MODULE)), address(0)); } function test_moduleRegistry_removeProtocolModule() public { @@ -77,38 +176,28 @@ contract ModuleRegistryTest is Test, AccessControlHelper { vm.startPrank(admin); vm.expectEmit(address(registry)); - emit ModuleAdded(registry.PROTOCOL_LEVEL(), "test", address(module)); - registry.registerProtocolModule("test", module); - assertEq(address(registry.moduleForKey("test")), address(module)); + emit ModuleAdded(address(0), string(abi.encodePacked(TEST_MODULE)), address(module)); + registry.registerProtocolModule(TEST_MODULE, module); + assertEq(address(registry.protocolModule(TEST_MODULE)), address(module)); vm.expectEmit(address(registry)); - emit ModuleRemoved(registry.PROTOCOL_LEVEL(), "test", address(module)); - registry.removeProtocolModule("test"); - assertEq(address(registry.moduleForKey("test")), address(0)); + emit ModuleRemoved(TEST_MODULE, address(module)); + registry.removeProtocolModule(TEST_MODULE); + assertEq(address(registry.protocolModule(TEST_MODULE)), address(0)); vm.stopPrank(); } - function test_moduleRegistry_revert_removeProtocolModuleModuleNotRegistered() public { - vm.expectRevert( - abi.encodeWithSelector( - Errors.ModuleRegistry_ModuleNotRegistered.selector, - "unregistered_module_key" - ) - ); + function test_moduleRegistry_revert_removeProtocolModuleModuleNotYetRegistered() public { + vm.expectRevert(Errors.ModuleRegistry_ModuleNotYetRegistered.selector); vm.prank(admin); - registry.removeProtocolModule("unregistered_module_key"); - assertEq(address(registry.moduleForKey("unregistered_module_key")), address(0)); + registry.removeProtocolModule(UNREGISTERED_MODULE); + assertEq(address(registry.protocolModule(UNREGISTERED_MODULE)), address(0)); } - function test_moduleRegistry_revert_configureModuleNotRegistered() public { + function test_moduleRegistry_revert_configureModuleNotYetRegistered() public { bytes memory encodedParams = abi.encode("test"); - vm.expectRevert( - abi.encodeWithSelector( - Errors.ModuleRegistry_ModuleNotRegistered.selector, - "unregistered_module_key" - ) - ); + vm.expectRevert(Errors.ModuleRegistry_ModuleNotYetRegistered.selector); registry.configure( IIPOrg(address(0x123)), "unregistered_module_key", diff --git a/test/foundry/modules/base/BaseModule.t.sol b/test/foundry/modules/base/BaseModule.t.sol index f3967547..033e8b27 100644 --- a/test/foundry/modules/base/BaseModule.t.sol +++ b/test/foundry/modules/base/BaseModule.t.sol @@ -6,6 +6,7 @@ import "forge-std/Test.sol"; import { BaseTest } from "test/foundry/utils/BaseTest.sol"; import { ModuleRegistry } from "contracts/modules/ModuleRegistry.sol"; import { IPAssetRegistry } from "contracts/IPAssetRegistry.sol"; +import { ModuleKey } from "contracts/lib/modules/Module.sol"; import { BaseModule } from "contracts/modules/base/BaseModule.sol"; import { HookRegistry } from "contracts/modules/base/HookRegistry.sol"; import { MockBaseModule } from "test/foundry/mocks/MockBaseModule.sol"; @@ -20,6 +21,9 @@ import { Create2 } from "@openzeppelin/contracts/utils/Create2.sol"; contract BaseModuleTest is BaseTest { + + ModuleKey constant TEST_MODULE = ModuleKey.wrap(keccak256(abi.encodePacked("test"))); + MockBaseModule module; IPAssetRegistry ipaRegistry = IPAssetRegistry(address(456)); MockIPOrg mockIpOrg; @@ -44,6 +48,12 @@ contract BaseModuleTest is BaseTest { }); } + function test_baseModule_revert_unauthorizedCaller() public { + moduleRegistry.registerProtocolModule(TEST_MODULE, module); + vm.expectRevert(Errors.BaseModule_Unauthorized.selector); + module.test(); + } + function test_baseModule_revert_constructorIpaRegistryIsZero() public { vm.prank(admin); vm.expectRevert(Errors.BaseModule_ZeroIpaRegistry.selector); diff --git a/test/foundry/utils/BaseTest.sol b/test/foundry/utils/BaseTest.sol index e3620b53..0edeb379 100644 --- a/test/foundry/utils/BaseTest.sol +++ b/test/foundry/utils/BaseTest.sol @@ -17,7 +17,8 @@ import "contracts/modules/licensing/LicenseRegistry.sol"; import "contracts/modules/licensing/LicensingModule.sol"; import { ShortString, ShortStrings } from "@openzeppelin/contracts/utils/ShortStrings.sol"; import { AccessControl } from "contracts/lib/AccessControl.sol"; -import { ModuleRegistryKeys } from "contracts/lib/modules/ModuleRegistryKeys.sol"; +import { LICENSING_MODULE_KEY, RELATIONSHIP_MODULE_KEY, REGISTRATION_MODULE_KEY } from "contracts/lib/modules/Module.sol"; +import { IGateway } from "contracts/interfaces/modules/IGateway.sol"; import { RegistrationModule } from "contracts/modules/registration/RegistrationModule.sol"; import { LicensingFrameworkRepo } from "contracts/modules/licensing/LicensingFrameworkRepo.sol"; import { Create2 } from "@openzeppelin/contracts/utils/Create2.sol"; @@ -93,7 +94,7 @@ contract BaseTest is BaseTestUtils, ProxyHelper, AccessControlHelper { address(licensingFrameworkRepo), defaultRevoker ); - moduleRegistry.registerProtocolModule(ModuleRegistryKeys.LICENSING_MODULE, licensingModule); + moduleRegistry.registerProtocolModule(LICENSING_MODULE_KEY, licensingModule); // Create Registration Module registrationModule = new RegistrationModule( @@ -105,7 +106,7 @@ contract BaseTest is BaseTestUtils, ProxyHelper, AccessControlHelper { }), address(accessControl) ); - moduleRegistry.registerProtocolModule(ModuleRegistryKeys.REGISTRATION_MODULE, registrationModule); + moduleRegistry.registerProtocolModule(REGISTRATION_MODULE_KEY, registrationModule); // Create Relationship Module relationshipModule = new RelationshipModule( @@ -117,8 +118,9 @@ contract BaseTest is BaseTestUtils, ProxyHelper, AccessControlHelper { }), address(accessControl) ); - moduleRegistry.registerProtocolModule(ModuleRegistryKeys.RELATIONSHIP_MODULE, relationshipModule); + moduleRegistry.registerProtocolModule(RELATIONSHIP_MODULE_KEY, relationshipModule); + // moduleRegistry.registerProtocolGateway(IGateway(spg)); IPOrgParams.RegisterIPOrgParams memory ipAssetOrgParams = IPOrgParams.RegisterIPOrgParams( address(registry), "IPOrgName",