diff --git a/.prettierrc b/.prettierrc index 6ce911d6..5b006662 100644 --- a/.prettierrc +++ b/.prettierrc @@ -1,7 +1,9 @@ { + "useTabs": false, + "printWidth": 120, "trailingComma": "es5", "tabWidth": 4, "semi": false, "singleQuote": false, "bracketSpacing": true -} \ No newline at end of file +} diff --git a/.solhint.json b/.solhint.json index 0fe768e1..44134d5a 100644 --- a/.solhint.json +++ b/.solhint.json @@ -3,10 +3,12 @@ "plugins": ["prettier"], "rules": { "code-complexity": ["error", 8], - "compiler-version": ["error", ">=0.8.0"], + "compiler-version": ["error", ">=0.8.19"], "const-name-snakecase": "off", + "no-empty-blocks": "off", "constructor-syntax": "error", "func-visibility": ["error", { "ignoreConstructors": true }], + "modifier-name-mixedcase": "error", "max-line-length": ["error", 120], "not-rely-on-time": "off", "reason-string": ["warn", { "maxLength": 64 }], @@ -17,4 +19,4 @@ "no-global-import": "error", "prettier/prettier": "error" } -} \ No newline at end of file +} diff --git a/Makefile b/Makefile index a7ded8d0..73ccb59f 100644 --- a/Makefile +++ b/Makefile @@ -26,7 +26,7 @@ snapshot :; forge snapshot slither :; slither ./contracts -format :; npx prettier --write contracts/**/*.sol && npx prettier --write contracts/*.sol +format :; npx prettier --write --plugin=prettier-plugin-solidity 'contracts/**/*.sol' && npx prettier --write --plugin=prettier-plugin-solidity --write 'contracts/*.sol' # remove `test` and `script` folders from coverage coverage: @@ -36,7 +36,7 @@ coverage: genhtml lcov.info --output-dir coverage # solhint should be installed globally -lint :; npx solhint contracts/**/*.sol && npx solhint contracts/*.sol +lint :; npx solhint 'contracts/**/*.sol' deploy-goerli :; npx hardhat run ./script/deploy-reveal-engine.js --network goerli verify-goerli :; npx hardhat verify --network goerli ${contract} diff --git a/contracts/IPAssetRegistry.sol b/contracts/IPAssetRegistry.sol index 18db2faa..7512308c 100644 --- a/contracts/IPAssetRegistry.sol +++ b/contracts/IPAssetRegistry.sol @@ -1,12 +1,10 @@ // SPDX-License-Identifier: UNLICENSED -// See Story Protocol Alpha Agreement: https://github.com/storyprotocol/protocol-contracts/blob/main/StoryProtocol-AlphaTestingAgreement-17942166.3.pdf +// See https://github.com/storyprotocol/protocol-contracts/blob/main/StoryProtocol-AlphaTestingAgreement-17942166.3.pdf pragma solidity ^0.8.19; import { IIPAssetRegistry } from "contracts/interfaces/IIPAssetRegistry.sol"; import { IRegistrationModule } from "contracts/interfaces/modules/registration/IRegistrationModule.sol"; 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"; @@ -18,15 +16,14 @@ import { Errors } from "contracts/lib/Errors.sol"; /// attributes related to an IP asset - all other attributes, which will be /// specific for a given module, will be queried through the module registry. contract IPAssetRegistry is IIPAssetRegistry { - /// @notice Core attributes that make up an IP Asset. struct IPA { - string name; // Human-readable identifier for the IP asset. - address registrant; // Address of the initial registrant of the IP asset. - uint8 status; // Current status of the IP asset (e.g. active, expired, etc.) - address ipOrg; // Address of the governing entity of the IP asset. - bytes32 hash; // A unique content hash of the IP asset for preserving integrity. - uint64 registrationDate; // Timestamp for which the IP asset was first registered. + string name; // Human-readable identifier for the IP asset. + address registrant; // Address of the initial registrant of the IP asset. + uint8 status; // Current status of the IP asset (e.g. active, expired, etc.) + address ipOrg; // Address of the governing entity of the IP asset. + bytes32 hash; // A unique content hash of the IP asset for preserving integrity. + uint64 registrationDate; // Timestamp for which the IP asset was first registered. } /// @notice Used for fetching modules associated with an IP asset. @@ -36,8 +33,7 @@ contract IPAssetRegistry is IIPAssetRegistry { mapping(uint256 => IPA) internal _ipAssets; /// @notice Tracks the total number of IP Assets in existence. - /// TODO(leeren) Switch from numerical ids to a universal namehash. - uint256 totalSupply = 0; + uint256 public totalSupply = 0; /// @notice Restricts calls to the registration module of the IP Asset. /// TODO(ramarti): Enable IPOrg-specific registration modules to be authorized. @@ -59,7 +55,7 @@ contract IPAssetRegistry is IIPAssetRegistry { constructor(address moduleRegistry_) { MODULE_REGISTRY = IModuleRegistry(moduleRegistry_); } - + /// @notice Registers a new IP asset. /// @param registrant_ The initial registrant for the IP asset. /// @param name_ A name given to describe the IP asset. @@ -70,26 +66,19 @@ contract IPAssetRegistry is IIPAssetRegistry { string memory name_, bytes32 hash_ ) public onlyRegistrationModule returns (uint256 ipAssetId) { - // Crate a new IP asset with the provided IP attributes. ipAssetId = ++totalSupply; uint64 registrationDate = uint64(block.timestamp); _ipAssets[ipAssetId] = IPA({ name: name_, // For now, let's assume 0 == unset, 1 is OK. TODO: Add status enum and synch with License status - status: 1, + status: 1, registrant: registrant_, ipOrg: ipOrg_, hash: hash_, registrationDate: registrationDate }); - emit Registered( - ipAssetId, - name_, - ipOrg_, - registrant_, - hash_ - ); + emit Registered(ipAssetId, name_, ipOrg_, registrant_, hash_); } /// @notice Changes the IP Org of an IP asset. @@ -135,5 +124,4 @@ contract IPAssetRegistry is IIPAssetRegistry { function ipAsset(uint256 ipAssetId_) public view returns (IPA memory) { return _ipAssets[ipAssetId_]; } - } diff --git a/contracts/StoryProtocol.sol b/contracts/StoryProtocol.sol index f13d49eb..bcb13262 100644 --- a/contracts/StoryProtocol.sol +++ b/contracts/StoryProtocol.sol @@ -1,48 +1,38 @@ // SPDX-License-Identifier: UNLICENSED -// See Story Protocol Alpha Agreement: https://github.com/storyprotocol/protocol-contracts/blob/main/StoryProtocol-AlphaTestingAgreement-17942166.3.pdf +// 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 { 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"; -import { ModuleRegistry } from "contracts/modules/ModuleRegistry.sol"; import { LibRelationship } from "contracts/lib/modules/LibRelationship.sol"; -import { Registration } from "contracts/lib/modules/Registration.sol"; -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 { 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"; -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 +/// @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 +/// 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 { - - // Modules which the Story Protocol gateway depends on. - IRegistrationModule public registrationModule; - ILicensingModule public licensingModule; - IRelationshipModule public relationshipModule; - + /// @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) - ) { + if (address(ipOrgController_) == address(0) || address(moduleRegistry_) == address(0)) { revert Errors.ZeroAddress(); } IP_ORG_CONTROLLER = ipOrgController_; @@ -57,60 +47,30 @@ contract StoryProtocol is Multicall { /// @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 - ); + 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 new IP asset type descriptors to add. - 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 - ); + /// @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. - /// TODO: Add module configurations to the IP Org registration process. - /// TODO: Add permissions for IP Org registration. 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_ - ); + return IP_ORG_CONTROLLER.registerIpOrg(owner_, name_, symbol_, ipAssetTypes_); } /// @notice Registers an IP Asset. @@ -136,7 +96,7 @@ contract StoryProtocol is Multicall { preHooksData_, postHooksData_ ); - // If the result is empty, then the registration module is pending for async hook execution. + // An empty result indicates that an async hook call is pending execution. if (result.length == 0) { return (0, 0); } @@ -160,10 +120,7 @@ contract StoryProtocol is Multicall { bytes[] calldata preHooksData_, bytes[] calldata postHooksData_ ) public { - bytes memory encodedParams = abi.encode( - Registration.TRANSFER_IP_ASSET, - abi.encode(from_, to_, ipAssetId_) - ); + bytes memory encodedParams = abi.encode(Registration.TRANSFER_IP_ASSET, abi.encode(from_, to_, ipAssetId_)); MODULE_REGISTRY.execute( IIPOrg(ipOrg_), msg.sender, @@ -178,10 +135,9 @@ contract StoryProtocol is Multicall { // Relationships // //////////////////////////////////////////////////////////////////////////// - - function addRelationshipType( - LibRelationship.AddRelationshipTypeParams calldata params_ - ) external { + /// @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, @@ -190,27 +146,29 @@ contract StoryProtocol is Multicall { ); } - function removeRelationshipType( - address ipOrg_, - string calldata relType - ) external { + /// @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) - ) + 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) { + ) external returns (uint256 relId) { bytes memory result = MODULE_REGISTRY.execute( IIPOrg(ipOrg_), msg.sender, @@ -226,13 +184,10 @@ contract StoryProtocol is Multicall { // Licensing // //////////////////////////////////////////////////////////////////////////// - /// Allows an IPOrg to configure its licensing framework (collection of commercial and non-commercial terms) - /// @param ipOrg_ the ipOrg address - /// @param config_ the licensing framework config - function configureIpOrgLicensing( - address ipOrg_, - Licensing.LicensingConfig calldata config_ - ) external { + /// @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, @@ -240,13 +195,13 @@ contract StoryProtocol is Multicall { abi.encode(Licensing.LICENSING_FRAMEWORK_CONFIG, abi.encode(config_)) ); } - - /// Creates a tradeable License NFT in License Registry. - /// @param ipOrg_ the ipOrg address - /// @param params_ LicenseCreation params - /// @param preHooksData_ Hooks data to embed with the registration pre-call. - /// @param postHooksData_ Hooks data to embed with the registration post-call. - /// @return id of the created license + + /// 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_, @@ -258,31 +213,22 @@ contract StoryProtocol is Multicall { IIPOrg(ipOrg_), msg.sender, LICENSING_MODULE, - abi.encode( - Licensing.CREATE_LICENSE, - params - ), + abi.encode(Licensing.CREATE_LICENSE, params), preHooksData_, postHooksData_ ); return abi.decode(result, (uint256)); } - /// Activates a license that's Pending Approval - /// @param ipOrg_ the ipOrg address - /// @param licenseId_ the license id - function activateLicense( - address ipOrg_, - uint256 licenseId_ - ) external { + /// 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_) - ), + abi.encode(Licensing.ACTIVATE_LICENSE, abi.encode(licenseId_)), new bytes[](0), new bytes[](0) ); @@ -292,32 +238,18 @@ contract StoryProtocol is Multicall { /// @param ipOrg_ the ipOrg address /// @param licenseId_ the license id /// @param ipaId_ the ipa id - function linkLnftToIpa( - address ipOrg_, - uint256 licenseId_, - uint256 ipaId_ - ) public { + 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 { + function _linkLnftToIpa(address ipOrg_, uint256 licenseId_, uint256 ipaId_, address caller_) private { MODULE_REGISTRY.execute( IIPOrg(ipOrg_), caller_, - ModuleRegistryKeys.LICENSING_MODULE, - abi.encode( - Licensing.LINK_LNFT_TO_IPA, - abi.encode(licenseId_, ipaId_) - ), + LICENSING_MODULE, + abi.encode(Licensing.LINK_LNFT_TO_IPA, abi.encode(licenseId_, ipaId_)), new bytes[](0), new bytes[](0) ); } - } diff --git a/contracts/access-control/AccessControlSingleton.sol b/contracts/access-control/AccessControlSingleton.sol index 9b745075..43267897 100644 --- a/contracts/access-control/AccessControlSingleton.sol +++ b/contracts/access-control/AccessControlSingleton.sol @@ -1,30 +1,22 @@ // SPDX-License-Identifier: UNLICENSED -// See Story Protocol Alpha Agreement: https://github.com/storyprotocol/protocol-contracts/blob/main/StoryProtocol-AlphaTestingAgreement-17942166.3.pdf - +// See https://github.com/storyprotocol/protocol-contracts/blob/main/StoryProtocol-AlphaTestingAgreement-17942166.3.pdf pragma solidity ^0.8.19; -import { Errors } from "contracts/lib/Errors.sol"; -import { AccessControl } from "contracts/lib/AccessControl.sol"; -import { IVersioned } from "contracts/interfaces/utils/IVersioned.sol"; -import { Multicall } from "@openzeppelin/contracts/utils/Multicall.sol"; import { AccessControlUpgradeable } from "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol"; +import { Multicall } from "@openzeppelin/contracts/utils/Multicall.sol"; import { UUPSUpgradeable } from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; +import { AccessControl } from "contracts/lib/AccessControl.sol"; +import { Errors } from "contracts/lib/Errors.sol"; + /// @title Access Control Singleton /// @notice This contract serves as the global source of truth for role-based authorization. /// Contracts that inherit the AccessControlled contract or its upgradable variant /// are may be granted granular access to certain roles by utilizing the `onlyRole` /// modifier with the required role input as a parameter. -contract AccessControlSingleton is -AccessControlUpgradeable, - UUPSUpgradeable, - Multicall, - IVersioned -{ - string public constant version = "0.1.0"; - - /// @notice Initializer method, access point to initialize inheritance tree. - /// @param admin_ address to be the PROTOCOL_ADMIN_ROLE. +contract AccessControlSingleton is AccessControlUpgradeable, UUPSUpgradeable, Multicall { + /// @notice Initialize the Access Control Singleton contract. + /// @param admin_ address to inherit the PROTOCOL_ADMIN_ROLE. function initialize(address admin_) external initializer { if (admin_ == address(0)) revert Errors.ZeroAddress(); __AccessControl_init(); @@ -32,19 +24,13 @@ AccessControlUpgradeable, _grantRole(AccessControl.PROTOCOL_ADMIN_ROLE, admin_); } - /// @notice Method for PROTOCOL_ADMIN_ROLE to create new roles, and define their role admin. - /// @param role_ id of the new role. Should be keccak256(""). - /// @param admin_ role id that will be the role admin for the new role. - function setRoleAdmin( - bytes32 role_, - bytes32 admin_ - ) external onlyRole(AccessControl.PROTOCOL_ADMIN_ROLE) { + /// @notice Defines the admin role associated for a given protocol role. + /// @param role_ The id of the new role, given by keccak256(""). + /// @param admin_ The id of the admin role provisioned for the provided role. + function setRoleAdmin(bytes32 role_, bytes32 admin_) external onlyRole(AccessControl.PROTOCOL_ADMIN_ROLE) { _setRoleAdmin(role_, admin_); } - /// @notice Access control for the upgrade process (UPGRADER_ROLE) - /// @param newImplementation_ address of the new deployed implementation. - function _authorizeUpgrade( - address newImplementation_ - ) internal virtual override onlyRole(AccessControl.UPGRADER_ROLE) {} + /// @notice Authorizes an upgrade for the contract. + function _authorizeUpgrade(address) internal virtual override onlyRole(AccessControl.UPGRADER_ROLE) {} } diff --git a/contracts/access-control/AccessControlled.sol b/contracts/access-control/AccessControlled.sol index af9887fe..5066fe8a 100644 --- a/contracts/access-control/AccessControlled.sol +++ b/contracts/access-control/AccessControlled.sol @@ -1,25 +1,27 @@ // SPDX-License-Identifier: UNLICENSED -// See Story Protocol Alpha Agreement: https://github.com/storyprotocol/protocol-contracts/blob/main/StoryProtocol-AlphaTestingAgreement-17942166.3.pdf +// See https://github.com/storyprotocol/protocol-contracts/blob/main/StoryProtocol-AlphaTestingAgreement-17942166.3.pdf +pragma solidity ^0.8.19; -pragma solidity ^0.8.9; +// 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 { Errors } from "contracts/lib/Errors.sol"; import { IAccessControlled } from "contracts/interfaces/access-control/IAccessControlled.sol"; -import { IAccessControl } from "@openzeppelin/contracts/access/IAccessControl.sol"; -// solhint-disable-next-line max-line-length -import { ERC165CheckerUpgradeable } from "@openzeppelin/contracts-upgradeable/utils/introspection/ERC165CheckerUpgradeable.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. +/// 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`, reverts if not. - /// @param role_ the role to be tested, defined in Roles.sol and set in AccessControlSingleton instance. + /// @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); @@ -36,25 +38,20 @@ abstract contract AccessControlled is IAccessControlled { emit AccessControlUpdated(accessControl_); } - /// @notice Sets AccessControlSingleton instance. Restricted to PROTOCOL_ADMIN_ROLE - /// @param accessControl_ address of the new instance of AccessControlSingleton. - function setAccessControl( - address accessControl_ - ) public onlyRole(AccessControl.PROTOCOL_ADMIN_ROLE) { + /// @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_); } - /// @notice Checks if `account has `role` assigned. - /// @param role_ the role to be tested, defined in Roles.sol and set in AccessControlSingleton instance. - /// @param account_ the address to be tested for the role. - /// @return return true if account has role, false otherwise. - function _hasRole( - bytes32 role_, - address account_ - ) internal view returns (bool) { + /// @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_); } } diff --git a/contracts/access-control/AccessControlledUpgradeable.sol b/contracts/access-control/AccessControlledUpgradeable.sol index 79de6e51..fb35b560 100644 --- a/contracts/access-control/AccessControlledUpgradeable.sol +++ b/contracts/access-control/AccessControlledUpgradeable.sol @@ -1,17 +1,18 @@ // SPDX-License-Identifier: UNLICENSED -// See Story Protocol Alpha Agreement: https://github.com/storyprotocol/protocol-contracts/blob/main/StoryProtocol-AlphaTestingAgreement-17942166.3.pdf +// See https://github.com/storyprotocol/protocol-contracts/blob/main/StoryProtocol-AlphaTestingAgreement-17942166.3.pdf pragma solidity ^0.8.19; -import { IAccessControl } from "@openzeppelin/contracts/access/IAccessControl.sol"; -import { UUPSUpgradeable } from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; // 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 { UUPSUpgradeable } from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; + import { AccessControl } from "contracts/lib/AccessControl.sol"; -import { IAccessControlled } from "contracts/interfaces/access-control/IAccessControlled.sol"; import { Errors } from "contracts/lib/Errors.sol"; +import { IAccessControlled } from "contracts/interfaces/access-control/IAccessControlled.sol"; -/// @title Access Controlled Contract (upgradeable variant) -/// @notice This contract is to be inherited by any upgradeable protocol components that require +/// @title Upgradeable Access Controlled Contract +/// @notice This contract is to be inherited by any upgradeable protocol components that require /// granular roles for execution, as defined by the Access Control Singleton contract. abstract contract AccessControlledUpgradeable is UUPSUpgradeable, IAccessControlled { using ERC165CheckerUpgradeable for address; @@ -22,11 +23,10 @@ abstract contract AccessControlledUpgradeable is UUPSUpgradeable, IAccessControl } // keccak256(bytes.concat(bytes32(uint256(keccak256("story-protocol.access-controlled-upgradeable.storage")) - 1))) - bytes32 private constant _STORAGE_LOCATION = - 0x06c308ca3b780cede1217f5877d0c7fbf50796d93f836cb3b60e6457b0cf03b6; + bytes32 private constant _STORAGE_LOCATION = 0x06c308ca3b780cede1217f5877d0c7fbf50796d93f836cb3b60e6457b0cf03b6; - /// @notice Checks if msg.sender has `role`, reverts if not. - /// @param role_ the role to be tested, defined in Roles.sol and set in AccessControlSingleton instance. + /// @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); @@ -34,11 +34,9 @@ abstract contract AccessControlledUpgradeable is UUPSUpgradeable, IAccessControl _; } - /// @notice Sets AccessControlSingleton instance. Restricted to PROTOCOL_ADMIN_ROLE - /// @param accessControl_ address of the new instance of AccessControlSingleton. - function setAccessControl( - address accessControl_ - ) public onlyRole(AccessControl.PROTOCOL_ADMIN_ROLE) { + /// @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"); AccessControlledStorage storage $ = _getAccessControlledUpgradeable(); @@ -52,11 +50,10 @@ abstract contract AccessControlledUpgradeable is UUPSUpgradeable, IAccessControl return address($.accessControl); } - /// @notice Initializer method, access point to initialize inheritance tree. - /// @param accessControl_ address of AccessControlSingleton. - function __AccessControlledUpgradeable_init( - address accessControl_ - ) internal initializer { + /// @dev Initializer function called during contract initialization. + /// @param accessControl_ Address of the protocol-wide Access Control Singleton. + // solhint-disable-next-line func-name-mixedcase + function __AccessControlledUpgradeable_init(address accessControl_) internal initializer { if (!accessControl_.supportsInterface(type(IAccessControl).interfaceId)) revert Errors.UnsupportedInterface("IAccessControl"); AccessControlledStorage storage $ = _getAccessControlledUpgradeable(); @@ -64,24 +61,17 @@ abstract contract AccessControlledUpgradeable is UUPSUpgradeable, IAccessControl emit AccessControlUpdated(accessControl_); } - /// @notice Checks if `account has `role` assigned. - /// @param role_ the role to be tested, defined in Roles.sol and set in AccessControlSingleton instance. - /// @param account_ the address to be tested for the role. - /// @return return true if account has role, false otherwise. - function _hasRole( - bytes32 role_, - address account_ - ) internal view returns (bool) { + /// @notice Checks if account `account_` has `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) { AccessControlledStorage storage $ = _getAccessControlledUpgradeable(); return $.accessControl.hasRole(role_, account_); } /// @dev Helper function to get the EIP-7201 storage slot for the contract. - function _getAccessControlledUpgradeable() - private - pure - returns (AccessControlledStorage storage $) - { + function _getAccessControlledUpgradeable() private pure returns (AccessControlledStorage storage $) { assembly { $.slot := _STORAGE_LOCATION } diff --git a/contracts/hooks/PolygonTokenHook.sol b/contracts/hooks/PolygonTokenHook.sol index 3dfc39e7..36ec82ef 100644 --- a/contracts/hooks/PolygonTokenHook.sol +++ b/contracts/hooks/PolygonTokenHook.sol @@ -1,29 +1,18 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.19; -import { HookResult } from "contracts/interfaces/hooks/base/IHook.sol"; import { AsyncBaseHook } from "contracts/hooks/base/AsyncBaseHook.sol"; import { Errors } from "contracts/lib/Errors.sol"; +import { IPolygonTokenClient } from "contracts/interfaces/utils/IPolygonTokenClient.sol"; import { PolygonToken } from "contracts/lib/hooks/PolygonToken.sol"; -interface IPolygonTokenClient { - function sendRequest( - bytes32 requestId, - address requester, - address tokenAddress, - address tokenOwnerAddress, - address callbackAddr, - bytes4 callbackFunctionSignature - ) external; -} - -/// @title PolygonTokenHook -/// @notice This is asynchronous hook used to verify a user owning specific Polygon tokens. +/// @title Polygon Token Hooks Contract +/// @notice Asynchronous hook used for verifying token balances on Polygon. contract PolygonTokenHook is AsyncBaseHook { /// @notice The address that is allowed to call the callback function. - /// @dev This address is set during contract deployment and cannot be changed afterwards. address private immutable CALLBACK_CALLER; + /// @notice The address of the Polygon client used for call fulfillment. address public immutable ORACLE_CLIENT; /// @notice A counter used to generate unique request IDs for each token request. @@ -32,27 +21,21 @@ contract PolygonTokenHook is AsyncBaseHook { /// @notice A mapping that links each request ID to a PolygonTokenRequest struct. mapping(bytes32 => PolygonTokenRequest) private requestIdToRequest; - /// @notice A struct used to store information about a token request. - /// @dev It includes the requester's address, the token's address, the token owner's address, a balance threshold, - /// and two boolean flags to indicate whether the request is completed and whether it exists. + /// @notice Stores information rlated to a async Polygon token balance request. struct PolygonTokenRequest { - address requester; - address tokenAddress; - address tokenOwnerAddress; - uint256 balanceThreshold; - bool isRequestCompleted; - bool exists; + address requester; // Address of the requestor. + address tokenAddress; // Address of the Polygon token whose balance is being queried for. + address tokenOwnerAddress; // Address of the owner of the Polygon token. + uint256 balanceThreshold; // The target token balance necessary for successful fulfillment. + bool isRequestCompleted; // Whether the request was completed or not. + bool exists; // Whether the request exists. } - /// @notice Initializes the contract during deployment. - /// @param accessControl_ The address of the access control contract. - /// @param oracleClient_ The address of the oracle client contract for access Polygon Token info. + /// @notice Initializes the Polygon Token Hook contract. + /// @param accessControl_ The address of the contract used for authorization. + /// @param oracleClient_ The address of the oracle client querying for Polygon Token info. /// @param callbackCaller_ The address of the callback caller contract. - constructor( - address accessControl_, - address oracleClient_, - address callbackCaller_ - ) AsyncBaseHook(accessControl_) { + constructor(address accessControl_, address oracleClient_, address callbackCaller_) AsyncBaseHook(accessControl_) { if (callbackCaller_ == address(0)) revert Errors.ZeroAddress(); if (oracleClient_ == address(0)) revert Errors.ZeroAddress(); CALLBACK_CALLER = callbackCaller_; @@ -62,15 +45,16 @@ contract PolygonTokenHook is AsyncBaseHook { /// @notice Handles the callback of a token request. /// @param requestId The unique ID of the request. /// @param balance The balance of the token. - /// @dev This function checks if the request exists and verifies the token balance against the balance threshold. - /// If the balance is less than the threshold, it sets an error message. Otherwise, it sets the isPassed flag to true. - /// It then deletes the request from the requestIdToRequest mapping. - /// Finally, it calls the _handleCallback() function, passing the requestId and the encoded isPassed flag and errorMessage. - /// The encoding is done using abi.encode(isPassed, errorMessage). + /// @dev This function checks if the request exists and verifies th token balance against the configured balance + /// threshold. If the balance is less than the threshold, an error message is set. Otherwise, the callback + /// is marked as successful via `isPassed=true`, and the request is deleted, after which the `handleCallback` + /// function is called with the encoding `abi.encode(isPassed, errorMessage)`. function handleCallback(bytes32 requestId, uint256 balance) external { bool isPassed = false; string memory errorMessage = ""; - require(requestIdToRequest[requestId].exists, "Request not found"); + if (!requestIdToRequest[requestId].exists) { + revert Errors.Hook_RequestedNotFound(); + } if (balance < requestIdToRequest[requestId].balanceThreshold) { errorMessage = "Balance of Token is not enough"; } else { @@ -80,15 +64,12 @@ contract PolygonTokenHook is AsyncBaseHook { _handleCallback(requestId, abi.encode(isPassed, errorMessage)); } - /// @notice Validates the configuration for the hook. - /// @dev This function checks if the tokenAddress and balanceThreshold in the configuration are valid. - /// It reverts if the tokenAddress is the zero address or if the balanceThreshold is zero. - /// @param hookConfig_ The configuration data for the hook, encoded as bytes. + /// @notice Validates the configuration for the async hook. + /// @dev Validates whether the configured token address and balance threshold are valid. + /// This function reverts when a zero token address or a zero balance threshold is configured. + /// @param hookConfig_ The configuration data for the hook, encoded as a bytes array. function _validateConfig(bytes memory hookConfig_) internal pure override { - PolygonToken.Config memory config = abi.decode( - hookConfig_, - (PolygonToken.Config) - ); + PolygonToken.Config memory config = abi.decode(hookConfig_, (PolygonToken.Config)); if (config.tokenAddress == address(0)) { revert Errors.Hook_InvalidHookConfig("tokenAddress is 0"); } @@ -97,25 +78,18 @@ contract PolygonTokenHook is AsyncBaseHook { } } - /// @dev Internal function to request an asynchronous call, - /// concrete hoot implementation should override the function. - /// The function should revert in case of error. + /// @dev Internal function for requesting an async call meant for implementations + /// to override. This function should revert in case of any errors. /// @param hookConfig_ The configuration of the hook. /// @param hookParams_ The parameters for the hook. /// @return hookData The data returned by the hook. - /// @return requestId The ID of the request. + /// @return requestId The id of the request. function _requestAsyncCall( bytes memory hookConfig_, bytes memory hookParams_ ) internal override returns (bytes memory hookData, bytes32 requestId) { - PolygonToken.Config memory config = abi.decode( - hookConfig_, - (PolygonToken.Config) - ); - PolygonToken.Params memory params = abi.decode( - hookParams_, - (PolygonToken.Params) - ); + PolygonToken.Config memory config = abi.decode(hookConfig_, (PolygonToken.Config)); + PolygonToken.Params memory params = abi.decode(hookParams_, (PolygonToken.Params)); requestId = keccak256(abi.encodePacked(this, nonce++)); hookData = ""; @@ -139,7 +113,7 @@ contract PolygonTokenHook is AsyncBaseHook { } /// @notice Returns the address of the callback caller. - /// @return The address of the callback caller. + /// @return The address of the caller of the callback. function _callbackCaller(bytes32) internal view override returns (address) { return CALLBACK_CALLER; } diff --git a/contracts/hooks/TokenGatedHook.sol b/contracts/hooks/TokenGatedHook.sol index fe07467f..7bc13ef0 100644 --- a/contracts/hooks/TokenGatedHook.sol +++ b/contracts/hooks/TokenGatedHook.sol @@ -1,24 +1,23 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.19; -import { HookResult } from "contracts/interfaces/hooks/base/IHook.sol"; -import { SyncBaseHook } from "contracts/hooks/base/SyncBaseHook.sol"; -import { Errors } from "contracts/lib/Errors.sol"; -import { TokenGated } from "contracts/lib/hooks/TokenGated.sol"; import { ERC165Checker } from "@openzeppelin/contracts/utils/introspection/ERC165Checker.sol"; import { IERC721 } from "@openzeppelin/contracts/token/ERC721/IERC721.sol"; -/// @title TokenGatedHook -/// @notice This contract is a hook that ensures the user is the owner of a specific NFT token. -/// @dev It extends SyncBaseHook and provides the implementation for validating the hook configuration and executing the hook. +import { Errors } from "contracts/lib/Errors.sol"; +import { SyncBaseHook } from "contracts/hooks/base/SyncBaseHook.sol"; +import { TokenGated } from "contracts/lib/hooks/TokenGated.sol"; + +/// @title Token Gated Hook. +/// @notice Synchronous hook for ensursing a user is the owner of an NFT token. contract TokenGatedHook is SyncBaseHook { using ERC165Checker for address; - /// @notice Constructs the TokenGatedHook contract. - /// @param accessControl_ The address of the access control contract. + /// @notice Constructs the Token Gated Hook contract. + /// @param accessControl_ The address of the global access control contract. constructor(address accessControl_) SyncBaseHook(accessControl_) {} - /// @notice Validates the configuration for the hook. + /// @notice Validates the configuration for the token gated hook. /// @dev This function checks if the tokenAddress is a valid ERC721 contract. /// @param hookConfig_ The configuration data for the hook. function _validateConfig(bytes memory hookConfig_) internal view override { @@ -27,21 +26,17 @@ contract TokenGatedHook is SyncBaseHook { if (tokenAddress == address(0)) { revert Errors.ZeroAddress(); } - // Check if the configured token address is a valid ERC 721 contract - if ( - !tokenAddress.supportsInterface( - type(IERC721).interfaceId - ) - ) { + // Check if the configured token address is a valid ERC 721 contract. + if (!tokenAddress.supportsInterface(type(IERC721).interfaceId)) { revert Errors.UnsupportedInterface("IERC721"); } } - /// @notice Executes token gated check in a synchronous manner. + /// @notice Executes a token gated check in a synchronous manner. /// @dev This function checks if the "tokenOwner" owns a token of the specified ERC721 token contract. /// @param hookConfig_ The configuration of the hook. /// @param hookParams_ The parameters for the hook. - /// @return hookData always return empty string as no return data from this hook. + /// @return An empty bytes object, as no data is retured from this hook. function _executeSyncCall( bytes memory hookConfig_, bytes memory hookParams_ diff --git a/contracts/hooks/base/AsyncBaseHook.sol b/contracts/hooks/base/AsyncBaseHook.sol index 4bed2340..6c90bab4 100644 --- a/contracts/hooks/base/AsyncBaseHook.sol +++ b/contracts/hooks/base/AsyncBaseHook.sol @@ -2,24 +2,28 @@ pragma solidity ^0.8.19; import { ERC165Checker } from "@openzeppelin/contracts/utils/introspection/ERC165Checker.sol"; -import { HookResult } from "contracts/interfaces/hooks/base/IHook.sol"; + +import { AccessControl } from "contracts/lib/AccessControl.sol"; import { BaseHook } from "contracts/hooks/base/BaseHook.sol"; -import { ICallbackHandler } from "contracts/interfaces/hooks/base/ICallbackHandler.sol"; import { Errors } from "contracts/lib/Errors.sol"; -import { AccessControlled } from "contracts/access-control/AccessControlled.sol"; -import { AccessControl } from "contracts/lib/AccessControl.sol"; import { Hook } from "contracts/lib/hooks/Hook.sol"; +import { HookResult } from "contracts/interfaces/hooks/base/IHook.sol"; +import { ICallbackHandler } from "contracts/interfaces/hooks/base/ICallbackHandler.sol"; -/// @title AsyncBaseHook -/// @notice This contract is the base contract for all asynchronous hooks. -/// @dev It provides the basic structure and functionality for asynchronous hook execution. +/// @title Asynchronous Base Hook +/// @notice This contract serves as the base functionality for all asynchronous hooks. abstract contract AsyncBaseHook is BaseHook { using ERC165Checker for address; - /// @dev requestId => callback handler + /// @dev Maps async hook request ids to callback handlers. mapping(bytes32 => ICallbackHandler) public callbackHandlers; - /// @dev Emitted when an asynchronous hook is executed. + /// @notice This event emits when an async hook is executed. + /// @param hookAddress Address of the executed hook. + /// @param callbackHandler The address of the handler of the callback. + /// @param result State of the hook (either pending or complete). + /// @param contextData Additional contextual data related to the execution. + /// @param returnData Data returned by the hook. event AsyncHookExecuted( address indexed hookAddress, address indexed callbackHandler, @@ -39,19 +43,16 @@ abstract contract AsyncBaseHook is BaseHook { /// @notice Constructs the AsyncBaseHook contract. /// @param accessControl_ The address of the access control contract. - /// @dev The constructor sets the access control and callback caller addresses. - constructor( - address accessControl_ - ) BaseHook(accessControl_) {} + constructor(address accessControl_) BaseHook(accessControl_) {} /// @notice Executes an asynchronous hook. - /// @dev Modules would utilize the function to make an async call. - /// Only a caller with the HOOK_CALLER_ROLE can call this function. - /// @param hookContext_ The context of executing a hook. + /// @dev Modules utilize this function to make asynchronous calls. + /// Only callers with the HOOK_CALLER_ROLE can call this function. + /// @param hookContext_ The context associated with hook execution. /// @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. + /// @return requestId The id of the async request. function executeAsync( bytes calldata hookContext_, address callbackHandler_ @@ -66,18 +67,11 @@ abstract contract AsyncBaseHook is BaseHook { revert Errors.ZeroAddress(); } // Check if the callback handler supports the ICallbackHandler interface - if ( - !callbackHandler_.supportsInterface( - type(ICallbackHandler).interfaceId - ) - ) { + if (!callbackHandler_.supportsInterface(type(ICallbackHandler).interfaceId)) { revert Errors.UnsupportedInterface("ICallbackHandler"); } - Hook.ExecutionContext memory context = abi.decode( - hookContext_, - (Hook.ExecutionContext) - ); + Hook.ExecutionContext memory context = abi.decode(hookContext_, (Hook.ExecutionContext)); _validateConfig(context.config); // Request an asynchronous call @@ -90,51 +84,43 @@ abstract contract AsyncBaseHook is BaseHook { emit AsyncHookExecuted(address(this), callbackHandler_, result, requestId, hookContext_, hookData); } - /// @dev Internal function to request an asynchronous call, - /// concrete hoot implementation should override the function. - /// The function should revert in case of error. + /// @dev Internal function to request an asynchronous call, intended to be overridden + /// by implementations. This function should revert in case of any errors. /// @param hookConfig_ The configuration of the hook. /// @param hookParams_ The parameters for the hook. /// @return hookData The data returned by the hook. - /// @return requestId The ID of the request. + /// @return requestId The id of the request. function _requestAsyncCall( bytes memory hookConfig_, bytes memory hookParams_ ) internal virtual returns (bytes memory hookData, bytes32 requestId); /// @dev Internal function to get the address of the callback caller. - /// concrete hoot implementation should override the function. - /// @param requestId_ The ID of the request. + /// @param requestId_ The id of the request. /// @return The address of the callback caller. function _callbackCaller(bytes32 requestId_) internal view virtual returns (address); /// @dev Internal function to handle a callback from an asynchronous call. - /// @param requestId_ The ID of the request. + /// @param requestId_ The id of the request. /// @param callbackData_ The data returned by the callback. - function _handleCallback( - bytes32 requestId_, - bytes memory callbackData_ - ) internal virtual { + function _handleCallback(bytes32 requestId_, bytes memory callbackData_) internal virtual { // Only designated callback caller can make a callback address caller = _callbackCaller(requestId_); if (msg.sender != caller) { - revert Errors.Hook_OnlyCallbackCallerCanCallback( - msg.sender, - caller - ); + revert Errors.Hook_OnlyCallbackCallerCanCallback(msg.sender, caller); } - // Checking if a callback handler exists for the given request ID + // Check if a callback handler exists for the given request id if (address(callbackHandlers[requestId_]) == address(0)) { revert Errors.Hook_InvalidAsyncRequestId(requestId_); } - // Emitting an event to signal that an async hook has been called back + // Emit an event to signal that an async hook has been called back emit AsyncHookCalledBack(address(this), address(callbackHandlers[requestId_]), requestId_, callbackData_); - // Invoking the callback handler to process the returned data + // Invokethe callback handler to process the returned data callbackHandlers[requestId_].handleHookCallback(requestId_, callbackData_); - // Cleaning up the callback handler mapping to free up storage + // Clean up the callback handler mapping to free up storage delete callbackHandlers[requestId_]; } } diff --git a/contracts/hooks/base/BaseHook.sol b/contracts/hooks/base/BaseHook.sol index 5a2b74f1..8c622724 100644 --- a/contracts/hooks/base/BaseHook.sol +++ b/contracts/hooks/base/BaseHook.sol @@ -1,66 +1,51 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.19; -import { IHook, HookResult } from "contracts/interfaces/hooks/base/IHook.sol"; -import { Errors } from "contracts/lib/Errors.sol"; -import { AccessControlled } from "contracts/access-control/AccessControlled.sol"; import { AccessControl } from "contracts/lib/AccessControl.sol"; +import { AccessControlled } from "contracts/access-control/AccessControlled.sol"; +import { Errors } from "contracts/lib/Errors.sol"; +import { IHook, HookResult } from "contracts/interfaces/hooks/base/IHook.sol"; -/// @title BaseHook +/// @title Base Hook /// @notice This contract serves as the foundation for all hook contracts. -/// @dev It provides the authorization check shared by all hooks. AsyncBaseHook and SyncBaseHook inherit from BaseHook. -/// Concrete hooks usually inherit from AsyncBaseHook or SyncBaseHook, not directly from BaseHook. +/// @dev This contract provides authorization checks shared by all hooks, and is inherited +/// by the AsyncBaseHook and SyncBaseHook, which hook implementations should extend from. abstract contract BaseHook is IHook, AccessControlled { - /// @notice Constructs the BaseHook contract. - /// @param accessControl_ The address of the access control contract. + /// @notice Creates the Base Hook contract. + /// @param accessControl_ The address of the contract used for authorization. constructor(address accessControl_) AccessControlled(accessControl_) {} /// @notice Executes a synchronous hook. - /// @dev By default, synchronous execution is disabled and this function reverts. + /// @dev By default, synchronous execution is disabled and this function reverts. /// Subclasses can enable synchronous execution by overriding this function. /// Only a caller with the HOOK_CALLER_ROLE can call this function. function executeSync( bytes calldata - ) - external - virtual - override - onlyRole(AccessControl.HOOK_CALLER_ROLE) - returns (HookResult, bytes memory) - { + ) external virtual override onlyRole(AccessControl.HOOK_CALLER_ROLE) returns (HookResult, bytes memory) { revert Errors.Hook_UnsupportedSyncOperation(); } /// @notice Executes an asynchronous hook. - /// @dev By default, asynchronous execution is disabled and this function reverts. + /// @dev By default, asynchronous execution is disabled and this function reverts. /// Subclasses can enable asynchronous execution by overriding this function. /// Only a caller with the HOOK_CALLER_ROLE can call this function. function executeAsync( bytes calldata, address - ) - external - virtual - override - onlyRole(AccessControl.HOOK_CALLER_ROLE) - returns (HookResult, bytes memory, bytes32) - { + ) external virtual override onlyRole(AccessControl.HOOK_CALLER_ROLE) returns (HookResult, bytes memory, bytes32) { revert Errors.Hook_UnsupportedAsyncOperation(); } /// @notice Validates the configuration for the hook. - /// @dev This function calls the internal _validateConfig function with the provided configuration data. - /// If the validation fails, it will revert with an error. + /// @dev If validation fails, this function will throw. /// @param hookConfig_ The configuration data for the hook. - function validateConfig( - bytes calldata hookConfig_ - ) external view override { + function validateConfig(bytes calldata hookConfig_) external view override { _validateConfig(hookConfig_); } /// @notice Validates the configuration for the hook. - /// @dev This function should be overridden by concrete hook to provide specific validation logic. - /// If the validation fails, it will revert with an error. + /// @dev This function is intended to be overridden by hook implementations to provide + /// specialized validation logic. If validation fails, this function should throw. /// @param hookConfig_ The configuration data for the hook. function _validateConfig(bytes memory hookConfig_) internal view virtual; } diff --git a/contracts/hooks/base/SyncBaseHook.sol b/contracts/hooks/base/SyncBaseHook.sol index cb1b04e1..c3073b42 100644 --- a/contracts/hooks/base/SyncBaseHook.sol +++ b/contracts/hooks/base/SyncBaseHook.sol @@ -1,56 +1,38 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.19; -import { HookResult } from "contracts/interfaces/hooks/base/IHook.sol"; -import { BaseHook } from "contracts/hooks/base/BaseHook.sol"; -import { Errors } from "contracts/lib/Errors.sol"; -import { AccessControlled } from "contracts/access-control/AccessControlled.sol"; import { AccessControl } from "contracts/lib/AccessControl.sol"; +import { BaseHook } from "contracts/hooks/base/BaseHook.sol"; import { Hook } from "contracts/lib/hooks/Hook.sol"; +import { HookResult } from "contracts/interfaces/hooks/base/IHook.sol"; -/// @title SyncBaseHook -/// @notice This contract is the base contract for all synchronous hooks. -/// @dev It provides the basic structure and functionality for synchronous hook execution. +/// @title Synchronous Base Hook +/// @notice This contract serves as the base for all synchronous hooks. abstract contract SyncBaseHook is BaseHook { - /// @dev Emitted when a synchronous hook is executed. - event SyncHookExecuted( - address indexed hookAddress, - HookResult indexed result, - bytes contextData, - bytes returnData - ); + /// @dev Emits when a synchronous hook is executed. + event SyncHookExecuted(address indexed hookAddress, HookResult indexed result, bytes contextData, bytes returnData); - /// @notice Constructs the SyncBaseHook contract. + /// @notice Constructs the Sync Base Hook contract. /// @param accessControl_ The address of the access control contract. - /// @dev The constructor sets the access control addresses. constructor(address accessControl_) BaseHook(accessControl_) {} /// @notice Executes a synchronous hook. - /// @dev Modules would utilize the function to make a sync call. - /// Only a caller with the HOOK_CALLER_ROLE can call this function. - /// @param hookContext_ The context of executing a hook. + /// @dev Modules utilize this function to make a synchronous call. + /// Only callers with the HOOK_CALLER_ROLE can call this function. + /// @param hookContext_ The context associated with hook execution. /// @return result The result of the hook execution. /// @return hookData The data returned by the hook. function executeSync( bytes calldata hookContext_ - ) - external - override - onlyRole(AccessControl.HOOK_CALLER_ROLE) - returns (HookResult result, bytes memory hookData) - { - Hook.ExecutionContext memory context = abi.decode( - hookContext_, - (Hook.ExecutionContext) - ); + ) external override onlyRole(AccessControl.HOOK_CALLER_ROLE) returns (HookResult result, bytes memory hookData) { + Hook.ExecutionContext memory context = abi.decode(hookContext_, (Hook.ExecutionContext)); _validateConfig(context.config); hookData = _executeSyncCall(context.config, context.params); result = HookResult.Completed; emit SyncHookExecuted(address(this), result, hookContext_, hookData); } - /// @dev Internal function to execute a synchronous call. - /// the function should revert in case of error + /// @dev Executes a synchronous call. This reverts in case of any errors. /// @param hookConfig_ The configuration of the hook. /// @param hookParams_ The parameters for the hook. /// @return hookData The data returned by the hook. diff --git a/contracts/interfaces/IIPAssetRegistry.sol b/contracts/interfaces/IIPAssetRegistry.sol index 51e8f8d1..2c12f4c7 100644 --- a/contracts/interfaces/IIPAssetRegistry.sol +++ b/contracts/interfaces/IIPAssetRegistry.sol @@ -1,11 +1,8 @@ -import { IPAsset } from "contracts/lib/IPAsset.sol"; - // SPDX-License-Identifier: MIT pragma solidity ^0.8.19; /// @title Global IP Asset Registry Interface interface IIPAssetRegistry { - /// @notice Emits when a new IP asset is registered. /// @param ipAssetId_ The global IP asset identifier. /// @param name_ The assigned name for the IP asset. @@ -24,20 +21,11 @@ interface IIPAssetRegistry { /// @param ipAssetId_ The identifier of the IP asset being transferred. /// @param oldIPOrg_ The original administering IP Org of the IP asset. /// @param newIPOrg_ The new administering IP Org of the IP asset. - event IPOrgTransferred( - uint256 indexed ipAssetId_, - address indexed oldIPOrg_, - address indexed newIPOrg_ - ); + event IPOrgTransferred(uint256 indexed ipAssetId_, address indexed oldIPOrg_, address indexed newIPOrg_); /// @notice Emits when an IP asset has its status changed. /// @param ipAssetId_ The identifier of the IP asset whose status changed. /// @param oldStatus_ The original status associated with the IP asset. /// @param newStatus_ The new status associated with the IP asset. - event StatusChanged( - uint256 indexed ipAssetId_, - uint8 oldStatus_, - uint8 newStatus_ - ); - + event StatusChanged(uint256 indexed ipAssetId_, uint8 oldStatus_, uint8 newStatus_); } diff --git a/contracts/interfaces/access-control/IAccessControlled.sol b/contracts/interfaces/access-control/IAccessControlled.sol index 6964494e..4f90f216 100644 --- a/contracts/interfaces/access-control/IAccessControlled.sol +++ b/contracts/interfaces/access-control/IAccessControlled.sol @@ -2,13 +2,16 @@ 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. + /// @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 AccessControlSingleton instance. - /// @param accessControl_ address of the new instance of AccessControlSingleton. + /// @notice Sets the Access Control Singleton instance. + /// @param accessControl_ address of the new instance of the Access Control Singleton. function setAccessControl(address accessControl_) external; } diff --git a/contracts/interfaces/hooks/base/ICallbackHandler.sol b/contracts/interfaces/hooks/base/ICallbackHandler.sol index 8058f168..94aca590 100644 --- a/contracts/interfaces/hooks/base/ICallbackHandler.sol +++ b/contracts/interfaces/hooks/base/ICallbackHandler.sol @@ -1,13 +1,14 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.19; + import { IERC165 } from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; -/// @title ICallbackHandler +/// @title Callback Handler Interface /// @notice This interface defines the method for handling hook callbacks. -/// @dev Modules that call the AsyncHook usually implement this interface. +/// @dev Modules that call AsyncHooks should implement this interface. interface ICallbackHandler is IERC165 { /// @notice Handles a callback from an asynchronous call. - /// @param requestId_ The ID of the request. + /// @param requestId_ The id of the request. /// @param callbackData_ The data returned by the callback. function handleHookCallback(bytes32 requestId_, bytes calldata callbackData_) external; } diff --git a/contracts/interfaces/hooks/base/IHook.sol b/contracts/interfaces/hooks/base/IHook.sol index 476eaf5e..07780423 100644 --- a/contracts/interfaces/hooks/base/IHook.sol +++ b/contracts/interfaces/hooks/base/IHook.sol @@ -1,42 +1,35 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.19; -/// @notice An enum representing the various result states of the hook. -/// @dev Pending: The hook is currently executing and has not yet completed; indicates asynchronous operation. -/// @dev Completed: The hook has completed successfully. +/// @notice Enum for representing various states of an async hook. enum HookResult { - Pending, - Completed + Pending, // Indicates the hook is ongoing execution. + Completed // Indicates the hook has successfully completed. } -/// @title IHook -/// @notice This interface defines the methods for synchronous and asynchronous hooks. +/// @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 executing a hook. It is an encoded version of Hook.ExecutionContext + /// @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); + function executeSync(bytes calldata hookContext_) external returns (HookResult result, bytes memory hookData); /// @notice Executes an asynchronous hook. - /// @param hookContext_ The context of executing a hook. It is an encoded version of Hook.ExecutionContext + /// @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. + /// @return requestId The id of the request. function executeAsync( bytes calldata hookContext_, address callbackHandler_ - ) - external - returns (HookResult result, bytes memory hookData, bytes32 requestId); + ) external returns (HookResult result, bytes memory hookData, bytes32 requestId); /// @notice Validates the configuration for the hook. - /// @dev This function should be overridden in concrete Hook to provide specific validation logic. + /// @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; } diff --git a/contracts/interfaces/ip-org/IIPOrg.sol b/contracts/interfaces/ip-org/IIPOrg.sol index 129b8435..08967045 100644 --- a/contracts/interfaces/ip-org/IIPOrg.sol +++ b/contracts/interfaces/ip-org/IIPOrg.sol @@ -1,12 +1,10 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.13; - -import { IERC721Metadata } from "@openzeppelin/contracts/token/ERC721/extensions/IERC721Metadata.sol"; -import { IPAsset } from "contracts/lib/IPAsset.sol"; +pragma solidity ^0.8.19; /// @notice IP Org Interface interface IIPOrg { - /// @notice Returns the current owner of the IP asset within th IP Org. + /// @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. @@ -28,12 +26,15 @@ interface IIPOrg { 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. + /// @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. diff --git a/contracts/interfaces/ip-org/IIPOrgController.sol b/contracts/interfaces/ip-org/IIPOrgController.sol index 994092b3..a4ef8ec2 100644 --- a/contracts/interfaces/ip-org/IIPOrgController.sol +++ b/contracts/interfaces/ip-org/IIPOrgController.sol @@ -1,43 +1,26 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.19; -import { IVersioned } from "../utils/IVersioned.sol"; -import { IPOrgParams } from "contracts/lib/IPOrgParams.sol"; - /// @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 - ); + 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 - ); + 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 - ); + event IPOrgPendingOwnerSet(address ipOrg, address pendingOwner); /// @notice Registers a new IP Org. /// @param owner_ The address of the IP Org owner. @@ -45,11 +28,11 @@ interface IIPOrgController { /// @param symbol_ Metadata symbol to attach to the IP Org. /// @param ipAssetTypes_ String descriptors of the IP asset types available. function registerIpOrg( - address owner_, + address owner_, string calldata name_, string calldata symbol_, string[] calldata ipAssetTypes_ - ) external returns(address); + ) external returns (address); /// @notice Checks whether an IP Org exists. function isIpOrg(address ipOrg_) external view returns (bool); diff --git a/contracts/interfaces/modules/IGateway.sol b/contracts/interfaces/modules/IGateway.sol index 317518b3..69f55264 100644 --- a/contracts/interfaces/modules/IGateway.sol +++ b/contracts/interfaces/modules/IGateway.sol @@ -8,7 +8,6 @@ import { ModuleDependencies } from "contracts/lib/modules/Module.sol"; /// 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. @@ -17,5 +16,4 @@ interface IGateway { /// @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 e7421594..a15e6fa4 100644 --- a/contracts/interfaces/modules/IModuleRegistry.sol +++ b/contracts/interfaces/modules/IModuleRegistry.sol @@ -4,45 +4,31 @@ 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 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 - ); + 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 - ); + 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 - ); + event ModuleRemoved(ModuleKey indexed key, address indexed module); /// @notice Emits when a module is executed for an IP Org. - event ModuleExecuted ( + event ModuleExecuted( address indexed ipOrg, string moduleKey, address indexed caller, @@ -52,26 +38,13 @@ interface IModuleRegistry { ); /// @notice Emits when a module is configured for an IP Org. - event ModuleConfigured( - address indexed ipOrg, - string moduleKey, - address indexed caller, - bytes params - ); + 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 - ); + 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 - ); + 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. @@ -93,5 +66,4 @@ interface IModuleRegistry { /// @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 9b76d9f5..2e4b9788 100644 --- a/contracts/interfaces/modules/base/IModule.sol +++ b/contracts/interfaces/modules/base/IModule.sol @@ -1,11 +1,13 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.13; +pragma solidity ^0.8.19; 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. +/// @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); @@ -19,14 +21,14 @@ interface IModule { 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. - /// It's up to the module to decode and encode params appropriately. - /// @param ipOrg_ address of the IPOrg or zero address - /// @param caller_ address requesting the execution - /// @param moduleParams_ encoded params for module action - /// @param preHookParams_ encoded params for pre action hooks - /// @param postHookParams_ encoded params for post action hooks + /// @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_, @@ -36,16 +38,12 @@ interface IModule { bytes[] calldata postHookParams_ ) external returns (bytes memory result); - /// @notice Configuration entrypoint. - /// @dev It will verify params and configure the module. - /// It's up to the module to decode and encode params appropriately. - /// @param ipOrg_ address of the IPOrg or zero address - /// @param caller_ address requesting the execution - /// @param params_ encoded params for module configuration - /// @return result of the module configuration - function configure( - IIPOrg ipOrg_, - address caller_, - bytes calldata params_ - ) 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); } diff --git a/contracts/interfaces/modules/licensing/ILicensingModule.sol b/contracts/interfaces/modules/licensing/ILicensingModule.sol index f77cfa1b..5d6a7e99 100644 --- a/contracts/interfaces/modules/licensing/ILicensingModule.sol +++ b/contracts/interfaces/modules/licensing/ILicensingModule.sol @@ -4,16 +4,14 @@ pragma solidity ^0.8.19; import { IModule } from "contracts/interfaces/modules/base/IModule.sol"; import { Licensing } from "contracts/lib/modules/Licensing.sol"; -/// @title ILicensingModule +/// @title Licensing Module Interface interface ILicensingModule is IModule { - - /// Emits when an IP org picks a licensing framework - /// and sets its configuration. - /// @param ipOrg address of the IP org - /// @param frameworkId the ID of the licensing framework - /// @param url to the legal document - /// @param licensorConfig the configuration of the licensor - /// @param values the values of the parameters (tag and bytes value) + /// Emits when an IP org picks a licensing framework and sets its configuration. + /// @param ipOrg Address of the IP org whose license framework is being set. + /// @param frameworkId The uint256 id of the set licensing framework. + /// @param url A string URL which points to the associated legal document. + /// @param licensorConfig Configuration associated with the framework's licensor. + /// @param values A list of terms describing the licensing framework. event IpOrgLicensingFrameworkSet( address indexed ipOrg, string frameworkId, @@ -22,15 +20,13 @@ interface ILicensingModule is IModule { Licensing.ParamValue[] values ); - /// Gets the licensing framework of an IP org. + /// @notice Gets the licensing framework for an IP org. + /// @param ipOrg_ The address of the selected IP Org. function getIpOrgLicensorConfig(address ipOrg_) external view returns (Licensing.LicensorConfig); /// Gets the value set by an IP org for a parameter of a licensing framework. /// If no value is set (bytes.length==0), licensors will be able to set their value. /// @param ipOrg_ address of the IP org /// @param paramTag_ string tag of the parameter - function getIpOrgValueForParam( - address ipOrg_, - string calldata paramTag_ - ) external view returns (bytes memory); + function getIpOrgValueForParam(address ipOrg_, string calldata paramTag_) external view returns (bytes memory); } diff --git a/contracts/interfaces/modules/registration/IRegistrationModule.sol b/contracts/interfaces/modules/registration/IRegistrationModule.sol index 1d8380e5..4d94471f 100644 --- a/contracts/interfaces/modules/registration/IRegistrationModule.sol +++ b/contracts/interfaces/modules/registration/IRegistrationModule.sol @@ -1,22 +1,15 @@ // SPDX-License-Identifier: MIT 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 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. /// @param baseURI The base token URI to be used for token metadata. /// @param contractURI The contract URI to be used for contract metadata. - event MetadataUpdated( - address indexed ipOrg, - string baseURI, - string contractURI - ); + event MetadataUpdated(address indexed ipOrg, string baseURI, string contractURI); /// @notice Emits when a new IP asset is registered. /// @param ipAssetId The identifier of the newly registered IP asset. @@ -66,7 +59,11 @@ interface IRegistrationModule is IModule { /// @param ipOrgAssetId_ The local id of the IP asset within the IP Org. /// @param ipOrgAssetType_ The IP Org asset type. /// @return The token URI associated with the IP Org. - function tokenURI(address ipOrg_, uint256 ipOrgAssetId_, uint8 ipOrgAssetType_) external view returns (string memory); + function tokenURI( + address ipOrg_, + uint256 ipOrgAssetId_, + uint8 ipOrgAssetType_ + ) external view returns (string memory); /// @notice Gets the contract URI for an IP Org. /// @param ipOrg_ The address of the IP Org. @@ -78,5 +75,4 @@ interface IRegistrationModule is IModule { /// @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 cc22d4c6..383da3a6 100644 --- a/contracts/interfaces/modules/relationships/IRelationshipModule.sol +++ b/contracts/interfaces/modules/relationships/IRelationshipModule.sol @@ -1,13 +1,12 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.13; +pragma solidity ^0.8.19; import { LibRelationship } from "contracts/lib/modules/LibRelationship.sol"; import { IModule } from "contracts/interfaces/modules/base/IModule.sol"; /// @title IRelationshipModule -/// @notice Interface for the RelationshipModule. +/// @notice Interface for the RelationshipModule. interface IRelationshipModule is IModule { - /// Emitted with a new Relationship Type definitions is created event RelationshipTypeSet( // Short string naming the type @@ -57,11 +56,17 @@ interface IRelationshipModule is IModule { /// @param ipOrg_ IP Org address or zero address for protocol level relationships /// @param relType_ the name of the relationship type /// @return result the relationship type definition - function getRelationshipType(address ipOrg_, string memory relType_) external view returns (LibRelationship.RelationshipType memory); + function getRelationshipType( + address ipOrg_, + string memory relType_ + ) external view returns (LibRelationship.RelationshipType memory); + /// Gets relationship definition for a given relationship id function getRelationship(uint256 relationshipId_) external view returns (LibRelationship.Relationship memory); + /// Gets relationship id for a given relationship function getRelationshipId(LibRelationship.Relationship calldata rel_) external view returns (uint256); + /// Checks if a relationship has been set - function relationshipExists(LibRelationship.Relationship calldata rel_) external view returns (bool); + function relationshipExists(LibRelationship.Relationship calldata rel_) external view returns (bool); } diff --git a/contracts/interfaces/utils/IPolygonTokenClient.sol b/contracts/interfaces/utils/IPolygonTokenClient.sol new file mode 100644 index 00000000..1e8d9cf5 --- /dev/null +++ b/contracts/interfaces/utils/IPolygonTokenClient.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +/// @notice Interface for processing token requests via clients on Polygon. +interface IPolygonTokenClient { + function sendRequest( + bytes32 requestId, + address requester, + address tokenAddress, + address tokenOwnerAddress, + address callbackAddr, + bytes4 callbackFunctionSignature + ) external; +} diff --git a/contracts/interfaces/utils/IVersioned.sol b/contracts/interfaces/utils/IVersioned.sol deleted file mode 100644 index 64f110ec..00000000 --- a/contracts/interfaces/utils/IVersioned.sol +++ /dev/null @@ -1,7 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.13; - -interface IVersioned { - function version() external pure returns (string memory); -} diff --git a/contracts/ip-org/IPOrg.sol b/contracts/ip-org/IPOrg.sol index 8ed84977..8ecbcf45 100644 --- a/contracts/ip-org/IPOrg.sol +++ b/contracts/ip-org/IPOrg.sol @@ -1,16 +1,12 @@ // 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; +// See https://github.com/storyprotocol/protocol-contracts/blob/main/StoryProtocol-AlphaTestingAgreement-17942166.3.pdf +pragma solidity ^0.8.19; -import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; import { IPOrgController } from "contracts/ip-org/IPOrgController.sol"; import { IIPOrg } from "contracts/interfaces/ip-org/IIPOrg.sol"; import { IModuleRegistry } from "contracts/interfaces/modules/IModuleRegistry.sol"; import { IRegistrationModule } from "contracts/interfaces/modules/registration/IRegistrationModule.sol"; -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 { REGISTRATION_MODULE_KEY } from "contracts/lib/modules/Module.sol"; import { Errors } from "contracts/lib/Errors.sol"; @@ -20,11 +16,7 @@ import { Errors } from "contracts/lib/Errors.sol"; /// the IP Asset group and as a conduit used by the IP registration module /// for transferring IP ownership and configuring its IP-related metadata. /// Crations of new IP Orgs happen through the IP Org Controller contract. -contract IPOrg is - IIPOrg, - ERC721Upgradeable -{ - +contract IPOrg is IIPOrg, ERC721Upgradeable { /// @notice Tracks the last index of the IP asset wrapper. uint256 public lastIndex; @@ -51,10 +43,7 @@ contract IPOrg is /// @notice Creates the IP Org implementation contract. /// @param controller_ Address of the IP Org controller. /// @param moduleRegistry_ Address of the IP asset module registry. - constructor( - address controller_, - address moduleRegistry_ - ) initializer { + constructor(address controller_, address moduleRegistry_) initializer { CONTROLLER = controller_; MODULE_REGISTRY = IModuleRegistry(moduleRegistry_); } @@ -71,9 +60,7 @@ contract IPOrg is /// @notice Retrieves the token URI for an IP Asset within the IP Asset Org. /// @param tokenId_ The id of the IP Asset within the IP Asset Org. - function tokenURI( - uint256 tokenId_ - ) public view override returns (string memory) { + function tokenURI(uint256 tokenId_) public view override returns (string memory) { address registrationModule = address(IModuleRegistry(MODULE_REGISTRY).protocolModule(REGISTRATION_MODULE_KEY)); return IRegistrationModule(registrationModule).tokenURI(address(this), tokenId_, ipOrgAssetType(tokenId_)); } @@ -95,11 +82,7 @@ contract IPOrg is /// @notice Initializes an IP Org. /// @param name_ Name to assign to the IP Org. /// @param symbol_ Symbol to assign to the IP Org. - function initialize( - string calldata name_, - string calldata symbol_ - ) public initializer { - + function initialize(string calldata name_, string calldata symbol_) public initializer { if (msg.sender != CONTROLLER) { revert Errors.Unauthorized(); } @@ -142,6 +125,4 @@ contract IPOrg is } return _ipOrgAssetTypes[id_]; } - - } diff --git a/contracts/ip-org/IPOrgController.sol b/contracts/ip-org/IPOrgController.sol index 9a3fc6dc..a4214ccd 100644 --- a/contracts/ip-org/IPOrgController.sol +++ b/contracts/ip-org/IPOrgController.sol @@ -1,15 +1,12 @@ // SPDX-License-Identifier: UNLICENSED -// See Story Protocol Alpha Agreement: https://github.com/storyprotocol/protocol-contracts/blob/main/StoryProtocol-AlphaTestingAgreement-17942166.3.pdf +// See https://github.com/storyprotocol/protocol-contracts/blob/main/StoryProtocol-AlphaTestingAgreement-17942166.3.pdf pragma solidity ^0.8.19; -import { Clones } from '@openzeppelin/contracts/proxy/Clones.sol'; +import { Clones } from "@openzeppelin/contracts/proxy/Clones.sol"; import { UUPSUpgradeable } from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; import { AccessControlledUpgradeable } from "contracts/access-control/AccessControlledUpgradeable.sol"; -import { IRegistrationModule } from "contracts/interfaces/modules/registration/IRegistrationModule.sol"; import { ModuleRegistry } from "contracts/modules/ModuleRegistry.sol"; -import { ModuleRegistryKeys } from "contracts/lib/modules/ModuleRegistryKeys.sol"; -import { IPOrgParams } from "contracts/lib/IPOrgParams.sol"; import { Errors } from "contracts/lib/Errors.sol"; import { Registration } from "contracts/lib/modules/Registration.sol"; import { IIPOrgController } from "contracts/interfaces/ip-org/IIPOrgController.sol"; @@ -22,12 +19,7 @@ import { REGISTRATION_MODULE } from "contracts/lib/modules/Module.sol"; /// @notice The IP Org Controller is the protocol-wide factory contract for creating /// and tracking IP Orgs. On top of this, it acts as the ownership controller /// for IP Orgs, allowing orgs to transfer ownership through a 2-step process. -contract IPOrgController is - UUPSUpgradeable, - AccessControlledUpgradeable, - IIPOrgController -{ - +contract IPOrgController is UUPSUpgradeable, AccessControlledUpgradeable, IIPOrgController { /// @notice Tracks ownership and registration of IPOrgs. struct IPOrgRecord { bool registered; @@ -43,14 +35,14 @@ contract IPOrgController is address owner; } + bytes32 private constant _STORAGE_LOCATION = + bytes32(uint256(keccak256("story-protocol.ip-org-factory.storage")) - 1); + /// @notice The IP asset module registry. address public immutable MODULE_REGISTRY; /// @notice The IP Org implementation address. - address public IP_ORG_IMPL; - - bytes32 private constant _STORAGE_LOCATION = bytes32(uint256(keccak256("story-protocol.ip-org-factory.storage")) - 1); - + address public ipOrgImpl; /// @notice Creates the IP Org Controller contract. /// @param moduleRegistry_ Address of the IP asset module registry. @@ -62,7 +54,7 @@ contract IPOrgController is /// @param accessControl_ Address of the contract responsible for access control. /// TODO(leeren): Deprecate this function in favor of an immutable factory. function initialize(address accessControl_) public initializer { - IP_ORG_IMPL = address(new IPOrg(address(this), MODULE_REGISTRY)); + ipOrgImpl = address(new IPOrg(address(this), MODULE_REGISTRY)); __UUPSUpgradeable_init(); __AccessControlledUpgradeable_init(accessControl_); } @@ -134,7 +126,7 @@ contract IPOrgController is IPOrgRecord storage record = _ipOrgRecord(ipOrg_); // Ensure the pending IP Org owner is accepting the ownership transfer. - if (record.pendingOwner != msg.sender) { + if (record.pendingOwner != msg.sender) { revert Errors.IPOrgController_InvalidIPOrgOwner(); } @@ -164,38 +156,17 @@ contract IPOrgController is revert Errors.ZeroAddress(); } - ipOrg_ = Clones.clone(IP_ORG_IMPL); - IPOrg(ipOrg_).initialize( - name_, - symbol_ - ); + ipOrg_ = Clones.clone(ipOrgImpl); + IPOrg(ipOrg_).initialize(name_, symbol_); // Set the registration status of the IP Asset Org to be true. IPOrgControllerStorage storage $ = _getIpOrgControllerStorage(); - $.ipOrgs[ipOrg_] = IPOrgRecord({ - registered: true, - owner: owner_, - pendingOwner: address(0) - }); - - bytes memory encodedParams = abi.encode( - Registration.SET_IP_ORG_ASSET_TYPES, - abi.encode(ipAssetTypes_) - ); - ModuleRegistry(MODULE_REGISTRY).configure( - IIPOrg(ipOrg_), - address(this), - REGISTRATION_MODULE, - encodedParams - ); - - emit IPOrgRegistered( - owner_, - ipOrg_, - name_, - symbol_, - ipAssetTypes_ - ); + $.ipOrgs[ipOrg_] = IPOrgRecord({ registered: true, owner: owner_, pendingOwner: address(0) }); + + bytes memory encodedParams = abi.encode(Registration.SET_IP_ORG_ASSET_TYPES, abi.encode(ipAssetTypes_)); + ModuleRegistry(MODULE_REGISTRY).configure(IIPOrg(ipOrg_), address(this), REGISTRATION_MODULE, encodedParams); + + emit IPOrgRegistered(owner_, ipOrg_, name_, symbol_, ipAssetTypes_); } /// @dev Gets the ownership record of an IP Org. @@ -209,19 +180,10 @@ contract IPOrgController is } /// @dev Authorizes upgrade to a new contract address via UUPS. - function _authorizeUpgrade(address) - internal - virtual - override - onlyRole(AccessControl.UPGRADER_ROLE) {} - + function _authorizeUpgrade(address) internal virtual override onlyRole(AccessControl.UPGRADER_ROLE) {} /// @dev Retrieves the ERC-1967 storage slot for the IP Org Controller. - function _getIpOrgControllerStorage() - private - pure - returns (IPOrgControllerStorage storage $) - { + function _getIpOrgControllerStorage() private pure returns (IPOrgControllerStorage storage $) { bytes32 storageLocation = _STORAGE_LOCATION; assembly { $.slot := storageLocation diff --git a/contracts/lib/AccessControl.sol b/contracts/lib/AccessControl.sol index 029c6cd4..f5de07a1 100644 --- a/contracts/lib/AccessControl.sol +++ b/contracts/lib/AccessControl.sol @@ -1,37 +1,35 @@ // SPDX-License-Identifier: UNLICENSED -// See Story Protocol Alpha Agreement: https://github.com/storyprotocol/protocol-contracts/blob/main/StoryProtocol-AlphaTestingAgreement-17942166.3.pdf +// 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 constant PROTOCOL_ADMIN_ROLE = bytes32(0); + bytes32 public constant PROTOCOL_ADMIN_ROLE = bytes32(0); // Role that can upgrade UUPS contracts or Beacon Proxies - bytes32 constant UPGRADER_ROLE = keccak256("UPGRADER_ROLE"); + bytes32 public constant UPGRADER_ROLE = keccak256("UPGRADER_ROLE"); - // Role that can perform admin tasks on the Protocol Relationship Module contract (e.g. adding new protocol-wide links) - bytes32 constant RELATIONSHIP_MANAGER_ROLE = keccak256( "RELATIONSHIP_MANAGER_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 constant LICENSING_MANAGER_ROLE = keccak256("LICENSING_MANAGER_ROLE"); + bytes32 public constant LICENSING_MANAGER_ROLE = keccak256("LICENSING_MANAGER_ROLE"); // Role that can call createIPOrg in the IPOrg Factory - bytes32 constant IPORG_CREATOR_ROLE = keccak256("IPORG_CREATOR_ROLE"); + bytes32 public constant IPORG_CREATOR_ROLE = keccak256("IPORG_CREATOR_ROLE"); // Role that can add new modules to the Module Registry - bytes32 constant MODULE_REGISTRAR_ROLE = keccak256("MODULE_REGISTRAR_ROLE"); + bytes32 public constant MODULE_REGISTRAR_ROLE = keccak256("MODULE_REGISTRAR_ROLE"); // Role that can execute modules - bytes32 constant MODULE_EXECUTOR_ROLE = keccak256("MODULE_EXECUTOR_ROLE"); + bytes32 public constant MODULE_EXECUTOR_ROLE = keccak256("MODULE_EXECUTOR_ROLE"); // Role that can execute Hooks - bytes32 constant HOOK_CALLER_ROLE = keccak256("HOOK_CALLER_ROLE"); + bytes32 public constant HOOK_CALLER_ROLE = keccak256("HOOK_CALLER_ROLE"); // Role to set legal terms in TermsRepository - bytes32 constant LICENSING_MANAGER = keccak256("LICENSING_MANAGER"); - + bytes32 public constant LICENSING_MANAGER = keccak256("LICENSING_MANAGER"); } diff --git a/contracts/lib/BitMask.sol b/contracts/lib/BitMask.sol index cbafa58c..9291a083 100644 --- a/contracts/lib/BitMask.sol +++ b/contracts/lib/BitMask.sol @@ -3,11 +3,10 @@ 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 an indeterminate large number of values, + * 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); @@ -24,13 +23,13 @@ library BitMask { } /// Sets the bit at `index`. - function _set(uint256 mask_, uint256 index_) internal pure returns(uint256) { + 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) { + function _unset(uint256 mask_, uint256 index_) internal pure returns (uint256) { uint256 indexMask = 1 << (index_ & 0xff); return mask_ &= ~indexMask; } @@ -43,7 +42,7 @@ library BitMask { if (_isSet(mask_, i)) { ++count; } - } + } uint8[] memory setBitIndexes = new uint8[](count); // Fill the array with indices of set bits uint256 index = 0; @@ -58,7 +57,7 @@ library BitMask { function _convertToMask(uint8[] memory indexes_) internal pure returns (uint256) { uint256 mask = 0; - for (uint256 i = 0; i < indexes_.length;) { + for (uint256 i = 0; i < indexes_.length; ) { mask |= 1 << (uint256(indexes_[i]) & 0xff); unchecked { i++; @@ -66,4 +65,4 @@ library BitMask { } return mask; } -} \ No newline at end of file +} diff --git a/contracts/lib/Errors.sol b/contracts/lib/Errors.sol index 2c702e35..881f486d 100644 --- a/contracts/lib/Errors.sol +++ b/contracts/lib/Errors.sol @@ -1,11 +1,9 @@ // SPDX-License-Identifier: UNLICENSED -// See Story Protocol Alpha Agreement: https://github.com/storyprotocol/protocol-contracts/blob/main/StoryProtocol-AlphaTestingAgreement-17942166.3.pdf +// See https://github.com/storyprotocol/protocol-contracts/blob/main/StoryProtocol-AlphaTestingAgreement-17942166.3.pdf pragma solidity ^0.8.19; -import { IPAsset } from "contracts/lib/IPAsset.sol"; - -/// @title Errors -/// @notice Library for all contract errors, including a set of global errors. +/// @title Errors Library +/// @notice Library for all Story Protocol contract errors. library Errors { //////////////////////////////////////////////////////////////////////////// // Globals // @@ -50,19 +48,27 @@ library Errors { /// @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 This error is thrown when the length of the hooks configuration array does not match the length of the hooks array. + + /// @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(); - error HookRegistry_RegisteringNonWhitelistedHook(address hookAddress); + /// @notice The provided hook has not been whitelisted. + error HookRegistry_RegisteringNonWhitelistedHook(address hookAddress); //////////////////////////////////////////////////////////////////////////// // BaseRelationshipProcessor // @@ -89,7 +95,7 @@ library Errors { /// @notice Hook has yet to be registered. error ModuleRegistry_HookNotRegistered(string hookKey); - + /// @notice The selected module was already registered. error ModuleRegistry_ModuleAlreadyRegistered(); @@ -323,7 +329,7 @@ library Errors { error LicenseRegistry_IPANotActive(); error LicenseRegistry_LicenseNotActive(); error LicenseRegistry_LicenseAlreadyLinkedToIpa(); - + //////////////////////////////////////////////////////////////////////////// // RegistrationModule // //////////////////////////////////////////////////////////////////////////// @@ -398,7 +404,7 @@ library Errors { /// @notice Relating unsupported src ipOrg asset type error RelationshipModule_InvalidSrcId(); - + /// @notice Relating unsupported dst ipOrg asset type error RelationshipModule_InvalidDstId(); @@ -414,10 +420,7 @@ library Errors { //////////////////////////////////////////////////////////////////////////// /// @notice Mismatch between parity of accounts and their respective allocations. - error RoyaltyNFT_AccountsAndAllocationsMismatch( - uint256 accountsLength, - uint256 allocationsLength - ); + error RoyaltyNFT_AccountsAndAllocationsMismatch(uint256 accountsLength, uint256 allocationsLength); /// @notice Invalid summation for royalty NFT allocations. error RoyaltyNFT_InvalidAllocationsSum(uint32 allocationsSum); @@ -426,6 +429,9 @@ library Errors { // Hook // //////////////////////////////////////////////////////////////////////////// + /// @notice The hook request was not found. + error Hook_RequestedNotFound(); + /// @notice The sync operation is not supported in Async hooks. error Hook_UnsupportedSyncOperation(); @@ -442,7 +448,7 @@ library Errors { error TokenGatedHook_NotTokenOwner(address tokenAddress, address ownerAddress); error Hook_AsyncHookError(bytes32 requestId, string reason); - + /// @notice Invalid Hook configuration. error Hook_InvalidHookConfig(string reason); diff --git a/contracts/lib/IPAsset.sol b/contracts/lib/IPAsset.sol index 37322a13..2f79da18 100644 --- a/contracts/lib/IPAsset.sol +++ b/contracts/lib/IPAsset.sol @@ -1,22 +1,18 @@ // SPDX-License-Identifier: UNLICENSED -// See Story Protocol Alpha Agreement: https://github.com/storyprotocol/protocol-contracts/blob/main/StoryProtocol-AlphaTestingAgreement-17942166.3.pdf +// See https://github.com/storyprotocol/protocol-contracts/blob/main/StoryProtocol-AlphaTestingAgreement-17942166.3.pdf pragma solidity ^0.8.19; -import { IPAsset } from "contracts/lib/IPAsset.sol"; -import { Errors } from "./Errors.sol"; - /// @title IP Asset Library /// @notice Library for constants, structs, and helper functions for IP assets. library IPAsset { - /// @notice Core attributes that make up an IP Asset. struct IPA { - string name; // Human-readable identifier for the IP asset. - address registrant; // Address of the initial registrant of the IP asset. - uint8 status; // Current status of the IP asset (e.g. active, expired, etc.) - address ipOrg; // Address of the governing entity of the IP asset. - bytes32 hash; // A unique content hash of the IP asset for preserving integrity. - uint64 registrationDate; // Timestamp for which the IP asset was first registered. + string name; // Human-readable identifier for the IP asset. + address registrant; // Address of the initial registrant of the IP asset. + uint8 status; // Current status of the IP asset (e.g. active, expired, etc.) + address ipOrg; // Address of the governing entity of the IP asset. + bytes32 hash; // A unique content hash of the IP asset for preserving integrity. + uint64 registrationDate; // Timestamp for which the IP asset was first registered. } /// @notice Struct for packing parameters related to IP asset registration. diff --git a/contracts/lib/IPOrgParams.sol b/contracts/lib/IPOrgParams.sol index e9e8c246..77a5a6f3 100644 --- a/contracts/lib/IPOrgParams.sol +++ b/contracts/lib/IPOrgParams.sol @@ -1,11 +1,10 @@ // SPDX-License-Identifier: UNLICENSED -// See Story Protocol Alpha Agreement: https://github.com/storyprotocol/protocol-contracts/blob/main/StoryProtocol-AlphaTestingAgreement-17942166.3.pdf +// See https://github.com/storyprotocol/protocol-contracts/blob/main/StoryProtocol-AlphaTestingAgreement-17942166.3.pdf pragma solidity ^0.8.19; /// @title IP Org Params Library /// @notice Library for constants, structs, and helper functions for IP Orgs. library IPOrgParams { - struct RegisterIPOrgParams { address registry; string name; @@ -20,5 +19,4 @@ library IPOrgParams { string name; string symbol; } - } diff --git a/contracts/lib/hooks/Hook.sol b/contracts/lib/hooks/Hook.sol index 46845810..e1366e6a 100644 --- a/contracts/lib/hooks/Hook.sol +++ b/contracts/lib/hooks/Hook.sol @@ -1,8 +1,10 @@ // SPDX-License-Identifier: UNLICENSED -// See Story Protocol Alpha Agreement: https://github.com/storyprotocol/protocol-contracts/blob/main/StoryProtocol-AlphaTestingAgreement-17942166.3.pdf +// See https://github.com/storyprotocol/protocol-contracts/blob/main/StoryProtocol-AlphaTestingAgreement-17942166.3.pdf pragma solidity ^0.8.19; + import { IHook } from "contracts/interfaces/hooks/base/IHook.sol"; -/// @title Hook + +/// @title Hooks Library /// @notice This library defines the ExecutionContext struct used when executing hooks. /// @dev The ExecutionContext struct contains two fields: config and params, both of type bytes. library Hook { @@ -12,7 +14,7 @@ library Hook { /// @dev The ExecutionContext struct is used as a parameter when executing hooks. struct ExecutionContext { /// @notice The configuration data for the hook, encoded as bytes. - /// @dev This data is used to configure the hook before execution. + /// @dev This data is used to configure the hook before execution. /// The configuration is stored in the Module. bytes config; /// @notice The parameters for the hook, encoded as bytes. diff --git a/contracts/lib/hooks/PolygonToken.sol b/contracts/lib/hooks/PolygonToken.sol index 3f3d122e..1ca68d8b 100644 --- a/contracts/lib/hooks/PolygonToken.sol +++ b/contracts/lib/hooks/PolygonToken.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: MIT +// SPDX-License-Identifier: MIT pragma solidity ^0.8.19; /** diff --git a/contracts/lib/hooks/TokenGated.sol b/contracts/lib/hooks/TokenGated.sol index d7c21f51..67b02d41 100644 --- a/contracts/lib/hooks/TokenGated.sol +++ b/contracts/lib/hooks/TokenGated.sol @@ -1,5 +1,4 @@ -// SPDX-License-Identifier: UNLICENSED -// See Story Protocol Alpha Agreement: https://github.com/storyprotocol/protocol-contracts/blob/main/StoryProtocol-AlphaTestingAgreement-17942166.3.pdf +// SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.19; /// @title TokenGated diff --git a/contracts/lib/modules/Collect.sol b/contracts/lib/modules/Collect.sol deleted file mode 100644 index 4b536f47..00000000 --- a/contracts/lib/modules/Collect.sol +++ /dev/null @@ -1,71 +0,0 @@ -// 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.19; - -/// @title Collect Module Library -library Collect { - - //////////////////////////////////////////////////////////////////////////// - // CollectModule // - //////////////////////////////////////////////////////////////////////////// - - /// @notice Parameters passed to initialize a collect module for an IP asset. - struct InitCollectParams { - uint256 ipAssetId; // The id of the IP asset under the franchise. - address collectNftImpl; // The address of the collect NFT impl to use. - bytes data; // Additional data to be used for initialization. - } - - /// @notice Parameters passed for collect processing for an IP asset. - struct CollectParams { - uint256 ipAssetId; // The id of the IP asset being collected. - address collector; // The address designated for NFT collection. - bytes collectData; // Additional data passed for module collection. - bytes collectNftInitData; // Additional data passed for NFT initialization. - bytes collectNftData; // Additional data passed for NFT collection. - } - - /// @notice Collect module settings saved for a franchise IP asset. - /// @dev A zero address `collectNftImpl` means to use a module default NFT impl. - struct CollectInfo { - bool initialized; // Whether the collect module was initialized. - address collectNft; // The collect NFT that an IP asset is bound to. - address collectNftImpl; // The collect NFT impl address being used. - } - - /// @notice Identifies the collect payment type configured for an IP asset. - /// TODO: Add ERC-721 and ERC-1155 as configurable payment types. - enum PaymentType { - NATIVE, // Utilize the native token (e.g. ETH on Ethereum or OP on Optimism) - ERC20 // Utilize an ERC-20 token - } - - /// @notice Payment collect module settings saved for a franchise IP asset. - struct CollectPaymentInfo { - address paymentToken; // The payment token address (if not native). - PaymentType paymentType; // The type of payment being made. - uint256 paymentAmount; // The required amount of the payment token. - address payable paymentRecipient; // Payment receipient address. - } - - /// @notice Parameters passed for collect payment processing for an IP asset. - /// TODO: Add a signature field to accept signature-relayed collects. - struct CollectPaymentParams { - address paymentToken; // The payment token address (if not native). - PaymentType paymentType; // The type of payment being made. - uint256 paymentAmount; // The required amount of the payment token. - } - - //////////////////////////////////////////////////////////////////////////// - // CollectNFT // - //////////////////////////////////////////////////////////////////////////// - - /// @notice Parameters passed to initialize a collect NFT. - struct InitCollectNFTParams { - address registry; // Address of the registry - address ipAssetOrg; // Address of the IP asset collection tied to the collect module. - uint256 ipAssetId; // The id of the IP asset bound to the collect NFT. - bytes data; // Additional data used for NFT initialization. - } - -} diff --git a/contracts/lib/modules/LibRelationship.sol b/contracts/lib/modules/LibRelationship.sol index 6f404675..bcba5950 100644 --- a/contracts/lib/modules/LibRelationship.sol +++ b/contracts/lib/modules/LibRelationship.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: UNLICENSED -// See Story Protocol Alpha Agreement: https://github.com/storyprotocol/protocol-contracts/blob/main/StoryProtocol-AlphaTestingAgreement-17942166.3.pdf +// See https://github.com/storyprotocol/protocol-contracts/blob/main/StoryProtocol-AlphaTestingAgreement-17942166.3.pdf pragma solidity ^0.8.19; /// @title Relationship Module Library @@ -43,10 +43,10 @@ library LibRelationship { struct CreateRelationshipParams { string relType; - address srcAddress; - uint srcId; + address srcAddress; + uint256 srcId; address dstAddress; - uint dstId; + uint256 dstId; } address public constant PROTOCOL_LEVEL_RELATIONSHIP = address(0); @@ -54,5 +54,4 @@ library LibRelationship { bytes32 public constant ADD_REL_TYPE_CONFIG = keccak256("ADD_REL_TYPE"); bytes32 public constant REMOVE_REL_TYPE_CONFIG = keccak256("REMOVE_REL_TYPE"); - } diff --git a/contracts/lib/modules/Licensing.sol b/contracts/lib/modules/Licensing.sol index 873c09b8..16394895 100644 --- a/contracts/lib/modules/Licensing.sol +++ b/contracts/lib/modules/Licensing.sol @@ -1,8 +1,7 @@ // SPDX-License-Identifier: UNLICENSED -// See Story Protocol Alpha Agreement: https://github.com/storyprotocol/protocol-contracts/blob/main/StoryProtocol-AlphaTestingAgreement-17942166.3.pdf +// See https://github.com/storyprotocol/protocol-contracts/blob/main/StoryProtocol-AlphaTestingAgreement-17942166.3.pdf pragma solidity ^0.8.19; -import { IHook } from "contracts/interfaces/hooks/base/IHook.sol"; import { FixedSet } from "contracts/utils/FixedSet.sol"; import { BitMask } from "contracts/lib/BitMask.sol"; import { Strings } from "@openzeppelin/contracts/utils/Strings.sol"; @@ -26,7 +25,7 @@ library Licensing { String, ShortStringArray, // uint256 bitmask representing indexes in choices array. ParamDefinition will have the available choices array. - MultipleChoice + MultipleChoice } enum LicensorConfig { @@ -93,17 +92,15 @@ library Licensing { LicensorConfig licensor; } - uint256 constant MAX_PARAM_TAGS = 150; + uint256 internal constant MAX_PARAM_TAGS = 150; /// Input for IpOrg legal terms configuration in LicensingModule (for now, the only option) - bytes32 constant LICENSING_FRAMEWORK_CONFIG = keccak256("LICENSING_FRAMEWORK_CONFIG"); - bytes32 constant CREATE_LICENSE = keccak256("CREATE_LICENSE"); - bytes32 constant ACTIVATE_LICENSE = keccak256("ACTIVATE_LICENSE"); - bytes32 constant LINK_LNFT_TO_IPA = keccak256("LINK_LNFT_TO_IPA"); + bytes32 public constant LICENSING_FRAMEWORK_CONFIG = keccak256("LICENSING_FRAMEWORK_CONFIG"); + bytes32 public constant CREATE_LICENSE = keccak256("CREATE_LICENSE"); + bytes32 public constant ACTIVATE_LICENSE = keccak256("ACTIVATE_LICENSE"); + bytes32 public constant LINK_LNFT_TO_IPA = keccak256("LINK_LNFT_TO_IPA"); - function _statusToString( - LicenseStatus status_ - ) internal pure returns (string memory) { + function _statusToString(LicenseStatus status_) internal pure returns (string memory) { if (status_ == LicenseStatus.Unset) { return "Unset"; } else if (status_ == LicenseStatus.Active) { @@ -130,17 +127,12 @@ library Licensing { return result; } - function _encodeMultipleChoice( - uint8[] memory choiceIndexes_ - ) internal pure returns (bytes memory value) { + function _encodeMultipleChoice(uint8[] memory choiceIndexes_) internal pure returns (bytes memory value) { uint256 mask = BitMask._convertToMask(choiceIndexes_); return abi.encode(mask); } - function _validateParamValue( - ParamDefinition memory paramDef_, - bytes memory value_ - ) internal pure returns (bool) { + 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("")) { @@ -149,35 +141,30 @@ library Licensing { if (paramDef_.paramType == Licensing.ParameterType.Bool) { abi.decode(value_, (bool)); return true; - } else if (paramDef_.paramType == Licensing.ParameterType.Number) { - if (abi.decode(value_, (uint256)) == 0) { - return false; - } - } else if (paramDef_.paramType == Licensing.ParameterType.Address) { + } 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 - if (abi.decode(value_, (address)) == address(0)) { - return false; - } - } else if (paramDef_.paramType == Licensing.ParameterType.String) { - abi.decode(value_, (string)); - // WARNING: Do proper string validation off chain. - if ( - keccak256(value_) == keccak256(abi.encode(" ")) || - keccak256(value_) == keccak256(abi.encode("")) - ) { - return false; - } - } else if (paramDef_.paramType == Licensing.ParameterType.ShortStringArray) { - // WARNING: Do proper string validation off chain. - ShortString[] memory result = abi.decode(value_, (ShortString[])); - if (result.length == 0) { - return false; - } - } else if (paramDef_.paramType == Licensing.ParameterType.MultipleChoice) { - ShortString[] memory available = abi.decode(paramDef_.availableChoices, (ShortString[])); - if (available.length == 0) { - return false; - } + 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; } @@ -187,16 +174,18 @@ library Licensing { uint256 len = ss.length; for (uint256 i = 0; i < len; i++) { ShortString s = ss[i]; - result = string(abi.encodePacked(result, '"', s.toString(), '"')); + result = string(abi.encodePacked(result, "\"", s.toString(), "\"")); // solhint-disable-line if (i != len - 1) { - result = string(abi.encodePacked(result, ',')); + result = string(abi.encodePacked(result, ",")); } - } return string(abi.encodePacked(result, "]")); } - function _getDecodedParamString(Licensing.ParamDefinition memory paramDef_, bytes memory value_) internal pure returns (string memory) { + 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) { @@ -215,5 +204,4 @@ library Licensing { } return ""; } - } diff --git a/contracts/lib/modules/ModuleRegistryKeys.sol b/contracts/lib/modules/ModuleRegistryKeys.sol index f196a280..4cc2cab6 100644 --- a/contracts/lib/modules/ModuleRegistryKeys.sol +++ b/contracts/lib/modules/ModuleRegistryKeys.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: UNLICENSED -// See Story Protocol Alpha Agreement: https://github.com/storyprotocol/protocol-contracts/blob/main/StoryProtocol-AlphaTestingAgreement-17942166.3.pdf +// See https://github.com/storyprotocol/protocol-contracts/blob/main/StoryProtocol-AlphaTestingAgreement-17942166.3.pdf pragma solidity ^0.8.19; library ModuleRegistryKeys { diff --git a/contracts/lib/modules/Registration.sol b/contracts/lib/modules/Registration.sol index 81eceb10..515d7257 100644 --- a/contracts/lib/modules/Registration.sol +++ b/contracts/lib/modules/Registration.sol @@ -1,10 +1,9 @@ // SPDX-License-Identifier: UNLICENSED -// See Story Protocol Alpha Agreement: https://github.com/storyprotocol/protocol-contracts/blob/main/StoryProtocol-AlphaTestingAgreement-17942166.3.pdf +// 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; @@ -30,5 +29,4 @@ library Registration { // 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"); - } diff --git a/contracts/lib/modules/Royalties.sol b/contracts/lib/modules/Royalties.sol index a3511f0a..b82aa8d9 100644 --- a/contracts/lib/modules/Royalties.sol +++ b/contracts/lib/modules/Royalties.sol @@ -1,14 +1,12 @@ // SPDX-License-Identifier: UNLICENSED -// See Story Protocol Alpha Agreement: https://github.com/storyprotocol/protocol-contracts/blob/main/StoryProtocol-AlphaTestingAgreement-17942166.3.pdf +// See https://github.com/storyprotocol/protocol-contracts/blob/main/StoryProtocol-AlphaTestingAgreement-17942166.3.pdf pragma solidity ^0.8.19; /// @title Royalties Module Library library Royalties { - - /// @notice Struct for configuring royalty allocations. + /// @notice Struct for configuring royalty allocations. struct ProportionData { address[] accounts; uint32[] percentAllocations; } - } diff --git a/contracts/lib/modules/SPUMLParams.sol b/contracts/lib/modules/SPUMLParams.sol index 882e2869..1e945117 100644 --- a/contracts/lib/modules/SPUMLParams.sol +++ b/contracts/lib/modules/SPUMLParams.sol @@ -1,10 +1,9 @@ // SPDX-License-Identifier: UNLICENSED -// See Story Protocol Alpha Agreement: https://github.com/storyprotocol/protocol-contracts/blob/main/StoryProtocol-AlphaTestingAgreement-17942166.3.pdf +// See https://github.com/storyprotocol/protocol-contracts/blob/main/StoryProtocol-AlphaTestingAgreement-17942166.3.pdf pragma solidity ^0.8.19; import { Licensing } from "contracts/lib/modules/Licensing.sol"; import { ShortString, ShortStrings } from "@openzeppelin/contracts/utils/ShortStrings.sol"; -import { Errors } from "contracts/lib/Errors.sol"; /// List of Protocol Term Ids (meaning the Licensing Module will have specific instructions /// for these terms without the need of a decoder) @@ -13,25 +12,25 @@ import { Errors } from "contracts/lib/Errors.sol"; library SPUMLParams { using ShortStrings for *; - string constant FRAMEWORK_ID = "SPUML-1.0"; + string public constant FRAMEWORK_ID = "SPUML-1.0"; //////////////////////////////////////////////////////////////////////////// // Parameters // //////////////////////////////////////////////////////////////////////////// - string constant CHANNELS_OF_DISTRIBUTION = "Channels-Of-Distribution"; - string constant ATTRIBUTION = "Attribution"; - string constant DERIVATIVES_ALLOWED = "Derivatives-Allowed"; - string constant DERIVATIVES_ALLOWED_OPTIONS = "Derivatives-Allowed-Options"; - + string public constant CHANNELS_OF_DISTRIBUTION = "Channels-Of-Distribution"; + string public constant ATTRIBUTION = "Attribution"; + string public constant DERIVATIVES_ALLOWED = "Derivatives-Allowed"; + string public constant DERIVATIVES_ALLOWED_OPTIONS = "Derivatives-Allowed-Options"; + //////////////////////////////////////////////////////////////////////////// // Derivative Options // //////////////////////////////////////////////////////////////////////////// - string constant ALLOWED_WITH_APPROVAL = "Allowed-With-Approval"; - uint8 constant ALLOWED_WITH_APPROVAL_INDEX = 0; - string constant ALLOWED_WITH_RECIPROCAL_LICENSE = "Allowed-Reciprocal-License"; - uint8 constant ALLOWED_WITH_RECIPROCAL_LICENSE_INDEX = 1; - string constant ALLOWED_WITH_ATTRIBUTION = "Allowed-With-Attribution"; - uint8 constant ALLOWED_WITH_ATTRIBUTION_INDEX = 2; - + string public constant ALLOWED_WITH_APPROVAL = "Allowed-With-Approval"; + uint8 public constant ALLOWED_WITH_APPROVAL_INDEX = 0; + string public constant ALLOWED_WITH_RECIPROCAL_LICENSE = "Allowed-Reciprocal-License"; + uint8 public constant ALLOWED_WITH_RECIPROCAL_LICENSE_INDEX = 1; + string public constant ALLOWED_WITH_ATTRIBUTION = "Allowed-With-Attribution"; + uint8 public constant ALLOWED_WITH_ATTRIBUTION_INDEX = 2; + // On beta version // Parameters: // string constant CONTENT_STANDARDS = "Content-Standards"; @@ -54,7 +53,6 @@ library SPUMLParams { // string constant ALLOWED_WITH_REVENUE_CEILING = "Allowed-With-Revenue-Ceiling"; // string constant DERIVATIVES_ALLOWED_TAG_AMOUNT = "Derivatives-Allowed-Tag-Amount"; - function _getDerivativeChoices() internal pure returns (ShortString[] memory) { ShortString[] memory choices = new ShortString[](3); choices[0] = ALLOWED_WITH_APPROVAL.toShortString(); @@ -63,11 +61,7 @@ library SPUMLParams { return choices; } - function _getParamDefs() - internal - pure - returns (Licensing.ParamDefinition[] memory paramDefs) - { + function _getParamDefs() internal pure returns (Licensing.ParamDefinition[] memory paramDefs) { paramDefs = new Licensing.ParamDefinition[](4); paramDefs[0] = Licensing.ParamDefinition( CHANNELS_OF_DISTRIBUTION.toShortString(), @@ -93,7 +87,5 @@ library SPUMLParams { "", // Since this is dependent on the above, default is unset abi.encode(_getDerivativeChoices()) ); - } - } diff --git a/contracts/modules/Gateway.sol b/contracts/modules/Gateway.sol index c2e51597..ea019ab5 100644 --- a/contracts/modules/Gateway.sol +++ b/contracts/modules/Gateway.sol @@ -7,12 +7,11 @@ 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 +/// @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; @@ -34,5 +33,4 @@ abstract contract Gateway is IGateway { /// @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 a2b18253..8cf1d555 100644 --- a/contracts/modules/ModuleRegistry.sol +++ b/contracts/modules/ModuleRegistry.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: UNLICENSED -// See Story Protocol Alpha Agreement: https://github.com/storyprotocol/protocol-contracts/blob/main/StoryProtocol-AlphaTestingAgreement-17942166.3.pdf +// 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"; @@ -9,21 +9,22 @@ 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. -/// It's also the entrypoint for execution and configuration of modules, either directly by users -/// or by MODULE_EXECUTOR_ROLE holders. +/// @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. @@ -32,7 +33,7 @@ contract ModuleRegistry is IModuleRegistry, AccessControlled, Multicall { /// @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_) { } + 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. @@ -60,9 +61,7 @@ contract ModuleRegistry is IModuleRegistry, AccessControlled, Multicall { /// @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) { + 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) { @@ -78,23 +77,20 @@ contract ModuleRegistry is IModuleRegistry, AccessControlled, Multicall { } // Authorize all module function dependencies for the gateway. - for (uint256 j = 0; j < fns.length; j++ ) { + 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) { + 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) { @@ -107,14 +103,13 @@ contract ModuleRegistry is IModuleRegistry, AccessControlled, Multicall { // Revoke authorizations made previously. // TODO: Change logic to track dependencies through the registry itself. - for (uint256 j = 0; j < fns.length; j++ ) { + 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); } - } } @@ -163,9 +158,7 @@ contract ModuleRegistry is IModuleRegistry, AccessControlled, Multicall { /// @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) { + function removeProtocolHook(string calldata hookKey) external onlyRole(AccessControl.MODULE_REGISTRAR_ROLE) { if (address(_protocolHooks[hookKey]) == address(0)) { revert Errors.ModuleRegistry_HookNotRegistered(hookKey); } @@ -174,13 +167,11 @@ contract ModuleRegistry is IModuleRegistry, AccessControlled, Multicall { 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) { + function removeProtocolModule(ModuleKey key_) external onlyRole(AccessControl.MODULE_REGISTRAR_ROLE) { if (_modules[key_] == address(0)) { revert Errors.ModuleRegistry_ModuleNotYetRegistered(); } @@ -245,11 +236,7 @@ contract ModuleRegistry is IModuleRegistry, AccessControlled, Multicall { /// @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 { + function configure(IIPOrg ipOrg_, string calldata moduleKey_, bytes calldata params_) external { _configure(ipOrg_, msg.sender, moduleKey_, params_); } @@ -267,6 +254,13 @@ contract ModuleRegistry is IModuleRegistry, AccessControlled, Multicall { 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_, @@ -284,6 +278,11 @@ contract ModuleRegistry is IModuleRegistry, AccessControlled, Multicall { 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_, diff --git a/contracts/modules/base/BaseModule.sol b/contracts/modules/base/BaseModule.sol index b820eede..b6bbd83d 100644 --- a/contracts/modules/base/BaseModule.sol +++ b/contracts/modules/base/BaseModule.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.13; +pragma solidity ^0.8.19; import { IModule } from "contracts/interfaces/modules/base/IModule.sol"; import { IHook, HookResult } from "contracts/interfaces/hooks/base/IHook.sol"; @@ -22,12 +22,7 @@ import { ModuleKey } from "contracts/lib/modules/Module.sol"; /// It's up to the module how to perform the actions, verifications and authorizations. /// @dev This contract should NOT have state in storage, in order to have upgradeable or non-upgradeable /// modules. -abstract contract BaseModule is - ERC165, - IModule, - ICallbackHandler, - HookRegistry -{ +abstract contract BaseModule is ERC165, IModule, ICallbackHandler, HookRegistry { using Hook for IHook; struct ModuleConstruction { @@ -69,9 +64,7 @@ abstract contract BaseModule is _; } - constructor( - ModuleConstruction memory params_ - ) HookRegistry(params_.moduleRegistry) { + constructor(ModuleConstruction memory params_) HookRegistry(params_.moduleRegistry) { if (address(params_.ipaRegistry) == address(0)) { revert Errors.BaseModule_ZeroIpaRegistry(); } @@ -117,9 +110,7 @@ abstract contract BaseModule is return _execute(context); } - function supportsInterface( - bytes4 interfaceId - ) public view virtual override(ERC165, IERC165) returns (bool) { + function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165, IERC165) returns (bool) { return interfaceId == type(ICallbackHandler).interfaceId || interfaceId == type(IModule).interfaceId || @@ -131,21 +122,12 @@ abstract contract BaseModule is /// It also will be used by ayhnc hooks to continue the execution of the module. /// @param context_ The execution context which includes all the parameters for the module execution. /// @return result The result of the execution in bytes format. - function _execute( - ModuleExecutionContext memory context_ - ) internal returns (bytes memory result) { - if ( - context_.executionHookType == HookType.PreAction && - !_executeHooks(context_) - ) { + function _execute(ModuleExecutionContext memory context_) internal returns (bytes memory result) { + if (context_.executionHookType == HookType.PreAction && !_executeHooks(context_)) { emit RequestPending(context_.caller); return ""; } - result = _performAction( - context_.ipOrg, - context_.caller, - context_.moduleParams - ); + result = _performAction(context_.ipOrg, context_.caller, context_.moduleParams); context_.executionHookType = HookType.PostAction; context_.hookPosition = 0; _executeHooks(context_); @@ -157,30 +139,23 @@ abstract contract BaseModule is /// @param ipOrg_ address of the IPOrg or zero address /// @param caller_ address requesting the execution /// @param params_ encoded configuration params - function configure(IIPOrg ipOrg_, address caller_, bytes calldata params_) onlyModuleRegistry external returns (bytes memory) { + function configure( + IIPOrg ipOrg_, + address caller_, + bytes calldata params_ + ) external onlyModuleRegistry returns (bytes memory) { return _configure(ipOrg_, caller_, params_); } - function _executeHooks( - ModuleExecutionContext memory context_ - ) internal virtual returns (bool) { - address[] memory hooks = _hooksForType( - context_.executionHookType, - context_.hookRegistryKey - ); - bytes[] memory hooksConfig = _hooksConfigForType( - context_.executionHookType, - context_.hookRegistryKey - ); + function _executeHooks(ModuleExecutionContext memory context_) internal virtual returns (bool) { + address[] memory hooks = _hooksForType(context_.executionHookType, context_.hookRegistryKey); + bytes[] memory hooksConfig = _hooksConfigForType(context_.executionHookType, context_.hookRegistryKey); uint256 hooksLength = hooks.length; - bytes[] memory hookParams = context_.executionHookType == - HookType.PreAction + bytes[] memory hookParams = context_.executionHookType == HookType.PreAction ? context_.preHookParams : context_.postHookParams; if (hookParams.length != hooksLength) { - revert Errors.BaseModule_HooksParamsLengthMismatch( - uint8(context_.executionHookType) - ); + revert Errors.BaseModule_HooksParamsLengthMismatch(uint8(context_.executionHookType)); } // Continue to execute each hook from the current executing position in the hook list. for (uint256 i = context_.hookPosition; i < hooksLength; i++) { @@ -193,9 +168,7 @@ abstract contract BaseModule is // check hook type, if async, call executeAsync, otherwise call executeSync HookResult result; if (IHook(hooks[i]).canSupportSyncCall()) { - (result, ) = IHook(hooks[i]).executeSync( - abi.encode(hookContext) - ); + (result, ) = IHook(hooks[i]).executeSync(abi.encode(hookContext)); } else { result = _executeAsyncHook(hooks[i], hookContext, context_); } @@ -205,17 +178,9 @@ abstract contract BaseModule is return true; } - function _configure( - IIPOrg ipOrg_, - address caller_, - bytes calldata params_ - ) internal virtual returns (bytes memory); + function _configure(IIPOrg ipOrg_, address caller_, bytes calldata params_) internal virtual returns (bytes memory); - function _verifyExecution( - IIPOrg ipOrg_, - address caller_, - bytes calldata params_ - ) internal virtual {} + function _verifyExecution(IIPOrg ipOrg_, address caller_, bytes calldata params_) internal virtual {} function _performAction( IIPOrg ipOrg_, @@ -231,7 +196,7 @@ abstract contract BaseModule is } /// @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. + /// This function should be overridden in derived contracts to provide actual logic for generating the registry key. /// @param ipOrg_ The address of the IPOrg. /// @param caller_ The address requesting the execution. /// @param params_ The encoded parameters for module action. @@ -247,10 +212,7 @@ abstract contract BaseModule is Hook.ExecutionContext memory hookContext_, ModuleExecutionContext memory moduleContext_ ) internal virtual returns (HookResult) { - (HookResult result, , bytes32 requestId) = IHook(hook_).executeAsync( - abi.encode(hookContext_), - address(this) - ); + (HookResult result, , bytes32 requestId) = IHook(hook_).executeAsync(abi.encode(hookContext_), address(this)); // only store the context if the hook is async if (result == HookResult.Pending) { _asyncContexts[requestId] = moduleContext_; @@ -262,14 +224,8 @@ abstract contract BaseModule is /// @dev This function is called by the external service when the asynchronous hook is completed. /// @param requestId_ The ID of the request. /// @param callbackData_ The data returned by the callback. - function handleHookCallback( - bytes32 requestId_, - bytes calldata callbackData_ - ) external virtual override { - (bool isPass, string memory errorMsg) = abi.decode( - callbackData_, - (bool, string) - ); + function handleHookCallback(bytes32 requestId_, bytes calldata callbackData_) external virtual override { + (bool isPass, string memory errorMsg) = abi.decode(callbackData_, (bool, string)); if (isPass) { _asyncContexts[requestId_].hookPosition++; diff --git a/contracts/modules/base/HookRegistry.sol b/contracts/modules/base/HookRegistry.sol index 6f596ef4..3a7031f1 100644 --- a/contracts/modules/base/HookRegistry.sol +++ b/contracts/modules/base/HookRegistry.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.13; +pragma solidity ^0.8.19; import { Errors } from "contracts/lib/Errors.sol"; import { IHook } from "contracts/interfaces/hooks/base/IHook.sol"; @@ -10,7 +10,7 @@ import { ModuleRegistry } from "contracts/modules/ModuleRegistry.sol"; /// @notice This contract is an abstract contract that manages the registration of hooks. /// Hooks are small pieces of code that are called before and after certain operations in the protocol. /// @dev Each module that inherits from HookRegistry has its own local hook registry. -/// The HookRegistry supports multiple arrays of hooks, each associated with a different configuration, separated by a `registryKey` +/// The HookRegistry supports multiple hook arrays, each associated with differentconfigs separated by a `registryKey` /// Each module can define its own approach to generate its unique registryKey. abstract contract HookRegistry { ModuleRegistry public immutable MODULE_REGISTRY; @@ -39,8 +39,7 @@ abstract contract HookRegistry { revert Errors.ZeroAddress(); } - if (msg.sender != ipOrg_.owner()) - revert Errors.HookRegistry_CallerNotIPOrgOwner(); + if (msg.sender != ipOrg_.owner()) revert Errors.HookRegistry_CallerNotIPOrgOwner(); _; } @@ -81,11 +80,7 @@ abstract contract HookRegistry { /// @param registryKey_ The registry key for the hook. /// @param hook_ The address of the hook. /// @return True if the hook is registered, false otherwise. - function isRegistered( - HookType hookType_, - bytes32 registryKey_, - address hook_ - ) external view returns (bool) { + function isRegistered(HookType hookType_, bytes32 registryKey_, address hook_) external view returns (bool) { return hookIndex(hookType_, registryKey_, hook_) != INDEX_NOT_FOUND; } @@ -95,11 +90,7 @@ abstract contract HookRegistry { /// @param registryKey_ The registry key for the hook. /// @param index_ The index of the hook. /// @return The address of the hook. - function hookAt( - HookType hookType_, - bytes32 registryKey_, - uint256 index_ - ) external view returns (address) { + function hookAt(HookType hookType_, bytes32 registryKey_, uint256 index_) external view returns (address) { address[] memory hooks = _hooksForType(hookType_, registryKey_); if (index_ >= hooks.length) { revert Errors.HookRegistry_IndexOutOfBounds(index_); @@ -129,10 +120,7 @@ abstract contract HookRegistry { /// @param hookType_ The type of the hooks. /// @param registryKey_ The registry key for the hooks. /// @return The total number of hooks. - function totalHooks( - HookType hookType_, - bytes32 registryKey_ - ) external view returns (uint256) { + function totalHooks(HookType hookType_, bytes32 registryKey_) external view returns (uint256) { return _hooksForType(hookType_, registryKey_).length; } @@ -140,10 +128,7 @@ abstract contract HookRegistry { /// @param hookType_ The type of the hooks. /// @param registryKey_ The registry key for the hooks. /// @return The total number of hook configurations. - function totalHooksConfig( - HookType hookType_, - bytes32 registryKey_ - ) external view returns (uint256) { + function totalHooksConfig(HookType hookType_, bytes32 registryKey_) external view returns (uint256) { return _hooksConfigForType(hookType_, registryKey_).length; } @@ -152,11 +137,7 @@ abstract contract HookRegistry { /// Can only be called by the IP Org owner. /// @param hookType_ The type of the hooks to clear. /// @param registryKey_ The registry key for the hooks. - function clearHooks( - HookType hookType_, - IIPOrg ipOrg_, - bytes32 registryKey_ - ) public onlyIpOrgOwner(ipOrg_) { + function clearHooks(HookType hookType_, IIPOrg ipOrg_, bytes32 registryKey_) public onlyIpOrgOwner(ipOrg_) { if (hookType_ == HookType.PreAction && _preActionHooks[registryKey_].length > 0) { delete _preActionHooks[registryKey_]; delete _preActionHooksConfig[registryKey_]; @@ -172,11 +153,7 @@ abstract contract HookRegistry { /// @param registryKey_ The registry key for the hook. /// @param hook_ The address of the hook. /// @return The index of the hook. Returns INDEX_NOT_FOUND if the hook is not registered. - function hookIndex( - HookType hookType_, - bytes32 registryKey_, - address hook_ - ) public view returns (uint256) { + function hookIndex(HookType hookType_, bytes32 registryKey_, address hook_) public view returns (uint256) { return _hookIndex(_hooksForType(hookType_, registryKey_), hook_); } @@ -184,10 +161,7 @@ abstract contract HookRegistry { /// @param hookType_ The type of the hooks. /// @param registryKey_ The registry key for the hooks. /// @return The array of hooks. - function _hooksForType( - HookType hookType_, - bytes32 registryKey_ - ) internal view returns (address[] storage) { + function _hooksForType(HookType hookType_, bytes32 registryKey_) internal view returns (address[] storage) { if (hookType_ == HookType.PreAction) { return _preActionHooks[registryKey_]; } else { @@ -199,10 +173,7 @@ abstract contract HookRegistry { /// @param hookType_ The type of the hooks. /// @param registryKey_ The registry key for the hooks. /// @return The array of hook configurations. - function _hooksConfigForType( - HookType hookType_, - bytes32 registryKey_ - ) internal view returns (bytes[] storage) { + function _hooksConfigForType(HookType hookType_, bytes32 registryKey_) internal view returns (bytes[] storage) { if (hookType_ == HookType.PreAction) { return _preActionHooksConfig[registryKey_]; } else { @@ -251,10 +222,7 @@ abstract contract HookRegistry { /// @param hooks The array of hooks. /// @param hook_ The hook to find. /// @return The index of the hook. Returns INDEX_NOT_FOUND if the hook is not found. - function _hookIndex( - address[] storage hooks, - address hook_ - ) private view returns (uint256) { + function _hookIndex(address[] storage hooks, address hook_) private view returns (uint256) { uint256 length = hooks.length; for (uint256 i = 0; i < length; ) { if (hooks[i] == hook_) { diff --git a/contracts/modules/licensing/LicenseRegistry.sol b/contracts/modules/licensing/LicenseRegistry.sol index e7ea0f77..43a2127f 100644 --- a/contracts/modules/licensing/LicenseRegistry.sol +++ b/contracts/modules/licensing/LicenseRegistry.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: UNLICENSED -// See Story Protocol Alpha Agreement: https://github.com/storyprotocol/protocol-contracts/blob/main/StoryProtocol-AlphaTestingAgreement-17942166.3.pdf +// See https://github.com/storyprotocol/protocol-contracts/blob/main/StoryProtocol-AlphaTestingAgreement-17942166.3.pdf pragma solidity ^0.8.19; import { Licensing } from "contracts/lib/modules/Licensing.sol"; @@ -8,15 +8,15 @@ import { Errors } from "contracts/lib/Errors.sol"; import { ModuleRegistry } from "contracts/modules/ModuleRegistry.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 { ShortStrings } from "@openzeppelin/contracts/utils/ShortStrings.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. /// It will only be written by licensing modules. -/// It should not be upgradeable, so once a license is registered, it will be there forever regardless of +/// It should not be upgradeable, so once a license is registered, it will be there forever regardless of /// the ipOrg potentially chaning the licensing framework or Story Protocol doing upgrades. /// Licenses can be made invalid by the revoker, according to the terms of the license. contract LicenseRegistry is ERC721 { @@ -24,10 +24,7 @@ contract LicenseRegistry is ERC721 { // TODO: Figure out data needed for indexing event LicenseRegistered(uint256 indexed id, Licensing.LicenseData licenseData); - event LicenseNftLinkedToIpa( - uint256 indexed licenseId, - uint256 indexed ipAssetId - ); + event LicenseNftLinkedToIpa(uint256 indexed licenseId, uint256 indexed ipAssetId); event LicenseActivated(uint256 indexed licenseId); event LicenseRevoked(uint256 indexed licenseId); @@ -59,10 +56,7 @@ contract LicenseRegistry is ERC721 { } modifier onlyActiveOrPending(Licensing.LicenseStatus status_) { - if ( - status_ != Licensing.LicenseStatus.Active && - status_ != Licensing.LicenseStatus.PendingLicensorApproval - ) { + if (status_ != Licensing.LicenseStatus.Active && status_ != Licensing.LicenseStatus.PendingLicensorApproval) { revert Errors.LicenseRegistry_InvalidLicenseStatus(); } _; @@ -91,9 +85,7 @@ contract LicenseRegistry is ERC721 { if (licensingFrameworkRepo_ == address(0)) { revert Errors.ZeroAddress(); } - LICENSING_FRAMEWORK_REPO = LicensingFrameworkRepo( - licensingFrameworkRepo_ - ); + LICENSING_FRAMEWORK_REPO = LicensingFrameworkRepo(licensingFrameworkRepo_); } /// Creates a tradeable License NFT. @@ -105,12 +97,7 @@ contract LicenseRegistry is ERC721 { Licensing.LicenseData memory newLicense_, address licensee_, Licensing.ParamValue[] memory values_ - ) - external - onlyLicensingModule - onlyActiveOrPending(newLicense_.status) - returns (uint256) - { + ) external onlyLicensingModule onlyActiveOrPending(newLicense_.status) returns (uint256) { // NOTE: check for parent ipa validity is done in // the licensing module uint256 licenseId = ++_licenseCount; @@ -137,10 +124,7 @@ contract LicenseRegistry is ERC721 { address licensor_, address licensee_, uint256 ipaId_ - ) - external - onlyLicensingModule - returns (uint256) { + ) external onlyLicensingModule returns (uint256) { if (!isLicenseActive(parentLicenseId_)) { revert Errors.LicenseRegistry_ParentLicenseNotActive(); } @@ -160,9 +144,7 @@ contract LicenseRegistry is ERC721 { } /// Gets License struct for input id - function getLicenseData( - uint256 id_ - ) public view returns (Licensing.LicenseData memory) { + function getLicenseData(uint256 id_) public view returns (Licensing.LicenseData memory) { Licensing.LicenseData storage license = _licenses[id_]; if (license.status == Licensing.LicenseStatus.Unset) { revert Errors.LicenseRegistry_UnknownLicenseId(); @@ -217,10 +199,7 @@ contract LicenseRegistry is ERC721 { /// Links the license to an IPA /// @param licenseId_ id of the license NFT /// @param ipaId_ id of the IPA - function linkLnftToIpa( - uint256 licenseId_, - uint256 ipaId_ - ) public onlyLicensingModuleOrLicensee(licenseId_) { + function linkLnftToIpa(uint256 licenseId_, uint256 ipaId_) public onlyLicensingModuleOrLicensee(licenseId_) { _linkNftToIpa(licenseId_, ipaId_); } @@ -234,8 +213,7 @@ contract LicenseRegistry is ERC721 { _licenses[licenseId_].status == Licensing.LicenseStatus.PendingLicensorApproval || _licenses[licenseId_].status == Licensing.LicenseStatus.Unset || _licenses[licenseId_].status == Licensing.LicenseStatus.Revoked - ) - return false; + ) return false; licenseId_ = _licenses[licenseId_].parentLicenseId; } return true; @@ -243,10 +221,7 @@ contract LicenseRegistry is ERC721 { /// Called by the licensing module to activate a license, after all the activation terms pass /// @param licenseId_ id of the license - function activateLicense( - uint256 licenseId_, - address caller_ - ) external onlyLicensingModule { + function activateLicense(uint256 licenseId_, address caller_) external onlyLicensingModule { _activateLicense(licenseId_, caller_); } @@ -269,28 +244,55 @@ contract LicenseRegistry is ERC721 { function tokenURI(uint256 tokenId) public view virtual override returns (string memory) { Licensing.LicenseData memory license = getLicenseData(tokenId); // Construct the base JSON metadata with custom name format - string memory baseJson = string(abi.encodePacked( - '{"name": "Story Protocol License NFT #', Strings.toString(tokenId), - '", "description": "License agreement stating the terms of a Story Protocol IP Org", "attributes": [' - )); - + string memory baseJson = string( + /* solhint-disable */ + abi.encodePacked( + '{"name": "Story Protocol License NFT #', + Strings.toString(tokenId), + '", "description": "License agreement stating the terms of a Story Protocol IP Org", "attributes": [' + ) + /* solhint-enable */ + ); + string memory licenseAttributes1 = string( + /* solhint-disable */ abi.encodePacked( - '{"trait_type": "IP Org", "value": "', Strings.toHexString(uint160(license.ipOrg), 20), '"},', - '{"trait_type": "Framework ID", "value": "', license.frameworkId.toString(), '"},', - '{"trait_type": "Framework URL", "value": "', LICENSING_FRAMEWORK_REPO.getLicenseTextUrl(license.frameworkId.toString()), '"},', - '{"trait_type": "Status", "value": "', Licensing._statusToString(license.status), '"},' + '{"trait_type": "IP Org", "value": "', + Strings.toHexString(uint160(license.ipOrg), 20), + '"},', + '{"trait_type": "Framework ID", "value": "', + license.frameworkId.toString(), + '"},', + '{"trait_type": "Framework URL", "value": "', + LICENSING_FRAMEWORK_REPO.getLicenseTextUrl(license.frameworkId.toString()), + '"},', + '{"trait_type": "Status", "value": "', + Licensing._statusToString(license.status), + '"},' ) + /* solhint-enable */ ); string memory licenseAttributes2 = string( + /* solhint-disable */ abi.encodePacked( - '{"trait_type": "Licensor", "value": "', Strings.toHexString(uint160(license.licensor), 20), '"},', - '{"trait_type": "Licensee", "value": "', Strings.toHexString(uint160(_ownerOf(tokenId)), 20), '"},', - '{"trait_type": "Revoker", "value": "', Strings.toHexString(uint160(license.revoker), 20), '"},', - '{"trait_type": "Parent License ID", "value": "', Strings.toString(license.parentLicenseId), '"},', - '{"trait_type": "Derivative IPA", "value": "', Strings.toString(license.ipaId), '"},' + '{"trait_type": "Licensor", "value": "', + Strings.toHexString(uint160(license.licensor), 20), + '"},', + '{"trait_type": "Licensee", "value": "', + Strings.toHexString(uint160(_ownerOf(tokenId)), 20), + '"},', + '{"trait_type": "Revoker", "value": "', + Strings.toHexString(uint160(license.revoker), 20), + '"},', + '{"trait_type": "Parent License ID", "value": "', + Strings.toString(license.parentLicenseId), + '"},', + '{"trait_type": "Derivative IPA", "value": "', + Strings.toString(license.ipaId), + '"},' ) + /* solhint-enable */ ); Licensing.ParamValue[] memory params = _licenseParams[tokenId]; uint256 paramCount = params.length; @@ -301,48 +303,47 @@ contract LicenseRegistry is ERC721 { params[i].tag ); string memory value = Licensing._getDecodedParamString(paramDef, params[i].value); - - if (paramDef.paramType != Licensing.ParameterType.MultipleChoice && paramDef.paramType != Licensing.ParameterType.ShortStringArray) { - value = string(abi.encodePacked( - '"', value, '"}' - )); + + if ( + paramDef.paramType != Licensing.ParameterType.MultipleChoice && + paramDef.paramType != Licensing.ParameterType.ShortStringArray + ) { + value = string(abi.encodePacked("\"", value, "\"}")); // solhint-disable-line } else { - value = string(abi.encodePacked( - value, '}' - )); + value = string(abi.encodePacked(value, "}")); } paramAttributes = string( abi.encodePacked( - paramAttributes, '{"trait_type": "', params[i].tag.toString(), '", "value": ', value + paramAttributes, + "{\"trait_type\": \"", // solhint-disable-line + params[i].tag.toString(), + "\", \"value\": ", // solhint-disable-line + value ) ); if (i != paramCount - 1) { - paramAttributes = string(abi.encodePacked(paramAttributes, ',')); + paramAttributes = string(abi.encodePacked(paramAttributes, ",")); } else { - paramAttributes = string(abi.encodePacked(paramAttributes, ']')); + paramAttributes = string(abi.encodePacked(paramAttributes, "]")); } } - return string(abi.encodePacked( - "data:application/json;base64,", - Base64.encode( - bytes( - string(abi.encodePacked( - baseJson, - licenseAttributes1, - licenseAttributes2, - paramAttributes, - '}' + return + string( + abi.encodePacked( + "data:application/json;base64,", + Base64.encode( + bytes( + string( + abi.encodePacked(baseJson, licenseAttributes1, licenseAttributes2, paramAttributes, "}") + ) + ) ) ) - )) - )); + ); } - function _linkNftToIpa( - uint256 licenseId_, - uint256 ipaId_ - ) private onlyActive(licenseId_) { + function _linkNftToIpa(uint256 licenseId_, uint256 ipaId_) private onlyActive(licenseId_) { if (IPA_REGISTRY.status(ipaId_) != 1) { revert Errors.LicenseRegistry_IPANotActive(); } diff --git a/contracts/modules/licensing/LicensingFrameworkRepo.sol b/contracts/modules/licensing/LicensingFrameworkRepo.sol index 06316cf6..9ac9fa3e 100644 --- a/contracts/modules/licensing/LicensingFrameworkRepo.sol +++ b/contracts/modules/licensing/LicensingFrameworkRepo.sol @@ -1,13 +1,11 @@ // SPDX-License-Identifier: UNLICENSED -// See Story Protocol Alpha Agreement: https://github.com/storyprotocol/protocol-contracts/blob/main/StoryProtocol-AlphaTestingAgreement-17942166.3.pdf +// See https://github.com/storyprotocol/protocol-contracts/blob/main/StoryProtocol-AlphaTestingAgreement-17942166.3.pdf pragma solidity ^0.8.19; import { Licensing } from "contracts/lib/modules/Licensing.sol"; -import { EnumerableSet } from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; import { ShortString, ShortStrings } from "@openzeppelin/contracts/utils/ShortStrings.sol"; import { Multicall } from "@openzeppelin/contracts/utils/Multicall.sol"; import { Errors } from "contracts/lib/Errors.sol"; -import { IHook } from "contracts/interfaces/hooks/base/IHook.sol"; import { AccessControlled } from "contracts/access-control/AccessControlled.sol"; import { AccessControl } from "contracts/lib/AccessControl.sol"; import { FixedSet } from "contracts/utils/FixedSet.sol"; @@ -16,25 +14,16 @@ contract LicensingFrameworkRepo is AccessControlled, Multicall { using FixedSet for FixedSet.ShortStringSet; using ShortStrings for *; - event FrameworkAdded( - string frameworkId, - string textUrl - ); + event FrameworkAdded(string frameworkId, string textUrl); - event ParamDefinitionAdded( - string frameworkId, - ShortString tag, - Licensing.ParamDefinition definition - ); + event ParamDefinitionAdded(string frameworkId, ShortString tag, Licensing.ParamDefinition definition); mapping(string => Licensing.FrameworkStorage) private _frameworks; mapping(bytes32 => Licensing.ParamDefinition) private _frameworkDefs; constructor(address accessControl_) AccessControlled(accessControl_) {} - function addFramework( - Licensing.SetFramework calldata input_ - ) external onlyRole(AccessControl.LICENSING_MANAGER) { + function addFramework(Licensing.SetFramework calldata input_) external onlyRole(AccessControl.LICENSING_MANAGER) { Licensing.FrameworkStorage storage framework = _frameworks[input_.id]; if (framework.paramTags.length() > 0) { revert Errors.LicensingFrameworkRepo_FrameworkAlreadyAdded(); @@ -50,10 +39,7 @@ contract LicensingFrameworkRepo is AccessControlled, Multicall { emit FrameworkAdded(input_.id, input_.textUrl); } - function _addParameter( - string calldata frameworkId_, - Licensing.ParamDefinition calldata paramDef_ - ) internal { + function _addParameter(string calldata frameworkId_, Licensing.ParamDefinition calldata paramDef_) internal { Licensing.FrameworkStorage storage framework = _frameworks[frameworkId_]; ShortString tag = paramDef_.tag; if (framework.paramTags.contains(tag)) { @@ -65,9 +51,7 @@ contract LicensingFrameworkRepo is AccessControlled, Multicall { emit ParamDefinitionAdded(frameworkId_, tag, paramDef_); } - function getLicenseTextUrl( - string calldata frameworkId_ - ) external view returns (string memory) { + function getLicenseTextUrl(string calldata frameworkId_) external view returns (string memory) { return _frameworks[frameworkId_].textUrl; } @@ -75,15 +59,11 @@ contract LicensingFrameworkRepo is AccessControlled, Multicall { string calldata frameworkId_, uint256 index ) external view returns (Licensing.ParamDefinition memory) { - Licensing.FrameworkStorage storage framework = _frameworks[ - frameworkId_ - ]; + Licensing.FrameworkStorage storage framework = _frameworks[frameworkId_]; return framework.paramDefs[index]; } - function getTotalParameters( - string calldata frameworkId_ - ) external view returns (uint256) { + function getTotalParameters(string calldata frameworkId_) external view returns (uint256) { return _frameworks[frameworkId_].paramDefs.length; } @@ -94,9 +74,7 @@ contract LicensingFrameworkRepo is AccessControlled, Multicall { return _frameworkDefs[keccak256(abi.encode(frameworkId_, tag_))]; } - function getParameterDefs( - string calldata frameworkId_ - ) external view returns (Licensing.ParamDefinition[] memory) { + function getParameterDefs(string calldata frameworkId_) external view returns (Licensing.ParamDefinition[] memory) { return _frameworks[frameworkId_].paramDefs; } } diff --git a/contracts/modules/licensing/LicensingModule.sol b/contracts/modules/licensing/LicensingModule.sol index 7068e6ee..df8927a7 100644 --- a/contracts/modules/licensing/LicensingModule.sol +++ b/contracts/modules/licensing/LicensingModule.sol @@ -1,17 +1,15 @@ // SPDX-License-Identifier: UNLICENSED -// See Story Protocol Alpha Agreement: https://github.com/storyprotocol/protocol-contracts/blob/main/StoryProtocol-AlphaTestingAgreement-17942166.3.pdf +// See https://github.com/storyprotocol/protocol-contracts/blob/main/StoryProtocol-AlphaTestingAgreement-17942166.3.pdf pragma solidity ^0.8.19; 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 { SPUMLParams } from "contracts/lib/modules/SPUMLParams.sol"; import { ShortStringOps } from "contracts/utils/ShortStringOps.sol"; import { BitMask } from "contracts/lib/BitMask.sol"; @@ -61,16 +59,11 @@ contract LicensingModule is BaseModule, ILicensingModule { return LICENSING_MODULE_KEY; } - function getIpOrgLicensorConfig( - address ipOrg_ - ) external view returns (Licensing.LicensorConfig) { + function getIpOrgLicensorConfig(address ipOrg_) external view returns (Licensing.LicensorConfig) { return _licensorConfig[ipOrg_]; } - function getIpOrgValueForParam( - address ipOrg_, - string calldata paramTag_ - ) external view returns (bytes memory) { + function getIpOrgValueForParam(address ipOrg_, string calldata paramTag_) external view returns (bytes memory) { return _ipOrgParamValues[ipOrg_][paramTag_.toShortString()]; } @@ -79,11 +72,7 @@ contract LicensingModule is BaseModule, ILicensingModule { //////////////////////////////////////////////////////////////////////////// /// Module entrypoing to verify execution call - function _verifyExecution( - IIPOrg ipOrg_, - address caller_, - bytes calldata params_ - ) internal virtual override { + function _verifyExecution(IIPOrg ipOrg_, address caller_, bytes calldata params_) internal virtual override { // Verification done in _performAction for efficiency } @@ -93,10 +82,7 @@ contract LicensingModule is BaseModule, ILicensingModule { address caller_, bytes memory params_ ) internal virtual override returns (bytes memory result) { - (bytes32 action, bytes memory actionParams) = abi.decode( - params_, - (bytes32, bytes) - ); + (bytes32 action, bytes memory actionParams) = abi.decode(params_, (bytes32, bytes)); if (action == Licensing.CREATE_LICENSE) { // Mint new license return _createLicense(ipOrg_, caller_, actionParams); @@ -107,10 +93,7 @@ contract LicensingModule is BaseModule, ILicensingModule { return bytes(""); } else if (action == Licensing.LINK_LNFT_TO_IPA) { // Link derivative license to derivative IPA - (uint256 licenseId, uint256 ipaId) = abi.decode( - actionParams, - (uint256, uint256) - ); + (uint256 licenseId, uint256 ipaId) = abi.decode(actionParams, (uint256, uint256)); LICENSE_REGISTRY.linkLnftToIpa(licenseId, ipaId); return bytes(""); } else { @@ -123,17 +106,9 @@ contract LicensingModule is BaseModule, ILicensingModule { address caller_, bytes memory params_ ) private returns (bytes memory result) { - Licensing.LicenseCreation memory input = abi.decode( - params_, - (Licensing.LicenseCreation) - ); + Licensing.LicenseCreation memory input = abi.decode(params_, (Licensing.LicenseCreation)); - address licensor = _getLicensor( - address(ipOrg_), - caller_, - input.parentLicenseId, - input.ipaId - ); + address licensor = _getLicensor(address(ipOrg_), caller_, input.parentLicenseId, input.ipaId); // ------ Derivative license checks ------ if (input.parentLicenseId != 0) { if (!LICENSE_REGISTRY.isLicenseActive(input.parentLicenseId)) { @@ -145,21 +120,13 @@ contract LicensingModule is BaseModule, ILicensingModule { } // If this is a derivative and parent is reciprocal, license parameters // cannot be changed in the new license - if ( - input.parentLicenseId != 0 && - LICENSE_REGISTRY.isReciprocal(input.parentLicenseId) - ) { + if (input.parentLicenseId != 0 && LICENSE_REGISTRY.isReciprocal(input.parentLicenseId)) { if (input.params.length > 0) { revert Errors.LicensingModule_ReciprocalCannotSetParams(); } return abi.encode( - LICENSE_REGISTRY.addReciprocalLicense( - input.parentLicenseId, - licensor, - caller_, - input.ipaId - ) + LICENSE_REGISTRY.addReciprocalLicense(input.parentLicenseId, licensor, caller_, input.ipaId) ); } else { // If this is not a derivative, or parent is not reciprocal, caller must be the licensor @@ -188,10 +155,7 @@ contract LicensingModule is BaseModule, ILicensingModule { string memory frameworkId_ ) private returns (uint256) { // Get all param tags from framework - Licensing.ParamDefinition[] - memory supportedParams = LICENSING_FRAMEWORK_REPO.getParameterDefs( - frameworkId_ - ); + Licensing.ParamDefinition[] memory supportedParams = LICENSING_FRAMEWORK_REPO.getParameterDefs(frameworkId_); // Parse license parameters ( Licensing.ParamValue[] memory licenseParams, @@ -201,10 +165,7 @@ contract LicensingModule is BaseModule, ILicensingModule { ) = _parseLicenseParameters(ipOrg_, input.params, supportedParams); Licensing.LicenseStatus newLicenseStatus; - if ( - input.parentLicenseId != 0 && - LICENSE_REGISTRY.derivativeNeedsApproval(input.parentLicenseId) - ) { + if (input.parentLicenseId != 0 && LICENSE_REGISTRY.derivativeNeedsApproval(input.parentLicenseId)) { // If parent license ID has `derivativeNeedsApproval` = true, then new license is pending licensor approval. // This condition is triggered when parent's `isReciprocal` = false but `derivativeNeedsApproval` = true. newLicenseStatus = Licensing.LicenseStatus.PendingLicensorApproval; @@ -243,9 +204,7 @@ contract LicensingModule is BaseModule, ILicensingModule { ) { uint256 inputLength_ = inputParams_.length; - mapping(ShortString => bytes) storage _ipOrgValues = _ipOrgParamValues[ - ipOrg_ - ]; + mapping(ShortString => bytes) storage _ipOrgValues = _ipOrgParamValues[ipOrg_]; uint256 supportedLength = supportedParams_.length; licenseParams = new Licensing.ParamValue[](supportedLength); @@ -265,11 +224,7 @@ contract LicensingModule is BaseModule, ILicensingModule { } } // Decide which value to use - bytes memory resultValue = _decideValueSource( - inputValue, - ipOrgValue, - paramDef - ); + bytes memory resultValue = _decideValueSource(inputValue, ipOrgValue, paramDef); // Set value in license params licenseParams[i] = Licensing.ParamValue(paramDef.tag, resultValue); @@ -280,18 +235,9 @@ contract LicensingModule is BaseModule, ILicensingModule { if (ShortStringOps._equal(paramDef.tag, SPUMLParams.DERIVATIVES_ALLOWED)) { derivativesAllowed = abi.decode(resultValue, (bool)); } else if (ShortStringOps._equal(paramDef.tag, SPUMLParams.DERIVATIVES_ALLOWED_OPTIONS)) { - uint256 derivativeIndexMask = abi.decode( - resultValue, - (uint256) - ); - derivativeNeedsApproval = BitMask._isSet( - derivativeIndexMask, - SPUMLParams.ALLOWED_WITH_APPROVAL_INDEX - ); - isReciprocal = BitMask._isSet( - derivativeIndexMask, - SPUMLParams.ALLOWED_WITH_RECIPROCAL_LICENSE_INDEX - ); + uint256 derivativeIndexMask = abi.decode(resultValue, (uint256)); + derivativeNeedsApproval = BitMask._isSet(derivativeIndexMask, SPUMLParams.ALLOWED_WITH_APPROVAL_INDEX); + isReciprocal = BitMask._isSet(derivativeIndexMask, SPUMLParams.ALLOWED_WITH_RECIPROCAL_LICENSE_INDEX); } } // In case there is misconfiguration. @@ -334,7 +280,7 @@ contract LicensingModule is BaseModule, ILicensingModule { /// Gets the licensor address for this IPA. function _getLicensor( address ipOrg_, - address caller_, + address, uint256 parentLicenseId_, uint256 ipaId_ ) private view returns (address) { @@ -368,10 +314,7 @@ contract LicensingModule is BaseModule, ILicensingModule { bytes calldata params_ ) internal virtual override returns (bytes memory) { // TODO: Revert if terms already exist - (bytes32 configType, bytes memory configData) = abi.decode( - params_, - (bytes32, bytes) - ); + (bytes32 configType, bytes memory configData) = abi.decode(params_, (bytes32, bytes)); if (configType == Licensing.LICENSING_FRAMEWORK_CONFIG) { return _setIpOrgFramework(ipOrg_, caller_, configData); } else { @@ -398,10 +341,7 @@ contract LicensingModule is BaseModule, ILicensingModule { if (ipOrg_.owner() != caller_) { revert Errors.LicensingModule_CallerNotIpOrgOwner(); } - Licensing.LicensingConfig memory config = abi.decode( - params_, - (Licensing.LicensingConfig) - ); + Licensing.LicensingConfig memory config = abi.decode(params_, (Licensing.LicensingConfig)); if (config.licensor == Licensing.LicensorConfig.Unset) { revert Errors.LicensingModule_InvalidLicensorConfig(); } @@ -410,19 +350,14 @@ contract LicensingModule is BaseModule, ILicensingModule { revert Errors.LicensingModule_IpOrgFrameworkAlreadySet(); } Licensing.ParamValue[] memory configParams = config.params; - if ( - configParams.length > - LICENSING_FRAMEWORK_REPO.getTotalParameters(config.frameworkId) - ) { + if (configParams.length > LICENSING_FRAMEWORK_REPO.getTotalParameters(config.frameworkId)) { revert Errors.LicensingModule_InvalidParamsLength(); } _licensorConfig[ipOrgAddress] = config.licensor; _ipOrgFrameworkIds[ipOrgAddress] = config.frameworkId; - mapping(ShortString => bytes) storage paramValues = _ipOrgParamValues[ - ipOrgAddress - ]; + mapping(ShortString => bytes) storage paramValues = _ipOrgParamValues[ipOrgAddress]; uint256 numParams = configParams.length; // Add the parameters to storage for (uint256 i = 0; i < numParams; i++) { @@ -430,8 +365,10 @@ contract LicensingModule is BaseModule, ILicensingModule { if (paramValues[param.tag].length > 0) { revert Errors.LicensingModule_DuplicateParam(); } - Licensing.ParamDefinition memory paramDef = LICENSING_FRAMEWORK_REPO - .getParamDefinition(config.frameworkId, param.tag); + Licensing.ParamDefinition memory paramDef = LICENSING_FRAMEWORK_REPO.getParamDefinition( + config.frameworkId, + param.tag + ); if (!Licensing._validateParamValue(paramDef, param.value)) { revert Errors.LicensingModule_InvalidParamValue(); } @@ -444,7 +381,7 @@ contract LicensingModule is BaseModule, ILicensingModule { config.licensor, configParams ); - + return ""; } @@ -452,11 +389,7 @@ contract LicensingModule is BaseModule, ILicensingModule { // Hooks // //////////////////////////////////////////////////////////////////////////// - function _hookRegistryKey( - IIPOrg ipOrg_, - address caller_, - bytes calldata params_ - ) internal view virtual override returns (bytes32) { + function _hookRegistryKey(IIPOrg, address, bytes calldata) internal view virtual override returns (bytes32) { return keccak256("TODO"); } } diff --git a/contracts/modules/registration/RegistrationModule.sol b/contracts/modules/registration/RegistrationModule.sol index 0378d465..3642089a 100644 --- a/contracts/modules/registration/RegistrationModule.sol +++ b/contracts/modules/registration/RegistrationModule.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: UNLICENSED -// See Story Protocol Alpha Agreement: https://github.com/storyprotocol/protocol-contracts/blob/main/StoryProtocol-AlphaTestingAgreement-17942166.3.pdf +// See https://github.com/storyprotocol/protocol-contracts/blob/main/StoryProtocol-AlphaTestingAgreement-17942166.3.pdf pragma solidity ^0.8.19; import { Strings } from "@openzeppelin/contracts/utils/Strings.sol"; @@ -10,50 +10,49 @@ import { IRegistrationModule } from "contracts/interfaces/modules/registration/I 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 /// metadata management of IP assets. During registration, this module will -/// create register the IP asset in the global IP asset registry, and then -/// wrap it as an NFT under its governing IP Org. +/// register an IP asset in the global IP asset registry, and then wraps +/// it as a localized IP asset NFT under its governing IP Org. contract RegistrationModule is BaseModule, IRegistrationModule, AccessControlled { - using Strings for uint256; /// @notice Representation of a wrapped IP asset within an IP Org. struct IPOrgAsset { - address ipOrg; - uint256 ipOrgAssetId; + address ipOrg; // The address of the governing IP Org. + uint256 ipOrgAssetId; // The localized if of the IP asset within the IP Org. } - /// @notice Maps global IP asset Ids to IP Org wrapped assets. - mapping(uint256 => IPOrgAsset) ipOrgAssets; + /// @notice Maps global IP asset ids to IP Org wrapped assets. + mapping(uint256 => IPOrgAsset) public ipOrgAssets; /// @notice Maps IP Orgs to their IPA configuration settings. - mapping(address => Registration.IPOrgConfig) ipOrgConfigs; + mapping(address => Registration.IPOrgConfig) public ipOrgConfigs; /// @notice Reverse lookup from IP Org asset to global IP asset ids. mapping(address => mapping(uint256 => uint256)) public ipAssetId; - /// @notice IP Org asset to its tokenURI. + /// @notice Maps IP Org assets to their token URIs. mapping(address => mapping(uint256 => string)) public tokenUris; /// @notice Maximum number of Ip Org asset types. uint256 public constant MAX_IP_ORG_ASSET_TYPES = type(uint8).max; /// @notice Initializes the registration module. + /// @param params_ Params necessary for all protocol-wide modules. + /// @param accessControl_ Global access control singleton used for protocol authorization. constructor( BaseModule.ModuleConstruction memory params_, address accessControl_ ) BaseModule(params_) AccessControlled(accessControl_) {} /// @notice Gets the protocol-wide module key for the registration module. + /// @return The module key used for identifying the registration module. function moduleKey() public pure override(BaseModule, IModule) returns (ModuleKey) { return REGISTRATION_MODULE_KEY; } @@ -79,6 +78,7 @@ contract RegistrationModule is BaseModule, IRegistrationModule, AccessControlled /// @notice Gets the contract URI for an IP Org. /// @param ipOrg_ The address of the IP Org. + /// @return The contract URI identifying an IP Org contract. function contractURI(address ipOrg_) public view returns (string memory) { string memory uri = ipOrgConfigs[ipOrg_].contractURI; if (bytes(uri).length == 0) { @@ -91,7 +91,12 @@ contract RegistrationModule is BaseModule, IRegistrationModule, AccessControlled /// @param ipOrg_ The address of the IP Org of the IP asset. /// @param ipOrgAssetId_ The local id of the IP asset within the IP Org. /// @param ipOrgAssetType_ The IP Org asset type. - function tokenURI(address ipOrg_, uint256 ipOrgAssetId_, uint8 ipOrgAssetType_) public view returns (string memory) { + /// @return The token URI associated with a specific IP Org localized IP asset. + function tokenURI( + address ipOrg_, + uint256 ipOrgAssetId_, + uint8 ipOrgAssetType_ + ) public view returns (string memory) { uint256 id = ipAssetId[ipOrg_][ipOrgAssetId_]; address owner = IIPOrg(ipOrg_).ownerOf(ipOrgAssetId_); if (owner == address(0)) { @@ -112,70 +117,100 @@ contract RegistrationModule is BaseModule, IRegistrationModule, AccessControlled IPAssetRegistry.IPA memory ipAsset = IPA_REGISTRY.ipAsset(id); // Construct the base JSON metadata with custom name format - string memory baseJson = string(abi.encodePacked( - '{"name": "Global IP Asset #', Strings.toString(id), - '", "description": "IP Org Asset Registration Details", "attributes": [', - '{"trait_type": "Name", "value": "', ipAsset.name, '"},' - )); - - string memory ipOrgAttributes = string(abi.encodePacked( - '{"trait_type": "IP Org", "value": "', Strings.toHexString(uint160(ipAsset.ipOrg), 20), '"},', - '{"trait_type": "Current IP Owner", "value": "', Strings.toHexString(uint160(owner), 20), '"},' - )); - - string memory ipAssetAttributes = string(abi.encodePacked( - '{"trait_type": "Initial Registrant", "value": "', Strings.toHexString(uint160(ipAsset.registrant), 20), '"},', - '{"trait_type": "IP Org Asset Type", "value": "', config.assetTypes[ipOrgAssetType_], '"},', - '{"trait_type": "Status", "value": "', Strings.toString(ipAsset.status), '"},', - '{"trait_type": "Hash", "value": "', Strings.toHexString(uint256(ipAsset.hash), 32), '"},', - '{"trait_type": "Registration Date", "value": "', Strings.toString(ipAsset.registrationDate), '"}' - )); - - return string(abi.encodePacked( - "data:application/json;base64,", - Base64.encode( - bytes( - string(abi.encodePacked( - baseJson, - ipOrgAttributes, - ipAssetAttributes, - ']}' - ) + string memory baseJson = string( + /* solhint-disable */ + abi.encodePacked( + '{"name": "Global IP Asset #', + Strings.toString(id), + '", "description": "IP Org Asset Registration Details", "attributes": [', + '{"trait_type": "Name", "value": "', + ipAsset.name, + '"},' + ) + /* solhint-enable */ + ); + + string memory ipOrgAttributes = string( + /* solhint-disable */ + abi.encodePacked( + '{"trait_type": "IP Org", "value": "', + Strings.toHexString(uint160(ipAsset.ipOrg), 20), + '"},', + '{"trait_type": "Current IP Owner", "value": "', + Strings.toHexString(uint160(owner), 20), + '"},' + ) + /* solhint-enable */ + ); + + string memory ipAssetAttributes = string( + /* solhint-disable */ + abi.encodePacked( + '{"trait_type": "Initial Registrant", "value": "', + Strings.toHexString(uint160(ipAsset.registrant), 20), + '"},', + '{"trait_type": "IP Org Asset Type", "value": "', + config.assetTypes[ipOrgAssetType_], + '"},', + '{"trait_type": "Status", "value": "', + Strings.toString(ipAsset.status), + '"},', + '{"trait_type": "Hash", "value": "', + Strings.toHexString(uint256(ipAsset.hash), 32), + '"},', + '{"trait_type": "Registration Date", "value": "', + Strings.toString(ipAsset.registrationDate), + '"}' + ) + /* solhint-enable */ + ); + + return + string( + abi.encodePacked( + "data:application/json;base64,", + Base64.encode(bytes(string(abi.encodePacked(baseJson, ipOrgAttributes, ipAssetAttributes, "]}")))) ) - )) - )); + ); } /// @notice Gets the asset types of an IP Org. + /// @param ipOrg_ Address of the IP Org whose asset types are being queried for. function getIpOrgAssetTypes(address ipOrg_) public view returns (string[] memory) { return ipOrgConfigs[ipOrg_].assetTypes; } - /// @notice returns true if the index for an IP Org asset type is supported. + /// @notice Checks whether an IP Org asset type is supported. + /// @param ipOrg_ Address of the IP Org to which the IP asset type belongs. + /// @param assetTypeIndex_ The index representing the targeted IP asset type. function isValidIpOrgAssetType(address ipOrg_, uint8 assetTypeIndex_) public view returns (bool) { return assetTypeIndex_ < ipOrgConfigs[ipOrg_].assetTypes.length; } /// @notice Gets the current owner of an IP asset. /// @param ipAssetId_ The global IP asset id being queried. + /// @return The address of the owner of the IP asset. function ownerOf(uint256 ipAssetId_) public view returns (address) { IPOrgAsset memory ipOrgAsset = ipOrgAssets[ipAssetId_]; return IIPOrg(ipOrgAsset.ipOrg).ownerOf(ipOrgAsset.ipOrgAssetId); } - /// Verifies that the relationship execute() wants to set is valid according to its type definition - /// @param ipOrg_ IPOrg address or zero address for protocol level relationships - /// @param params_ encoded params for module action - function _verifyExecution(IIPOrg ipOrg_, address caller_, bytes calldata params_) virtual override internal { + /// @dev Verifies if execution of an IP asset registration or transfer is successful. + /// @param ipOrg_ Address of the relevant IP Org (used only for registration). + /// @param params_ Encoded params used for registration processing (see Registration module). + function _verifyExecution(IIPOrg ipOrg_, address caller_, bytes calldata params_) internal virtual override { (bytes32 executionType, bytes memory executionData) = abi.decode(params_, (bytes32, bytes)); - if (executionType == Registration.TRANSFER_IP_ASSET) { - (address from, address to, uint256 id) = abi.decode(executionData, (address, address, uint256)); - if (caller_ != from || ownerOf(id) != caller_) { - revert Errors.RegistrationModule_InvalidCaller(); - } + if (executionType == Registration.TRANSFER_IP_ASSET) { + (address from, , uint256 id) = abi.decode(executionData, (address, address, uint256)); + if (caller_ != from || ownerOf(id) != caller_) { + revert Errors.RegistrationModule_InvalidCaller(); + } } else if (executionType == Registration.REGISTER_IP_ASSET) { - Registration.RegisterIPAssetParams memory params = abi.decode(executionData, (Registration.RegisterIPAssetParams)); + Registration.RegisterIPAssetParams memory params = abi.decode( + executionData, + (Registration.RegisterIPAssetParams) + ); if (params.owner != caller_) { revert Errors.RegistrationModule_InvalidCaller(); } @@ -183,22 +218,24 @@ contract RegistrationModule is BaseModule, IRegistrationModule, AccessControlled } else { revert Errors.RegistrationModule_InvalidExecutionOperation(); } - - // TODO(leeren): Perform additional vetting on name, IP type, and CID. } /// @dev Configures the registration settings for a specific IP Org. /// @param ipOrg_ The IP Org being configured. /// @param caller_ The caller authorized to perform configuration. /// @param params_ Parameters passed for registration configuration. - function _configure(IIPOrg ipOrg_, address caller_, bytes calldata params_) virtual override internal returns (bytes memory) { - _verifyConfigCaller(ipOrg_, caller_); + function _configure( + IIPOrg ipOrg_, + address caller_, + bytes calldata params_ + ) internal virtual override returns (bytes memory) { + _verifyConfigCaller(ipOrg_, caller_); (bytes32 configType, bytes memory configData) = abi.decode(params_, (bytes32, bytes)); if (configType == Registration.SET_IP_ORG_METADATA) { - (string memory baseURI, string memory contractURI__) = abi.decode(configData, (string, string)); - _setMetadata(address(ipOrg_), baseURI, contractURI__); + (string memory baseURI, string memory contractUri) = abi.decode(configData, (string, string)); + _setMetadata(address(ipOrg_), baseURI, contractUri); } else if (configType == Registration.SET_IP_ORG_ASSET_TYPES) { - (string[] memory ipAssetTypes) = abi.decode(configData, (string[])); + string[] memory ipAssetTypes = abi.decode(configData, (string[])); _addIPAssetTypes(address(ipOrg_), ipAssetTypes); } else { revert Errors.RegistrationModule_InvalidConfigOperation(); @@ -208,16 +245,23 @@ contract RegistrationModule is BaseModule, IRegistrationModule, AccessControlled /// @notice Registers an IP Asset. /// @param params_ encoded RegisterIPAParams for module action - /// @return encoded registry and IP Org id of the IP asset. - function _performAction(IIPOrg ipOrg_, address caller_, bytes memory params_) virtual override internal returns (bytes memory) { + /// @return Encoded registry and IP Org id of the IP asset. + function _performAction( + IIPOrg ipOrg_, + address, + bytes memory params_ + ) internal virtual override returns (bytes memory) { (bytes32 executionType, bytes memory executionData) = abi.decode(params_, (bytes32, bytes)); if (executionType == Registration.TRANSFER_IP_ASSET) { (address from, address to, uint256 id) = abi.decode(executionData, (address, address, uint256)); _transferIPAsset(ipOrg_, id, from, to); return ""; } else if (executionType == Registration.REGISTER_IP_ASSET) { - Registration.RegisterIPAssetParams memory params = abi.decode(executionData, (Registration.RegisterIPAssetParams)); - (uint256 ipAssetId__, uint256 ipOrgAssetId) = _registerIPAsset( + Registration.RegisterIPAssetParams memory params = abi.decode( + executionData, + (Registration.RegisterIPAssetParams) + ); + (uint256 ipAsset, uint256 ipOrgAssetId) = _registerIPAsset( ipOrg_, params.owner, params.name, @@ -225,7 +269,7 @@ contract RegistrationModule is BaseModule, IRegistrationModule, AccessControlled params.hash, params.mediaUrl ); - return abi.encode(ipAssetId__, ipOrgAssetId); + return abi.encode(ipAsset, ipOrgAssetId); } return ""; } @@ -245,12 +289,7 @@ contract RegistrationModule is BaseModule, IRegistrationModule, AccessControlled bytes32 hash_, string memory mediaUrl_ ) internal returns (uint256 ipAssetId_, uint256 ipOrgAssetId_) { - ipAssetId_ = IPA_REGISTRY.register( - address(ipOrg_), - owner_, - name_, - hash_ - ); + ipAssetId_ = IPA_REGISTRY.register(address(ipOrg_), owner_, name_, hash_); ipOrgAssetId_ = ipOrg_.mint(owner_, ipOrgAssetType_); ipAssetId[address(ipOrg_)][ipOrgAssetId_] = ipAssetId_; IPOrgAsset memory ipOrgAsset = IPOrgAsset(address(ipOrg_), ipOrgAssetId_); @@ -275,68 +314,23 @@ contract RegistrationModule is BaseModule, IRegistrationModule, AccessControlled /// @param ipOrgAssetId_ The local id of the IP asset within the IP Org. /// @param from_ The current owner of the IP asset within the IP Org. /// @param to_ The new owner of the IP asset within the IP Org. - function _transferIPAsset( - IIPOrg ipOrg_, - uint256 ipOrgAssetId_, - address from_, - address to_ - ) internal { + function _transferIPAsset(IIPOrg ipOrg_, uint256 ipOrgAssetId_, address from_, address to_) internal { ipOrg_.transferFrom(from_, to_, ipOrgAssetId_); uint256 id = ipAssetId[address(ipOrg_)][ipOrgAssetId_]; - emit IPAssetTransferred( - id, - address(ipOrg_), - ipOrgAssetId_, - from_, - to_ - ); + emit IPAssetTransferred(id, address(ipOrg_), ipOrgAssetId_, from_, to_); } - /// @dev Transfers an IP asset to a new governing IP Org. - /// @param fromIpOrg_ The address of the original governing IP Org. - /// @param fromIpOrgAssetId_ The existing id of the IP asset within the IP Org. - /// @param toIpOrg_ The address of the new governing IP Org. - /// @param toIpOrgType_ The type of the IP asset within the new IP Org. - /// TODO(leeren) Expose this function to FE once IP Orgs are finalized. - /// NOTE: This function is currently not used, but will be used in the future. Commented out for now. - // function _transferIPAssetToIPOrg( - // address fromIpOrg_, - // uint256 fromIpOrgAssetId_, - // address toIpOrg_, - // uint8 toIpOrgType_, - // address from_, - // address to_ - // ) internal returns (uint256 ipAssetId_, uint256 ipOrgAssetId_) { - // uint256 id = ipAssetId[address(fromIpOrg_)][fromIpOrgAssetId_]; - // address owner = IIPOrg(fromIpOrg_).ownerOf(ipOrgAssetId_); - // delete ipAssetId[address(fromIpOrg_)][fromIpOrgAssetId_]; - // delete ipOrgAssets[id]; - // IIPOrg(fromIpOrg_).burn(ipOrgAssetId_); - // IPA_REGISTRY.transferIPOrg( - // ipAssetId_, - // toIpOrg_ - // ); - // ipOrgAssetId_ = IIPOrg(toIpOrg_).mint(owner, toIpOrgType_); - // IPOrgAsset memory ipOrgAsset = IPOrgAsset(toIpOrg_, ipOrgAssetId_); - // ipOrgAssets[id] = ipOrgAsset; - // ipAssetId[address(toIpOrg_)][ipOrgAssetId_] = id; - // } - - /// @dev Adds new IP asset types to an IP Org. /// @param ipOrg_ The address of the IP Org whose asset types we are adding. /// @param ipOrgTypes_ String descriptors of the asset types being added. /// TODO: Add ability to deprecate asset types. - function _addIPAssetTypes( - address ipOrg_, - string[] memory ipOrgTypes_ - ) internal { + function _addIPAssetTypes(address ipOrg_, string[] memory ipOrgTypes_) internal { uint256 assetsLength = ipOrgTypes_.length; if (assetsLength > MAX_IP_ORG_ASSET_TYPES) { revert Errors.RegistrationModule_TooManyAssetTypes(); } Registration.IPOrgConfig storage ipOrg = ipOrgConfigs[ipOrg_]; - for (uint i = 0; i < assetsLength; i++) { + for (uint256 i = 0; i < assetsLength; i++) { // TODO: this should be a set, and check empty strings ipOrg.assetTypes.push(ipOrgTypes_[i]); } @@ -346,11 +340,7 @@ contract RegistrationModule is BaseModule, IRegistrationModule, AccessControlled /// @param ipOrg_ The address of the IP Org whose metadata is changing. /// @param baseURI_ The new base URI to assign for the IP Org. /// @param contractURI_ The new base contract URI to assign for the IP Org. - function _setMetadata( - address ipOrg_, - string memory baseURI_, - string memory contractURI_ - ) internal { + function _setMetadata(address ipOrg_, string memory baseURI_, string memory contractURI_) internal { Registration.IPOrgConfig storage config = ipOrgConfigs[ipOrg_]; config.baseURI = baseURI_; config.contractURI = contractURI_; @@ -358,13 +348,17 @@ contract RegistrationModule is BaseModule, IRegistrationModule, AccessControlled } /// @dev Verifies the caller of a configuration action. - /// TODO(leeren): Deprecate in favor of policy-based function auth. + /// @param ipOrg_ The IP Org associated with the registration configuration. + /// @param caller_ The address of the calling entity performing configuration. function _verifyConfigCaller(IIPOrg ipOrg_, address caller_) private view { if (ipOrg_.owner() != caller_ && address(IP_ORG_CONTROLLER) != caller_) { revert Errors.Unauthorized(); } } + /// @dev Verifies whether an IP Org asset type is valid. + /// @param ipOrg_ Address of the IP Org under which the asset type lives. + /// @param ipOrgAssetType_ The index used for identifying the IP asset type. function _verifyIpOrgAssetType(address ipOrg_, uint8 ipOrgAssetType_) private view { uint8 length = uint8(ipOrgConfigs[ipOrg_].assetTypes.length); if (ipOrgAssetType_ >= length) { @@ -372,6 +366,9 @@ contract RegistrationModule is BaseModule, IRegistrationModule, AccessControlled } } + /// @dev Gets the hook registry key associated with an IP Org and execution type. + /// @param ipOrg_ Address of the IP Org under which the hook is registered. + /// @param moduleParams_ Registration config params from which the type is sourced. function _hookRegistryKey( IIPOrg ipOrg_, address, @@ -381,13 +378,9 @@ contract RegistrationModule is BaseModule, IRegistrationModule, AccessControlled return _generateRegistryKey(ipOrg_, executionType); } - function _generateRegistryKey( - IIPOrg ipOrg_, - bytes32 executionType_ - ) private pure returns (bytes32) { - return - keccak256( - abi.encode(address(ipOrg_), executionType_, "REGISTRATION") - ); + /// @dev Creates a new hooks registration key for the registration module. + /// @param ipOrg_ The IP Org under which the key is associated. + function _generateRegistryKey(IIPOrg ipOrg_, bytes32 executionType_) private pure returns (bytes32) { + return keccak256(abi.encode(address(ipOrg_), executionType_, "REGISTRATION")); } } diff --git a/contracts/modules/relationships/RelationshipModule.sol b/contracts/modules/relationships/RelationshipModule.sol index 018d814a..81c71431 100644 --- a/contracts/modules/relationships/RelationshipModule.sol +++ b/contracts/modules/relationships/RelationshipModule.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: UNLICENSED -// See Story Protocol Alpha Agreement: https://github.com/storyprotocol/protocol-contracts/blob/main/StoryProtocol-AlphaTestingAgreement-17942166.3.pdf +// See https://github.com/storyprotocol/protocol-contracts/blob/main/StoryProtocol-AlphaTestingAgreement-17942166.3.pdf pragma solidity ^0.8.19; import { BaseModule } from "contracts/modules/base/BaseModule.sol"; @@ -12,51 +12,59 @@ import { LibRelationship } from "contracts/lib/modules/LibRelationship.sol"; 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. -/// There are protocol level relationships, that are available for all IPOrgs, and IPOrg level relationships, -/// that are only available for a specific IPOrg. -/// Relationship types are configurable, allowing to link together different types of entities: -/// - IPA (Intellectual Property Asset) -/// - IPOrg Entry, including subcategories -/// - Licenses -/// - Addresses -/// - External NFTs -/// And combinations of them. -/// NOTE: This is an alpha version, a more efficient way of storing and verifying relationships will be implemented in the future. +/// @notice Handles creation and management of relationships between IP entities. +/// Note that two types of relationships exist, those that are available across +/// all IP Orgs (protocol-wide), and those that are exclusive to IP Orgs. +/// Relationship types link different IP entities together, including: +/// - IPAs (Intellectual Property Assets) +/// - IPOrg Assets +/// - Licenses +/// - Addresses +/// - External NFTs contract RelationshipModule is BaseModule, IRelationshipModule, AccessControlled { - using Address for address; + /// @notice Maps protocol-wide relationship types to their definitions. mapping(string => LibRelationship.RelationshipType) private _protocolRelTypes; + + /// @notice Maps IP Org relationship types to their definitions. + /// @dev The key is given by the keccak-256 hash of (string relName, address ipOrg). mapping(bytes32 => LibRelationship.RelationshipType) private _ipOrgRelTypes; + /// @dev Internal counter for tracking the current relationship id. uint256 private _relationshipIdCounter; + + /// @dev Tracks relationship ids to their relationships. mapping(uint256 => LibRelationship.Relationship) private _relationships; + + /// @dev Maps relationship hashes to their underlying ids. mapping(bytes32 => uint256) private _relHashes; + /// @notice Creates a new relationship module. + /// @param params_ Core attributes required by all protocol modules. + /// @param accessControl_ Global access singleton contract used for protocol authorization. constructor( BaseModule.ModuleConstruction memory params_, address accessControl_ ) BaseModule(params_) AccessControlled(accessControl_) {} - /// @notice Gets the protocol-wide module key for the relationship module. + /// @return The protocol-wide key configured 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. + /// @notice Registers hooks on behalf of an IP Org for a specific hook and relationship type. /// @dev This function can only be called by the IP Org owner. - /// @param hType_ The type of the hooks to register. - /// @param ipOrg_ The IP Org for which the hooks are being registered. - /// @param relType_ The relationship type for which the hooks are being registered. + /// @param hType_ The type of hooks to register. + /// @param ipOrg_ The IP Org for which the hooks are being registered for. + /// @param relType_ The relationship type he hooks are being registered under. /// @param hooks_ The addresses of the hooks to register. - /// @param hooksConfig_ The configurations for the hooks. + /// @param hooksConfig_ The associated configurations for the hooks. function registerHooks( HookType hType_, IIPOrg ipOrg_, @@ -68,12 +76,15 @@ contract RelationshipModule is BaseModule, IRelationshipModule, AccessControlled registerHooks(hType_, ipOrg_, registryKey, hooks_, hooksConfig_); } - /// Gets relationship type definition for a given relationship type name - /// Will revert if no relationship type is found - /// @param ipOrg_ IP Org address or zero address for protocol level relationships - /// @param relType_ the name of the relationship type - /// @return result the relationship type definition - function getRelationshipType(address ipOrg_, string memory relType_) virtual override public view returns (LibRelationship.RelationshipType memory result) { + /// @notice Gets the type definition for a given relationship type name. + /// @dev This function Will revert if no relationship type is found. + /// @param ipOrg_ Address of the IP Org or zero address if it is a protocol-wide relationship. + /// @param relType_ the name of the relationship type. + /// @return result The relationship type definition. + function getRelationshipType( + address ipOrg_, + string memory relType_ + ) public view virtual override returns (LibRelationship.RelationshipType memory result) { if (ipOrg_ == LibRelationship.PROTOCOL_LEVEL_RELATIONSHIP) { result = _protocolRelTypes[relType_]; } else { @@ -85,28 +96,40 @@ contract RelationshipModule is BaseModule, IRelationshipModule, AccessControlled return result; } - /// Gets relationship definition for a given relationship id + /// @notice Gets the relationship definition for a given relationship id. + /// @param relationshipId_ The identifier for the relationship. + /// @return The underlying relationship. function getRelationship(uint256 relationshipId_) external view returns (LibRelationship.Relationship memory) { return _relationships[relationshipId_]; } - /// Gets relationship id for a given relationship - function getRelationshipId(LibRelationship.Relationship calldata rel_) external virtual override view returns (uint256) { + /// @notice Gets the relationship id for a given relationship. + /// @param rel_ The data structure of the relationship. + /// @return The id of the relationship. + function getRelationshipId( + LibRelationship.Relationship calldata rel_ + ) external view virtual override returns (uint256) { return _relHashes[keccak256(abi.encode(rel_))]; } - /// Checks if a relationship has been set - function relationshipExists(LibRelationship.Relationship calldata rel_) external virtual override view returns (bool) { + /// @notice Checks whether a given relationship exists or not. + /// @param rel_ The relationship entity being checked for. + /// @return True if the relationship exists, False otherwise. + function relationshipExists( + LibRelationship.Relationship calldata rel_ + ) external view virtual override returns (bool) { return _relHashes[keccak256(abi.encode(rel_))] != 0; } - /// Relationship module supports configuration to add or remove relationship types + /// @dev Configures a relationship, adding or removing new relationship types. + // @param ipOrg_ IP Org address or zero address if configuring across the protocol. + // @param params_ Encoded relationship data (see LibRelationship for details). function _configure( IIPOrg ipOrg_, address caller_, bytes calldata params_ - ) virtual override internal returns (bytes memory) { - _verifyConfigCaller(ipOrg_, caller_); + ) internal virtual override returns (bytes memory) { + _verifyConfigCaller(ipOrg_, caller_); (bytes32 configType, bytes memory configData) = abi.decode(params_, (bytes32, bytes)); if (configType == LibRelationship.ADD_REL_TYPE_CONFIG) { _addRelationshipType(abi.decode(configData, (LibRelationship.AddRelationshipTypeParams))); @@ -119,11 +142,11 @@ contract RelationshipModule is BaseModule, IRelationshipModule, AccessControlled return ""; } - /// Auth check for caller, if wanting to configure a protocol level relationship type, - /// caller must have RELATIONSHIP_MANAGER_ROLE, if it's an IPOrg level relationship type, - /// caller must be the owner of the IPOrg - /// @param ipOrg_ zero address for protocol level relationships, IPOrg address for IPOrg level relationships - /// @param caller_ initiator of the configuration + /// @dev Verifies whether configuration for a relationship is authorized. For + /// protocol-wide relationships, the caller must have the RELATIONSHIP_MANAGER_ROLE. + /// For IP Org relationships, the caller must be the owner of the IP Org. + /// @param ipOrg_ Addrss of the IP Org, or the zero address for protocol-wide relationships. + /// @param caller_ Address of the caller of the configuration setting. function _verifyConfigCaller(IIPOrg ipOrg_, address caller_) private view { if (address(ipOrg_) == LibRelationship.PROTOCOL_LEVEL_RELATIONSHIP) { if (!_hasRole(AccessControl.RELATIONSHIP_MANAGER_ROLE, caller_)) { @@ -136,12 +159,11 @@ contract RelationshipModule is BaseModule, IRelationshipModule, AccessControlled } } - /// Gets the address and subtype mask to set a relationship type definition - /// @param relatable_ which element category is being configured - /// @param ipOrg_ IPOrg address - /// @param allowedTypes_ ipOrg related types, if applicable - /// @return a tuple with the accepted address and the subtype mask for this node of a - /// relationship type definition + /// @notice Gets the controlling address and subtype mask for setting relationship type definitions. + /// @param relatable_ The type of entity being set as part of a relationship. + /// @param ipOrg_ Address of the IP Org or the zero address (if protocol-wide). + /// @param allowedTypes_ The allowable set of IP Org relationship types. + /// @return The controlling address and subtype mask for applying the relationship typedefs. function _addressConfigFor( LibRelationship.Relatables relatable_, address ipOrg_, @@ -168,10 +190,10 @@ contract RelationshipModule is BaseModule, IRelationshipModule, AccessControlled revert Errors.RelationshipModule_InvalidRelatable(); } - function _verifySupportedIpOrgIndexType( - address ipOrg_, - uint8[] memory allowedTypes_ - ) private view { + /// @dev Checks whether provided relationship types are valid. + /// @param ipOrg_ Address of the IP Org or zero address (if protocol-wide). + /// @param allowedTypes_ The provided set of relationship types being checked for. + function _verifySupportedIpOrgIndexType(address ipOrg_, uint8[] memory allowedTypes_) private view { IRegistrationModule regModule = IRegistrationModule( address(MODULE_REGISTRY.protocolModule(REGISTRATION_MODULE_KEY)) ); @@ -182,13 +204,20 @@ contract RelationshipModule is BaseModule, IRelationshipModule, AccessControlled } } } - - /// Configures a Relationship Type from the more user friendly AddRelationshipTypeParams struct, - /// and adds it to the appropriate mapping (protocol or IPOrg) - /// @param params_ AddRelationshipTypeParams + + /// @dev Configures a new protocol-wide or IP Org relationship type. + /// @param params_ Parameters associated with the relationship type creation. function _addRelationshipType(LibRelationship.AddRelationshipTypeParams memory params_) private { - (address src, uint256 srcSubtypesMask) = _addressConfigFor(params_.allowedElements.src, params_.ipOrg, params_.allowedSrcs); - (address dst, uint256 dstSubtypesMask) = _addressConfigFor(params_.allowedElements.dst, params_.ipOrg, params_.allowedDsts); + (address src, uint256 srcSubtypesMask) = _addressConfigFor( + params_.allowedElements.src, + params_.ipOrg, + params_.allowedSrcs + ); + (address dst, uint256 dstSubtypesMask) = _addressConfigFor( + params_.allowedElements.dst, + params_.ipOrg, + params_.allowedDsts + ); LibRelationship.RelationshipType memory relDef = LibRelationship.RelationshipType({ src: src, srcSubtypesMask: srcSubtypesMask, @@ -212,9 +241,9 @@ contract RelationshipModule is BaseModule, IRelationshipModule, AccessControlled ); } - /// Removes a relationship type definition from the appropriate mapping (protocol or IPOrg) - /// @param ipOrg_ zero address for protocol level relationships, IPOrg address for IPOrg level relationships - /// @param relType_ name of the relationship type + /// @dev Removes a relationship type from an IP Org or across thep rotocol. + /// @param ipOrg_ Address of the IP Org or the zero address (if protocol-wide). + /// @param relType_ Name of the relationship type. function _removeRelationshipType(address ipOrg_, string memory relType_) private { if (ipOrg_ == address(0)) { delete _protocolRelTypes[relType_]; @@ -224,49 +253,51 @@ contract RelationshipModule is BaseModule, IRelationshipModule, AccessControlled emit RelationshipTypeUnset(relType_, ipOrg_); } - /// Verifies that the relationship execute() wants to set is valid according to its type definition - /// @param ipOrg_ IPOrg address or zero address for protocol level relationships - /// @param params_ encoded params for module action - function _verifyExecution(IIPOrg ipOrg_, address, bytes calldata params_) virtual override internal { - LibRelationship.CreateRelationshipParams memory createParams = abi.decode(params_, (LibRelationship.CreateRelationshipParams)); + /// @dev Verifies that a relationship config is valid according to its typedef. + /// @param ipOrg_ Address of the IP Org or the zero address (if protocol-wide). + /// @param params_ Encoded parameters used for relationship processing (see LibRelationship). + function _verifyExecution(IIPOrg ipOrg_, address, bytes calldata params_) internal virtual override { + LibRelationship.CreateRelationshipParams memory createParams = abi.decode( + params_, + (LibRelationship.CreateRelationshipParams) + ); LibRelationship.RelationshipType memory relType = getRelationshipType(address(ipOrg_), createParams.relType); // Source checks if (createParams.srcAddress == address(0)) { revert Errors.RelationshipModule_InvalidSrcAddress(); } - if(relType.src != LibRelationship.NO_ADDRESS_RESTRICTIONS) { - if (createParams.srcAddress != relType.src) { - revert Errors.RelationshipModule_InvalidSrcAddress(); - } + if (relType.src != LibRelationship.NO_ADDRESS_RESTRICTIONS && createParams.srcAddress != relType.src) { + revert Errors.RelationshipModule_InvalidSrcAddress(); } - if (relType.srcSubtypesMask != 0) { - uint8 srcType = ipOrg_.ipOrgAssetType(createParams.srcId); - if (!BitMask._isSet(relType.srcSubtypesMask, srcType)) { - revert Errors.RelationshipModule_InvalidSrcId(); - } + if ( + relType.srcSubtypesMask != 0 && + !BitMask._isSet(relType.srcSubtypesMask, ipOrg_.ipOrgAssetType(createParams.srcId)) + ) { + revert Errors.RelationshipModule_InvalidSrcId(); } // Destination checks if (createParams.dstAddress == address(0)) { revert Errors.RelationshipModule_InvalidDstAddress(); } - if (relType.dst != LibRelationship.NO_ADDRESS_RESTRICTIONS) { - if (createParams.dstAddress != relType.dst) { - revert Errors.RelationshipModule_InvalidDstAddress(); - } + if (relType.dst != LibRelationship.NO_ADDRESS_RESTRICTIONS && createParams.dstAddress != relType.dst) { + revert Errors.RelationshipModule_InvalidDstAddress(); } - if (relType.dstSubtypesMask != 0) { - uint8 dstType = ipOrg_.ipOrgAssetType(createParams.dstId); - if (!BitMask._isSet(relType.dstSubtypesMask, dstType)) { - revert Errors.RelationshipModule_InvalidDstId(); - } + if ( + relType.dstSubtypesMask != 0 && + !BitMask._isSet(relType.dstSubtypesMask, ipOrg_.ipOrgAssetType(createParams.dstId)) + ) { + revert Errors.RelationshipModule_InvalidDstId(); } } - /// Creates and stores a relationship and emits the RelationshipCreated event. Ignores first 2 parameters - /// @param params_ encoded CreateRelationshipParams for module action - /// @return encoded relationship id (uint256) - function _performAction(IIPOrg, address, bytes memory params_) virtual override internal returns (bytes memory) { - LibRelationship.CreateRelationshipParams memory createParams = abi.decode(params_, (LibRelationship.CreateRelationshipParams)); + /// @dev Processes the configuration of a new relationship. + /// @param params_ Encoded parameters used for relationship processing (see LibRelationship). + /// @return The encoded uint256 relationship identifier. + function _performAction(IIPOrg, address, bytes memory params_) internal virtual override returns (bytes memory) { + LibRelationship.CreateRelationshipParams memory createParams = abi.decode( + params_, + (LibRelationship.CreateRelationshipParams) + ); uint256 relationshipId = ++_relationshipIdCounter; LibRelationship.Relationship memory rel = LibRelationship.Relationship({ relType: createParams.relType, @@ -288,16 +319,24 @@ contract RelationshipModule is BaseModule, IRelationshipModule, AccessControlled return abi.encode(relationshipId); } + /// @dev Gets the hook registry key associated with an IP Org and relationship type. + /// @param ipOrg_ Address of the IP Org under which the hook is registered. + /// @param params_ Relationship config params from which the type is sourced. function _hookRegistryKey( IIPOrg ipOrg_, address, bytes calldata params_ - ) internal view virtual override returns(bytes32) { - LibRelationship.CreateRelationshipParams memory createParams = abi.decode(params_, (LibRelationship.CreateRelationshipParams)); + ) internal view virtual override returns (bytes32) { + LibRelationship.CreateRelationshipParams memory createParams = abi.decode( + params_, + (LibRelationship.CreateRelationshipParams) + ); return _generateRegistryKey(ipOrg_, createParams.relType); } - function _generateRegistryKey(IIPOrg ipOrg_, string memory relType_) private pure returns(bytes32) { + /// @dev Creates a new hooks registration key for the relationship module. + /// @param ipOrg_ The IP Org under which the key is associated. + function _generateRegistryKey(IIPOrg ipOrg_, string memory relType_) private pure returns (bytes32) { return keccak256(abi.encode(address(ipOrg_), relType_)); } } diff --git a/contracts/utils/FixedSet.sol b/contracts/utils/FixedSet.sol index 68816d22..7c3cc3a2 100644 --- a/contracts/utils/FixedSet.sol +++ b/contracts/utils/FixedSet.sol @@ -1,36 +1,31 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.19; + import { ShortString, ShortStrings } from "@openzeppelin/contracts/utils/ShortStrings.sol"; -/// @title FixedSet -/// @notice Library for Set data structures, based in OpenZeppelin's, with the following changes: -/// - Values cannot be removed from the set, so order is preserved -/// - Index of a value can be obtained -/// - Adds ShortString data type +/// @title FixedSet Library +/// @notice Fork of OZ's set data structures library with the following changs: +/// - Values cannot be removed from the set for order preservation +/// - The library allows obtaining indexes of values +/// - Adds ShortString as a data type library FixedSet { using ShortStrings for *; - uint256 constant INDEX_NOT_FOUND = type(uint256).max; + uint256 internal constant INDEX_NOT_FOUND = type(uint256).max; + /// @notice Data structure for composing a fixed set. struct Set { - // Storage of set values + // Array for storing values within the fixed set. bytes32[] _values; - // Position of the value in the `values` array, plus 1 because index 0 - // means a value is not in the set. + // One-based index of the set value (0 is a sentinel valu). mapping(bytes32 => uint256) _indexes; } - /** - * @dev Add a value to a set. O(1). - * - * Returns true if the value was added to the set, that is if it was not - * already present. - */ + /// @dev Adds a value to a set. function _add(Set storage set, bytes32 value) private returns (bool) { if (!_contains(set, value)) { set._values.push(value); - // The value is stored at length-1, but we add 1 to all indexes - // and use 0 as a sentinel value + // Value is stored at values.length due to one-indexing. set._indexes[value] = set._values.length; return true; } else { @@ -38,50 +33,28 @@ library FixedSet { } } - /** - * @dev Returns true if the value is in the set. O(1). - */ + /// @dev Checks whether a value is contained in the set. function _contains(Set storage set, bytes32 value) private view returns (bool) { return set._indexes[value] != 0; } - /** - * @dev Returns the number of values on the set. O(1). - */ + /// @dev Returns the length of the set. function _length(Set storage set) private view returns (uint256) { return set._values.length; } - /** - * @dev Returns the index of the value in the set, or INDEX_NOT_FOUND if not present. O(1). - */ + /// @dev Returns the index of a value within the set. function _indexOf(Set storage set, bytes32 value) private view returns (uint256) { uint256 index = set._indexes[value]; return index == 0 ? INDEX_NOT_FOUND : index - 1; } - /** - * @dev Returns the value stored at position `index` in the set. O(1). - * - * Note that there are no guarantees on the ordering of values inside the - * array, and it may change when more values are added or removed. - * - * Requirements: - * - * - `index` must be strictly less than {length}. - */ + /// @dev Returns the value stored at the one-indexed positioned within the set. function _at(Set storage set, uint256 index) private view returns (bytes32) { return set._values[index]; } - /** - * @dev Return the entire set in an array - * - * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed - * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that - * this function has an unbounded cost, and using it as part of a state-changing function may render the function - * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block. - */ + /// @dev Returns the entire fixed set as a bytes32-array. function _values(Set storage set) private view returns (bytes32[] memory) { return set._values; } @@ -90,64 +63,37 @@ library FixedSet { // Bytes32Set // //////////////////////////////////////////////////////////////////////////// - + /// @notice Struct for composing fixed sets of bytes32 objects. struct Bytes32Set { Set _inner; } - /** - * @dev Add a value to a set. O(1). - * - * Returns true if the value was added to the set, that is if it was not - * already present. - */ + /// @dev Adds a value to the bytes32 set. function add(Bytes32Set storage set, bytes32 value) internal returns (bool) { return _add(set._inner, value); } - /** - * @dev Returns true if the value is in the set. O(1). - */ + /// @dev Checks whether a bytes32 set contains a value. function contains(Bytes32Set storage set, bytes32 value) internal view returns (bool) { return _contains(set._inner, value); } - /** - * @dev Returns the number of values in the set. O(1). - */ + /// @dev Gets the length of the bytes32 set. function length(Bytes32Set storage set) internal view returns (uint256) { return _length(set._inner); } - /** - * @dev Returns the value stored at position `index` in the set. O(1). - * - * Note that there are no guarantees on the ordering of values inside the - * array, and it may change when more values are added or removed. - * - * Requirements: - * - * - `index` must be strictly less than {length}. - */ + /// @dev Gets the value stored at the one-indexed position within the set. function at(Bytes32Set storage set, uint256 index) internal view returns (bytes32) { return _at(set._inner, index); } - /** - * @dev Returns the index of the value in the set, or INDEX_NOT_FOUND if not present. O(1). - */ + /// @dev Returns the one-indexed position of the value within the set. function indexOf(Bytes32Set storage set, bytes32 value) internal view returns (uint256) { return _indexOf(set._inner, value); } - /** - * @dev Return the entire set in an array - * - * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed - * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that - * this function has an unbounded cost, and using it as part of a state-changing function may render the function - * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block. - */ + /// @dev Returns the entire set of bytes32 objects as an array. function values(Bytes32Set storage set) internal view returns (bytes32[] memory) { bytes32[] memory store = _values(set._inner); bytes32[] memory result; @@ -159,69 +105,42 @@ library FixedSet { return result; } - + //////////////////////////////////////////////////////////////////////////// // ShortStringSet // //////////////////////////////////////////////////////////////////////////// - + /// @notice Fixed set data structure for representing SortStrings. struct ShortStringSet { Set _inner; } - /** - * @dev Add a value to a set. O(1). - * - * Returns true if the value was added to the set, that is if it was not - * already present. - */ + /// @dev Adds a value to the ShortString data set. function add(ShortStringSet storage set, ShortString value) internal returns (bool) { return _add(set._inner, ShortString.unwrap(value)); } - /** - * @dev Returns true if the value is in the set. O(1). - */ + /// @dev Checks whether a ShortString set contains a value. function contains(ShortStringSet storage set, ShortString value) internal view returns (bool) { return _contains(set._inner, ShortString.unwrap(value)); } - /** - * @dev Returns the number of values in the set. O(1). - */ + /// @dev Returns the length of the ShortString set. function length(ShortStringSet storage set) internal view returns (uint256) { return _length(set._inner); } - /** - * @dev Returns the value stored at position `index` in the set. O(1). - * - * Note that there are no guarantees on the ordering of values inside the - * array, and it may change when more values are added or removed. - * - * Requirements: - * - * - `index` must be strictly less than {length}. - */ + /// @dev Returns the value stored at the one-indexed position in the set. function at(ShortStringSet storage set, uint256 index) internal view returns (ShortString) { return ShortString.wrap(_at(set._inner, index)); } - /** - * @dev Returns the index of the value in the set, or INDEX_NOT_FOUND if not present. O(1). - */ + /// @dev Returns the index of the value within the ShortString set. function indexOf(ShortStringSet storage set, ShortString value) internal view returns (uint256) { return _indexOf(set._inner, ShortString.unwrap(value)); } - /** - * @dev Return the entire set in an array - * - * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed - * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that - * this function has an unbounded cost, and using it as part of a state-changing function may render the function - * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block. - */ + /// @dev Returns the entire ShortString data set. function values(ShortStringSet storage set) internal view returns (ShortString[] memory) { bytes32[] memory store = _values(set._inner); ShortString[] memory result; @@ -238,63 +157,37 @@ library FixedSet { // AddressSet // //////////////////////////////////////////////////////////////////////////// + /// @notice Fixed set data structure for representing addresses. struct AddressSet { Set _inner; } - /** - * @dev Add a value to a set. O(1). - * - * Returns true if the value was added to the set, that is if it was not - * already present. - */ + /// @dev Adds a value to the fixed address set. function add(AddressSet storage set, address value) internal returns (bool) { return _add(set._inner, bytes32(uint256(uint160(value)))); } - /** - * @dev Returns true if the value is in the set. O(1). - */ + /// @dev Checks whether a fixed address set contains a value. function contains(AddressSet storage set, address value) internal view returns (bool) { return _contains(set._inner, bytes32(uint256(uint160(value)))); } - /** - * @dev Returns the number of values in the set. O(1). - */ + /// @dev Returns the length of the fixed address set. function length(AddressSet storage set) internal view returns (uint256) { return _length(set._inner); } - /** - * @dev Returns the value stored at position `index` in the set. O(1). - * - * Note that there are no guarantees on the ordering of values inside the - * array, and it may change when more values are added or removed. - * - * Requirements: - * - * - `index` must be strictly less than {length}. - */ + /// @dev Returns the value stored at the one-indexed position in the set. function at(AddressSet storage set, uint256 index) internal view returns (address) { return address(uint160(uint256(_at(set._inner, index)))); } - /** - * @dev Returns the index of the value in the set, or INDEX_NOT_FOUND if not present. O(1). - */ + /// @dev Gets the index of an address within the fixed set. function indexOf(AddressSet storage set, address value) internal view returns (uint256) { return _indexOf(set._inner, bytes32(uint256(uint160(value)))); } - /** - * @dev Return the entire set in an array - * - * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed - * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that - * this function has an unbounded cost, and using it as part of a state-changing function may render the function - * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block. - */ + /// @dev Returns the entire suite of addresses stored in the set. function values(AddressSet storage set) internal view returns (address[] memory) { bytes32[] memory store = _values(set._inner); address[] memory result; @@ -311,64 +204,37 @@ library FixedSet { // UintSet // //////////////////////////////////////////////////////////////////////////// + /// @notice Fixed set data structure for storing uint256 numbers. struct UintSet { Set _inner; } - /** - * @dev Add a value to a set. O(1). - * - * Returns true if the value was added to the set, that is if it was not - * already present. - */ + /// @dev Adds a uint256 value into the fixed set. function add(UintSet storage set, uint256 value) internal returns (bool) { return _add(set._inner, bytes32(value)); } - /** - * @dev Returns true if the value is in the set. O(1). - */ + /// @dev Checks whether the fixed set contains a uint256 value. function contains(UintSet storage set, uint256 value) internal view returns (bool) { return _contains(set._inner, bytes32(value)); } - /** - * @dev Returns the number of values in the set. O(1). - */ + /// @dev Returns the length of the uint256 set. function length(UintSet storage set) internal view returns (uint256) { return _length(set._inner); } - /** - * @dev Returns the value stored at position `index` in the set. O(1). - * - * Note that there are no guarantees on the ordering of values inside the - * array, and it may change when more values are added or removed. - * - * Requirements: - * - * - `index` must be strictly less than {length}. - */ + /// @dev Returns the value stored at a one-indexed position within the set. function at(UintSet storage set, uint256 index) internal view returns (uint256) { return uint256(_at(set._inner, index)); } - /** - * @dev Returns the index of the value in the set, or INDEX_NOT_FOUND if not present. O(1). - */ + /// @dev Returns the index of a uint256 value within the set. function indexOf(UintSet storage set, uint256 value) internal view returns (uint256) { return _indexOf(set._inner, bytes32(value)); } - - /** - * @dev Return the entire set in an array - * - * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed - * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that - * this function has an unbounded cost, and using it as part of a state-changing function may render the function - * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block. - */ + /// @dev Returns the entire suite of uint256 values within the set. function values(UintSet storage set) internal view returns (uint256[] memory) { bytes32[] memory store = _values(set._inner); uint256[] memory result; diff --git a/contracts/utils/LibDuration.sol b/contracts/utils/LibDuration.sol deleted file mode 100644 index 40e848ab..00000000 --- a/contracts/utils/LibDuration.sol +++ /dev/null @@ -1,50 +0,0 @@ -// 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; - -import { Errors } from "contracts/lib/Errors.sol"; - -library LibDuration { - - struct TimeConfig { - uint64 ttl; - uint64 startTime; - address renewer; - } - - uint64 public constant START_TIME_NOT_SET = uint64(0); - - function isActive(TimeConfig memory self_) internal view returns (bool) { - return self_.startTime != START_TIME_NOT_SET && block.timestamp >= self_.startTime && block.timestamp < self_.startTime + self_.ttl; - } - - function renew(TimeConfig memory self_, uint64 ttl_) view internal { - if (!isRenewable(self_)) revert Errors.LibDuration_NotRenewable(); - if (msg.sender != self_.renewer) revert Errors.LibDuration_CallerNotRenewer(); - if (ttl_ == 0) revert Errors.LibDuration_ZeroTTL(); - self_.ttl = ttl_; - self_.startTime = uint64(block.timestamp); - } - - function createRunningTimeConfig(uint64 ttl_, address renewer_) internal view returns (TimeConfig memory) { - if (ttl_ == 0) revert Errors.LibDuration_ZeroTTL(); - return TimeConfig({ - ttl: ttl_, - startTime: uint64(block.timestamp), - renewer: renewer_ - }); - } - - function isRenewable(TimeConfig memory self_) internal pure returns (bool) { - return self_.renewer != address(0); - } - - function createStoppedTimeConfig(uint64 ttl_, address renewer_) internal pure returns (TimeConfig memory) { - if (ttl_ == 0) revert Errors.LibDuration_ZeroTTL(); - return TimeConfig({ - ttl: ttl_, - startTime: 0, - renewer: renewer_ - }); - } -} diff --git a/contracts/utils/ShortStringOps.sol b/contracts/utils/ShortStringOps.sol index 2ae3bf74..2a86936e 100644 --- a/contracts/utils/ShortStringOps.sol +++ b/contracts/utils/ShortStringOps.sol @@ -1,51 +1,40 @@ // SPDX-License-Identifier: UNLICENSED -// See Story Protocol Alpha Agreement: https://github.com/storyprotocol/protocol-contracts/blob/main/StoryProtocol-AlphaTestingAgreement-17942166.3.pdf +// See https://github.com/storyprotocol/protocol-contracts/blob/main/StoryProtocol-AlphaTestingAgreement-17942166.3.pdf pragma solidity ^0.8.19; import { ShortString, ShortStrings } from "@openzeppelin/contracts/utils/ShortStrings.sol"; +/// @notice Library for working with Openzeppelin's ShortString data types. library ShortStringOps { using ShortStrings for *; - function _equal( - ShortString a, - ShortString b - ) internal pure returns (bool) { + /// @dev Compares whether two ShortStrings are equal. + function _equal(ShortString a, ShortString b) internal pure returns (bool) { return ShortString.unwrap(a) == ShortString.unwrap(b); } - function _equal( - ShortString a, - string memory b - ) internal pure returns (bool) { + /// @dev Checks whether a ShortString and a regular string are equal. + function _equal(ShortString a, string memory b) internal pure returns (bool) { return _equal(a, b.toShortString()); } - function _equal( - string memory a, - ShortString b - ) internal pure returns (bool) { + /// @dev Checks whether a regular string and a ShortString are equal. + function _equal(string memory a, ShortString b) internal pure returns (bool) { return _equal(a.toShortString(), b); } - function _equal( - bytes32 a, - ShortString b - ) internal pure returns (bool) { + /// @dev Checks whether a bytes32 object and ShortString are equal. + function _equal(bytes32 a, ShortString b) internal pure returns (bool) { return a == ShortString.unwrap(b); } - function _equal( - string memory a, - bytes32 b - ) internal pure returns (bool) { + /// @dev Checks whether a string and bytes32 object are equal. + function _equal(string memory a, bytes32 b) internal pure returns (bool) { return _equal(a, ShortString.wrap(b)); } - function _equal( - bytes32 a, - string memory b - ) internal pure returns (bool) { + /// @dev Checks whether a bytes32 object and string are equal. + function _equal(bytes32 a, string memory b) internal pure returns (bool) { return _equal(ShortString.wrap(a), b); } } diff --git a/package.json b/package.json index 19cf163e..46bac88b 100644 --- a/package.json +++ b/package.json @@ -26,10 +26,10 @@ "hardhat-deploy": "^0.11.25", "hardhat-gas-reporter": "^1.0.9", "mocha": "^10.2.0", - "prettier": "2.8.7", - "prettier-plugin-solidity": "^1.1.3", + "prettier": "^3.1.0", + "prettier-plugin-solidity": "^1.2.0", "solhint": "^3.6.2", - "solhint-plugin-prettier": "^0.0.5", + "solhint-plugin-prettier": "^0.1.0", "solidity-coverage": "^0.8.2", "solidity-docgen": "^0.6.0-beta.36" }, diff --git a/yarn.lock b/yarn.lock index fe78e254..a8f74cbd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1162,6 +1162,11 @@ proper-lockfile "^4.1.1" solidity-ast "^0.4.15" +"@prettier/sync@^0.3.0": + version "0.3.0" + resolved "https://registry.yarnpkg.com/@prettier/sync/-/sync-0.3.0.tgz#91f2cfc23490a21586d1cf89c6f72157c000ca1e" + integrity sha512-3dcmCyAxIcxy036h1I7MQU/uEEBq8oLwf1CE3xeze+MPlgkdlb/+w6rGR/1dhp6Hqi17fRS6nvwnOzkESxEkOw== + "@resolver-engine/core@^0.3.3": version "0.3.3" resolved "https://registry.npmjs.org/@resolver-engine/core/-/core-0.3.3.tgz" @@ -1303,6 +1308,13 @@ dependencies: antlr4ts "^0.5.0-alpha.4" +"@solidity-parser/parser@^0.16.2": + version "0.16.2" + resolved "https://registry.yarnpkg.com/@solidity-parser/parser/-/parser-0.16.2.tgz#42cb1e3d88b3e8029b0c9befff00b634cd92d2fa" + integrity sha512-PI9NfoA3P8XK2VBkK5oIfRgKDsicwDZfkVq9ZTBCQYGOP1N2owgY2dyLGyU5/J/hQs8KRk55kdmvTLjy3Mu3vg== + dependencies: + antlr4ts "^0.5.0-alpha.4" + "@trufflesuite/bigint-buffer@1.1.10": version "1.1.10" resolved "https://registry.npmjs.org/@trufflesuite/bigint-buffer/-/bigint-buffer-1.1.10.tgz" @@ -5243,20 +5255,25 @@ prettier-linter-helpers@^1.0.0: dependencies: fast-diff "^1.1.2" -prettier-plugin-solidity@^1.1.3: - version "1.1.3" - resolved "https://registry.npmjs.org/prettier-plugin-solidity/-/prettier-plugin-solidity-1.1.3.tgz" - integrity sha512-fQ9yucPi2sBbA2U2Xjh6m4isUTJ7S7QLc/XDDsktqqxYfTwdYKJ0EnnywXHwCGAaYbQNK+HIYPL1OemxuMsgeg== +prettier-plugin-solidity@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/prettier-plugin-solidity/-/prettier-plugin-solidity-1.2.0.tgz#dc620b4fc7708a60687a87cdc803e57a1856b6fd" + integrity sha512-fgxcUZpVAP+LlRfy5JI5oaAkXGkmsje2VJ5krv/YMm+rcTZbIUwFguSw5f+WFuttMjpDm6wB4UL7WVkArEfiVA== dependencies: - "@solidity-parser/parser" "^0.16.0" - semver "^7.3.8" + "@solidity-parser/parser" "^0.16.2" + semver "^7.5.4" solidity-comments-extractor "^0.0.7" -prettier@2.8.7, prettier@^2.3.1, prettier@^2.7.1, prettier@^2.8.3: +prettier@^2.3.1, prettier@^2.7.1, prettier@^2.8.3: version "2.8.7" resolved "https://registry.npmjs.org/prettier/-/prettier-2.8.7.tgz" integrity sha512-yPngTo3aXUUmyuTjeTUT75txrf+aMh9FiD7q9ZE/i6r0bPb22g4FsE6Y338PQX1bmfy08i9QQCB7/rcUAVntfw== +prettier@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.1.0.tgz#c6d16474a5f764ea1a4a373c593b779697744d5e" + integrity sha512-TQLvXjq5IAibjh8EpBIkNKxO749UEWABoiIZehEPiY4GNpVdhaFKqSTu+QrlU6D2dPAfubRmtJTi4K4YkQ5eXw== + process-nextick-args@~2.0.0: version "2.0.1" resolved "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz" @@ -5710,7 +5727,7 @@ semver@^6.3.0: resolved "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz" integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== -semver@^7.3.4, semver@^7.3.8, semver@^7.5.2, semver@^7.5.3: +semver@^7.3.4, semver@^7.5.2, semver@^7.5.3, semver@^7.5.4: version "7.5.4" resolved "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz" integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA== @@ -5849,11 +5866,12 @@ solc@0.8.15: semver "^5.5.0" tmp "0.0.33" -solhint-plugin-prettier@^0.0.5: - version "0.0.5" - resolved "https://registry.npmjs.org/solhint-plugin-prettier/-/solhint-plugin-prettier-0.0.5.tgz" - integrity sha512-7jmWcnVshIrO2FFinIvDQmhQpfpS2rRRn3RejiYgnjIE68xO2bvrYvjqVNfrio4xH9ghOqn83tKuTzLjEbmGIA== +solhint-plugin-prettier@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/solhint-plugin-prettier/-/solhint-plugin-prettier-0.1.0.tgz#2f46999e26d6c6bc80281c22a7a21e381175bef7" + integrity sha512-SDOTSM6tZxZ6hamrzl3GUgzF77FM6jZplgL2plFBclj/OjKP8Z3eIPojKU73gRr0MvOS8ACZILn8a5g0VTz/Gw== dependencies: + "@prettier/sync" "^0.3.0" prettier-linter-helpers "^1.0.0" solhint@^3.6.2: