diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b3ddb91f..7166555f 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -67,20 +67,3 @@ jobs: # run: # forge coverage --report lcov --report summary # id: forge-code-coverage - - hardhat-test: - strategy: - fail-fast: true - - name: Hardhat Unit Test - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - with: - submodules: recursive - - name: Environment - uses: actions/setup-node@v3 - - name: Test - uses: ambersun1234/hardhat-test-action@v0.0.1 - with: - network: hardhat diff --git a/contracts/IPAssetRegistry.sol b/contracts/IPAssetRegistry.sol index 17fd684a..1b01a833 100644 --- a/contracts/IPAssetRegistry.sol +++ b/contracts/IPAssetRegistry.sol @@ -7,27 +7,17 @@ import { IModuleRegistry } from "contracts/interfaces/modules/IModuleRegistry.so import { IIPOrg } from "contracts/interfaces/ip-org/IIPOrg.sol"; import { ModuleRegistryKeys } from "contracts/lib/modules/ModuleRegistryKeys.sol"; import { Errors } from "contracts/lib/Errors.sol"; +import { IPAsset } from "contracts/lib/IPAsset.sol"; /// @title Global IP Asset Registry /// @notice The source of truth for IP on Story Protocol. contract IPAssetRegistry is IIPAssetRegistry { - /// @notice Core attributes that make up an IP Asset. - struct IPA { - string name; // Human-readable identifier for the IP asset. - uint64 ipAssetType; // Numerical code corresponding to IP type (e.g. patent, copyright, etc.) - 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. IModuleRegistry public immutable MODULE_REGISTRY; /// @notice Mapping from IP asset ids to registry records. - mapping(uint256 => IPA) internal _ipAssets; + mapping(uint256 => IPAsset.IPA) internal _ipAssets; /// @notice Tracks the total number of IP Assets in existence. /// TODO(leeren) Switch from numerical ids to a universal namehash. @@ -73,10 +63,10 @@ contract IPAssetRegistry is IIPAssetRegistry { // Crate a new IP asset with the provided IP attributes. ipAssetId = totalSupply++; uint64 registrationDate = uint64(block.timestamp); - _ipAssets[ipAssetId] = IPA({ + _ipAssets[ipAssetId] = IPAsset.IPA({ name: name_, ipAssetType: ipAssetType_, - status: 0, // TODO(ramarti): Define status types. + status: 1, // TODO(ramarti): Define status types. registrant: registrant_, ipOrg: msg.sender, hash: hash_, @@ -132,8 +122,8 @@ contract IPAssetRegistry is IIPAssetRegistry { /// @notice Returns all attributes related to an IP asset. /// @param ipAssetId_ The id of the IP asset being queried for. - function ipAsset(uint256 ipAssetId_) public view returns (IPA memory) { + function ipAsset(uint256 ipAssetId_) public view returns (IPAsset.IPA memory) { return _ipAssets[ipAssetId_]; } -} +} \ No newline at end of file diff --git a/contracts/StoryProtocol.sol b/contracts/StoryProtocol.sol index 9f6fdbfe..327010c6 100644 --- a/contracts/StoryProtocol.sol +++ b/contracts/StoryProtocol.sol @@ -10,6 +10,7 @@ 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"; contract StoryProtocol { @@ -82,10 +83,6 @@ contract StoryProtocol { bytes[] calldata preHooksData_, bytes[] calldata postHooksData_ ) public returns (uint256, uint256) { - bytes memory encodedParams = abi.encode( - Registration.REGISTER_IP_ASSET, - abi.encode(params_) - ); bytes memory result = MODULE_REGISTRY.execute( IIPOrg(ipOrg_), msg.sender, @@ -170,4 +167,85 @@ contract StoryProtocol { ); return abi.decode(result, (uint256)); } + + //////////////////////////////////////////////////////////////////////////// + // Licensing // + //////////////////////////////////////////////////////////////////////////// + + /// Allows an IPOrg to configure its licensing framework (collection of commercial and non-commercial terms) + /// @param ipOrg_ the ipOrg address + /// @param framework_ licensing term id array, and matching term data array to configure them + function configureIpOrgLicensing( + address ipOrg_, + Licensing.FrameworkConfig calldata framework_ + ) external { + MODULE_REGISTRY.configure( + IIPOrg(ipOrg_), + msg.sender, + ModuleRegistryKeys.LICENSING_MODULE, + abi.encode(Licensing.LICENSING_FRAMEWORK_CONFIG, abi.encode(framework_)) + ); + } + + /// Creates a tradeable License NFT in License Registry. + /// @param ipOrg_ the ipOrg address + /// @param params_ LicenseCreation params + /// @param licensee_ address of the licensee (and owner of the NFT) + /// @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 + function createLicenseNft( + address ipOrg_, + Licensing.LicenseCreation calldata params_, + address licensee_, + bytes[] calldata preHooksData_, + bytes[] calldata postHooksData_ + ) external returns (uint256) { + bytes memory params = abi.encode( + params_, + Licensing.LicenseeType.LNFTHolder, + abi.encode(licensee_) + ); + return abi.decode( + MODULE_REGISTRY.execute( + IIPOrg(ipOrg_), + msg.sender, + ModuleRegistryKeys.LICENSING_MODULE, + params, + preHooksData_, + postHooksData_ + ), + (uint256) + ); + } + + /// Creates a License bound to a certain IPA. It's not an NFT, the licensee will be the owner of the IPA. + /// @param ipOrg_ the ipOrg address + /// @param params_ LicenseCreation params + /// @param ipaId_ id of the bound IPA + /// @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 + function createIpaBoundLicense( + address ipOrg_, + Licensing.LicenseCreation calldata params_, + uint256 ipaId_, + bytes[] calldata preHooksData_, + bytes[] calldata postHooksData_ + ) external returns (uint256) { + bytes memory params = abi.encode( + params_, + Licensing.LicenseeType.BoundToIpa, + abi.encode(ipaId_) + ); + return abi.decode( MODULE_REGISTRY.execute( + IIPOrg(ipOrg_), + msg.sender, + ModuleRegistryKeys.LICENSING_MODULE, + params, + preHooksData_, + postHooksData_ + ), + (uint256)); + } } diff --git a/contracts/access-control/AccessControlled.sol b/contracts/access-control/AccessControlled.sol index a8d4f903..d30a2748 100644 --- a/contracts/access-control/AccessControlled.sol +++ b/contracts/access-control/AccessControlled.sol @@ -8,7 +8,6 @@ import { IAccessControlled } from "contracts/interfaces/access-control/IAccessCo 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 { UnsupportedInterface } from "../errors/General.sol"; abstract contract AccessControlled is IAccessControlled { using ERC165CheckerUpgradeable for address; diff --git a/contracts/errors/General.sol b/contracts/errors/General.sol deleted file mode 100644 index a20ef42c..00000000 --- a/contracts/errors/General.sol +++ /dev/null @@ -1,11 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 - -pragma solidity ^0.8.13; - -error ZeroAddress(); -error ZeroAmount(); -error UnsupportedInterface(string name); -error Unauthorized(); -error NonExistentID(uint256 id); -error EmptyArray(); -error LengthMismatch(); diff --git a/contracts/interfaces/modules/base/IModule.sol b/contracts/interfaces/modules/base/IModule.sol index 7a496aad..870487f7 100644 --- a/contracts/interfaces/modules/base/IModule.sol +++ b/contracts/interfaces/modules/base/IModule.sol @@ -37,6 +37,11 @@ interface IModule { /// @param ipOrg_ address of the IPOrg or zero address /// @param caller_ address requesting the execution /// @param params_ encoded params for module configuration - function configure(IIPOrg ipOrg_, address caller_, bytes calldata params_) external; + /// @return result of the module configuration + function configure( + IIPOrg ipOrg_, + address caller_, + bytes calldata params_ + ) external returns (bytes memory result); } diff --git a/contracts/interfaces/modules/licensing/IERC5218.sol b/contracts/interfaces/modules/licensing/IERC5218.sol deleted file mode 100644 index dd8b3416..00000000 --- a/contracts/interfaces/modules/licensing/IERC5218.sol +++ /dev/null @@ -1,150 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.0; - -import { IERC721Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC721/IERC721Upgradeable.sol"; -import { ITermsProcessor } from "./terms/ITermsProcessor.sol"; -import { Licensing } from "contracts/lib/modules/Licensing.sol"; - -/// @title ERC-5218: NFT Rights Management -interface IERC5218 is IERC721Upgradeable { - /// @dev This emits when a new license is created by any mechanism. - event CreateLicense( - uint256 _licenseId, - uint256 _tokenId, - uint256 _parentLicenseId, - address _licenseHolder, - string _uri, - address _revoker - ); - - // NOTE: MODIFIED ERC-5218, this may be refactored into ILicenseTermsExecutor - /// @dev emits when license terms are executed to activate a license - event ExecuteTerms(uint256 _licenseId, bytes _data); - /// @dev This emits when the terms of a license are updated, after executing a processor - event TermsUpdated(uint256 indexed licenseId, address processor, bytes termsData); - - /// @dev This emits when a license is revoked. Note that under some - /// license terms, the sublicenses may be `implicitly` revoked following the - /// revocation of some ancestral license. In that case, your smart contract - /// may only emit this event once for the ancestral license, and the revocation - /// of all its sublicenses can be implied without consuming additional gas. - event RevokeLicense(uint256 _licenseId); - - /// @dev This emits when the a license is transferred to a new holder. The - /// root license of an NFT should be transferred with the NFT in an ERC721 - /// `transfer` function call. - event TransferLicense(uint256 _licenseId, address _licenseHolder); - - - /// @notice Create a new license. - /// @dev Throws unless the NFT `_tokenId` exists. Throws unless the parent - /// license `_parentLicenseId` is active, or `_parentLicenseId` is a special - /// identifier not referring to any license (such as 0) and the NFT - /// `_tokenId` doesn't have a root license tethered to it. Throws unless the - /// message sender is eligible to create the license, i.e., either the - /// license to be created is a root license and `msg.sender` is the NFT owner, - /// or the license to be created is a sublicense and `msg.sender` is the holder - /// of the parent license. - /// @param _tokenId The identifier for the NFT the license is issued upon - /// @param _parentLicenseId The identifier for the parent license - /// @param _licenseHolder The address of the license holder - /// @param _uri The URI of the license terms - /// @param _revoker The revoker address - /// @param _commercial Whether the license granted is commercial or non commercial - /// @param _canSublicense Whether the license holder can sublicense the license - /// @param _terms The license terms - /// @return The identifier of the created license - function createLicense( - uint256 _tokenId, - uint256 _parentLicenseId, - address _licenseHolder, - string memory _uri, - address _revoker, - bool _commercial, // NOTE: MODIFIED ERC-5218 - bool _canSublicense, // NOTE: MODIFIED ERC-5218 - Licensing.TermsProcessorConfig memory _terms // NOTE: MODIFIED ERC-5218 - ) external returns (uint256); - - /// @notice Revoke a license. - /// @dev Throws unless the license is active and the message sender is the - /// eligible revoker. This function should be used for revoking both root - /// licenses and sublicenses. Note that if a root license is revoked, the - /// NFT should be transferred back to its creator. - /// @param _licenseId The identifier for the queried license - function revokeLicense(uint256 _licenseId) external; - - /// @notice Transfer a sublicense. - /// @dev Throws unless the sublicense is active and `msg.sender` is the license - /// holder. Note that the root license of an NFT should be tethered to and - /// transferred with the NFT. Whenever an NFT is transferred by calling the - /// ERC721 `transfer` function, the holder of the root license should be - /// changed to the new NFT owner. - /// @param _licenseId The identifier for the queried license - /// @param _licenseHolder The new license holder - function transferSublicense( - uint256 _licenseId, - address _licenseHolder - ) external; - - /// @notice Check if a license is active. - /// @dev A non-existing or revoked license is inactive and this function must - /// return `false` upon it. Under some license terms, a license may become - /// inactive because some ancestral license has been revoked. In that case, - /// this function should return `false`. - /// @param _licenseId The identifier for the queried license - /// @return Whether the queried license is active - function isLicenseActive(uint256 _licenseId) external view returns (bool); - - /// @notice Retrieve the token identifier a license was issued upon. - /// @dev Throws unless the license is active. - /// @param _licenseId The identifier for the queried license - /// @return The token identifier the queried license was issued upon - function getLicenseTokenId( - uint256 _licenseId - ) external view returns (uint256); - - /// @notice Retrieve the parent license identifier of a license. - /// @dev Throws unless the license is active. If a license doesn't have a - /// parent license, return a special identifier not referring to any license - /// (such as 0). - /// @param _licenseId The identifier for the queried license - /// @return The parent license identifier of the queried license - function getParentLicenseId( - uint256 _licenseId - ) external view returns (uint256); - - /// @notice Retrieve the holder of a license. - /// @dev Throws unless the license is active. - /// @param _licenseId The identifier for the queried license - /// @return The holder address of the queried license - function getLicenseHolder( - uint256 _licenseId - ) external view returns (address); - - /// @notice Retrieve the URI of a license. - /// @dev Throws unless the license is active. - /// @param _licenseId The identifier for the queried license - /// @return The URI of the queried license - function getLicenseURI( - uint256 _licenseId - ) external view returns (string memory); - - /// @notice Retrieve the revoker address of a license. - /// @dev Throws unless the license is active. - /// @param _licenseId The identifier for the queried license - /// @return The revoker address of the queried license - function getLicenseRevoker( - uint256 _licenseId - ) external view returns (address); - - /// @notice Retrieve the root license identifier of an NFT. - /// @dev Throws unless the queried NFT exists. If the NFT doesn't have a root - /// license tethered to it, return a special identifier not referring to any - /// license (such as 0). - /// @param _tokenId The identifier for the queried NFT - /// @return The root license identifier of the queried NFT - function getLicenseIdByTokenId( // NOTE: This will not work for sublicenses that have a tokenId associated. Rename to getRootLicenseIdByTokenId?? - uint256 _tokenId, - bool _commercial // NOTE: MODIFIED ERC-5218 - ) external view returns (uint256); -} diff --git a/contracts/interfaces/modules/licensing/ILicenseRegistry.sol b/contracts/interfaces/modules/licensing/ILicenseRegistry.sol deleted file mode 100644 index 2f788435..00000000 --- a/contracts/interfaces/modules/licensing/ILicenseRegistry.sol +++ /dev/null @@ -1,18 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.13; -import { IERC721 } from "@openzeppelin/contracts/token/ERC721/IERC721.sol"; - - - -/// @title ILicenseRegistry -/// @author Raul Martinez -/// @notice Interface for NFT tracking the ownership of tradeable Licenses emitted by a RightsManager. -interface ILicenseRegistry is IERC721 { - - function mint(address to_, uint256 tokenId_) external; - function exists(uint256 tokenId_) external view returns (bool); - function name() external view returns (string memory); - function symbol() external view returns (string memory); - function getRightsManager() external view returns (address); - -} diff --git a/contracts/interfaces/modules/licensing/ILicensingModule.sol b/contracts/interfaces/modules/licensing/ILicensingModule.sol deleted file mode 100644 index 5d531f05..00000000 --- a/contracts/interfaces/modules/licensing/ILicensingModule.sol +++ /dev/null @@ -1,23 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.13; -import { ZeroAddress, Unauthorized } from "contracts/errors/General.sol"; -import { IPOrgController } from "contracts/ip-org/IPOrgController.sol"; -import { UUPSUpgradeable } from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; -import { AccessControlledUpgradeable } from "contracts/access-control/AccessControlledUpgradeable.sol"; -import { ITermsProcessor } from "./terms/ITermsProcessor.sol"; -import { Licensing } from "contracts/lib/modules/Licensing.sol"; -import { IERC5218 } from "./IERC5218.sol"; - -interface ILicensingModule { - - event NonCommercialLicenseUriSet(string uri); - - event IPOrgConfigSet(address ipAssetOrg, Licensing.IPOrgConfig config); - - function configureIpOrgLicensing(address ipAssetOrg_, Licensing.IPOrgConfig memory config_) external; - - function getIpOrgConfig(address ipAssetOrg_) external view returns (Licensing.IPOrgConfig memory); - - function getNonCommercialLicenseURI() external view returns (string memory); - -} diff --git a/contracts/lib/Errors.sol b/contracts/lib/Errors.sol index 8a67ecbb..954f1457 100644 --- a/contracts/lib/Errors.sol +++ b/contracts/lib/Errors.sol @@ -62,7 +62,7 @@ library Errors { error HookRegistry_IndexOutOfBounds(uint256 hooksIndex); //////////////////////////////////////////////////////////////////////////// - // BaseRelationshipProcessor // + // BaseRelationshipProcessor // //////////////////////////////////////////////////////////////////////////// /// @notice Call may only be processed by the relationship module. @@ -196,7 +196,7 @@ library Errors { error LibUintArrayMask_InvalidType(IPAsset.IPAssetType ipAsset); //////////////////////////////////////////////////////////////////////////// - // IPOrg // + // IPOrg // //////////////////////////////////////////////////////////////////////////// /// @notice IP identifier is over bounds. @@ -239,66 +239,49 @@ library Errors { /// @notice A zero TTL may not be used for configuration. error LibDuration_ZeroTTL(); - - //////////////////////////////////////////////////////////////////////////// - // LicensingModule // - //////////////////////////////////////////////////////////////////////////// - - /// @notice The franchise does not exist. - error LicensingModule_NonExistentIPOrg(); - - /// @notice The root license is not active - error LicensingModule_RootLicenseNotActive(uint256 rootLicenseId); - - /// @notice The revoker may not be a zero address. - error LicensingModule_ZeroRevokerAddress(); //////////////////////////////////////////////////////////////////////////// - // RightsManager // + // TermsRegistry // //////////////////////////////////////////////////////////////////////////// - /// @notice Root license is already configured. - error RightsManager_AlreadyHasRootLicense(); - - /// @notice License cannot be sublicensed. - error RightsManager_CannotSublicense(); - - /// @notice Commercial terms do not match. - error RightsManager_CommercialTermsMismatch(); - - /// @notice License is inactive. - error RightsManager_InactiveLicense(); - - /// @notice Parent license is inactive. - error RightsManager_InactiveParentLicense(); - - /// @notice The license registry is not configured. - error RightsManager_LicenseRegistryNotConfigured(); - - /// @notice NFT is not associated with a license. - error RightsManager_NFTHasNoAssociatedLicense(); - - /// @notice Caller is not owner of parent license. - error RightsManager_NotOwnerOfParentLicense(); - - /// @notice The targeted license is not a sublicense. - error RightsManager_NotSublicense(); - - /// @notice Sender is not the license revoker. - error RightsManager_SenderNotRevoker(); - - /// @notice A create franchise root license must be used. - error RightsManager_UseCreateIPOrgRootLicenseInstead(); - - /// @notice The revoker may not be the zero address. - error RightsManager_ZeroRevokerAddress(); + error TermsRegistry_UnsupportedTermCategory(); + error TermsRegistry_UnsupportedTerm(); + error TermsRegistry_TermAlreadyExists(); + error TermsRegistry_CommercialStatusUnset(); //////////////////////////////////////////////////////////////////////////// - // MultiTermsProcessor // + // LicenseCreatorModule // //////////////////////////////////////////////////////////////////////////// - /// @notice Too many terms were selected. - error MultiTermsProcessor_TooManyTermsProcessors(); + /// @notice The franchise does not exist. + error LicensingModule_NonExistentIPOrg(); + error LicensingModule_CallerNotIpOrgOwner(); + error LicensingModule_InvalidConfigType(); + error LicensingModule_InvalidTermCommercialStatus(); + error LicensingModule_IpOrgFrameworkAlreadySet(); + error LicensingModule_DuplicateTermId(); + error LicensingModule_InvalidIntent(); + error LicensingModule_IpaNotActive(); + error LicensingModule_IpaIdRequired(); + error LicensingModule_CommercialLicenseNotAllowed(); + error LicensingModule_NonCommercialTermsRequired(); + error LicensingModule_IpOrgNotConfigured(); + error LicensingModule_ipOrgTermNotFound(); + error LicensingModule_ShareAlikeDisabled(); + + //////////////////////////////////////////////////////////////////////////// + // LicenseRegistry // + //////////////////////////////////////////////////////////////////////////// + + error LicensingModule_InvalidLicenseeType(); + error LicenseRegistry_ZeroIpaRegistryAddress(); + error LicenseRegistry_LNFTShouldNotHaveIpaId(); + error LicenseRegistry_BoundToIpaShouldHaveIpaId(); + error LicenseRegistry_UnknownLicenseId(); + error LicenseRegistry_NotLicenseNFT(); + error LicenseRegistry_InvalidIpa(); + error LicenseRegistry_ZeroModuleRegistryAddress(); + error LicenseRegistry_CallerNotLicensingModule(); //////////////////////////////////////////////////////////////////////////// // RegistrationModule // diff --git a/contracts/lib/IPAsset.sol b/contracts/lib/IPAsset.sol index 368cc470..8988817c 100644 --- a/contracts/lib/IPAsset.sol +++ b/contracts/lib/IPAsset.sol @@ -11,6 +11,17 @@ library IPAsset { uint256 private constant _ID_RANGE = 10 ** 12; + /// @notice Core attributes that make up an IP Asset. + struct IPA { + string name; // Human-readable identifier for the IP asset. + uint64 ipAssetType; // Numerical code corresponding to IP type (e.g. patent, copyright, etc.) + 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. + } + enum IPAssetType { UNDEFINED, STORY, diff --git a/contracts/lib/modules/Licensing.sol b/contracts/lib/modules/Licensing.sol index 24c52043..adda8a24 100644 --- a/contracts/lib/modules/Licensing.sol +++ b/contracts/lib/modules/Licensing.sol @@ -1,49 +1,121 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.19; -import { ITermsProcessor } from "contracts/interfaces/modules/licensing/terms/ITermsProcessor.sol"; +import { IHook } from "contracts/interfaces/hooks/base/IHook.sol"; +import { FixedSet } from "contracts/utils/FixedSet.sol"; +import { ShortString } from "@openzeppelin/contracts/utils/ShortStrings.sol"; /// @title Licensing Module Library +/// Structs needed by the Licensing Modules and registries library Licensing { - /// @notice Configuration for terms processing. - struct TermsProcessorConfig { - ITermsProcessor processor; + /// @notice Struct that holds the data for a license + struct License { + /// States the commercial nature of the license. All terms will follow. + bool isCommercial; + /// address granting the license + address licensor; + /// address that could make a license invalid + address revoker; + /// address of the ip org that produced the terms + address ipOrg; + /// Defines how to find the address of the licensee + LicenseeType licenseeType; + /// If the licensee is bound to an IPA, this is the IPA id. 0 otherwise + uint256 ipaId; + /// The id of the parent license. 0 if this this is tied to the first IPA of an IPOrg + uint256 parentLicenseId; + /// The ids of the Licensing Terms that make up the license. + /// The terms definitions are in TermsRepository contract + ShortString[] termIds; + /// The data configuring each term. May be empty bytes. May be passed to the term hook + bytes[] termsData; + /// Future use bytes data; } - /// @notice IP asset configuration for IP licensing. - struct IpAssetConfig { - bool canSublicense; // If false, this IPAsset cannot be parentLicenseId of any other IPAsset - uint256 ipAssetOrgRootLicenseId; // If set, root IPAsset licenses will have this as their parentLicenseId - // TODO: allowed license terms? processors? - // TODO: limit medium of sublicenses? As in, you can only license prose to prose? something like LibIPAssetMask? - // TODO: limit who you can sublicense to? + /// User facing parameters for creating a license + struct LicenseCreation { + bool isCommercial; + uint256 parentLicenseId; + // TODO: How do we do per user configured terms? + // ShortString[] extraTermIds; + // bytes[] extraTermsData; } - /// @notice IPOrg configuration for IP licensing. - struct IPOrgConfig { - IpAssetConfig nonCommercialConfig; - TermsProcessorConfig nonCommercialTerms; - IpAssetConfig commercialConfig; - TermsProcessorConfig commercialTerms; - bool rootIpAssetHasCommercialRights; + /// Input to add a License the LicenseRegistry + struct RegistryAddition { + /// States the commercial nature of the license. All terms will follow. + bool isCommercial; + /// address granting the license + address licensor; + /// address that could make a license invalid address revoker; - string commercialLicenseUri; + /// address of the ip org that produced the terms + address ipOrg; + /// The id of the parent license. 0 if this this is tied to the first IPA of an IPOrg + uint256 parentLicenseId; + /// The ids of the Licensing Terms that make up the license. + ShortString[] termIds; + /// The data configuring each term. May be empty bytes. May be passed to the term hook + bytes[] termsData; + /// Future use + bytes data; } + enum LicenseeType { + // Empty value + Unset, + // The licensee is the owner of the IPA + BoundToIpa, + // The licensee is the owner of the NFT. ipaId will be 0 in the license + LNFTHolder + } - /// @notice Core licensing structure. - struct License { - bool active; - bool canSublicense; - bool commercial; - uint256 parentLicenseId; - uint256 tokenId; - address revoker; - string uri; // NOTE: should we merge this with IPOrg tokenURI for Licenses who are rights? - ITermsProcessor termsProcessor; - bytes termsData; + /// Defines commercial status on a licensing term + enum CommercialStatus { + /// Empty value + Unset, + /// Term can only be used for commercial licenses + Commercial, + /// Term can only be used for non commercial licenses + NonCommercial, + /// Term could be used for both commercial and non commercial licenses + Both + } + + /// Data needed to create a Licensing Term. A collection of terms can be used to render + /// a license text + struct LicensingTerm { + /// Defines commercial status on a licensing term + CommercialStatus comStatus; + /// URL where the term text can be found. If the destination is not available of changes + /// Licenses created with this term will be candidates for revocation + string url; + /// Hash of the license text + string hash; + /// Hashing algorithm used + string algorithm; + /// If the Licensing term is enforceable on chain, this is the hook that will be called + IHook hook; + // Some terms just need to decode bytes data, not a full blown hook. + // ITermDecoder decoder; // TODO: For now the LicensingModule knows how to decode the data per term id } -} + /// Defines a collection of termIds and their config data. Must be same length + /// @dev: we cannot use this in structs that are going to be saved + /// to storage, like License + struct TermsConfig { + ShortString[] termIds; + bytes[] termData; + } + + /// Input for IpOrg legal terms configuration in LicenseCreatorModule + struct FrameworkConfig { + TermsConfig comTermsConfig; + TermsConfig nonComTermsConfig; + } + + /// Input for IpOrg legal terms configuration in LicenseCreatorModule (for now, the only option) + bytes32 constant LICENSING_FRAMEWORK_CONFIG = keccak256("LICENSING_FRAMEWORK_CONFIG"); +} \ No newline at end of file diff --git a/contracts/lib/modules/ProtocolLicensingTerms.sol b/contracts/lib/modules/ProtocolLicensingTerms.sol new file mode 100644 index 00000000..cac774d6 --- /dev/null +++ b/contracts/lib/modules/ProtocolLicensingTerms.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.19; + +/// List of Licensing Term categories +library TermCategories { + string constant FORMAT_CATEGORIES = "FORMAT_CATEGORIES"; + string constant SHARE_ALIKE = "SHARE_ALIKE"; +} + +/// List of Protocol Term Ids (meaning the Licensing Module will have specific instructions +/// for these terms without the need of a decoder) +/// @dev must be < 32 bytes long, or they will blow up at some point +/// see https://docs.openzeppelin.com/contracts/4.x/api/utils#ShortStrings +library TermIds { + string constant NFT_SHARE_ALIKE = "NFT_SHARE_ALIKE"; +} diff --git a/contracts/modules/ModuleRegistry.sol b/contracts/modules/ModuleRegistry.sol index 392fbc33..94817576 100644 --- a/contracts/modules/ModuleRegistry.sol +++ b/contracts/modules/ModuleRegistry.sol @@ -62,6 +62,11 @@ contract ModuleRegistry is IModuleRegistry, AccessControlled, Multicall { return _protocolModules[moduleKey]; } + // Returns true if the provided address is a module. + function isModule(string calldata moduleKey, address caller_) external view returns (bool) { + return address(_protocolModules[moduleKey]) == caller_; + } + /// Execution entrypoint, callable by any address on its own behalf. /// @param ipOrg_ address of the IPOrg, or address(0) for protocol-level stuff /// @param moduleKey_ short module descriptor @@ -71,10 +76,10 @@ contract ModuleRegistry is IModuleRegistry, AccessControlled, Multicall { /// @return encoded result of the module execution function execute( IIPOrg ipOrg_, - string calldata moduleKey_, - bytes calldata moduleParams_, - bytes[] calldata preHookParams_, - bytes[] calldata postHookParams_ + string memory moduleKey_, + bytes memory moduleParams_, + bytes[] memory preHookParams_, + bytes[] memory postHookParams_ ) external returns (bytes memory) { return _execute(ipOrg_, msg.sender, moduleKey_, moduleParams_, preHookParams_, postHookParams_); } @@ -120,17 +125,17 @@ contract ModuleRegistry is IModuleRegistry, AccessControlled, Multicall { address caller_, string calldata moduleKey_, bytes calldata params_ - ) external onlyRole(AccessControl.MODULE_EXECUTOR_ROLE) { - _configure(ipOrg_, caller_, moduleKey_, params_); + ) external onlyRole(AccessControl.MODULE_EXECUTOR_ROLE) returns (bytes memory) { + return _configure(ipOrg_, caller_, moduleKey_, params_); } function _execute( IIPOrg ipOrg_, address caller_, - string calldata moduleKey_, - bytes calldata moduleParams_, - bytes[] calldata preHookParams_, - bytes[] calldata postHookParams_ + string memory moduleKey_, + bytes memory moduleParams_, + bytes[] memory preHookParams_, + bytes[] memory postHookParams_ ) private returns (bytes memory result) { BaseModule module = _protocolModules[moduleKey_]; if (address(module) == address(0)) { @@ -146,15 +151,13 @@ contract ModuleRegistry is IModuleRegistry, AccessControlled, Multicall { address caller_, string calldata moduleKey_, bytes calldata params_ - ) private { - // if (IIPOrg(ipOrg_).owner() != msg.sender) { - // revert Errors.ModuleRegistry_CallerNotOrgOwner(); - //} + ) private returns (bytes memory result) { BaseModule module = _protocolModules[moduleKey_]; if (address(module) == address(0)) { revert Errors.ModuleRegistry_ModuleNotRegistered(moduleKey_); } - module.configure(ipOrg_, caller_, params_); + result = module.configure(ipOrg_, caller_, params_); emit ModuleConfigured(address(ipOrg_), moduleKey_, caller_, params_); + return result; } } diff --git a/contracts/modules/base/BaseModule.sol b/contracts/modules/base/BaseModule.sol index 85702d48..df403434 100644 --- a/contracts/modules/base/BaseModule.sol +++ b/contracts/modules/base/BaseModule.sol @@ -9,6 +9,7 @@ import { Errors } from "contracts/lib/Errors.sol"; import { IIPOrg } from "contracts/interfaces/ip-org/IIPOrg.sol"; import { ModuleRegistry } from "contracts/modules/ModuleRegistry.sol"; import { IPAssetRegistry } from "contracts/IPAssetRegistry.sol"; +import { LicenseRegistry } from "contracts/modules/licensing/LicenseRegistry.sol"; /// @title BaseModule /// @notice Base implementation for all modules in Story Protocol. This is meant to ensure @@ -20,12 +21,12 @@ abstract contract BaseModule is IModule, HookRegistry { struct ModuleConstruction { IPAssetRegistry ipaRegistry; ModuleRegistry moduleRegistry; - address licenseRegistry; + LicenseRegistry licenseRegistry; } IPAssetRegistry public immutable IPA_REGISTRY; ModuleRegistry public immutable MODULE_REGISTRY; - address public immutable LICENSE_REGISTRY; + LicenseRegistry public immutable LICENSE_REGISTRY; modifier onlyModuleRegistry() { if (msg.sender != address(MODULE_REGISTRY)) { @@ -43,7 +44,7 @@ abstract contract BaseModule is IModule, HookRegistry { revert Errors.BaseModule_ZeroModuleRegistry(); } MODULE_REGISTRY = params_.moduleRegistry; - if (params_.licenseRegistry == address(0)) { + if (address(params_.licenseRegistry) == address(0)) { revert Errors.BaseModule_ZeroLicenseRegistry(); } LICENSE_REGISTRY = params_.licenseRegistry; @@ -80,8 +81,8 @@ abstract contract BaseModule is IModule, HookRegistry { /// @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 { - _configure(ipOrg_, caller_, params_); + function configure(IIPOrg ipOrg_, address caller_, bytes calldata params_) onlyModuleRegistry external returns (bytes memory) { + return _configure(ipOrg_, caller_, params_); } function _executeHooks( @@ -125,7 +126,7 @@ abstract contract BaseModule is IModule, HookRegistry { } function _hookRegistryAdmin() virtual override internal view returns (address); - function _configure(IIPOrg ipOrg_, address caller_, bytes calldata params_) virtual internal; + function _configure(IIPOrg ipOrg_, address caller_, bytes calldata params_) virtual internal returns (bytes memory); function _verifyExecution(IIPOrg ipOrg_, address caller_, bytes calldata params_) virtual internal {} function _performAction(IIPOrg ipOrg_, address caller_, bytes calldata params_) virtual internal returns (bytes memory result) {} diff --git a/contracts/modules/licensing/LicenseCreatorModule.sol b/contracts/modules/licensing/LicenseCreatorModule.sol new file mode 100644 index 00000000..f7638a6a --- /dev/null +++ b/contracts/modules/licensing/LicenseCreatorModule.sol @@ -0,0 +1,399 @@ +// SPDX-License-Identifier: BUSL-1.1 +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 { LibRelationship } from "contracts/lib/modules/LibRelationship.sol"; +import { BaseModule } from "contracts/modules/base/BaseModule.sol"; +import { IIPOrg } from "contracts/interfaces/ip-org/IIPOrg.sol"; +import { RelationshipModule } from "../relationships/RelationshipModule.sol"; +import { TermsRepository } from "./TermsRepository.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 { TermIds } from "contracts/lib/modules/ProtocolLicensingTerms.sol"; + +/// @title License Creator module +/// @notice Story Protocol module that: +/// - Enables each IP Org to select a collection of terms from the TermsRepository to form +/// their licensing framework. +/// - Enables Other modules to attach licensing terms to IPAs +/// - Enables license holders to create derivative licenses +contract LicenseCreatorModule is BaseModule, TermsRepository { + using ShortStrings for *; + using FixedSet for FixedSet.ShortStringSet; + + // NOTE: emitting this event can be very expensive, check if terms can be indexed some + // other way + event IpOrgTermsSet(address indexed ipOrg, bool commercial, ShortString[] termIds, bytes[] termData); + + /// Per ipOrg commercial licensing term Ids. + mapping(address => FixedSet.ShortStringSet) private _comIpOrgTermIds; + /// Per ipOrg data to configure commercial licensing terms, corresponding to the ids. + mapping(address => bytes[]) private _comIpOrgTermData; + /// Per ipOrg non-commercial licensing term Ids. + mapping(address => FixedSet.ShortStringSet) private _nonComIpOrgTermIds; + /// Per ipOrg data to configure non-commercial licensing terms, corresponding to the ids. + mapping(address => bytes[]) private _nonComIpOrgTermData; + + constructor(ModuleConstruction memory params_) BaseModule(params_) {} + + /// Returns true if the ipOrg has commercial terms configured, false otherwise + function ipOrgAllowsCommercial(address ipOrg_) public view returns (bool) { + return _comIpOrgTermIds[ipOrg_].length() > 0; + } + + /// Get all term ids configured for an ipOrg, along the config data + /// @dev WARNING: this will copy all terms to memory, it can be expensive + /// @param commercial true for commercial terms, false for non-commercial terms + function getIpOrgTerms(bool commercial, address ipOrg_) public view returns (ShortString[] memory, bytes[] memory) { + if (commercial) { + return ( + _comIpOrgTermIds[ipOrg_].values(), + _comIpOrgTermData[ipOrg_] + ); + } else { + return ( + _nonComIpOrgTermIds[ipOrg_].values(), + _nonComIpOrgTermData[ipOrg_] + ); + } + } + + /// Get the number of terms configured for an ipOrg + /// @param commercial_ true for commercial terms, false for non-commercial terms + /// @param ipOrg_ the ipOrg address + /// @return the number of terms configured for the ipOrg + function getTotalIpOrgTerms(bool commercial_, address ipOrg_) public view returns (uint256) { + if (commercial_) { + return _comIpOrgTermIds[ipOrg_].length(); + } else { + return _nonComIpOrgTermIds[ipOrg_].length(); + } + } + + /// Check if an ipOrg has a term configured + /// @param commercial_ true for commercial terms, false for non-commercial terms + /// @param ipOrg_ the ipOrg address + /// @param termId_ the term id + /// @return true if the term is configured, false otherwise + function ipOrgTermsContains(bool commercial_, address ipOrg_, ShortString termId_) public view returns (bool) { + if (commercial_) { + return _comIpOrgTermIds[ipOrg_].contains(termId_); + } else { + return _nonComIpOrgTermIds[ipOrg_].contains(termId_); + } + } + + /// Get the data for a term configured for an ipOrg + /// @dev method will revert if the term is not configured + /// @param commercial_ true for commercial terms, false for non-commercial terms + /// @param ipOrg_ the ipOrg address + /// @param termId_ the term id + /// @return the term data + function ipOrgTermData(bool commercial_, address ipOrg_, ShortString termId_) public view returns (bytes memory) { + if (commercial_) { + uint256 index = _comIpOrgTermIds[ipOrg_].indexOf(termId_); + if (index == type(uint256).max) { + revert Errors.LicensingModule_ipOrgTermNotFound(); + } + return _comIpOrgTermData[ipOrg_][index]; + } else { + uint256 index = _nonComIpOrgTermIds[ipOrg_].indexOf(termId_); + if (index == type(uint256).max) { + revert Errors.LicensingModule_ipOrgTermNotFound(); + } + return _nonComIpOrgTermData[ipOrg_][index]; + } + } + + /// Gets the pair of ipOrg term Id and data at a certain index + /// @param commercial_ true for commercial terms, false for non-commercial terms + /// @param ipOrg_ the ipOrg address + /// @param index_ the index + /// @return termId term Id + /// @return data the term data + function ipOrgTermsAt(bool commercial_, address ipOrg_, uint index_) public view returns (ShortString termId, bytes memory data) { + if (commercial_) { + return (_comIpOrgTermIds[ipOrg_].at(index_), _comIpOrgTermData[ipOrg_][index_]); + } else { + return (_nonComIpOrgTermIds[ipOrg_].at(index_), _nonComIpOrgTermData[ipOrg_][index_]); + } + } + + function _hookRegistryAdmin() + internal + view + virtual + override + returns (address) + { + return address(0); + } + + //////////////////////////////////////////////////////////////////////////// + // Create License // + //////////////////////////////////////////////////////////////////////////// + + /// Module entrypoing to verify execution call + function _verifyExecution( + IIPOrg ipOrg_, + address caller_, + bytes calldata params_ + ) virtual internal override { + ( + Licensing.LicenseCreation memory lParams, + Licensing.LicenseeType licenseeType, + bytes memory data + ) = abi.decode( + params_, + ( + Licensing.LicenseCreation, + Licensing.LicenseeType, + bytes + ) + ); + // At least non commercial terms must be set + if (_nonComIpOrgTermData[address(ipOrg_)].length == 0) { + revert Errors.LicensingModule_IpOrgNotConfigured(); + } + // ------ Commercial status checks ------ + if (!ipOrgAllowsCommercial(address(ipOrg_)) && lParams.isCommercial) { + revert Errors.LicensingModule_CommercialLicenseNotAllowed(); + } + if (licenseeType == Licensing.LicenseeType.Unset) { + revert Errors.LicensingModule_InvalidLicenseeType(); + } + // ------ Root Ipa license checks ------ + if (lParams.parentLicenseId == 0) { + if (ipOrg_.owner() != caller_) { + revert Errors.LicensingModule_CallerNotIpOrgOwner(); + } + } else { + // ------ Derivative license checks: Terms ------ + FixedSet.ShortStringSet storage termIds = _getIpOrgTermIds(lParams.isCommercial, address(ipOrg_)); + bytes[] storage termData = _getIpOrgTermData(lParams.isCommercial, address(ipOrg_)); + + // Share Alike ---- + uint256 nftShareAlikeIndex = termIds.indexOf(TermIds.NFT_SHARE_ALIKE.toShortString()); + // If there is no NFT_SHARE_ALIKE term, or if it is false then we cannot have + // a derivative license unless caller owns the parent license + if (nftShareAlikeIndex == FixedSet.INDEX_NOT_FOUND || + !abi.decode(termData[nftShareAlikeIndex], (bool)) + ) { + address parentLicensor = LICENSE_REGISTRY.getLicensor(lParams.parentLicenseId); + if (parentLicensor != caller_) { + revert Errors.LicensingModule_ShareAlikeDisabled(); + } + } + } + } + + /// Module entrypoint to create licenses + function _performAction( + IIPOrg ipOrg_, + address caller_, + bytes calldata params_ + ) virtual internal override returns (bytes memory result) { + ( + Licensing.LicenseCreation memory lParams, + Licensing.LicenseeType licenseeType, + bytes memory data + ) = abi.decode( + params_, + ( + Licensing.LicenseCreation, + Licensing.LicenseeType, + bytes + ) + ); + (ShortString[] memory termIds, bytes[] memory termsData) = getIpOrgTerms( + lParams.isCommercial, + address(ipOrg_) + ); + uint256 ipaId = Licensing.LicenseeType.BoundToIpa == licenseeType ? abi.decode(data, (uint256)) : 0; + // TODO: compose IpOrg terms with user provider Terms + Licensing.RegistryAddition memory rParams = Licensing.RegistryAddition({ + isCommercial: lParams.isCommercial, + licensor: _getLicensor( + ipaId, + LICENSE_REGISTRY.getLicensor(lParams.parentLicenseId) + ), + revoker: _getRevoker(ipOrg_), + ipOrg: address(ipOrg_), + parentLicenseId: lParams.parentLicenseId, + termIds: termIds, + termsData: termsData, + data: data + }); + uint256 licenseId; + if (licenseeType == Licensing.LicenseeType.BoundToIpa) { + licenseId = LICENSE_REGISTRY.addBoundToIpaLicense( + rParams, + abi.decode(data, (uint256)) + ); + } else { + licenseId = LICENSE_REGISTRY.addTradeableLicense( + rParams, + abi.decode(data, (address)) + ); + } + + return abi.encode(licenseId); + } + + /// Gets the licensor address for this IPA. + function _getLicensor( + uint256 ipaId, + address parentLicenseOwner + ) private view returns (address) { + // TODO: Check for Licensor term in terms registry. + if (parentLicenseOwner != address(0) || ipaId == 0) { + return parentLicenseOwner; + } + return IPA_REGISTRY.ipAssetOwner(ipaId); + } + + /// Gets the revoker address for this IPOrg. + function _getRevoker(IIPOrg ipOrg) private view returns (address) { + // TODO: Check Revoker term in terms registry to chose disputer + // For now, ipOrgOwner + return ipOrg.owner(); + } + + /// Helper method to get a storage pointer to term Ids + function _getIpOrgTermIds(bool commercial, address ipOrg_) private view returns (FixedSet.ShortStringSet storage) { + if (commercial) { + return _comIpOrgTermIds[ipOrg_]; + } else { + return _nonComIpOrgTermIds[ipOrg_]; + } + } + + /// Helper method to get a storage pointer to term data + function _getIpOrgTermData(bool commercial, address ipOrg_) private view returns (bytes[] storage) { + if (commercial) { + return _comIpOrgTermData[ipOrg_]; + } else { + return _nonComIpOrgTermData[ipOrg_]; + } + } + + //////////////////////////////////////////////////////////////////////////// + // Config // + //////////////////////////////////////////////////////////////////////////// + + /// Module entrypoint for configuration. It allows an IPOrg to set licensing term + function _configure( + IIPOrg ipOrg_, + address caller_, + bytes calldata params_ + ) virtual override internal returns (bytes memory) { + // TODO: Revert if terms already exist + (bytes32 configType, bytes memory configData) = abi.decode(params_, (bytes32, bytes)); + if (configType == Licensing.LICENSING_FRAMEWORK_CONFIG) { + return _setIpOrgFramework(ipOrg_, caller_, configData); + } else { + // TODO: We need to define if a license holder can modify the terms of a license + } + revert Errors.LicensingModule_InvalidConfigType(); + } + + //////////////////////////////////////////////////////////////////////////// + // ipOrgConfig // + //////////////////////////////////////////////////////////////////////////// + + /// Gets commercial and non-commercial terms, and checks for misconfigurations in them before + // setting them + /// @param ipOrg_ the ipOrg contract interface + /// @param caller_ address requesting execution + /// @param params_ encoded Licensing.FrameworkConfig struct + function _setIpOrgFramework( + IIPOrg ipOrg_, + address caller_, + bytes memory params_ + ) virtual internal returns (bytes memory) { + if (ipOrg_.owner() != caller_) { + revert Errors.LicensingModule_CallerNotIpOrgOwner(); + } + address ipOrgAddress = address(ipOrg_); + + Licensing.FrameworkConfig memory framework = abi.decode(params_, (Licensing.FrameworkConfig)); + + // Set non-commercial terms + Licensing.TermsConfig memory nonComTermsConfig = framework.nonComTermsConfig; + // IP Org has to have non-commercial terms + if (nonComTermsConfig.termIds.length == 0) { + revert Errors.LicensingModule_NonCommercialTermsRequired(); + } + bytes[] storage nonComTermData = _nonComIpOrgTermData[ipOrgAddress]; + FixedSet.ShortStringSet storage nonComTermIds = _nonComIpOrgTermIds[ipOrgAddress]; + if (nonComTermIds.length() > 0) { + // We assume an ipOrg licensing framework cannot change, so if the terms are not empty + // we revert + revert Errors.LicensingModule_IpOrgFrameworkAlreadySet(); + } + _setTerms(false, nonComTermsConfig, nonComTermIds, nonComTermData); + emit IpOrgTermsSet(ipOrgAddress, false, nonComTermIds.values(), nonComTermData); + + Licensing.TermsConfig memory comTermsConfig = framework.comTermsConfig; + // Set commercial terms + bytes[] storage comTermData = _comIpOrgTermData[ipOrgAddress]; + FixedSet.ShortStringSet storage comTermIds = _comIpOrgTermIds[ipOrgAddress]; + _setTerms(true, comTermsConfig, comTermIds, comTermData); + emit IpOrgTermsSet(ipOrgAddress, true, comTermIds.values(), comTermData); + + return ""; + } + + /// Validate input licensing terms and populate ipOrg licensing framework + /// @param commercial true for commercial terms, false for non-commercial terms + /// @param termsConfig_ arrays for termIds and their ipOrg level config data + /// @param termIds_ ipOrg terms set, where the termIds will be added + /// @param ipOrgTermData_ ipOrg config data for terms, where the term data will be added + function _setTerms( + bool commercial, + Licensing.TermsConfig memory termsConfig_, + FixedSet.ShortStringSet storage termIds_, + bytes[] storage ipOrgTermData_ + ) internal { + uint256 termsLength = termsConfig_.termIds.length; + for (uint256 i = 0; i < termsLength; i++) { + ShortString termId = termsConfig_.termIds[i]; + if (termIds_.contains(termId)) { + revert Errors.LicensingModule_DuplicateTermId(); + } + Licensing.LicensingTerm memory term = getTerm(termId); + // Since there is CommercialStatus.Both, we need to be specific here + if ( + commercial && term.comStatus == Licensing.CommercialStatus.NonCommercial || + !commercial && term.comStatus == Licensing.CommercialStatus.Commercial + ) { + // We assume that CommercialStatus.Unset is not possible, since + // TermsRepository checks for that + revert Errors.LicensingModule_InvalidTermCommercialStatus(); + } + bytes memory data = termsConfig_.termData[i]; + if (address(term.hook) != address(0)) { + // Reverts if decoding fails + term.hook.validateConfig(abi.encode(termId, data)); + } + termIds_.add(termId); + ipOrgTermData_.push(data); + } + } + + //////////////////////////////////////////////////////////////////////////// + // Hooks // + //////////////////////////////////////////////////////////////////////////// + + function _hookRegistryKey( + IIPOrg ipOrg_, + address caller_, + bytes calldata params_ + ) internal view virtual override returns(bytes32) { + return keccak256("TODO"); + } + +} diff --git a/contracts/modules/licensing/LicenseRegistry.sol b/contracts/modules/licensing/LicenseRegistry.sol index fc656ca0..91bf43cb 100644 --- a/contracts/modules/licensing/LicenseRegistry.sol +++ b/contracts/modules/licensing/LicenseRegistry.sol @@ -1,64 +1,156 @@ // SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.13; -import { ERC721 } from "@openzeppelin/contracts/token/ERC721/ERC721.sol"; -import { ZeroAddress, Unauthorized } from "contracts/errors/General.sol"; -import { IERC5218 } from "contracts/interfaces/modules/licensing/IERC5218.sol"; -import { ILicenseRegistry } from "contracts/interfaces/modules/licensing/ILicenseRegistry.sol"; -import { Errors } from "contracts/lib/Errors.sol"; +pragma solidity ^0.8.19; +import { Licensing } from "contracts/lib/modules/Licensing.sol"; +import { IPAssetRegistry } from "contracts/IPAssetRegistry.sol"; +import { Errors } from "contracts/lib/Errors.sol"; +import { ERC721 } from "@openzeppelin/contracts/token/ERC721/ERC721.sol"; +import { ModuleRegistry } from "contracts/modules/ModuleRegistry.sol"; +import { ModuleRegistryKeys } from "contracts/lib/modules/ModuleRegistryKeys.sol"; /// @title LicenseRegistry -/// @author Raul Martinez -/// @notice Simple NFT tracking the ownership of tradeable Licenses emitted by a RightsManager. -contract LicenseRegistry is ILicenseRegistry, ERC721 { +/// @notice This contract is the source of truth for all licenses that are registered in the protocol. +/// It will only be called by licensing modules. +/// It should not be upgradeable, so once a license is registered, it will be there forever. +/// Licenses can be made invalid by the revoker, according to the terms of the license. +contract LicenseRegistry is ERC721 { + // TODO: Figure out data needed for indexing + event LicenseRegistered( + uint256 indexed id + ); + event LicenseNftBoundedToIpa( + uint256 indexed licenseId, + uint256 indexed ipaId + ); - IERC5218 private immutable _RIGHTS_MANAGER; - - constructor(address rightsManager_, string memory name_, string memory symbol_) ERC721(name_, symbol_) { - if (rightsManager_ == address(0)) { - revert Errors.ZeroAddress(); + /// license Id => License + mapping(uint256 => Licensing.License) private _licenses; + /// counder for license Ids + uint256 private _licenseCount; + + IPAssetRegistry public immutable IPA_REGISTRY; + ModuleRegistry public immutable MODULE_REGISTRY; + + modifier onlyLicensingModule() { + if (!MODULE_REGISTRY.isModule(ModuleRegistryKeys.LICENSING_MODULE, msg.sender)) { + revert Errors.LicenseRegistry_CallerNotLicensingModule(); } - _RIGHTS_MANAGER = IERC5218(rightsManager_); + _; } - modifier onlyRightsManager() { - if (msg.sender != address(_RIGHTS_MANAGER)) revert Errors.Unauthorized(); - _; + constructor( + address ipaRegistry_, + address moduleRegistry_ + ) ERC721("Story Protocol License NFT", "LNFT") { + if (ipaRegistry_ == address(0)) { + revert Errors.LicenseRegistry_ZeroIpaRegistryAddress(); + } + IPA_REGISTRY = IPAssetRegistry(ipaRegistry_); + if (moduleRegistry_ == address(0)) { + revert Errors.LicenseRegistry_ZeroModuleRegistryAddress(); + } + MODULE_REGISTRY = ModuleRegistry(moduleRegistry_); } - - /// @notice Mint a License to the given address. Only caller allowed is the RightsManager. - /// @param to_ The address to mint the License to. - /// @param tokenId_ The ID of the License to mint. - function mint(address to_, uint256 tokenId_) external onlyRightsManager { - _mint(to_, tokenId_); + + /// Creates a License bound to a certain IPA + /// @param params_ RegistryAddition params + /// @param ipaId_ id of the bound IPA + /// @return id of the created license + function addBoundToIpaLicense( + Licensing.RegistryAddition memory params_, + uint256 ipaId_ + ) external onlyLicensingModule returns (uint256) { + // TODO statuses + if (IPA_REGISTRY.status(ipaId_) == 0) { + revert Errors.LicenseRegistry_InvalidIpa(); + } + return _addLicense( + Licensing.License({ + isCommercial: params_.isCommercial, + licenseeType: Licensing.LicenseeType.BoundToIpa, + licensor: params_.licensor, + revoker: params_.revoker, + ipOrg: params_.ipOrg, + termIds: params_.termIds, + termsData: params_.termsData, + ipaId: ipaId_, + parentLicenseId: params_.parentLicenseId, + data: params_.data + }) + ); } - function getRightsManager() external view override returns (address) { - return address(_RIGHTS_MANAGER); + /// Creates a tradeable License NFT. + /// If the license is to create an IPA in the future, when registering, this license will be + /// bound to the IPA. + /// @param params_ RegistryAddition params + /// @param licensee_ address of the licensee (and owner of the NFT) + function addTradeableLicense( + Licensing.RegistryAddition memory params_, + address licensee_ + ) external onlyLicensingModule returns (uint256) { + _addLicense( + Licensing.License({ + isCommercial: params_.isCommercial, + licenseeType: Licensing.LicenseeType.LNFTHolder, + licensor: params_.licensor, + revoker: params_.revoker, + ipOrg: params_.ipOrg, + termIds: params_.termIds, + termsData: params_.termsData, + ipaId: 0, + parentLicenseId: params_.parentLicenseId, + data: params_.data + }) + ); + _mint(licensee_, _licenseCount); + return _licenseCount; } - function exists(uint256 tokenId_) external view returns (bool) { - return _exists(tokenId_); + + function _addLicense(Licensing.License memory license_) private returns (uint256) { + // TODO: Check valid parent license + _licenseCount++; + _licenses[_licenseCount] = license_; + emit LicenseRegistered(_licenseCount); + return _licenseCount; } - function name() public view override(ERC721, ILicenseRegistry) returns (string memory) { - return super.name(); + /// Gets License struct for input id + function getLicense(uint256 id_) external view returns (Licensing.License memory) { + return _licenses[id_]; } - function symbol() public view override(ERC721, ILicenseRegistry) returns (string memory) { - return super.symbol(); + /// Gets the address granting a license, by id + function getLicensor(uint256 id_) external view returns (address) { + return _licenses[id_].licensor; + } + /// Gets the address a license is granted to + /// @param id_ of the license + /// @return licensee address, NFT owner if the license is tradeable, or IPA owner if bound to IPA + function getLicensee(uint256 id_) external view returns (address) { + Licensing.LicenseeType licenseeType_ = _licenses[id_].licenseeType; + if (licenseeType_ == Licensing.LicenseeType.Unset) { + revert Errors.LicenseRegistry_UnknownLicenseId(); + } + if (_licenses[id_].licenseeType == Licensing.LicenseeType.BoundToIpa) { + return IPA_REGISTRY.ipAssetOwner(id_); + } else { + return ownerOf(id_); + } } - function _beforeTokenTransfer( - address from_, - address to_, - uint256 firstTokenId_, - uint256 batchSize_ - ) internal virtual override { - // Minting has already been checked by the RightsManager, but transfers need to pass some checks. - if (from_ != address(0)) { - _RIGHTS_MANAGER.transferSublicense(firstTokenId_, to_); + /// Burns a license NFT and binds the license to an IPA + /// @param licenseId_ id of the license NFT + /// @param ipaId_ id of the IPA + function boundLnftToIpa(uint256 licenseId_, uint256 ipaId_) external onlyLicensingModule { + Licensing.License memory license_ = _licenses[licenseId_]; + if (license_.licenseeType != Licensing.LicenseeType.LNFTHolder) { + revert Errors.LicenseRegistry_NotLicenseNFT(); } - super._beforeTokenTransfer(from_, to_, firstTokenId_, batchSize_); + _licenses[licenseId_].licenseeType = Licensing.LicenseeType.BoundToIpa; + _licenses[licenseId_].ipaId = ipaId_; + _burn(licenseId_); + emit LicenseNftBoundedToIpa(licenseId_, ipaId_); } } diff --git a/contracts/modules/licensing/LicensingModule.sol b/contracts/modules/licensing/LicensingModule.sol deleted file mode 100644 index 44a59ce8..00000000 --- a/contracts/modules/licensing/LicensingModule.sol +++ /dev/null @@ -1,106 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.13; -import { Errors } from "contracts/lib/Errors.sol"; -import { IPOrgController } from "contracts/ip-org/IPOrgController.sol"; -import { UUPSUpgradeable } from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; -import { AccessControlledUpgradeable } from "contracts/access-control/AccessControlledUpgradeable.sol"; -import { AccessControl } from "contracts/lib/AccessControl.sol"; -import { ITermsProcessor } from "contracts/interfaces/modules/licensing/terms/ITermsProcessor.sol"; -import { IIPOrg } from "contracts/interfaces/ip-org/IIPOrg.sol"; -import { IERC5218 } from "contracts/interfaces/modules/licensing/IERC5218.sol"; -import { ILicensingModule } from "contracts/interfaces/modules/licensing/ILicensingModule.sol"; -import { Licensing } from "contracts/lib/modules/Licensing.sol"; - - -/// @title LicensingModule -/// @author Raul Martinez -/// @notice Contract for configuring and managing licensing for a IPOrg. -/// A licensing framework may be definbed through a IPOrgConfig, which is set by the IPOrg owner. -/// The non commercial license URI is set by a protocol admin key, since it will be common for all Story Protocol -contract LicensingModule is ILicensingModule, AccessControlledUpgradeable { - - struct LicensingModuleStorage { - /// ipAssetOrgId => IPOrgConfig - mapping(address => Licensing.IPOrgConfig) ipAssetOrgConfigs; - string nonCommercialLicenseURI; - } - - // keccak256(bytes.concat(bytes32(uint256(keccak256("story-protocol.licensing-module.storage")) - 1))) - bytes32 private constant _STORAGE_LOCATION = 0x80b4ea8c21e869c68acfd93c8ef2c0d867835b92e2fded15a1d74d7e7ff3312d; - - IPOrgController public immutable IP_ASSET_ORG_FACTORY; - - constructor(address franchise_) { - if (franchise_ == address(0)) { - revert Errors.ZeroAddress(); - } - IP_ASSET_ORG_FACTORY = IPOrgController(franchise_); - _disableInitializers(); - } - - function initialize(address accessControl_, string calldata nonCommercialLicenseUri_) public initializer { - __AccessControlledUpgradeable_init(accessControl_); - _getLicensingModuleStorage().nonCommercialLicenseURI = nonCommercialLicenseUri_; - } - - function _getLicensingModuleStorage() internal pure returns (LicensingModuleStorage storage $) { - bytes32 position = _STORAGE_LOCATION; - assembly { - $.slot := position - } - } - - function getNonCommercialLicenseURI() public view returns (string memory) { - return _getLicensingModuleStorage().nonCommercialLicenseURI; - } - - - /// Set the URI for non-commercial licenses across Story Protocol. Setting this does NOT affect existing licenses, only new ones. - /// @param nonCommercialLicenseURI_ The URI to set for non-commercial licenses - function setNonCommercialLicenseURI(string calldata nonCommercialLicenseURI_) external onlyRole(AccessControl.LICENSING_MANAGER_ROLE) { - _getLicensingModuleStorage().nonCommercialLicenseURI = nonCommercialLicenseURI_; - emit NonCommercialLicenseUriSet(nonCommercialLicenseURI_); - } - - - /// Set the IPOrgConfig for a IPOrg, configuring its licensing framework. - /// @dev if setting root licenses, they should be active. A revoker address must be set, and it will be - /// common for all licenses in the IPOrg. - /// @param ipAssetOrg_ The address of the IPOrg to set the config for - /// @param config_ The IPOrgConfig to set - function configureIpOrgLicensing(address ipAssetOrg_, Licensing.IPOrgConfig memory config_) external { - if (msg.sender != IIPOrg(ipAssetOrg_).owner()) { - revert Errors.Unauthorized(); - } - _verifyRootLicense(ipAssetOrg_, config_.nonCommercialConfig.ipAssetOrgRootLicenseId); - _verifyRootLicense(ipAssetOrg_, config_.commercialConfig.ipAssetOrgRootLicenseId); - if (config_.revoker == address(0)) { - revert Errors.LicensingModule_ZeroRevokerAddress(); - } - LicensingModuleStorage storage $ = _getLicensingModuleStorage(); - $.ipAssetOrgConfigs[ipAssetOrg_] = config_; - emit IPOrgConfigSet(ipAssetOrg_, config_); - } - - function _verifyRootLicense(address ipAssetOrg_, uint256 rootLicenseId_) internal view { - if (rootLicenseId_ != 0) { - IERC5218 rightsManager = IERC5218(ipAssetOrg_); - if (address(rightsManager) == address(0)) { - // IP_ASSET_ORG_FACTORY.ownerOf(ipAssetOrgId) should take care of this, - // but leaving it in case IPAssetRegistration creation fails somewhow. - revert Errors.LicensingModule_NonExistentIPOrg(); - } - if (!rightsManager.isLicenseActive(rootLicenseId_)) { - revert Errors.LicensingModule_RootLicenseNotActive(rootLicenseId_); - } - } - } - - function getIpOrgConfig(address ipAssetOrg_) public view returns (Licensing.IPOrgConfig memory) { - return _getLicensingModuleStorage().ipAssetOrgConfigs[ipAssetOrg_]; - } - - function _authorizeUpgrade( - address newImplementation_ - ) internal virtual override onlyRole(AccessControl.UPGRADER_ROLE) {} -} diff --git a/contracts/modules/licensing/ProtocolTermsHelper.sol b/contracts/modules/licensing/ProtocolTermsHelper.sol new file mode 100644 index 00000000..d991e65e --- /dev/null +++ b/contracts/modules/licensing/ProtocolTermsHelper.sol @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.19; + +import { Licensing } from "contracts/lib/modules/Licensing.sol"; +import { IHook } from "contracts/interfaces/hooks/base/IHook.sol"; + +library ProtocolTermsHelper { + + function _getExcludedCategoriesTerm( + Licensing.CommercialStatus comStatus_, + IHook hook + ) internal pure returns (Licensing.LicensingTerm memory) { + return Licensing.LicensingTerm({ + comStatus: comStatus_, + url: "https://excluded.com", + hash: "qwertyu", + algorithm: "sha256", + hook: hook + }); + } + + function _getNftShareAlikeTerm( + Licensing.CommercialStatus comStatus_ + ) internal pure returns (Licensing.LicensingTerm memory) { + return Licensing.LicensingTerm({ + comStatus: comStatus_, + url: "https://sharealike.com", + hash: "qwertyu", + algorithm: "sha256", + hook: IHook(address(0)) + }); + } + +} \ No newline at end of file diff --git a/contracts/modules/licensing/RightsManager.sol b/contracts/modules/licensing/RightsManager.sol deleted file mode 100644 index f48559ed..00000000 --- a/contracts/modules/licensing/RightsManager.sol +++ /dev/null @@ -1,403 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.13; -import { IERC721 } from "@openzeppelin/contracts/token/ERC721/IERC721.sol"; -import { ERC721Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol"; -import { LibDuration } from "../timing/LibDuration.sol"; -import { IERC5218 } from "contracts/interfaces/modules/licensing/IERC5218.sol"; -import { ILicenseRegistry } from "contracts/interfaces/modules/licensing/ILicenseRegistry.sol"; -import { IIPOrg } from "contracts/interfaces/ip-org/IIPOrg.sol"; -import { ERC165CheckerUpgradeable } from "@openzeppelin/contracts-upgradeable/utils/introspection/ERC165CheckerUpgradeable.sol"; -import { ERC165Upgradeable } from "@openzeppelin/contracts-upgradeable/utils/introspection/ERC165Upgradeable.sol"; -import { ITermsProcessor } from "contracts/interfaces/modules/licensing/terms/ITermsProcessor.sol"; -import { Errors } from "contracts/lib/Errors.sol"; -import { Licensing } from "contracts/lib/modules/Licensing.sol"; - -/// @title RightsManager -/// @author Raul Martinez -/// @notice IERC-5218 implementation. -/// Allows us to grant 2 type of licenses: -/// 1. Rights: Licenses tied to a tokenId (IPAsset id), in which the license holder is always the owner of the tokenId. Each tokenId can have commercial or non commercial license tied to it defining it. -/// 2. Tradeable Licenses): The license holder is the owner of the correspondent LicenseRegistry NFT. They are either: -/// 2.1 IPOrg root license: LicenseRegistry enabled license minted by a IPOrg owner to govern commercial or non commercial rights for all the IPAssetRegistries. -/// 2.2 Sublicense: a license coming from Rights or other Licenses, minted by the parent license owner. These would be the future "movie adaptation" type licenses that can be sold. -/// Allows license holders to execute terms to activate the license to activate them. -/// Tracks active licenses along the license trees. -abstract contract RightsManager is - ERC721Upgradeable, - IERC5218 -{ - using ERC165CheckerUpgradeable for address; - - struct RightsManagerStorage { - mapping(uint256 => Licensing.License) licenses; - // keccack256(commercial, tokenId) => licenseId - mapping(bytes32 => uint256) licensesForTokenId; - uint256 licenseCounter; - ILicenseRegistry licenseRegistry; - } - - // keccak256(bytes.concat(bytes32(uint256(keccak256("story-protocol.rights-manager.storage")) - 1))) - bytes32 private constant _STORAGE_LOCATION = 0x315576c20e31e03ef3e70482445a4c33e45baf13beff28e79f2adf6d06cc0bee; - - // TODO(ramarti): Refactor licensing ids to work via the registry modules. - uint256 private constant _UNSET_LICENSE_ID = 0; - uint256 public constant ROOT_LICENSE_ID = type(uint256).max; - - // TODO(ramarti): Separate IP Asset Org from the Rights Manager so that they are decoupled. - IIPOrg public IP_ASSET_ORG; - - /// @dev Initialize the Rights Manager contract. - /// TODO(ramarti): Ensure the rights manager depends on the IP Asset Registry instead of an IP Asset Org. - function __RightsManager_init( - address ipAssetOrg_, - string memory name_, - string memory symbol_ - ) public { - if (ipAssetOrg_ == address(0)) { - revert Errors.ZeroAddress(); - } - IP_ASSET_ORG = IIPOrg(ipAssetOrg_); - __ERC721_init(name_, symbol_); - } - - function setLicenseRegistry(address licenseRegistry_) external { - // NOTE: This assumes no need to change ILicenseRegistry implementation. - if (address(_getRightsManagerStorage().licenseRegistry) != address(0)) revert Errors.Unauthorized(); - if (licenseRegistry_ == address(0)) revert Errors.ZeroAddress(); - _getRightsManagerStorage().licenseRegistry = ILicenseRegistry(licenseRegistry_); - } - - function _getRightsManagerStorage() - private - pure - returns (RightsManagerStorage storage $) - { - assembly { - $.slot := _STORAGE_LOCATION - } - } - - - /// @notice Creates a tradeable sublicense. - /// @dev Throws if trying to create a franchise level or root license. - /// @param tokenId_ The tokenId of the specific IPOrg to create the sublicense for. - /// @param parentLicenseId_ The parent license to create the sublicense from. - /// @param licenseHolder_ The address of the sublicense holder, will own the ILicenseRegistry NFT. - /// @param uri_ License terms URI. - /// @param revoker_ address that can revoke the license. - /// @param commercial_ if the license is commercial or not. - /// @param canSublicense_ if the license can be parentLicense of another one - /// @param terms_ the on chain terms of the license, via executor and data - /// @return licenseId - /// TODO(ramarti): Refactor license creation to use the IP Asset Registry id instead of IP Asset Org id. - function createLicense( - uint256 tokenId_, // Question: should sublicenses be created with a tokenId or just a parentLicenseId? - uint256 parentLicenseId_, - address licenseHolder_, - string memory uri_, - address revoker_, - bool commercial_, - bool canSublicense_, - Licensing.TermsProcessorConfig memory terms_ - ) external override returns (uint256) { - if (tokenId_ == ROOT_LICENSE_ID || parentLicenseId_ == _UNSET_LICENSE_ID) { - revert Errors.RightsManager_UseCreateIPOrgRootLicenseInstead(); - } - if (msg.sender != getLicenseHolder(parentLicenseId_)) revert Errors.Unauthorized(); - return _createLicense( - tokenId_, - parentLicenseId_, - licenseHolder_, - uri_, - revoker_, - commercial_, - canSublicense_, - terms_, - true - ); - } - - - /// Creates the root licenses that all other licenses of a IPOrg may be based on. - /// @dev Throws if caller not owner of the IPOrgController NFt. - /// @param licenseHolder_ The address of the sublicense holder, will own the ILicenseRegistry NFT. - /// @param uri_ License terms URI. - /// @param revoker_ address that can revoke the license. - /// @param commercial_ if the license is commercial or not. - /// @param canSublicense_ if the license can be parentLicense of another one - /// @param terms_ the on chain terms of the license, via executor and data - /// @return licenseId - function createIPOrgRootLicense( - address licenseHolder_, - string memory uri_, - address revoker_, - bool commercial_, - bool canSublicense_, - Licensing.TermsProcessorConfig memory terms_ - ) external returns (uint256) { - if (msg.sender != IP_ASSET_ORG.owner()) revert Errors.Unauthorized(); - return _createLicense( - ROOT_LICENSE_ID, - _UNSET_LICENSE_ID, - licenseHolder_, - uri_, - revoker_, - commercial_, - canSublicense_, - terms_, - true - ); - } - - function _createLicense( - uint256 tokenId_, - uint256 parentLicenseId_, - address licenseHolder_, - string memory uri_, - address revoker_, - bool commercial_, - bool canSublicense_, - Licensing.TermsProcessorConfig memory terms_, - bool inLicenseRegistry_ - ) internal returns (uint256) { - // TODO: should revoker come from allowed revoker list? - if (revoker_ == address(0)) revert Errors.RightsManager_ZeroRevokerAddress(); - RightsManagerStorage storage $ = _getRightsManagerStorage(); - // Only licenses minted to the IPOrgController Owner as a root license should - // have tokenId = ROOT_LICENSE_ID, otherwise the tokenId should be a minted NFT (IPAsset.IPAssetType) - // Checks for the IPOrgController Owner should be done in the calling function - if (tokenId_ != ROOT_LICENSE_ID) { - if (!_exists(tokenId_)) { - revert Errors.NonExistentID(tokenId_); - } - } - // If this is not a LicenseRegsitry license, check that the tokenId doesn't already have a root license - if (!inLicenseRegistry_) { - if ($.licensesForTokenId[keccak256(abi.encode(commercial_, tokenId_))] != _UNSET_LICENSE_ID) { - revert Errors.RightsManager_AlreadyHasRootLicense(); - } - } else { - if($.licenseRegistry == ILicenseRegistry(address(0))) revert Errors.RightsManager_LicenseRegistryNotConfigured(); - if(tokenId_ != ROOT_LICENSE_ID && parentLicenseId_ != _UNSET_LICENSE_ID) { - // If this is a sublicense, check that this is a valid sublicense - Licensing.License memory parentLicense = $.licenses[parentLicenseId_]; - if (!parentLicense.active) revert Errors.RightsManager_InactiveParentLicense(); - if (!parentLicense.canSublicense) revert Errors.RightsManager_CannotSublicense(); - if (parentLicense.commercial != commercial_) revert Errors.RightsManager_CommercialTermsMismatch(); - if (getLicenseHolder(parentLicenseId_) != licenseHolder_) revert Errors.RightsManager_NotOwnerOfParentLicense(); - } - } - // Check that the terms are valid - _verifyTerms(terms_); - - // Create the license and increment the licenseCounter - uint256 licenseId = ++$.licenseCounter; - $.licenses[licenseId] = Licensing.License({ - active: true, - canSublicense: canSublicense_, - commercial: commercial_, - parentLicenseId: parentLicenseId_, - tokenId: tokenId_, - revoker: revoker_, - uri: uri_, - termsProcessor: terms_.processor, - termsData: terms_.data - }); - - // Mint the license in the LicenseRegistry if requested. Should not do this for IPAsset Rights, but - // the checks on inLicenseRegistry should be done in the calling function - if (inLicenseRegistry_) { - $.licenseRegistry.mint(licenseHolder_, licenseId); - } else { - // Save tokenId => licenseId relationship IF this is a root license - $.licensesForTokenId[keccak256(abi.encode(commercial_, tokenId_))] = licenseId; - } - - // Emit events - emit CreateLicense( - licenseId, - tokenId_, - parentLicenseId_, - licenseHolder_, - uri_, - revoker_ - ); - emit TransferLicense(licenseId, licenseHolder_); - return licenseId; - } - - - function revokeLicense(uint256 licenseId_) external override { - if (!isLicenseSet(licenseId_)) revert Errors.NonExistentID(licenseId_); - RightsManagerStorage storage $ = _getRightsManagerStorage(); - Licensing.License storage license = $.licenses[licenseId_]; - if (msg.sender != license.revoker) revert Errors.RightsManager_SenderNotRevoker(); - license.active = false; - emit RevokeLicense(licenseId_); - // TODO: should we burn the license if it's from the LicenseRegistry? - // TODO: delete the rootLicenseForTokenId mapping for licenseId if root license - } - - - /// If set, runs the TermsExecutor with the terms data stored in the license. - /// If the terms execution returns different data, the license is updated with the new data. - /// @param licenseId_ The identifier for the queried license - function executeTerms(uint256 licenseId_) external { - RightsManagerStorage storage $ = _getRightsManagerStorage(); - if (msg.sender != $.licenseRegistry.ownerOf(licenseId_)) revert Errors.Unauthorized(); - Licensing.License storage license = $.licenses[licenseId_]; - if (license.termsProcessor != ITermsProcessor(address(0))) { - bytes memory newData = license.termsProcessor.executeTerms(license.termsData); - if (keccak256(license.termsData) != keccak256(newData)) { - license.termsData = newData; - emit TermsUpdated(licenseId_, address(license.termsProcessor), newData); - } - } - emit ExecuteTerms(licenseId_, license.termsData); - } - - /// returns true if the license is active (non revoked and terms returning true) and all its parent licenses are active, false otherwise - function isLicenseActive( - uint256 licenseId_ - ) public view virtual returns (bool) { - // TODO: limit to the tree depth - if (licenseId_ == 0) return false; - RightsManagerStorage storage $ = _getRightsManagerStorage(); - while (licenseId_ != 0) { - Licensing.License memory license = $.licenses[licenseId_]; - if (!_isActiveAndTermsOk(license)) return false; - licenseId_ = license.parentLicenseId; - } - return true; - } - - function _isActiveAndTermsOk(Licensing.License memory license_) view private returns (bool) { - if (address(license_.termsProcessor) == address(0)) return license_.active; - return license_.active && license_.termsProcessor.termsExecutedSuccessfully(license_.termsData); - } - - function getLicense(uint256 licenseId_) public view returns (Licensing.License memory, address holder) { - return ( - _getRightsManagerStorage().licenses[licenseId_], - getLicenseHolder(licenseId_) - ); - } - - function _beforeTokenTransfer( - address from_, - address to_, - uint256 firstTokenId_, - uint256 batchSize_ - ) internal virtual override { - if (from_ != address(0)) { - for (uint256 i = firstTokenId_; i < batchSize_;) { - _verifyRightsTransfer(from_, to_, i); - unchecked { - i++; - } - } - } - super._beforeTokenTransfer(from_, to_, firstTokenId_, batchSize_); - } - - function _verifyRightsTransfer( - address from_, - address to_, - uint256 tokenId_ - ) internal { - // TODO: trigger rights transfer check, check granting terms, banned marketplaces, etc. - RightsManagerStorage storage $ = _getRightsManagerStorage(); - // NOTE: We are assuming a revoked Non Commercial License impedes the transfer of the NFT. - // Should revoked commercial rights also impede the transfer? - uint256 licenseId = $.licensesForTokenId[keccak256(abi.encode(false, tokenId_))]; - if (licenseId != _UNSET_LICENSE_ID) revert Errors.RightsManager_NFTHasNoAssociatedLicense(); // This should not happen, if fired there is a bug somewhere - if (isLicenseActive(licenseId)) revert Errors.RightsManager_InactiveLicense(); // NOTE: Should we freeze invalid licenses? burn them? - emit TransferLicense(licenseId, to_); - } - - function _verifyTerms(Licensing.TermsProcessorConfig memory terms_) private view { - if (address(terms_.processor) != address(0) && - !terms_.processor.supportsInterface(type(ITermsProcessor).interfaceId)) { - revert Errors.UnsupportedInterface("ITermsProcessor"); - } - } - - function getLicenseTokenId( - uint256 licenseId_ - ) external view override returns (uint256) { - return _getRightsManagerStorage().licenses[licenseId_].tokenId; - } - - function getParentLicenseId( - uint256 licenseId_ - ) external view override returns (uint256) { - return _getRightsManagerStorage().licenses[licenseId_].parentLicenseId; - } - - - function getLicenseHolder( - uint256 licenseId_ - ) public view override returns (address) { - RightsManagerStorage storage $ = _getRightsManagerStorage(); - if ($.licenseRegistry.exists(licenseId_)) { - return $.licenseRegistry.ownerOf(licenseId_); - } else { - Licensing.License storage license = $.licenses[ - licenseId_ - ]; - return ownerOf(license.tokenId); - } - } - - function getLicenseURI( - uint256 licenseId_ - ) external view override returns (string memory) { - return _getRightsManagerStorage().licenses[licenseId_].uri; - } - - function getLicenseRevoker( - uint256 licenseId_ - ) external view override returns (address) { - return _getRightsManagerStorage().licenses[licenseId_].revoker; - } - - function getLicenseIdByTokenId( - uint256 tokenId_, - bool commercial_ - ) public view override returns (uint256) { - return - _getRightsManagerStorage().licensesForTokenId[ - keccak256(abi.encode(commercial_, tokenId_)) - ]; - } - - function getLicenseRegistry() external view returns (ILicenseRegistry) { - return _getRightsManagerStorage().licenseRegistry; - } - - function isRootLicense( - uint256 licenseId_ - ) public view returns (bool) { - return _getRightsManagerStorage().licenses[licenseId_].parentLicenseId == _UNSET_LICENSE_ID && isLicenseSet(licenseId_); - } - - function isLicenseSet(uint256 licenseId_) public view returns (bool) { - return _getRightsManagerStorage().licenses[licenseId_].revoker != address(0); - } - - - /// Since the LicenseRegistry tracks sublicense ownership, this method can only be called by the LicenseRegistry. - /// @dev Throws if the license is not active. Basically exists to not break ERC-5218. - /// @param licenseId_ the license to transfer - /// @param licenseHolder_ the new license holder - function transferSublicense( - uint256 licenseId_, - address licenseHolder_ - ) public virtual override(IERC5218) { - RightsManagerStorage storage $ = _getRightsManagerStorage(); - if (msg.sender != address($.licenseRegistry)) revert Errors.Unauthorized(); - if (!isLicenseActive(licenseId_)) revert Errors.RightsManager_InactiveLicense(); - emit TransferLicense(licenseId_, licenseHolder_); - } - -} diff --git a/contracts/modules/licensing/TermsRepository.sol b/contracts/modules/licensing/TermsRepository.sol new file mode 100644 index 00000000..f171c70e --- /dev/null +++ b/contracts/modules/licensing/TermsRepository.sol @@ -0,0 +1,150 @@ +// SPDX-License-Identifier: BUSL-1.1 +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 "forge-std/console.sol"; + +contract TermsRepository is Multicall { + using EnumerableSet for EnumerableSet.Bytes32Set; + using ShortStrings for *; + + event TermCategoryAdded(string category); + event TermCategoryRemoved(string category); + event TermAdded(string category, string termId); + event TermDisabled(string category, string termId); + + EnumerableSet.Bytes32Set private _termCategories; + // TermId -> LicensingTerm + mapping(ShortString => Licensing.LicensingTerm) private _terms; + // CategoryId -> TermIds[] + mapping(ShortString => EnumerableSet.Bytes32Set) private _termIdsByCategory; + // TermId -> CategoryId + mapping(ShortString => ShortString) private _termCategoryByTermId; + + modifier onlyValidTerm(ShortString termId_) { + if (_terms[termId_].comStatus == Licensing.CommercialStatus.Unset) { + revert Errors.TermsRegistry_UnsupportedTerm(); + } + _; + } + + modifier onlyValidTermString(string memory termId_) { + ShortString termId = termId_.toShortString(); + if (_terms[termId].comStatus == Licensing.CommercialStatus.Unset) { + revert Errors.TermsRegistry_UnsupportedTerm(); + } + _; + } + + function addCategory(string calldata category_) public { + _termCategories.add(ShortString.unwrap(category_.toShortString())); + emit TermCategoryAdded(category_); + } + + function removeTermCategory(string calldata category_) public { + _termCategories.remove(ShortString.unwrap(category_.toShortString())); + emit TermCategoryRemoved(category_); + } + + function totalTermCategories() public view returns (uint256) { + return _termCategories.length(); + } + + function termCategoryAt( + uint256 index_ + ) public view returns (string memory) { + return ShortString.wrap(_termCategories.at(index_)).toString(); + } + + // TODO: access control + function addTerm( + string calldata category_, + string calldata termId_, + Licensing.LicensingTerm calldata term_ + ) public { + ShortString category = category_.toShortString(); + _verifyCategoryExists(category); + if (term_.comStatus == Licensing.CommercialStatus.Unset) { + console.log("TermsRegistry_CommercialStatusUnset"); + revert Errors.TermsRegistry_CommercialStatusUnset(); + } + ShortString termId = termId_.toShortString(); + if (_terms[termId].comStatus != Licensing.CommercialStatus.Unset) { + console.log("TermsRegistry_TermAlreadyExists"); + revert Errors.TermsRegistry_TermAlreadyExists(); + } + _terms[termId] = term_; + _termIdsByCategory[category].add(ShortString.unwrap(termId)); + emit TermAdded(category_, termId_); + } + + // TODO: access control + function disableTerm( + string calldata category_, + string calldata termId_ + ) public { + ShortString category = category_.toShortString(); + _verifyCategoryExists(category); + ShortString termId = termId_.toShortString(); + _termIdsByCategory[category].add(ShortString.unwrap(termId)); + emit TermDisabled(category_, termId_); + } + + function categoryForTerm( + string calldata termId_ + ) public view returns (string memory) { + return _termCategoryByTermId[termId_.toShortString()].toString(); + } + + function getTerm( + ShortString termId_ + ) public view onlyValidTerm(termId_) returns (Licensing.LicensingTerm memory) { + return _terms[termId_]; + } + + function getTermHook( + ShortString termId_ + ) public view onlyValidTerm(termId_) returns (IHook) { + return getTerm(termId_).hook; + } + + function getTerm( + string memory termId_ + ) public view onlyValidTermString(termId_) returns (Licensing.LicensingTerm memory) { + ShortString termId = termId_.toShortString(); + return _terms[termId]; + } + + function totalTermsForCategory( + string calldata category_ + ) public view returns (uint256) { + ShortString category = category_.toShortString(); + _verifyCategoryExists(category); + return _termIdsByCategory[category].length(); + } + + function termForCategoryAt( + string calldata category_, + uint256 index_ + ) public view returns (Licensing.LicensingTerm memory) { + ShortString category = category_.toShortString(); + _verifyCategoryExists(category); + ShortString termId = ShortString.wrap( + _termIdsByCategory[category].at(index_) + ); + return _terms[termId]; + } + + function _verifyCategoryExists(ShortString category_) private view { + if (!_termCategories.contains(ShortString.unwrap(category_))) { + console.log("TermsRegistry_UnsupportedTermCategory"); + revert Errors.TermsRegistry_UnsupportedTermCategory(); + } + } +} diff --git a/contracts/modules/licensing/terms/BaseTermsProcessor.sol b/contracts/modules/licensing/terms/BaseTermsProcessor.sol deleted file mode 100644 index 64b66ef6..00000000 --- a/contracts/modules/licensing/terms/BaseTermsProcessor.sol +++ /dev/null @@ -1,46 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.19; - -import { ITermsProcessor } from "contracts/interfaces/modules/licensing/terms/ITermsProcessor.sol"; -import { Errors } from "contracts/lib/Errors.sol"; -import { IERC165 } from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; -import { ERC165 } from "@openzeppelin/contracts/utils/introspection/ERC165.sol"; - -/// @title BaseTermsProcessor -/// @notice Base contract for licensing terms processors, which encode, decode and execute the terms set on an IERC5218 license parameters, -/// in particular the TermsProcessorConfig struct for the terms parameter in createLicense(). -/// TermsProcessors need to be deployed once per AUTHORIZED_EXECUTOR, which is usually each IPOrg IPOrg. -/// @dev TermsProcessor are intended to be reused accross the protocol, so they should be generic enough to be used by different modules. -/// Most will be stateless, and if a terms processor needs to update something license specific, -/// it should return the updated encoded data in executeTerms() so it is stored back on the license. -/// There could be cases where other methods or state is needed for more complicated flows. -abstract contract BaseTermsProcessor is ITermsProcessor, ERC165 { - - address public immutable AUTHORIZED_EXECUTOR; - - constructor(address authorizedExecutor_) { - if (authorizedExecutor_ == address(0)) { - revert Errors.ZeroAddress(); - } - AUTHORIZED_EXECUTOR = authorizedExecutor_; - } - - modifier onlyAuthorizedExecutor() { - if(msg.sender != AUTHORIZED_EXECUTOR) revert Errors.Unauthorized(); - _; - } - - /// @inheritdoc ITermsProcessor - function executeTerms(bytes calldata data_) onlyAuthorizedExecutor external returns(bytes memory newData) { - return _executeTerms(data_); - } - - function supportsInterface( - bytes4 interfaceId_ - ) public view virtual override(ERC165, IERC165) returns (bool) { - return interfaceId_ == type(ITermsProcessor).interfaceId || super.supportsInterface(interfaceId_); - } - - /// method defining the actual execution of the terms, with no access control for caller, to be implemented by the child contract - function _executeTerms(bytes calldata data_) internal virtual returns (bytes memory newData); -} diff --git a/contracts/modules/licensing/terms/MultiTermsProcessor.sol b/contracts/modules/licensing/terms/MultiTermsProcessor.sol deleted file mode 100644 index facdaafa..00000000 --- a/contracts/modules/licensing/terms/MultiTermsProcessor.sol +++ /dev/null @@ -1,85 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.19; - -import { ITermsProcessor } from "contracts/interfaces/modules/licensing/terms/ITermsProcessor.sol"; -import { BaseTermsProcessor } from "./BaseTermsProcessor.sol"; -import { Errors } from "contracts/lib/Errors.sol"; - -/// NOTE: this contract is not tested yet, do not use. -/// @title MultiTermsProcessor -/// @author Raul Martinez -/// @notice Contract that allow to compose multiple terms processors into one, to allow for complex license arrangements. -/// Either all processors are executed successfully, or none are. -contract MultiTermsProcessor is BaseTermsProcessor { - - event ProcessorsSet(ITermsProcessor[] processors); - - ITermsProcessor[] public processors; - - /// arbitrary limit to avoid gas limit issues. If the processors are complex, gas DOS might be reached anyway. - uint256 public constant MAX_PROCESSORS = 50; - - constructor(address authorizedExecutor_, ITermsProcessor[] memory processors_) BaseTermsProcessor(authorizedExecutor_) { - _setProcessors(processors_); - } - - /// Checks if all the terms are executed, in order. If one fails, it returns false. - function termsExecutedSuccessfully(bytes calldata data_) external view override returns (bool) { - uint256 length = processors.length; - bytes[] memory encodedTerms = new bytes[](length); - encodedTerms = abi.decode(data_, (bytes[])); - bool result = true; - for (uint256 i = 0; i < length;) { - result = result && processors[i].termsExecutedSuccessfully(encodedTerms[i]); - unchecked { - i++; - } - } - return result; - } - - /// ERC165 interface support, but for ITermsProcessor it only returns true if only all processors support the interface. - function supportsInterface( - bytes4 interfaceId_ - ) public view override returns (bool) { - bool supported = true; - if (interfaceId_ == type(ITermsProcessor).interfaceId) { - uint256 length = processors.length; - for (uint256 i = 0; i < length;) { - supported && processors[i].supportsInterface(interfaceId_); - unchecked { - i++; - } - } - return supported; - } - return super.supportsInterface(interfaceId_); - } - - /// Decode the data into an array of bytes with length == processors length, and execute each processor in order. - /// Encode the results into a new array of bytes and return it. - /// @param data_ must be decodable into an array of bytes with length == processors length. - /// @return newData the encoded bytes array with the results of each processor execution. - function _executeTerms(bytes calldata data_) internal override returns (bytes memory newData) { - uint256 length = processors.length; - bytes[] memory encodedTerms = new bytes[](length); - encodedTerms = abi.decode(data_, (bytes[])); - bytes[] memory newEncodedTerms = new bytes[](length); - for (uint256 i = 0; i < length;) { - newEncodedTerms[i] = processors[i].executeTerms(encodedTerms[i]); - unchecked { - i++; - } - } - return abi.encode(newEncodedTerms); - } - - /// Sets the processors to be executed in order. - function _setProcessors(ITermsProcessor[] memory processors_) private { - if (processors_.length == 0) revert Errors.EmptyArray(); - if (processors_.length > MAX_PROCESSORS) - revert Errors.MultiTermsProcessor_TooManyTermsProcessors(); - processors = processors_; - emit ProcessorsSet(processors_); - } -} diff --git a/contracts/modules/licensing/terms/TimeTermsProcessor.sol b/contracts/modules/licensing/terms/TimeTermsProcessor.sol deleted file mode 100644 index afd93b14..00000000 --- a/contracts/modules/licensing/terms/TimeTermsProcessor.sol +++ /dev/null @@ -1,36 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.19; - -import { BaseTermsProcessor } from "./BaseTermsProcessor.sol"; -import { LibDuration } from "../../timing/LibDuration.sol"; - - -/// @title TimeTermsProcessor -/// @author Raul Martinez -/// @notice Processor to set time limits to Licenses up to a Time To Live (TTL). It has 2 modes of operation: -/// 1- Timer starts on a specific date set on License creation, and ends after a certain amount of time. -/// To do this, set startTime to a timestemp when encoding the terms in createLicense(). -/// 2- Timer starts on License execution, and ends after a certain amount of time. -/// To do this, set startTime to LibDuration.START_TIME_NOT_SET (0) when encoding the terms in createLicense(). -/// The processor will set the startTime to the block.timestamp when the terms are executed. -/// Use case for this would be to indicate "this license is valid within 1 year after the first time it is used" -contract TimeTermsProcessor is BaseTermsProcessor { - using LibDuration for LibDuration.TimeConfig; - - constructor(address authorizedExecutor_) BaseTermsProcessor(authorizedExecutor_) {} - - /// returns true if the current block.timestamp is within the start and start + ttl, false otherwise - function termsExecutedSuccessfully(bytes calldata data_) external view override returns (bool) { - LibDuration.TimeConfig memory config = abi.decode(data_, (LibDuration.TimeConfig)); - return config.isActive(); - } - - /// If startTime is not set, set it to block.timestamp and return the new encoded data. If startTime is set, return the same data. - function _executeTerms(bytes calldata data_) internal virtual override returns (bytes memory newData) { - LibDuration.TimeConfig memory config = abi.decode(data_, (LibDuration.TimeConfig)); - if (config.startTime == LibDuration.START_TIME_NOT_SET) { - config.startTime = uint64(block.timestamp); - } - return abi.encode(config); - } -} diff --git a/contracts/modules/registration/RegistrationModule.sol b/contracts/modules/registration/RegistrationModule.sol index 3c9c181f..9e62697b 100644 --- a/contracts/modules/registration/RegistrationModule.sol +++ b/contracts/modules/registration/RegistrationModule.sol @@ -13,6 +13,7 @@ import { LibRelationship } from "contracts/lib/modules/LibRelationship.sol"; import { Registration } from "contracts/lib/modules/Registration.sol"; import { LibUintArrayMask } from "contracts/lib/LibUintArrayMask.sol"; import { Errors } from "contracts/lib/Errors.sol"; +import { IPAsset } from "contracts/lib/IPAsset.sol"; /// @title Registration Module /// @notice Handles registration and transferring of IP assets.. @@ -66,7 +67,7 @@ contract RegistrationModule is BaseModule, IRegistrationModule, AccessControlled return string(abi.encodePacked(config.baseURI, Strings.toString(id))); } - IPAssetRegistry.IPA memory ipAsset = IPA_REGISTRY.ipAsset(id); + IPAsset.IPA memory ipAsset = IPA_REGISTRY.ipAsset(id); // Construct the base JSON metadata with custom name format string memory baseJson = string(abi.encodePacked( @@ -138,15 +139,16 @@ contract RegistrationModule is BaseModule, IRegistrationModule, AccessControlled /// @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 { + function _configure(IIPOrg ipOrg_, address caller_, bytes calldata params_) virtual override internal 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 { revert Errors.RegistrationModule_InvalidConfigOperation(); } + return ""; } /// @notice Registers an IP Asset. @@ -160,8 +162,8 @@ contract RegistrationModule is BaseModule, IRegistrationModule, AccessControlled return ""; } else if (executionType == Registration.REGISTER_IP_ASSET) { Registration.RegisterIPAssetParams memory params = abi.decode(executionData, (Registration.RegisterIPAssetParams)); - (uint256 ipAssetId, uint256 ipOrgAssetId) = _registerIPAsset(ipOrg_, params.owner, params.name, params.ipAssetType, params.hash); - return abi.encode(ipAssetId, ipOrgAssetId); + (uint256 ipAssetId__, uint256 ipOrgAssetId) = _registerIPAsset(ipOrg_, params.owner, params.name, params.ipAssetType, params.hash); + return abi.encode(ipAssetId__, ipOrgAssetId); } return ""; } diff --git a/contracts/modules/relationships/RelationshipModule.sol b/contracts/modules/relationships/RelationshipModule.sol index 4054c0f3..de24d0de 100644 --- a/contracts/modules/relationships/RelationshipModule.sol +++ b/contracts/modules/relationships/RelationshipModule.sol @@ -60,6 +60,10 @@ contract RelationshipModule is BaseModule, IRelationshipModule, AccessControlled /// Gets relationship definition for a given relationship id function getRelationship(uint256 relationshipId_) external view returns (LibRelationship.Relationship memory) { return _relationships[relationshipId_]; + // TODO: store hash(src) -> hash(dst), + // easier to check if a relation exist + // we can traverse the graph + // if we delete one end, it's easier to propagate } /// Gets relationship id for a given relationship @@ -83,7 +87,11 @@ contract RelationshipModule is BaseModule, IRelationshipModule, AccessControlled } /// Relationship module supports configuration to add or remove relationship types - function _configure(IIPOrg ipOrg_, address caller_, bytes calldata params_) virtual override internal { + function _configure( + IIPOrg ipOrg_, + address caller_, + bytes calldata params_ + ) virtual override internal returns (bytes memory) { _verifyConfigCaller(ipOrg_, caller_); (bytes32 configType, bytes memory configData) = abi.decode(params_, (bytes32, bytes)); if (configType == LibRelationship.ADD_REL_TYPE_CONFIG) { @@ -94,6 +102,7 @@ contract RelationshipModule is BaseModule, IRelationshipModule, AccessControlled } else { revert Errors.RelationshipModule_InvalidConfigOperation(); } + return ""; } /// Auth check for caller, if wanting to configure a protocol level relationship type, @@ -129,7 +138,7 @@ contract RelationshipModule is BaseModule, IRelationshipModule, AccessControlled } else if (relatable_ == LibRelationship.Relatables.IPORG_ENTRY) { return (address(ipOrg_), LibUintArrayMask._convertToMask(allowedTypes_)); } else if (relatable_ == LibRelationship.Relatables.LICENSE) { - return (LICENSE_REGISTRY, 0); + return (address(LICENSE_REGISTRY), 0); } else if (relatable_ == LibRelationship.Relatables.ADDRESS) { return (LibRelationship.NO_ADDRESS_RESTRICTIONS, 0); } else if (relatable_ == LibRelationship.Relatables.EXTERNAL_NFT) { diff --git a/contracts/utils/FixedSet.sol b/contracts/utils/FixedSet.sol new file mode 100644 index 00000000..45d114f9 --- /dev/null +++ b/contracts/utils/FixedSet.sol @@ -0,0 +1,384 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; +import { ShortString, ShortStrings } from "@openzeppelin/contracts/utils/ShortStrings.sol"; + +/// @title FixedSet +/// @author Raul Martinez @ethicraul +/// @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 +library FixedSet { + using ShortStrings for *; + + uint256 constant INDEX_NOT_FOUND = type(uint256).max; + + struct Set { + // Storage of set values + bytes32[] _values; + // Position of the value in the `values` array, plus 1 because index 0 + // means a value is not in the set. + 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. + */ + 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 + set._indexes[value] = set._values.length; + return true; + } else { + return false; + } + } + + /** + * @dev Returns true if the value is in the set. O(1). + */ + 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). + */ + 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). + */ + 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}. + */ + 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. + */ + function _values(Set storage set) private view returns (bytes32[] memory) { + return set._values; + } + + //////////////////////////////////////////////////////////////////////////// + // Bytes32Set // + //////////////////////////////////////////////////////////////////////////// + + + 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. + */ + 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). + */ + 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). + */ + 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}. + */ + 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). + */ + 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. + */ + function values(Bytes32Set storage set) internal view returns (bytes32[] memory) { + bytes32[] memory store = _values(set._inner); + bytes32[] memory result; + + /// @solidity memory-safe-assembly + assembly { + result := store + } + + return result; + } + + //////////////////////////////////////////////////////////////////////////// + // ShortStringSet // + //////////////////////////////////////////////////////////////////////////// + + + 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. + */ + 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). + */ + 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). + */ + 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}. + */ + 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). + */ + 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. + */ + function values(ShortStringSet storage set) internal view returns (ShortString[] memory) { + bytes32[] memory store = _values(set._inner); + ShortString[] memory result; + + /// @solidity memory-safe-assembly + assembly { + result := store + } + + return result; + } + + //////////////////////////////////////////////////////////////////////////// + // AddressSet // + //////////////////////////////////////////////////////////////////////////// + + 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. + */ + 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). + */ + 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). + */ + 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}. + */ + 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). + */ + 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. + */ + function values(AddressSet storage set) internal view returns (address[] memory) { + bytes32[] memory store = _values(set._inner); + address[] memory result; + + /// @solidity memory-safe-assembly + assembly { + result := store + } + + return result; + } + + //////////////////////////////////////////////////////////////////////////// + // UintSet // + //////////////////////////////////////////////////////////////////////////// + + 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. + */ + 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). + */ + 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). + */ + 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}. + */ + 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). + */ + function indexOf(AddressSet 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. + */ + function values(UintSet storage set) internal view returns (uint256[] memory) { + bytes32[] memory store = _values(set._inner); + uint256[] memory result; + + /// @solidity memory-safe-assembly + assembly { + result := store + } + + return result; + } +} diff --git a/contracts/modules/timing/LibDuration.sol b/contracts/utils/LibDuration.sol similarity index 100% rename from contracts/modules/timing/LibDuration.sol rename to contracts/utils/LibDuration.sol diff --git a/contracts/utils/RevertingIPAssetGroup.sol b/contracts/utils/RevertingIPAssetGroup.sol deleted file mode 100644 index 85ad1c40..00000000 --- a/contracts/utils/RevertingIPAssetGroup.sol +++ /dev/null @@ -1,15 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 - -pragma solidity ^0.8.13; - -/// @title RevertingIPOrg -/// @author Raul Martinez -/// @notice Only used to initialize the beacon in IPOrgFactor, -/// breaking a circular dependency on creation and keeping the beacon immutable -contract RevertingIPOrg { - error DontUseThisContract(); - - function initialize() external pure { - revert DontUseThisContract(); - } -} diff --git a/contracts/utils/ShortStringOps.sol b/contracts/utils/ShortStringOps.sol new file mode 100644 index 00000000..9fc582ee --- /dev/null +++ b/contracts/utils/ShortStringOps.sol @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.19; + +import { ShortString, ShortStrings } from "@openzeppelin/contracts/utils/ShortStrings.sol"; + +library ShortStringOps { + using ShortStrings for *; + + 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) { + return _equal(a, b.toShortString()); + } + + 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) { + return a == ShortString.unwrap(b); + } + + 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) { + return _equal(ShortString.wrap(a), b); + } +} diff --git a/script/foundry/deployment/Main.s.sol b/script/foundry/deployment/Main.s.sol index 138b090b..6ecaf1ec 100644 --- a/script/foundry/deployment/Main.s.sol +++ b/script/foundry/deployment/Main.s.sol @@ -9,7 +9,6 @@ import "script/foundry/utils/JsonDeploymentHandler.s.sol"; import "contracts/ip-org/IPOrg.sol"; import "contracts/ip-org/IPOrgController.sol"; import "contracts/access-control/AccessControlSingleton.sol"; -import "contracts/modules/licensing/LicensingModule.sol"; import "test/foundry/mocks/MockCollectNFT.sol"; import "test/foundry/mocks/MockCollectModule.sol"; import "contracts/modules/royalties/RoyaltyDistributor.sol"; diff --git a/test/foundry/IPOrgTest.t.sol b/test/foundry/IPOrgTest.t.sol index 07534f15..89808abe 100644 --- a/test/foundry/IPOrgTest.t.sol +++ b/test/foundry/IPOrgTest.t.sol @@ -12,7 +12,6 @@ import { IPAssetRegistry } from "contracts/IPAssetRegistry.sol"; import { AccessControlHelper } from "./utils/AccessControlHelper.sol"; import { MockCollectNFT } from "./mocks/MockCollectNFT.sol"; import { MockCollectModule } from "./mocks/MockCollectModule.sol"; -import { MockLicensingModule } from "./mocks/MockLicensingModule.sol"; import { MockIPOrgController } from "./mocks/MockIPOrgController.sol"; import 'test/foundry/utils/ProxyHelper.sol'; import "forge-std/Test.sol"; diff --git a/test/foundry/_old_modules/licensing/LicenseRegistry.t.sol b/test/foundry/_old_modules/licensing/LicenseRegistry.t.sol deleted file mode 100644 index 1d6f8692..00000000 --- a/test/foundry/_old_modules/licensing/LicenseRegistry.t.sol +++ /dev/null @@ -1,77 +0,0 @@ -// // SPDX-License-Identifier: BUSDL-1.1 -// pragma solidity ^0.8.13; - -// import "forge-std/Test.sol"; -// import 'test/foundry/utils/BaseTest.sol'; -// import 'test/foundry/mocks/MockLicensingModule.sol'; -// import 'test/foundry/mocks/MockTermsProcessor.sol'; -// import 'test/foundry/mocks/RightsManagerHarness.sol'; -// import "test/foundry/mocks/MockERC721.sol"; -// import "contracts/errors/General.sol"; -// import { Errors } from "contracts/lib/Errors.sol"; - -// contract LicenseRegistryTest is BaseTest { - -// address licenseHolder = address(0x888888); -// address receiver = address(0x999999); -// uint256 licenseId; - -// function setUp() virtual override public { -// deployProcessors = false; -// super.setUp(); -// vm.prank(licenseHolder); -// (, uint256 ipAssetId) = ipAssetOrg.createIpAsset(IPAsset.CreateIpAssetParams({ -// ipAssetType: IPAsset.IPAssetType(1), -// name: "name", -// description: "description", -// mediaUrl: "mediaUrl", -// to: licenseHolder, -// parentIpOrgId: 0, -// collectData: "" -// })); -// uint256 parentLicenseId = ipAssetOrg.getLicenseIdByTokenId(ipAssetId, false); -// (Licensing.TermsProcessorConfig memory terms,) = LibMockIPOrgConfig.getTermsProcessorConfig(); -// vm.prank(licenseHolder); -// licenseId = ipAssetOrg.createLicense( -// ipAssetId, -// parentLicenseId, -// licenseHolder, -// NON_COMMERCIAL_LICENSE_URI, -// revoker, -// false, -// false, -// terms -// ); - -// } - -// function test_setUp() public { -// assertEq(licenseRegistry.ownerOf(licenseId), licenseHolder); -// assertEq(licenseRegistry.name(), "Licenses for IPOrgName"); -// assertEq(licenseRegistry.symbol(), "slFRN"); -// assertEq(address(licenseRegistry.getRightsManager()), address(ipAssetOrg)); -// assertEq(licenseRegistry.exists(licenseId), true); -// } - -// function test_revert_mint_non_rights_manager() public { -// vm.expectRevert(Errors.Unauthorized.selector); -// licenseRegistry.mint(licenseHolder, 1); -// } - -// function test_transfer() public { -// assertEq(licenseRegistry.ownerOf(licenseId), licenseHolder); -// vm.prank(licenseHolder); -// licenseRegistry.transferFrom(licenseHolder, receiver, licenseId); -// assertEq(licenseRegistry.ownerOf(licenseId), receiver); -// } - -// function test_revert_transfer_inactive_license() public { -// vm.prank(revoker); -// ipAssetOrg.revokeLicense(licenseId); - -// vm.expectRevert(Errors.RightsManager_InactiveLicense.selector); -// vm.prank(licenseHolder); -// licenseRegistry.transferFrom(licenseHolder, receiver, licenseId); -// } - -// } diff --git a/test/foundry/_old_modules/licensing/LicensingModule.t.sol b/test/foundry/_old_modules/licensing/LicensingModule.t.sol deleted file mode 100644 index 20c089b3..00000000 --- a/test/foundry/_old_modules/licensing/LicensingModule.t.sol +++ /dev/null @@ -1,95 +0,0 @@ -// // SPDX-License-Identifier: BUSDL-1.1 -// pragma solidity ^0.8.13; - -// import "forge-std/Test.sol"; -// import 'test/foundry/utils/BaseTest.sol'; -// import 'test/foundry/mocks/MockLicensingModule.sol'; -// import 'test/foundr/mocks/MockTermsProcessor.sol'; -// import { Errors } from "contracts/lib/Errors.sol"; -// import { Licensing } from "contracts/lib/modules/Licensing.sol"; - -// contract LicensingModuleTest is BaseTest { - -// function setUp() virtual override public { -// deployProcessors = false; -// super.setUp(); -// } - -// function test_setUp() public { -// assertEq(licensingModule.getNonCommercialLicenseURI(), NON_COMMERCIAL_LICENSE_URI); -// } - -// function test_configIPOrg() public { -// vm.startPrank(ipAssetOrgOwner); -// Licensing.TermsProcessorConfig memory termsConfig = Licensing.TermsProcessorConfig({ -// processor: commercialTermsProcessor, -// data: abi.encode("root") -// }); - -// uint256 rootLicenseId = ipAssetOrg.createIPOrgRootLicense(ipAssetOrgOwner, "commercial_uri_root", revoker, true, true, termsConfig); -// assertEq(licenseRegistry.ownerOf(rootLicenseId), ipAssetOrgOwner); -// assertEq(rootLicenseId, 1); - -// Licensing.IPOrgConfig memory config = _getLicensingConfig(); -// config.revoker = address(0x5656565); -// config.commercialConfig.ipAssetOrgRootLicenseId = rootLicenseId; -// config.commercialTerms.data = abi.encode("bye"); -// config.nonCommercialTerms.data = abi.encode("hi"); - -// licensingModule.configureIpOrgLicensing(address(ipAssetOrg), config); -// Licensing.IPOrgConfig memory configResult = licensingModule.getIpOrgConfig(address(ipAssetOrg)); -// assertEq(configResult.nonCommercialConfig.canSublicense, true); -// assertEq(configResult.nonCommercialConfig.ipAssetOrgRootLicenseId, 0); -// assertEq(address(configResult.nonCommercialTerms.processor), address(nonCommercialTermsProcessor)); -// assertEq(configResult.nonCommercialTerms.data, abi.encode("hi")); -// assertEq(configResult.commercialConfig.canSublicense, false); -// assertEq(configResult.commercialConfig.ipAssetOrgRootLicenseId, 1); -// assertEq(address(configResult.commercialTerms.processor), address(commercialTermsProcessor)); -// assertEq(configResult.commercialTerms.data, abi.encode("bye")); -// assertEq(configResult.rootIpAssetHasCommercialRights, false); -// assertEq(configResult.revoker, address(0x5656565)); -// vm.stopPrank(); -// } - -// function test_revert_nonAuthorizedConfigSetter() public { -// vm.expectRevert(Errors.Unauthorized.selector); -// licensingModule.configureIpOrgLicensing(address(ipAssetOrg), LibMockIPOrgConfig.getMockIPOrgConfig()); -// } - -// function test_revert_nonExistingIPOrg() public { -// // TODO: Changing licensing module to check if address exists. -// vm.expectRevert(); -// licensingModule.configureIpOrgLicensing(address(0x6954321), LibMockIPOrgConfig.getMockIPOrgConfig()); -// } - -// function test_revert_zeroRevokerAddress() public { -// vm.startPrank(ipAssetOrgOwner); -// Licensing.IPOrgConfig memory config = LibMockIPOrgConfig.getMockIPOrgConfig(); -// config.revoker = address(0); -// vm.expectRevert(Errors.LicensingModule_ZeroRevokerAddress.selector); -// licensingModule.configureIpOrgLicensing(address(ipAssetOrg), config); -// vm.stopPrank(); -// } - -// function test_revert_rootLicenseNotActiveCommercial() public { - -// Licensing.TermsProcessorConfig memory termsConfig = Licensing.TermsProcessorConfig({ -// processor: commercialTermsProcessor, -// data: abi.encode("root") -// }); - -// vm.prank(ipAssetOrgOwner); -// uint256 rootLicenseId = ipAssetOrg.createIPOrgRootLicense(ipAssetOrgOwner, "commercial_uri_root", revoker, true, true, termsConfig); - -// commercialTermsProcessor.setSuccess(false); - -// Licensing.IPOrgConfig memory config = _getLicensingConfig(); -// config.commercialConfig.ipAssetOrgRootLicenseId = rootLicenseId; -// vm.startPrank(ipAssetOrgOwner); -// vm.expectRevert(abi.encodeWithSignature("LicensingModule_RootLicenseNotActive(uint256)", 1)); -// licensingModule.configureIpOrgLicensing(address(ipAssetOrg), config); -// vm.stopPrank(); - -// } - -// } diff --git a/test/foundry/_old_modules/licensing/RightsManager.IPAsset.t.sol b/test/foundry/_old_modules/licensing/RightsManager.IPAsset.t.sol deleted file mode 100644 index ba510ef5..00000000 --- a/test/foundry/_old_modules/licensing/RightsManager.IPAsset.t.sol +++ /dev/null @@ -1,436 +0,0 @@ -// // SPDX-License-Identifier: BUSDL-1.1 -// pragma solidity ^0.8.13; - -// import "forge-std/Test.sol"; -// import 'test/foundry/utils/BaseTest.sol'; -// import 'test/foundry/mocks/MockLicensingModule.sol'; -// import 'test/foundry/mocks/MockTermsProcessor.sol'; -// import 'test/foundry/mocks/RightsManagerHarness.sol'; -// import "test/foundry/mocks/MockERC721.sol"; -// import { Errors } from "contracts/lib/Errors.sol"; -// import { Licensing } from "contracts/lib/modules/Licensing.sol"; - -// contract RightsManagerIPAssetRightsTest is BaseTest { - -// address ipAssetCreator = address(0x999999); -// address licenseHolder = address(0x888888); - -// function setUp() virtual override public { -// deployProcessors = false; -// super.setUp(); -// } - -// function test_setUp() public { -// assertEq(licenseRegistry.name(), "Licenses for IPOrgName"); -// assertEq(licenseRegistry.symbol(), "slFRN"); -// assertEq(address(licenseRegistry.getRightsManager()), address(ipAssetOrg)); - -// // Default licensing is root non-commercial with sublicense on, no commercial rights -// Licensing.IPOrgConfig memory configResult = licensingModule.getIpOrgConfig(address(ipAssetOrg)); -// assertEq(configResult.nonCommercialConfig.canSublicense, true, "nonCommercialConfig.canSublicense"); -// assertEq(configResult.nonCommercialConfig.ipAssetOrgRootLicenseId, 0, "nonCommercialConfig.ipAssetOrgRootLicenseId"); -// assertEq(address(configResult.nonCommercialTerms.processor), address(nonCommercialTermsProcessor), "nonCommercialTerms.processor"); -// assertEq(configResult.nonCommercialTerms.data, abi.encode("nonCommercial"), "nonCommercialTerms.data"); -// assertEq(configResult.commercialConfig.canSublicense, false, "commercialConfig.canSublicense"); -// assertEq(configResult.commercialConfig.ipAssetOrgRootLicenseId, 0, "commercialConfig.ipAssetOrgRootLicenseId"); -// assertEq(address(configResult.commercialTerms.processor), address(commercialTermsProcessor), "commercialTerms.processor"); -// assertEq(configResult.commercialTerms.data, abi.encode("commercial"), "commercialTerms.data"); -// assertEq(configResult.rootIpAssetHasCommercialRights, false, "rootIpAssetHasCommercialRights"); -// assertEq(configResult.revoker, revoker, "revoker"); - -// } - -// function test_create_ip_asset_root_noncommercial() public { -// (, uint256 ipAssetId) = ipAssetOrg.createIpAsset(IPAsset.CreateIpAssetParams({ -// ipAssetType: IPAsset.IPAssetType(1), -// name: "name", -// description: "description", -// mediaUrl: "mediaUrl", -// to: ipAssetCreator, -// parentIpOrgId: 0, -// collectData: "" -// })); -// bool commercial = false; -// uint256 licenseId = ipAssetOrg.getLicenseIdByTokenId(ipAssetId, commercial); -// assertEq(licenseId, 1); -// assertEq(ipAssetOrg.getLicenseTokenId(licenseId), ipAssetId); -// (Licensing.License memory license, address owner) = ipAssetOrg.getLicense(licenseId); -// assertEq(owner, ipAssetCreator); -// assertEq(license.active, true); -// assertEq(license.canSublicense, true); -// assertEq(license.commercial, commercial); -// assertEq(license.parentLicenseId, 0); -// assertEq(license.tokenId, ipAssetId); -// assertEq(license.revoker, revoker); -// assertEq(license.uri, "https://noncommercial.license"); -// assertEq(address(license.termsProcessor), address(nonCommercialTermsProcessor)); -// assertEq(license.termsData, abi.encode("nonCommercial")); - -// commercial = true; -// licenseId = ipAssetOrg.getLicenseIdByTokenId(ipAssetId, commercial); -// assertEq(licenseId, 0); -// vm.expectRevert("ERC721: invalid token ID"); -// licenseRegistry.ownerOf(licenseId); -// } - -// function test_create_ip_asset_noncommercial_and_commercial() public { -// _configIPOrg(true, true, true); -// (, uint256 ipAssetId) = ipAssetOrg.createIpAsset(IPAsset.CreateIpAssetParams({ -// ipAssetType: IPAsset.IPAssetType(1), -// name: "name", -// description: "description", -// mediaUrl: "mediaUrl", -// to: ipAssetCreator, -// parentIpOrgId: 0, -// collectData: "" -// })); -// bool commercial = false; -// uint256 licenseId = ipAssetOrg.getLicenseIdByTokenId(ipAssetId, commercial); -// assertEq(licenseId, 1); -// assertEq(ipAssetOrg.getLicenseTokenId(licenseId), ipAssetId); -// (Licensing.License memory license, address owner) = ipAssetOrg.getLicense(licenseId); -// assertEq(owner, ipAssetCreator); -// assertEq(license.active, true); -// assertEq(license.canSublicense, true); -// assertEq(license.commercial, commercial); -// assertEq(license.parentLicenseId, 0); -// assertEq(license.tokenId, ipAssetId); -// assertEq(license.revoker, revoker); -// assertEq(license.uri, "https://noncommercial.license"); -// assertEq(address(license.termsProcessor), address(nonCommercialTermsProcessor)); -// assertEq(license.termsData, abi.encode("nonCommercial")); -// vm.expectRevert("ERC721: invalid token ID"); -// licenseRegistry.ownerOf(licenseId); - -// commercial = true; -// uint256 commercialLicenseId = ipAssetOrg.getLicenseIdByTokenId(ipAssetId, commercial); -// assertEq(commercialLicenseId, 2); -// assertEq(ipAssetOrg.getLicenseTokenId(commercialLicenseId), ipAssetId); -// (license, owner) = ipAssetOrg.getLicense(commercialLicenseId); -// assertEq(owner, ipAssetCreator); -// assertEq(license.active, true); -// assertEq(license.canSublicense, true); -// assertEq(license.commercial, commercial); -// assertEq(license.parentLicenseId, 0); -// assertEq(license.tokenId, ipAssetId); -// assertEq(license.revoker, revoker); -// assertEq(license.uri, "https://commercial.license"); -// assertEq(address(license.termsProcessor), address(commercialTermsProcessor)); -// assertEq(license.termsData, abi.encode("commercial")); - -// vm.expectRevert("ERC721: invalid token ID"); -// licenseRegistry.ownerOf(commercialLicenseId); - -// } - -// function test_create_derivative_ip_asset_from_non_commercial() public { -// (, uint256 rootIpAsset) = ipAssetOrg.createIpAsset(IPAsset.CreateIpAssetParams({ -// ipAssetType: IPAsset.IPAssetType(1), -// name: "name", -// description: "description", -// mediaUrl: "mediaUrl", -// to: ipAssetCreator, -// parentIpOrgId: 0, -// collectData: "" -// })); -// (, uint256 ipAssetId) = ipAssetOrg.createIpAsset(IPAsset.CreateIpAssetParams({ -// ipAssetType: IPAsset.IPAssetType(1), -// name: "name derv", -// description: "description deriv", -// mediaUrl: "mediaUrl deriv", -// to: ipAssetCreator, -// parentIpOrgId: rootIpAsset, -// collectData: "" -// })); - -// bool commercial = false; -// uint256 licenseId = ipAssetOrg.getLicenseIdByTokenId(ipAssetId, commercial); - -// vm.expectRevert("ERC721: invalid token ID"); -// licenseRegistry.ownerOf(licenseId); -// assertEq(licenseId, 2); -// assertEq(ipAssetOrg.getLicenseTokenId(licenseId), ipAssetId); -// (Licensing.License memory license, address owner) = ipAssetOrg.getLicense(licenseId); -// assertEq(owner, ipAssetCreator); -// assertEq(license.active, true); -// assertEq(license.canSublicense, true); -// assertEq(license.commercial, commercial); -// assertEq(license.parentLicenseId, 1); -// assertEq(license.tokenId, ipAssetId); -// assertEq(license.revoker, revoker); -// assertEq(license.uri, "https://noncommercial.license"); -// assertEq(address(license.termsProcessor), address(nonCommercialTermsProcessor)); -// assertEq(license.termsData, abi.encode("nonCommercial")); - -// commercial = true; -// licenseId = ipAssetOrg.getLicenseIdByTokenId(ipAssetId, commercial); -// assertEq(licenseId, 0); -// } - -// function test_create_derivative_ip_asset_from_commercial() public { -// _configIPOrg(true, true, true); -// (, uint256 rootIpAsset) = ipAssetOrg.createIpAsset(IPAsset.CreateIpAssetParams({ -// ipAssetType: IPAsset.IPAssetType(1), -// name: "name", -// description: "description", -// mediaUrl: "mediaUrl", -// to: ipAssetCreator, -// parentIpOrgId: 0, -// collectData: "" -// })); -// (, uint256 ipAssetId) = ipAssetOrg.createIpAsset(IPAsset.CreateIpAssetParams({ -// ipAssetType: IPAsset.IPAssetType(1), -// name: "name deriv", -// description: "description deriv", -// mediaUrl: "mediaUrl deriv", -// to: ipAssetCreator, -// parentIpOrgId: rootIpAsset, -// collectData: "" -// })); - -// bool commercial = false; -// uint256 licenseId = ipAssetOrg.getLicenseIdByTokenId(ipAssetId, commercial); - -// vm.expectRevert("ERC721: invalid token ID"); -// licenseRegistry.ownerOf(licenseId); - -// assertEq(licenseId, 3); -// assertEq(ipAssetOrg.getLicenseTokenId(licenseId), ipAssetId); -// (Licensing.License memory license, address owner) = ipAssetOrg.getLicense(licenseId); -// assertEq(owner, ipAssetCreator); -// assertEq(license.active, true); -// assertEq(license.canSublicense, true); -// assertEq(license.commercial, commercial); -// assertEq(license.parentLicenseId, 1); -// assertEq(license.tokenId, ipAssetId); -// assertEq(license.revoker, revoker); -// assertEq(license.uri, "https://noncommercial.license"); -// assertEq(address(license.termsProcessor), address(nonCommercialTermsProcessor)); -// assertEq(license.termsData, abi.encode("nonCommercial")); - -// commercial = true; -// licenseId = ipAssetOrg.getLicenseIdByTokenId(ipAssetId, commercial); -// assertEq(licenseId, 0); - -// } - -// function test_create_license() public { -// (, uint256 ipAssetId) = ipAssetOrg.createIpAsset(IPAsset.CreateIpAssetParams({ -// ipAssetType: IPAsset.IPAssetType(1), -// name: "name", -// description: "description", -// mediaUrl: "mediaUrl", -// to: licenseHolder, -// parentIpOrgId: 0, -// collectData: "" -// })); -// uint256 parentLicenseId = ipAssetOrg.getLicenseIdByTokenId(ipAssetId, false); -// bool commercial = false; -// vm.prank(licenseHolder); -// uint256 licenseId = ipAssetOrg.createLicense( -// ipAssetId, -// parentLicenseId, -// licenseHolder, -// "licenseUri", -// revoker, -// false, -// false, -// Licensing.TermsProcessorConfig({ -// processor: nonCommercialTermsProcessor, -// data: abi.encode("terms") -// }) -// ); -// (Licensing.License memory license, address owner) = ipAssetOrg.getLicense(licenseId); -// assertEq(owner, licenseHolder); -// assertEq(license.active, true); -// assertEq(license.canSublicense, false); -// assertEq(license.commercial, commercial); -// assertEq(license.parentLicenseId, parentLicenseId); -// assertEq(license.tokenId, ipAssetId); -// assertEq(license.revoker, revoker); -// assertEq(license.uri, "licenseUri"); -// assertEq(address(license.termsProcessor), address(nonCommercialTermsProcessor)); -// assertEq(license.termsData, abi.encode("terms")); -// assertEq(licenseRegistry.ownerOf(licenseId), licenseHolder); - -// } - -// function test_revert_create_license_unauthorized() public { -// (, uint256 ipAssetId) = ipAssetOrg.createIpAsset(IPAsset.CreateIpAssetParams({ -// ipAssetType: IPAsset.IPAssetType(1), -// name: "name", -// description: "description", -// mediaUrl: "mediaUrl", -// to: ipAssetCreator, -// parentIpOrgId: 0, -// collectData: "" -// })); -// uint256 parentLicenseId = 1; -// (Licensing.TermsProcessorConfig memory terms,) = LibMockIPOrgConfig.getTermsProcessorConfig(); -// vm.expectRevert(Errors.Unauthorized.selector); -// ipAssetOrg.createLicense( -// ipAssetId, -// parentLicenseId, -// licenseHolder, -// "licenseUri", -// revoker, -// false, -// false, -// terms -// ); -// } - -// function test_revert_create_license_franchise_owned_tokenId() public { -// uint256 tokenId = ipAssetOrg.ROOT_LICENSE_ID(); -// uint256 parentLicenseId = 1; -// (Licensing.TermsProcessorConfig memory terms,) = LibMockIPOrgConfig.getTermsProcessorConfig(); -// vm.startPrank(ipAssetOrgOwner); -// vm.expectRevert(Errors.RightsManager_UseCreateIPOrgRootLicenseInstead.selector); -// ipAssetOrg.createLicense( -// tokenId, -// parentLicenseId, -// licenseHolder, -// "licenseUri", -// revoker, -// false, -// false, -// terms -// ); -// vm.stopPrank(); -// } - -// function test_revert_create_license_unset_parent() public { -// uint256 tokenId = 1; -// uint256 parentLicenseId = 0; -// (Licensing.TermsProcessorConfig memory terms,) = LibMockIPOrgConfig.getTermsProcessorConfig(); -// vm.startPrank(ipAssetOrgOwner); -// vm.expectRevert(Errors.RightsManager_UseCreateIPOrgRootLicenseInstead.selector); -// ipAssetOrg.createLicense( -// tokenId, -// parentLicenseId, -// licenseHolder, -// "licenseUri", -// revoker, -// false, -// false, -// terms -// ); -// vm.stopPrank(); -// } - -// function test_revert_create_license_terms_mismatch() public { -// (, uint256 ipAssetId) = ipAssetOrg.createIpAsset(IPAsset.CreateIpAssetParams({ -// ipAssetType: IPAsset.IPAssetType(1), -// name: "name", -// description: "description", -// mediaUrl: "mediaUrl", -// to: ipAssetCreator, -// parentIpOrgId: 0, -// collectData: "" -// })); - -// uint256 parentLicenseId = ipAssetOrg.getLicenseIdByTokenId(ipAssetId, false); -// bool commercial = true; -// vm.expectRevert(Errors.RightsManager_CommercialTermsMismatch.selector); -// vm.prank(ipAssetCreator); -// ipAssetOrg.createLicense( -// ipAssetId, -// parentLicenseId, -// licenseHolder, -// "licenseUri", -// revoker, -// commercial, -// false, -// Licensing.TermsProcessorConfig({ -// processor: nonCommercialTermsProcessor, -// data: abi.encode("terms") -// }) -// ); -// } - -// // This one we can just call the internal method -// function test_create_root_license() public { -// (Licensing.TermsProcessorConfig memory terms,) = LibMockIPOrgConfig.getTermsProcessorConfig(); -// vm.prank(ipAssetOrgOwner); -// uint256 licenseId = ipAssetOrg.createIPOrgRootLicense( -// ipAssetOrgOwner, -// "licenseUri", -// revoker, -// true, -// true, -// terms -// ); -// (Licensing.License memory license, address owner) = ipAssetOrg.getLicense(licenseId); -// assertEq(owner, ipAssetOrgOwner); -// assertEq(license.active, true); -// assertEq(license.canSublicense, true); -// assertEq(license.commercial, true); -// assertEq(license.parentLicenseId, 0); -// assertEq(license.tokenId, ipAssetOrg.ROOT_LICENSE_ID()); -// assertEq(license.revoker, revoker); -// assertEq(license.uri, "licenseUri"); -// assertEq(address(license.termsProcessor), address(terms.processor)); -// assertEq(license.termsData, abi.encode("terms")); -// } - -// function test_revert_create_root_license_unauthorized() public { -// (Licensing.TermsProcessorConfig memory terms,) = LibMockIPOrgConfig.getTermsProcessorConfig(); -// vm.expectRevert(Errors.Unauthorized.selector); -// ipAssetOrg.createIPOrgRootLicense( -// ipAssetOrgOwner, -// "licenseUri", -// revoker, -// true, -// true, -// terms -// ); -// } - -// function _verifyLicense(uint256 tokenId, MockTermsProcessor termsProcessor) private returns(uint256) { -// uint256 licenseId = ipAssetOrg.getLicenseIdByTokenId(tokenId, true); -// assertEq(licenseId, 1); -// assertEq(ipAssetOrg.getLicenseTokenId(licenseId), tokenId); -// assertEq(ipAssetOrg.getParentLicenseId(licenseId), 0); -// assertTrue(ipAssetOrg.isLicenseActive(licenseId)); -// assertEq(ipAssetOrg.getLicenseURI(licenseId), "licenseUri"); -// (Licensing.License memory license, address owner) = ipAssetOrg.getLicense(licenseId); -// assertEq(owner, licenseHolder, "internal method will not create ipasset, but we mockMinted in RightsManagerHarness"); -// assertEq(license.active, true, "license active"); -// assertEq(license.canSublicense, true, "license canSublicense"); -// assertEq(license.commercial, true, "license commercial"); -// assertEq(license.parentLicenseId, 0, "license parentLicenseId"); -// assertEq(license.tokenId, tokenId, "license tokenId"); -// assertEq(license.revoker, revoker, "license revoker"); -// assertEq(license.uri, "licenseUri", "license uri"); -// assertEq(address(license.termsProcessor), address(termsProcessor), "license termsProcessor"); -// assertEq(license.termsData, abi.encode("terms"), "license termsData"); -// return licenseId; -// } - -// function _configIPOrg(bool sublicenseCommercial, bool sublicenseNonCommercial, bool rootIpAssetHasCommercialRights) private { -// Licensing.IPOrgConfig memory config = Licensing.IPOrgConfig({ -// nonCommercialConfig: Licensing.IpAssetConfig({ -// canSublicense: sublicenseNonCommercial, -// ipAssetOrgRootLicenseId: 0 -// }), -// nonCommercialTerms: Licensing.TermsProcessorConfig({ -// processor: nonCommercialTermsProcessor, -// data: abi.encode("nonCommercial") -// }), -// commercialConfig: Licensing.IpAssetConfig({ -// canSublicense: sublicenseCommercial, -// ipAssetOrgRootLicenseId: 0 -// }), -// commercialTerms: Licensing.TermsProcessorConfig({ -// processor: commercialTermsProcessor, -// data: abi.encode("commercial") -// }), -// rootIpAssetHasCommercialRights: rootIpAssetHasCommercialRights, -// revoker: revoker, -// commercialLicenseUri: "https://commercial.license" -// }); -// vm.prank(ipAssetOrgOwner); -// licensingModule.configureIpOrgLicensing(address(ipAssetOrg), config); -// } - -// } diff --git a/test/foundry/_old_modules/licensing/RightsManager.Internal.t.sol b/test/foundry/_old_modules/licensing/RightsManager.Internal.t.sol deleted file mode 100644 index dd833137..00000000 --- a/test/foundry/_old_modules/licensing/RightsManager.Internal.t.sol +++ /dev/null @@ -1,477 +0,0 @@ -// // SPDX-License-Identifier: BUSDL-1.1 -// pragma solidity ^0.8.13; - -// import 'test/foundry/utils/BaseTest.sol'; -// import 'test/foundry/mocks/MockLicensingModule.sol'; -// import 'test/foundry/mocks/MockTermsProcessor.sol'; -// import 'test/foundry/mocks/RightsManagerHarness.sol'; -// import "test/foundry/mocks/MockERC721.sol"; -// import { IPAsset } from "contracts/lib/IPAsset.sol"; -// import { Errors } from "contracts/lib/Errors.sol"; -// import { IPAssetRegistry } from "contracts/IPAssetRegistry.sol"; -// import { Licensing } from "contracts/lib/modules/Licensing.sol"; - -// contract RightsManagerInternalTest is Test, ProxyHelper { - -// IPAssetRegistry registry; -// MockERC721 mockIPOrgController; -// RightsManagerHarness rightsManager; -// address constant mockEventEmitter = address(0x1234567); -// address constant mockLicensingModule = address(0x23445); -// address constant mockCollectModule = address(0x13371); -// address constant licenseHolder = address(0x8073465); -// address constant revoker = address(0x123456722222); - -// function setUp() public { - -// registry = new IPAssetRegistry(); -// mockIPOrgController = new MockERC721(); -// RightsManagerHarness impl = new RightsManagerHarness(); -// rightsManager = RightsManagerHarness( -// _deployUUPSProxy( -// address(impl), -// abi.encodeWithSelector( -// bytes4(keccak256(bytes("initialize((address,address,string,string,string,address,address))"))), -// IPAsset.InitIPOrgParams({ -// registry: address(registry), -// owner: address(this), -// name: "name", -// symbol: "symbol", -// description: "description", -// licensingModule: address(mockLicensingModule), -// collectModule: address(mockCollectModule) -// }) -// ) -// ) -// ); -// LicenseRegistry licenseRegistry = new LicenseRegistry(address(rightsManager), "name", "symbol"); -// rightsManager.setLicenseRegistry(address(licenseRegistry)); -// } - -// function test_setup() public { -// // TODO(ramarti): Decouple rights manager from IP Asset Org -// assertEq(address(rightsManager.IP_ASSET_ORG()), address(rightsManager)); -// assertEq(rightsManager.name(), "name"); -// assertEq(rightsManager.symbol(), "symbol"); -// } - -// function test_revert_transfer_sublicense() public { -// vm.expectRevert(Errors.Unauthorized.selector); -// rightsManager.transferSublicense(1, address(0x123456)); -// } - -// function test_internal_create_license_rootLicense_notmockMinting() public { -// uint256 tokenId = 1; -// rightsManager.mockMint(licenseHolder, tokenId); -// uint256 parentLicenseId = 0; -// (Licensing.TermsProcessorConfig memory terms, MockTermsProcessor termsProcessor) = LibMockIPOrgConfig.getTermsProcessorConfig(); -// // TODO test events -// uint256 licenseId = rightsManager.createLicense_exposed( -// tokenId, -// parentLicenseId, -// licenseHolder, -// "licenseUri", -// revoker, -// true, -// true, -// terms, -// false -// ); -// assertEq(licenseId, rightsManager.getLicenseIdByTokenId(tokenId, true)); -// _verifyLicense(licenseId, parentLicenseId, tokenId, termsProcessor, true, true); -// ILicenseRegistry licenseRegistry = ILicenseRegistry(rightsManager.getLicenseRegistry()); -// vm.expectRevert("ERC721: invalid token ID"); -// licenseRegistry.ownerOf(licenseId); - -// } - -// function test_internal_create_license_rootLicense_mockMinting() public { -// uint256 tokenId = 1; -// rightsManager.mockMint(licenseHolder, tokenId); -// uint256 parentLicenseId = 0; -// (Licensing.TermsProcessorConfig memory terms, MockTermsProcessor termsProcessor) = LibMockIPOrgConfig.getTermsProcessorConfig(); -// // TODO test events -// uint256 licenseId = rightsManager.createLicense_exposed( -// tokenId, -// parentLicenseId, -// licenseHolder, -// "licenseUri", -// revoker, -// true, -// true, -// terms, -// false -// ); -// assertEq(licenseId, rightsManager.getLicenseIdByTokenId(tokenId, true), "wtf"); -// _verifyLicense(licenseId, parentLicenseId, tokenId, termsProcessor, true, true); -// ILicenseRegistry licenseRegistry = ILicenseRegistry(rightsManager.getLicenseRegistry()); -// vm.expectRevert("ERC721: invalid token ID"); -// licenseRegistry.ownerOf(licenseId); -// } - -// function test_internal_create_license_nonRootLicense_notmockMinting() public { -// uint256 tokenId = 1; -// rightsManager.mockMint(licenseHolder, tokenId); -// (Licensing.TermsProcessorConfig memory terms, MockTermsProcessor termsProcessor) = LibMockIPOrgConfig.getTermsProcessorConfig(); -// // mockMint root -// uint256 parentLicenseId = rightsManager.createLicense_exposed( -// tokenId, -// 0, -// licenseHolder, -// "licenseUri", -// revoker, -// true, -// true, -// terms, -// false -// ); -// // mockMint derivative ip -// uint256 nextTokenId = tokenId + 1; -// rightsManager.mockMint(licenseHolder, nextTokenId); -// // mockMint sublicense -// uint256 licenseId = rightsManager.createLicense_exposed( -// nextTokenId, -// parentLicenseId, -// licenseHolder, -// "licenseUri", -// revoker, -// true, -// true, -// terms, -// false -// ); -// assertEq(licenseId, rightsManager.getLicenseIdByTokenId(nextTokenId, true)); -// _verifyLicense(licenseId, parentLicenseId, nextTokenId, termsProcessor, true, true); -// ILicenseRegistry licenseRegistry = ILicenseRegistry(rightsManager.getLicenseRegistry()); -// vm.expectRevert("ERC721: invalid token ID"); -// licenseRegistry.ownerOf(licenseId); - -// } - -// function test_internal_create_license_nonRootLicense_mockMinting() public { -// uint256 tokenId = 1; -// rightsManager.mockMint(licenseHolder, tokenId); -// (Licensing.TermsProcessorConfig memory terms, MockTermsProcessor termsProcessor) = LibMockIPOrgConfig.getTermsProcessorConfig(); -// // mockMint root -// uint256 parentLicenseId = rightsManager.createLicense_exposed( -// tokenId, -// 0, -// licenseHolder, -// "licenseUri", -// revoker, -// true, -// true, -// terms, -// true -// ); -// // mockMint sublicense -// uint256 licenseId = rightsManager.createLicense_exposed( -// tokenId, -// parentLicenseId, -// licenseHolder, -// "licenseUri", -// revoker, -// true, -// true, -// terms, -// true -// ); -// _verifyLicense(licenseId, parentLicenseId, tokenId, termsProcessor, true, true); -// ILicenseRegistry licenseRegistry = ILicenseRegistry(rightsManager.getLicenseRegistry()); -// assertEq(licenseRegistry.ownerOf(licenseId), licenseHolder); -// } - -// function test_revert_internal_createLicense_zeroRevoker() public { -// uint256 tokenId = 1; -// rightsManager.mockMint(licenseHolder, tokenId); -// uint256 parentLicenseId = 0; -// (Licensing.TermsProcessorConfig memory terms,) = LibMockIPOrgConfig.getTermsProcessorConfig(); -// vm.expectRevert(Errors.RightsManager_ZeroRevokerAddress.selector); -// rightsManager.createLicense_exposed( -// tokenId, -// parentLicenseId, -// licenseHolder, -// "licenseUri", -// address(0), -// true, -// true, -// terms, -// false -// ); -// } - -// function test_revert_internal_createLicense_nonExistentId() public { -// uint256 tokenId = 1; -// uint256 parentLicenseId = 0; -// (Licensing.TermsProcessorConfig memory terms,) = LibMockIPOrgConfig.getTermsProcessorConfig(); -// vm.expectRevert(abi.encodeWithSignature("NonExistentID(uint256)", 1)); -// rightsManager.createLicense_exposed( -// tokenId, -// parentLicenseId, -// licenseHolder, -// "licenseUri", -// revoker, -// true, -// true, -// terms, -// false -// ); -// } - -// function test_revert_internal_createLicense_alreadyHasRootLicense() public { -// uint256 tokenId = 1; -// rightsManager.mockMint(licenseHolder, tokenId); -// uint256 parentLicenseId = 0; -// (Licensing.TermsProcessorConfig memory terms,) = LibMockIPOrgConfig.getTermsProcessorConfig(); -// // mockMint root -// rightsManager.createLicense_exposed( -// tokenId, -// 0, -// licenseHolder, -// "licenseUri", -// revoker, -// true, -// true, -// terms, -// false -// ); -// // mockMint root again -// vm.expectRevert(Errors.RightsManager_AlreadyHasRootLicense.selector); -// rightsManager.createLicense_exposed( -// tokenId, -// parentLicenseId, -// licenseHolder, -// "licenseUri", -// revoker, -// true, -// true, -// terms, -// false -// ); -// } - -// function test_revert_internal_createLicense_notOwnerOfParentLicense() public { -// uint256 tokenId = 1; -// rightsManager.mockMint(licenseHolder, tokenId); -// uint256 parentLicenseId = 1; -// (Licensing.TermsProcessorConfig memory terms,) = LibMockIPOrgConfig.getTermsProcessorConfig(); -// // mockMint root -// rightsManager.createLicense_exposed( -// tokenId, -// 0, -// licenseHolder, -// "licenseUri", -// revoker, -// true, -// true, -// terms, -// false -// ); -// // mockMint sublicense -// vm.expectRevert(Errors.RightsManager_NotOwnerOfParentLicense.selector); -// rightsManager.createLicense_exposed( -// tokenId, -// parentLicenseId, -// address(0x123456), -// "licenseUri", -// revoker, -// true, -// true, -// terms, -// true -// ); -// } - -// function test_revert_internal_createLicense_inactiveParentLicense() public { -// uint256 tokenId = 1; -// rightsManager.mockMint(licenseHolder, tokenId); -// uint256 parentLicenseId = 1; -// (Licensing.TermsProcessorConfig memory terms,) = LibMockIPOrgConfig.getTermsProcessorConfig(); -// // mockMint root -// rightsManager.createLicense_exposed( -// tokenId, -// 0, -// licenseHolder, -// "licenseUri", -// revoker, -// true, -// true, -// terms, -// false -// ); - -// vm.prank(revoker); -// rightsManager.revokeLicense(parentLicenseId); - -// // mockMint sublicense -// vm.expectRevert(Errors.RightsManager_InactiveParentLicense.selector); -// rightsManager.createLicense_exposed( -// tokenId, -// parentLicenseId, -// licenseHolder, -// "licenseUri", -// revoker, -// true, -// true, -// terms, -// true -// ); -// } - -// function test_revert_internal_createLicense_cannotSublicense() public { -// uint256 tokenId = 1; -// rightsManager.mockMint(licenseHolder, tokenId); -// uint256 parentLicenseId = 1; -// (Licensing.TermsProcessorConfig memory terms,) = LibMockIPOrgConfig.getTermsProcessorConfig(); -// // mockMint root -// rightsManager.createLicense_exposed( -// tokenId, -// 0, -// licenseHolder, -// "licenseUri", -// revoker, -// true, -// false, -// terms, -// false -// ); -// // mockMint sublicense -// vm.expectRevert(Errors.RightsManager_CannotSublicense.selector); -// rightsManager.createLicense_exposed( -// tokenId, -// parentLicenseId, -// licenseHolder, -// "licenseUri", -// revoker, -// true, -// true, -// terms, -// true -// ); -// } - -// function test_revert_internal_createLicense_commercialTermsMismatch() public { -// uint256 tokenId = 1; -// rightsManager.mockMint(licenseHolder, tokenId); -// uint256 parentLicenseId = 1; -// (Licensing.TermsProcessorConfig memory terms,) = LibMockIPOrgConfig.getTermsProcessorConfig(); -// // mockMint root -// rightsManager.createLicense_exposed( -// tokenId, -// 0, -// licenseHolder, -// "licenseUri", -// revoker, -// true, -// true, -// terms, -// false -// ); -// // mockMint sublicense -// vm.expectRevert(Errors.RightsManager_CommercialTermsMismatch.selector); -// rightsManager.createLicense_exposed( -// tokenId, -// parentLicenseId, -// licenseHolder, -// "licenseUri", -// revoker, -// false, -// true, -// terms, -// true -// ); -// } - -// function test_revert_internal_createLicense_nonCommercialTermsMismatch() public { -// uint256 tokenId = 1; -// rightsManager.mockMint(licenseHolder, tokenId); -// uint256 parentLicenseId = 1; -// (Licensing.TermsProcessorConfig memory terms,) = LibMockIPOrgConfig.getTermsProcessorConfig(); -// // mockMint root -// rightsManager.createLicense_exposed( -// tokenId, -// 0, -// licenseHolder, -// "licenseUri", -// revoker, -// false, -// true, -// terms, -// false -// ); -// // mockMint sublicense -// vm.expectRevert(Errors.RightsManager_CommercialTermsMismatch.selector); -// rightsManager.createLicense_exposed( -// tokenId, -// parentLicenseId, -// licenseHolder, -// "licenseUri", -// revoker, -// true, -// true, -// terms, -// true -// ); -// // Uncomment this if we ever allow commercial sublicenses attached to children tokenIds -// /* -// vm.expectRevert(Errors.RightsManager_CommercialTermsMismatch.selector); -// rightsManager.createLicense_exposed( -// tokenId, -// parentLicenseId, -// licenseHolder, -// "licenseUri", -// revoker, -// true, -// true, -// terms, -// false -// );*/ -// } - -// function test_revert_internal_createLicense_termsProcessorUnsupportedInterface() public { -// uint256 tokenId = 1; -// rightsManager.mockMint(licenseHolder, tokenId); - -// vm.expectRevert(abi.encodeWithSignature("UnsupportedInterface(string)", "ITermsProcessor")); -// // mockMint root -// rightsManager.createLicense_exposed( -// tokenId, -// 0, -// licenseHolder, -// "licenseUri", -// revoker, -// false, -// true, -// Licensing.TermsProcessorConfig({ -// processor: ITermsProcessor(address(rightsManager)), -// data: abi.encode("terms") -// }), -// false -// ); -// } - -// function test_revert_unknown_license() public { -// vm.expectRevert("ERC721: invalid token ID"); -// rightsManager.getLicense(222); -// vm.expectRevert("ERC721: invalid token ID"); -// rightsManager.getLicense(0); -// } - -// function _verifyLicense(uint256 licenseId, uint256 parentLicenseId, uint256 tokenId, MockTermsProcessor termsProcessor, bool canSublicense, bool commercial) private { -// assertEq(rightsManager.getLicenseTokenId(licenseId), tokenId, "license tokenId"); -// assertEq(rightsManager.getParentLicenseId(licenseId), parentLicenseId, "license parentLicenseId"); -// assertTrue(rightsManager.isLicenseActive(licenseId), "license active"); -// assertEq(rightsManager.getLicenseURI(licenseId), "licenseUri"); -// (Licensing.License memory license, address owner) = rightsManager.getLicense(licenseId); -// assertEq(owner, licenseHolder, "internal method will not create ipasset, but we mockMinted in RightsManagerHarness"); -// assertEq(license.active, true, "license active"); -// assertEq(license.canSublicense, canSublicense, "license canSublicense"); -// assertEq(license.commercial, commercial, "license commercial"); -// assertEq(license.parentLicenseId, parentLicenseId, "license parentLicenseId"); -// assertEq(license.tokenId, tokenId, "license tokenId"); -// assertEq(license.revoker, revoker, "license revoker"); -// assertEq(license.uri, "licenseUri", "license uri"); -// assertEq(address(license.termsProcessor), address(termsProcessor), "license termsProcessor"); -// assertEq(license.termsData, abi.encode("terms"), "license termsData"); -// } -// } diff --git a/test/foundry/_old_modules/licensing/terms/TimeTermsProcessor.t.sol b/test/foundry/_old_modules/licensing/terms/TimeTermsProcessor.t.sol deleted file mode 100644 index 73663366..00000000 --- a/test/foundry/_old_modules/licensing/terms/TimeTermsProcessor.t.sol +++ /dev/null @@ -1,175 +0,0 @@ -// // SPDX-License-Identifier: BUSDL-1.1 -// pragma solidity ^0.8.13; - -// import "forge-std/Test.sol"; -// import 'test/foundry/utils/BaseTest.sol'; -// import "contracts/errors/General.sol"; -// import "contracts/interfaces/modules/licensing/terms/ITermsProcessor.sol"; -// import "contracts/modules/licensing/terms/TimeTermsProcessor.sol"; -// import "contracts/modules/timing/LibDuration.sol"; - -// contract LicenseRegistryTest is BaseTest { - -// address licenseHolder = address(0x888888); -// ITermsProcessor processor; -// uint256 licenseId; -// uint256 ipAssetId; -// uint256 parentLicenseId; - -// function setUp() virtual override public { -// deployProcessors = false; -// super.setUp(); -// (, ipAssetId) = ipAssetOrg.createIpAsset(IPAsset.CreateIpAssetParams({ -// ipAssetType: IPAsset.IPAssetType(1), -// name: "name", -// description: "description", -// mediaUrl: "mediaUrl", -// to: licenseHolder, -// parentIpOrgId: 0, -// collectData: "" -// })); -// parentLicenseId = ipAssetOrg.getLicenseIdByTokenId(ipAssetId, false); -// processor = getTermsProcessor(); -// } - -// function test_revert_execute_terms_unauthorized() public { -// bytes memory data = getTermsData(abi.encode(1)); -// vm.expectRevert(Unauthorized.selector); -// processor.executeTerms(data); -// } - -// function test_execute_terms_start_on_license_creation() public virtual { -// uint64 ttl = 1000; -// uint64 startTime = uint64(block.timestamp) + 100; -// address renewer = address(0); - -// LibDuration.TimeConfig memory config = LibDuration.TimeConfig( -// ttl, -// startTime, -// renewer -// ); -// bytes memory encodedConfig = getTermsConfig(abi.encode(config)); - -// Licensing.TermsProcessorConfig memory termsConfig = Licensing.TermsProcessorConfig({ -// processor: processor, -// data: encodedConfig -// }); - -// assertFalse(processor.termsExecutedSuccessfully(encodedConfig), "terms should be inactive before start time"); - -// vm.prank(licenseHolder); -// licenseId = ipAssetOrg.createLicense( -// ipAssetId, -// parentLicenseId, -// licenseHolder, -// "licenseUri", -// revoker, -// false, -// false, -// termsConfig -// ); -// vm.prank(licenseHolder); -// ipAssetOrg.executeTerms(licenseId); -// assertFalse(ipAssetOrg.isLicenseActive(licenseId), "execution is a noop if start time set"); -// assertFalse(processor.termsExecutedSuccessfully(encodedConfig), "execution is a noop if start time set"); -// vm.warp(startTime + 100); -// assertTrue(ipAssetOrg.isLicenseActive(licenseId), "license should be active after start time"); -// assertTrue(processor.termsExecutedSuccessfully(encodedConfig), "terms should be active after start time"); -// vm.warp(startTime + ttl + 1); -// assertFalse(processor.termsExecutedSuccessfully(encodedConfig), "terms should be inactive after ttl"); -// assertFalse(ipAssetOrg.isLicenseActive(licenseId), "license should be inactive after ttl"); - -// } - -// function test_terms_always_false_if_not_started() public virtual { -// uint64 ttl = 1000; -// uint64 startTime = 0; // unset so it fills with block.timestamp in terms execution -// address renewer = address(0); - -// LibDuration.TimeConfig memory config = LibDuration.TimeConfig( -// ttl, -// startTime, -// renewer -// ); -// bytes memory encodedConfig = getTermsConfig(abi.encode(config)); -// Licensing.TermsProcessorConfig memory termsConfig = Licensing.TermsProcessorConfig({ -// processor: processor, -// data: encodedConfig -// }); - -// assertFalse(processor.termsExecutedSuccessfully(encodedConfig)); - -// vm.prank(licenseHolder); -// licenseId = ipAssetOrg.createLicense( -// ipAssetId, -// parentLicenseId, -// licenseHolder, -// "licenseUri", -// revoker, -// false, -// false, -// termsConfig -// ); -// assertFalse(ipAssetOrg.isLicenseActive(licenseId)); -// assertFalse(processor.termsExecutedSuccessfully(encodedConfig)); -// vm.warp(block.timestamp + 100); -// assertFalse(ipAssetOrg.isLicenseActive(licenseId)); -// assertFalse(processor.termsExecutedSuccessfully(encodedConfig)); -// vm.warp(block.timestamp + ttl + 1); -// assertFalse(processor.termsExecutedSuccessfully(encodedConfig)); -// assertFalse(ipAssetOrg.isLicenseActive(licenseId)); - -// } - -// function test_execute_terms_start_license_countdown() public virtual { -// uint64 ttl = 1000; -// uint64 startTime = 0; // unset so it fills with block.timestamp in terms execution -// address renewer = address(0); - -// LibDuration.TimeConfig memory config = LibDuration.TimeConfig( -// ttl, -// startTime, -// renewer -// ); -// bytes memory encodedConfig = getTermsConfig(abi.encode(config)); -// Licensing.TermsProcessorConfig memory termsConfig = Licensing.TermsProcessorConfig({ -// processor: processor, -// data: encodedConfig -// }); - -// assertFalse(processor.termsExecutedSuccessfully(encodedConfig), "terms should be inactive before start time"); - -// vm.prank(licenseHolder); -// licenseId = ipAssetOrg.createLicense( -// ipAssetId, -// parentLicenseId, -// licenseHolder, -// "licenseUri", -// revoker, -// false, -// false, -// termsConfig -// ); -// assertFalse(ipAssetOrg.isLicenseActive(licenseId), "terms not executed yet"); -// vm.prank(licenseHolder); -// ipAssetOrg.executeTerms(licenseId); -// assertTrue(ipAssetOrg.isLicenseActive(licenseId), "license started after terms execution"); -// vm.warp(block.timestamp + 100); -// assertTrue(ipAssetOrg.isLicenseActive(licenseId), "license should be active after start time"); -// vm.warp(block.timestamp + ttl + 1); -// assertFalse(ipAssetOrg.isLicenseActive(licenseId), "license should be inactive after ttl"); - -// } - -// function getTermsProcessor() internal virtual returns (ITermsProcessor) { -// return new TimeTermsProcessor(address(ipAssetOrg)); -// } - -// function getTermsData(bytes memory data) internal virtual returns (bytes memory) { -// return data; -// } - -// function getTermsConfig(bytes memory config) internal virtual returns (bytes memory) { -// return config; -// } -// } diff --git a/test/foundry/mocks/MockBaseModule.sol b/test/foundry/mocks/MockBaseModule.sol index e9a0cd34..80b4fe1b 100644 --- a/test/foundry/mocks/MockBaseModule.sol +++ b/test/foundry/mocks/MockBaseModule.sol @@ -50,7 +50,7 @@ contract MockBaseModule is BaseModule { IIPOrg ipOrg_, address caller_, bytes calldata params_ - ) internal virtual override { + ) internal virtual override returns (bytes memory) { _callStack.push(BaseModuleCall(address(ipOrg_), caller_, params_)); } diff --git a/test/foundry/mocks/MockLicensingModule.sol b/test/foundry/mocks/MockLicensingModule.sol deleted file mode 100644 index e5ef36b5..00000000 --- a/test/foundry/mocks/MockLicensingModule.sol +++ /dev/null @@ -1,69 +0,0 @@ -pragma solidity ^0.8.19; - -import { ILicensingModule } from "contracts/interfaces/modules/licensing/ILicensingModule.sol"; -import { ITermsProcessor } from "contracts/interfaces/modules/licensing/terms/ITermsProcessor.sol"; -import { MockTermsProcessor } from "./MockTermsProcessor.sol"; -import { Licensing } from "contracts/lib/modules/Licensing.sol"; - -library LibMockIPOrgConfig { - function getMockIPOrgConfig() - internal - pure - returns (Licensing.IPOrgConfig memory) - { - return - Licensing.IPOrgConfig({ - nonCommercialConfig: Licensing.IpAssetConfig({ - canSublicense: false, - ipAssetOrgRootLicenseId: 0 - }), - nonCommercialTerms: Licensing.TermsProcessorConfig({ - processor: ITermsProcessor(address(0)), - data: "" - }), - commercialConfig: Licensing.IpAssetConfig({ - canSublicense: false, - ipAssetOrgRootLicenseId: 0 - }), - commercialTerms: Licensing.TermsProcessorConfig({ - processor: ITermsProcessor(address(0)), - data: "" - }), - rootIpAssetHasCommercialRights: false, - revoker: address(0x5656565), - commercialLicenseUri: "" - }); - } - - function getTermsProcessorConfig() public returns(Licensing.TermsProcessorConfig memory terms, MockTermsProcessor termsProcessor){ - termsProcessor = new MockTermsProcessor(); - terms = Licensing.TermsProcessorConfig({ - processor: termsProcessor, - data: abi.encode("terms") - }); - } -} - -contract MockLicensingModule is ILicensingModule { - function configureIpOrgLicensing( - address ipAssetOrg, - Licensing.IPOrgConfig memory config - ) external override { - // No-op - } - - function getIpOrgConfig( - address - ) external pure override returns (Licensing.IPOrgConfig memory) { - return LibMockIPOrgConfig.getMockIPOrgConfig(); - } - - function getNonCommercialLicenseURI() - external - pure - override - returns (string memory) - { - return "mockmock"; - } -} diff --git a/test/foundry/mocks/MockTermsProcessor.sol b/test/foundry/mocks/MockTermsProcessor.sol deleted file mode 100644 index 90a86050..00000000 --- a/test/foundry/mocks/MockTermsProcessor.sol +++ /dev/null @@ -1,34 +0,0 @@ -// SPDX-License-Identifier: BUSDL-1.1 -pragma solidity ^0.8.13; - -import "contracts/interfaces/modules/licensing/terms/ITermsProcessor.sol"; -import "@openzeppelin/contracts/utils/introspection/ERC165.sol"; -import "@openzeppelin/contracts/utils/introspection/IERC165.sol"; - - -contract MockTermsProcessor is ITermsProcessor, ERC165 { - - bool private _success = true; - - function setSuccess(bool value) external { - _success = value; - } - - function supportsInterface( - bytes4 - ) public pure override(ERC165, IERC165) returns (bool) { - return true; - } - - function executeTerms( - bytes calldata data - ) external pure override returns (bytes memory newData) { - return data; - } - - function termsExecutedSuccessfully( - bytes calldata - ) external view override returns (bool) { - return _success; - } -} diff --git a/test/foundry/mocks/RightsManagerHarness.sol b/test/foundry/mocks/RightsManagerHarness.sol deleted file mode 100644 index 96bbb284..00000000 --- a/test/foundry/mocks/RightsManagerHarness.sol +++ /dev/null @@ -1,57 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.13; - -import { IPOrg } from "contracts/ip-org/IPOrg.sol"; -import { IPAsset } from "contracts/lib/IPAsset.sol"; -import { ILicensingModule } from "contracts/interfaces/modules/licensing/ILicensingModule.sol"; -import { ITermsProcessor } from "contracts/interfaces/modules/licensing/terms/ITermsProcessor.sol"; -import { Licensing } from "contracts/lib/modules/Licensing.sol"; - - -contract RightsManagerHarness is IPOrg { - - constructor( - address ipOrgController_, - address moduleRegistry_ - ) IPOrg(ipOrgController_, moduleRegistry_) {} - - function mockMint(address to, uint256 tokenId) external { - _mint(to, tokenId); - } - - function mockMintWithRights(address to, uint256 tokenId, address revoker) external { - _mint(to, tokenId); - // _setNonCommercialRights(tokenId, 0, to, revoker, Licensing.IpAssetConfig({ - // canSublicense: true, - // ipAssetOrgRootLicenseId: 0 - // }), Licensing.TermsProcessorConfig({ - // processor: ITermsProcessor(address(0)), - // data: "" - // })); - } - - function createLicense_exposed( - uint256 tokenId, - uint256 parentLicenseId, - address licenseHolder, - string memory uri, - address revoker, - bool commercial, - bool canSublicense, - Licensing.TermsProcessorConfig memory _terms, - bool inLicenseRegistry - ) external returns(uint256 licenseId) { - // return _createLicense( - // tokenId, - // parentLicenseId, - // licenseHolder, - // uri, - // revoker, - // commercial, - // canSublicense, - // _terms, - // inLicenseRegistry - // ); - } - -} diff --git a/test/foundry/modules/ModuleRegistry.t.sol b/test/foundry/modules/ModuleRegistry.t.sol index b45dc6e2..28da51e8 100644 --- a/test/foundry/modules/ModuleRegistry.t.sol +++ b/test/foundry/modules/ModuleRegistry.t.sol @@ -26,7 +26,7 @@ contract ModuleRegistryTest is Test, AccessControlHelper { BaseModule.ModuleConstruction memory moduleConstruction = BaseModule.ModuleConstruction( IPAssetRegistry(address(0x123)), ModuleRegistry(address(0x983)), - address(0x123) + LicenseRegistry(address(0x123)) ); MockBaseModule module = new MockBaseModule(admin, moduleConstruction); vm.prank(admin); @@ -38,7 +38,7 @@ contract ModuleRegistryTest is Test, AccessControlHelper { BaseModule.ModuleConstruction memory moduleConstruction = BaseModule.ModuleConstruction( IPAssetRegistry(address(0x123)), ModuleRegistry(address(0x983)), - address(0x123) + LicenseRegistry(address(0x123)) ); MockBaseModule module = new MockBaseModule(admin, moduleConstruction); vm.startPrank(admin); diff --git a/test/foundry/modules/base/BaseModule.t.sol b/test/foundry/modules/base/BaseModule.t.sol index 738aba3d..6184bf0f 100644 --- a/test/foundry/modules/base/BaseModule.t.sol +++ b/test/foundry/modules/base/BaseModule.t.sol @@ -29,7 +29,7 @@ contract BaseModuleTest is BaseTest { mockIpOrg = new MockIPOrg(admin); vm.startPrank(admin); - module = new MockBaseModule(admin, BaseModule.ModuleConstruction(ipaRegistry, moduleRegistry, address(888))); + module = new MockBaseModule(admin, BaseModule.ModuleConstruction(ipaRegistry, moduleRegistry, licenseRegistry)); accessControl.grantRole(AccessControl.HOOK_CALLER_ROLE, address(module)); vm.stopPrank(); moduleExecutionParams = MockBaseModule.ModuleExecutionParams({ @@ -42,13 +42,27 @@ contract BaseModuleTest is BaseTest { function test_baseModule_revert_constructorIpaRegistryIsZero() public { vm.prank(admin); vm.expectRevert(Errors.BaseModule_ZeroIpaRegistry.selector); - new MockBaseModule(admin, BaseModule.ModuleConstruction(IPAssetRegistry(address(0)), moduleRegistry, address(888))); + new MockBaseModule( + admin, + BaseModule.ModuleConstruction( + IPAssetRegistry(address(0)), + moduleRegistry, + licenseRegistry + ) + ); } function test_baseModule_revert_constructorModuleRegistryIsZero() public { vm.prank(admin); vm.expectRevert(Errors.BaseModule_ZeroModuleRegistry.selector); - module = new MockBaseModule(admin, BaseModule.ModuleConstruction(ipaRegistry, ModuleRegistry(address(0)), address(888))); + module = new MockBaseModule( + admin, + BaseModule.ModuleConstruction( + ipaRegistry, + ModuleRegistry(address(0)), + licenseRegistry + ) + ); } function test_baseModule_setup() public { diff --git a/test/foundry/modules/licensing/BaseLicensingTest.sol b/test/foundry/modules/licensing/BaseLicensingTest.sol new file mode 100644 index 00000000..844d7acf --- /dev/null +++ b/test/foundry/modules/licensing/BaseLicensingTest.sol @@ -0,0 +1,306 @@ +// // SPDX-License-Identifier: BUSDL-1.1 +// pragma solidity ^0.8.13; + +// import "forge-std/Test.sol"; +// import { Licensing } from "contracts/lib/modules/Licensing.sol"; +// import { ShortStrings, ShortString } from "@openzeppelin/contracts/utils/ShortStrings.sol"; +// import 'test/foundry/utils/BaseTest.sol'; +// import { IHook } from "contracts/interfaces/hooks/base/IHook.sol"; +// import { TermCategories, TermIds } from "contracts/lib/modules/ProtocolLicensingTerms.sol"; +// import { ProtocolTermsHelper } from "contracts/modules/licensing/ProtocolTermsHelper.sol"; + +// contract BaseLicensingTest is BaseTest { +// using ShortStrings for *; + +// ShortString public textTermId = "text_term_id".toShortString(); +// ShortString public nonCommTextTermId = "non_comm_text_term_id".toShortString(); +// ShortString public commTextTermId = "comm_text_term_id".toShortString(); + +// uint256 public rootIpaId; +// address public ipaOwner = address(0x13333); + +// uint256 public commRootLicenseId; +// uint256 public nonCommRootLicenseId; + +// ShortString[] public nonCommTermIds; +// bytes[] public nonCommTermData; +// ShortString[] public commTermIds; +// bytes[] public commTermData; + +// modifier withNonCommFrameworkShareAlike() { +// vm.prank(ipOrg.owner()); +// spg.configureIpOrgLicensing( +// address(ipOrg), +// getNonCommFramework(true) +// ); +// _; +// } + +// modifier withNonCommFrameworkNoShareAlike() { +// vm.prank(ipOrg.owner()); +// spg.configureIpOrgLicensing( +// address(ipOrg), +// getNonCommFramework(false) +// ); +// _; +// } + +// modifier withNonCommFrameworkShareAlikeAnd( +// ShortString termId, +// bytes memory data +// ) { +// vm.prank(ipOrg.owner()); +// spg.configureIpOrgLicensing( +// address(ipOrg), +// getNonCommFrameworkAndPush(true, termId, data) +// ); +// _; +// } + +// modifier withNonCommFrameworkNoShareAlikeAnd( +// ShortString termId, +// bytes memory data +// ) { +// vm.prank(ipOrg.owner()); +// spg.configureIpOrgLicensing( +// address(ipOrg), +// getNonCommFrameworkAndPush(false, termId, data) +// ); +// _; +// } + +// modifier withCommFrameworkShareAlike() { +// vm.prank(ipOrg.owner()); +// spg.configureIpOrgLicensing( +// address(ipOrg), +// getCommFramework(true, true) +// ); +// _; +// } + +// modifier withCommFrameworkNoShareAlike() { +// vm.prank(ipOrg.owner()); +// spg.configureIpOrgLicensing( +// address(ipOrg), +// getCommFramework(false, false) +// ); +// _; +// } + +// modifier withCommFrameworkShareAlikeAnd( +// ShortString ncTermId, +// bytes memory ncData, +// ShortString cTermId, +// bytes memory cData +// ) { +// vm.prank(ipOrg.owner()); +// spg.configureIpOrgLicensing( +// address(ipOrg), +// getCommFrameworkAndPush(true, ncTermId, ncData, true, cTermId, cData) +// ); +// _; +// } + +// modifier withRootLicense(bool commercial) { +// vm.prank(ipOrg.owner()); +// uint256 lId = spg.createIpaBoundLicense( +// address(ipOrg), +// Licensing.LicenseCreation({ +// parentLicenseId: 0, +// isCommercial: commercial +// }), +// rootIpaId, +// new bytes[](0), +// new bytes[](0) +// ); +// if (commercial) { +// commRootLicenseId = lId; +// } else { +// nonCommRootLicenseId = lId; +// } +// _; +// } + +// function setUp() virtual override public { +// super.setUp(); +// _addShareAlike(Licensing.CommercialStatus.Both); +// _addTextTerms(); +// nonCommTermIds = [textTermId, nonCommTextTermId]; +// nonCommTermData = [bytes(""), bytes("")]; +// commTermIds = [commTextTermId]; +// commTermData = [bytes("")]; +// (uint256 rootIpaId, uint256 ignored) = spg.registerIPAsset( +// address(ipOrg), +// Registration.RegisterIPAssetParams({ +// owner: ipaOwner, +// name: "bob", +// ipAssetType: 2, +// hash: keccak256("test") +// }), +// new bytes[](0), +// new bytes[](0) +// ); +// } + +// function getEmptyFramework() public pure returns (Licensing.FrameworkConfig memory) { +// return +// Licensing.FrameworkConfig({ +// comTermsConfig: Licensing.TermsConfig({ +// termIds: new ShortString[](0), +// termData: new bytes[](0) +// }), +// nonComTermsConfig: Licensing.TermsConfig({ +// termIds: new ShortString[](0), +// termData: new bytes[](0) +// }) +// }); +// } + +// function getCommFramework(bool comShareAlike, bool nonComShareAlike) public returns (Licensing.FrameworkConfig memory) { +// commTermIds.push(TermIds.NFT_SHARE_ALIKE.toShortString()); +// commTermData.push(abi.encode(comShareAlike)); +// nonCommTermIds.push(TermIds.NFT_SHARE_ALIKE.toShortString()); +// nonCommTermData.push(abi.encode(nonComShareAlike)); +// return +// Licensing.FrameworkConfig({ +// comTermsConfig: Licensing.TermsConfig({ +// termIds: commTermIds, +// termData: commTermData +// }), +// nonComTermsConfig: Licensing.TermsConfig({ +// termIds: nonCommTermIds, +// termData: nonCommTermData +// }) +// }); +// } + +// function getNonCommFramework(bool shareAlike) public returns (Licensing.FrameworkConfig memory) { +// nonCommTermIds.push(TermIds.NFT_SHARE_ALIKE.toShortString()); +// nonCommTermData.push(abi.encode(shareAlike)); +// return +// Licensing.FrameworkConfig({ +// comTermsConfig: Licensing.TermsConfig({ +// termIds: new ShortString[](0), +// termData: new bytes[](0) +// }), +// nonComTermsConfig: Licensing.TermsConfig({ +// termIds: nonCommTermIds, +// termData: nonCommTermData +// }) +// }); +// } + +// function getNonCommFrameworkAndPush( +// bool shareAlike, +// ShortString termId, +// bytes memory data +// ) public returns (Licensing.FrameworkConfig memory) { +// nonCommTermIds.push(termId); +// nonCommTermData.push(data); +// return getNonCommFramework(shareAlike); +// } + +// function getCommFrameworkAndPush( +// bool cShareAlike, +// ShortString ncTermId, +// bytes memory ncData, +// bool ncShareAlike, +// ShortString cTermId, +// bytes memory cData +// ) public returns (Licensing.FrameworkConfig memory) { +// nonCommTermIds.push(ncTermId); +// nonCommTermData.push(ncData); + +// commTermIds.push(cTermId); +// commTermData.push(cData); + +// return getCommFramework(cShareAlike, ncShareAlike); +// } + + +// function assertTerms(Licensing.License memory license) public { +// (ShortString[] memory ipOrgTermsId, bytes[] memory ipOrgTermsData) = licensingModule.getIpOrgTerms( +// license.isCommercial, address(ipOrg) +// ); +// assertEq(license.termIds.length, ipOrgTermsId.length); +// assertEq(license.termsData.length, ipOrgTermsData.length); +// for (uint256 i = 0; i < license.termIds.length; i++) { +// assertTrue(ShortStringOps._equal(license.termIds[i], ipOrgTermsId[i])); +// assertTrue(keccak256(license.termsData[i]) == keccak256(ipOrgTermsData[i])); +// Licensing.LicensingTerm memory term = licensingModule.getTerm(ipOrgTermsId[i]); +// if (license.isCommercial) { +// assertTrue( +// term.comStatus == Licensing.CommercialStatus.Commercial || +// term.comStatus == Licensing.CommercialStatus.Both +// ); +// } else { +// assertTrue( +// term.comStatus == Licensing.CommercialStatus.NonCommercial || +// term.comStatus == Licensing.CommercialStatus.Both +// ); +// } +// } +// } + +// function assertTermsSetInIpOrg(bool commercial) public { +// (ShortString[] memory ipOrgTermsId, bytes[] memory ipOrgTermsData) = licensingModule.getIpOrgTerms( +// commercial, address(ipOrg) +// ); +// ShortString[] memory termIds = commercial ? commTermIds : nonCommTermIds; +// bytes[] memory termData = commercial ? commTermData : nonCommTermData; +// assertEq(termIds.length, ipOrgTermsId.length); +// assertEq(termData.length, ipOrgTermsData.length); +// for (uint256 i = 0; i < termIds.length; i++) { +// assertTrue(ShortStringOps._equal(termIds[i], ipOrgTermsId[i])); +// assertTrue(keccak256(termData[i]) == keccak256(ipOrgTermsData[i])); +// } +// } + +// function _addShareAlike(Licensing.CommercialStatus comStatus) private { +// licensingModule.addCategory(TermCategories.SHARE_ALIKE); +// Licensing.LicensingTerm memory term = ProtocolTermsHelper._getNftShareAlikeTerm(comStatus); +// licensingModule.addTerm( +// TermCategories.SHARE_ALIKE, +// TermIds.NFT_SHARE_ALIKE, +// term +// ); +// } + +// function _addTextTerms() private { +// licensingModule.addCategory("test_category"); +// licensingModule.addTerm( +// "test_category", +// "text_term_id", +// Licensing.LicensingTerm({ +// comStatus: Licensing.CommercialStatus.Both, +// url: "https://text_term_id.com", +// hash: "qwertyu", +// algorithm: "sha256", +// hook: IHook(address(0)) +// } +// )); +// licensingModule.addTerm( +// "test_category", +// "non_comm_text_term_id", +// Licensing.LicensingTerm({ +// comStatus: Licensing.CommercialStatus.NonCommercial, +// url: "https://non_comm_text_term_id.com", +// hash: "qwertyu", +// algorithm: "sha256", +// hook: IHook(address(0)) +// } +// )); +// licensingModule.addTerm( +// "test_category", +// "comm_text_term_id", +// Licensing.LicensingTerm({ +// comStatus: Licensing.CommercialStatus.Commercial, +// url: "https://comm_text_term_id.com", +// hash: "qwertyu", +// algorithm: "sha256", +// hook: IHook(address(0)) +// } +// )); +// } + +// } diff --git a/test/foundry/modules/licensing/LicenseCreatorModule.Terms.sol b/test/foundry/modules/licensing/LicenseCreatorModule.Terms.sol new file mode 100644 index 00000000..aaaf3dbb --- /dev/null +++ b/test/foundry/modules/licensing/LicenseCreatorModule.Terms.sol @@ -0,0 +1,110 @@ +// // SPDX-License-Identifier: BUSDL-1.1 +// pragma solidity ^0.8.13; + +// import "forge-std/Test.sol"; +// import "test/foundry/utils/BaseTest.sol"; +// import "contracts/modules/relationships/RelationshipModule.sol"; +// import "contracts/lib/modules/LibRelationship.sol"; +// import { AccessControl } from "contracts/lib/AccessControl.sol"; +// import { Licensing } from "contracts/lib/modules/Licensing.sol"; +// import { TermIds, TermCategories } from "contracts/lib/modules/ProtocolLicensingTerms.sol"; +// import { IHook } from "contracts/interfaces/hooks/base/IHook.sol"; +// import { IPAsset } from "contracts/lib/IPAsset.sol"; +// import { BaseLicensingTest } from "./BaseLicensingTest.sol"; +// import { ProtocolTermsHelper } from "contracts/modules/licensing/ProtocolTermsHelper.sol"; + +// contract LicensingCreatorModuleTermsTest is BaseLicensingTest { +// using ShortStrings for *; + +// address licensee = address(0x22222); +// address ipaOwner2 = address(0x33333); + +// function setUp() public override { +// super.setUp(); +// } + +// function test_LicensingModule_terms_shareAlikeOn() +// public +// withNonCommFrameworkShareAlike +// withRootLicense(false) +// { +// // TODO: This should be just creating an derivative IPA +// (uint256 ipaId2, uint256 ignored) = spg.registerIPAsset( +// address(ipOrg), +// Registration.RegisterIPAssetParams({ +// owner: ipaOwner, +// name: "bob", +// ipAssetType: 2, +// hash: keccak256("test") +// }), +// new bytes[](0), +// new bytes[](0) +// ); +// vm.prank(ipaOwner2); +// uint256 lId = spg.createIpaBoundLicense( +// address(ipOrg), +// Licensing.LicenseCreation({ +// parentLicenseId: nonCommRootLicenseId, +// isCommercial: false +// }), +// ipaId2, +// new bytes[](0), +// new bytes[](0) +// ); +// Licensing.License memory license = licenseRegistry.getLicense(lId); +// assertTerms(license); +// vm.expectRevert(); +// licenseRegistry.ownerOf(lId); +// assertEq(license.ipaId, ipaId2); +// assertEq(license.parentLicenseId, nonCommRootLicenseId); +// } + +// function test_LicensingModule_terms_revert_shareAlikeOff() +// public +// withNonCommFrameworkNoShareAlike +// withRootLicense(false) { +// // TODO: this should be create derivative IPA +// // expect revert if share alike is off +// vm.startPrank(ipaOwner2); +// vm.expectRevert(Errors.LicensingModule_ShareAlikeDisabled.selector); +// spg.createIpaBoundLicense( +// address(ipOrg), +// Licensing.LicenseCreation({ +// parentLicenseId: nonCommRootLicenseId, +// isCommercial: false +// }), +// 1, +// new bytes[](0), +// new bytes[](0) +// ); +// vm.stopPrank(); +// // have licensor create a license +// console.log("nonCommRootLicenseId", nonCommRootLicenseId); + +// vm.prank(ipaOwner); +// uint256 lId = spg.createLicenseNft( +// address(ipOrg), +// Licensing.LicenseCreation({ +// parentLicenseId: nonCommRootLicenseId, +// isCommercial: false +// }), +// ipaOwner, +// new bytes[](0), +// new bytes[](0) +// ); +// Licensing.License memory license = licenseRegistry.getLicense(lId); +// console.log("lId", lId); +// console.log("licenseeType", uint8(license.licenseeType)); +// // Non Commercial +// assertEq(licenseRegistry.ownerOf(lId), ipaOwner); +// assertEq(licenseRegistry.getLicensee(lId), ipaOwner); +// // transfer license to other guy +// vm.prank(ipaOwner); +// licenseRegistry.transferFrom(ipaOwner, ipaOwner2, lId); +// // have other guy activate license + +// // have other guy mint ipa, burn LNFT and tie it to IPA + +// } + +// } diff --git a/test/foundry/modules/licensing/LicensingCreatorModule.Config.t.sol b/test/foundry/modules/licensing/LicensingCreatorModule.Config.t.sol new file mode 100644 index 00000000..88b7b027 --- /dev/null +++ b/test/foundry/modules/licensing/LicensingCreatorModule.Config.t.sol @@ -0,0 +1,108 @@ +// // SPDX-License-Identifier: BUSDL-1.1 +// pragma solidity ^0.8.13; + +// import "forge-std/Test.sol"; +// import "contracts/modules/relationships/RelationshipModule.sol"; +// import "contracts/lib/modules/LibRelationship.sol"; +// import { AccessControl } from "contracts/lib/AccessControl.sol"; +// import { Licensing } from "contracts/lib/modules/Licensing.sol"; +// import { TermCategories, TermIds } from "contracts/lib/modules/ProtocolLicensingTerms.sol"; +// import { IHook } from "contracts/interfaces/hooks/base/IHook.sol"; +// import { BaseLicensingTest } from "./BaseLicensingTest.sol"; +// import { ShortStringOps } from "contracts/utils/ShortStringOps.sol"; +// import { ShortString } from "@openzeppelin/contracts/utils/ShortStrings.sol"; + +// contract LicensingCreatorModuleConfigTest is BaseLicensingTest { +// function setUp() public override { +// super.setUp(); +// } + +// function test_LicensingModule_configIpOrg_revertIfNotIpOrgOwner() public { +// vm.expectRevert(Errors.LicensingModule_CallerNotIpOrgOwner.selector); +// spg.configureIpOrgLicensing( +// address(ipOrg), +// getNonCommFramework(true) +// ); +// } + +// function test_LicensingModule_configIpOrg_ipOrgWithoutCommercialTermsIsNonCommercial() +// public +// withNonCommFrameworkShareAlike +// { +// assertFalse(licensingModule.ipOrgAllowsCommercial(address(ipOrg)), "should be non commercial"); +// ( +// ShortString[] memory comTermIds, +// bytes[] memory comTermData +// ) = licensingModule.getIpOrgTerms(true, address(ipOrg)); +// assertTrue(comTermIds.length == 0, "commercial terms should be empty"); +// assertTermsSetInIpOrg(false); // non commercial terms +// } + +// function test_LicensingModule_configIpOrg_ipOrgWithCommercialTermsIsCommercial() +// public +// withCommFrameworkShareAlike +// { +// assertTrue(licensingModule.ipOrgAllowsCommercial(address(ipOrg)), "not commercial"); +// assertTermsSetInIpOrg(true); // commercial terms +// assertTermsSetInIpOrg(false); // non commercial terms too +// } + +// function test_LicensingModule_configIpOrg_revert_noEmptyNonCommercialTerms() +// public +// { +// vm.startPrank(ipOrg.owner()); +// vm.expectRevert( +// Errors.LicensingModule_NonCommercialTermsRequired.selector +// ); +// spg.configureIpOrgLicensing(address(ipOrg), getEmptyFramework()); +// vm.stopPrank(); +// } + +// function test_LicensingModule_configIpOrg_revert_IfWrongTermCommercialStatus() +// public +// { +// vm.startPrank(ipOrg.owner()); +// vm.expectRevert( +// Errors.LicensingModule_InvalidTermCommercialStatus.selector +// ); +// spg.configureIpOrgLicensing( +// address(ipOrg), +// getNonCommFrameworkAndPush( +// false, +// commTextTermId, +// bytes("") +// ) +// ); +// vm.stopPrank(); +// } + +// function test_LicensingModule_configIpOrg_revertIfIpOrgAlreadyConfigured() +// public +// { +// // Todo +// } + +// function test_LicensingModule_configIpOrg_setsHooksForCreatingCommercialLicenses() +// public +// { +// // Todo +// } + +// function test_LicensingModule_configIpOrg_setsHooksForCreatingNonCommercialLicenses() +// public +// { +// // Todo +// } + +// function test_LicensingModule_configIpOrg_commercialLicenseActivationHooksCanBeSet() +// public +// { +// // TODO +// } + +// function test_LicensingModule_configIpOrg_nonCommercialLicenseActivationHooksCanBeSet() +// public +// { +// // TODO +// } +// } diff --git a/test/foundry/modules/licensing/LicensingCreatorModule.Licensing.sol b/test/foundry/modules/licensing/LicensingCreatorModule.Licensing.sol new file mode 100644 index 00000000..a75ba5b5 --- /dev/null +++ b/test/foundry/modules/licensing/LicensingCreatorModule.Licensing.sol @@ -0,0 +1,91 @@ +// // SPDX-License-Identifier: BUSDL-1.1 +// pragma solidity ^0.8.13; + +// import "forge-std/Test.sol"; +// import "test/foundry/utils/BaseTest.sol"; +// import "contracts/modules/relationships/RelationshipModule.sol"; +// import "contracts/lib/modules/LibRelationship.sol"; +// import { AccessControl } from "contracts/lib/AccessControl.sol"; +// import { Licensing } from "contracts/lib/modules/Licensing.sol"; +// import { TermCategories, TermIds } from "contracts/lib/modules/ProtocolLicensingTerms.sol"; +// import { IHook } from "contracts/interfaces/hooks/base/IHook.sol"; +// import { IPAsset } from "contracts/lib/IPAsset.sol"; +// import { BaseLicensingTest } from "./BaseLicensingTest.sol"; + +// contract LicensingCreatorLicensingTest is BaseLicensingTest { +// using ShortStrings for *; + +// address lnftOwner = address(0x13334); + +// function setUp() public override { +// super.setUp(); + +// } + +// function test_LicensingModule_configIpOrg_commercialLicenseActivationHooksCanBeSet() +// public +// { +// // TODO +// } + +// function test_LicensingModule_configIpOrg_nonCommercialLicenseActivationHooksCanBeSet() +// public +// { +// // TODO +// } + +// function test_LicensingModule_licensing_createNonCommercialRootLicense() +// public +// withNonCommFrameworkShareAlike +// { +// // TODO: this should be create root IPA +// vm.prank(ipOrg.owner()); +// uint256 lId = spg.createIpaBoundLicense( +// address(ipOrg), +// Licensing.LicenseCreation({ +// parentLicenseId: 0, +// isCommercial: false +// }), +// rootIpaId, +// new bytes[](0), +// new bytes[](0) +// ); +// // Non Commercial +// Licensing.License memory license = licenseRegistry.getLicense(lId); +// assertFalse(license.isCommercial); +// assertEq(license.revoker, ipOrg.owner()); +// assertEq(license.licensor, ipaOwner, "licensor"); + +// assertTerms(license); +// assertEq(license.ipaId, rootIpaId); +// } + +// function test_LicensingModule_licensing_createsCommercialSubLicense_noDestIpa() +// public +// withCommFrameworkShareAlike +// withRootLicense(false) +// withRootLicense(true) +// { +// vm.prank(lnftOwner); +// uint256 lId = spg.createLicenseNft( +// address(ipOrg), +// Licensing.LicenseCreation({ +// parentLicenseId: commRootLicenseId, +// isCommercial: true +// }), +// lnftOwner, +// new bytes[](0), +// new bytes[](0) +// ); +// // Non Commercial +// Licensing.License memory license = licenseRegistry.getLicense(lId); +// assertTrue(license.isCommercial); +// assertEq(licenseRegistry.ownerOf(lId), lnftOwner); +// assertEq(licenseRegistry.getLicensee(lId), lnftOwner); +// assertEq(license.revoker, ipOrg.owner()); +// assertEq(license.licensor, ipaOwner, "licensor"); +// assertEq(license.ipaId, 0); +// assertEq(license.parentLicenseId, commRootLicenseId); +// } + +// } diff --git a/test/foundry/utils/BaseTest.sol b/test/foundry/utils/BaseTest.sol index 6bbbf9b4..2a01c8b2 100644 --- a/test/foundry/utils/BaseTest.sol +++ b/test/foundry/utils/BaseTest.sol @@ -10,50 +10,39 @@ import "contracts/StoryProtocol.sol"; import "contracts/ip-org/IPOrgController.sol"; import "contracts/ip-org/IPOrg.sol"; import "contracts/lib/IPOrgParams.sol"; - import "contracts/IPAssetRegistry.sol"; import "contracts/access-control/AccessControlSingleton.sol"; -import "contracts/errors/General.sol"; -import "contracts/modules/relationships/RelationshipModule.sol"; import "contracts/IPAssetRegistry.sol"; import "contracts/interfaces/modules/collect/ICollectModule.sol"; - +import "contracts/modules/relationships/RelationshipModule.sol"; +import "contracts/modules/licensing/LicenseRegistry.sol"; +import "contracts/modules/licensing/LicenseCreatorModule.sol"; +import { ShortString, ShortStrings } from "@openzeppelin/contracts/utils/ShortStrings.sol"; +import { ShortStringOps } from "contracts/utils/ShortStringOps.sol"; import { AccessControl } from "contracts/lib/AccessControl.sol"; import { ModuleRegistryKeys } from "contracts/lib/modules/ModuleRegistryKeys.sol"; +import { RegistrationModule } from "contracts/modules/registration/RegistrationModule.sol"; -// On active refactor -// import "contracts/modules/licensing/LicensingModule.sol"; -// import "contracts/interfaces/modules/licensing/terms/ITermsProcessor.sol"; -// import "contracts/modules/licensing/LicenseRegistry.sol"; -// import '../mocks/MockTermsProcessor.sol'; -// import { Licensing } from "contracts/lib/modules/Licensing.sol"; - -// TODO: Commented out contracts in active refactor. -// Run tests from make lint, which will not run collect and license contract BaseTest is BaseTestUtils, ProxyHelper, AccessControlHelper { + using ShortStrings for *; IPOrg public ipOrg; IPOrgController public ipOrgController; ModuleRegistry public moduleRegistry; - // LicensingModule public licensingModule; - // ILicenseRegistry public licenseRegistry; - // MockTermsProcessor public nonCommercialTermsProcessor; - // MockTermsProcessor public commercialTermsProcessor; ICollectModule public collectModule; RelationshipModule public relationshipModule; IPAssetRegistry public registry; StoryProtocol public spg; + LicenseCreatorModule public licensingModule; + LicenseRegistry public licenseRegistry; + RegistrationModule public registrationModule; address public defaultCollectNftImpl; address public collectModuleImpl; address constant upgrader = address(6969); address constant ipAssetOrgOwner = address(456); - address constant revoker = address(789); - // string constant NON_COMMERCIAL_LICENSE_URI = "https://noncommercial.license"; - // string constant COMMERCIAL_LICENSE_URI = "https://commercial.license"; - - constructor() {} + address constant relManager = address(9999); function setUp() virtual override(BaseTestUtils) public { super.setUp(); @@ -85,29 +74,39 @@ contract BaseTest is BaseTestUtils, ProxyHelper, AccessControlHelper { _grantRole(vm, AccessControl.MODULE_EXECUTOR_ROLE, address(spg)); _grantRole(vm, AccessControl.MODULE_REGISTRAR_ROLE, address(this)); + // Create Licensing contracts + licenseRegistry = new LicenseRegistry(address(registry), address(moduleRegistry)); + licensingModule = new LicenseCreatorModule( + BaseModule.ModuleConstruction({ + ipaRegistry: registry, + moduleRegistry: moduleRegistry, + licenseRegistry: licenseRegistry + }) + ); + moduleRegistry.registerProtocolModule(ModuleRegistryKeys.LICENSING_MODULE, licensingModule); + + // Create Registration Module + registrationModule = new RegistrationModule( + BaseModule.ModuleConstruction({ + ipaRegistry: registry, + moduleRegistry: moduleRegistry, + licenseRegistry: licenseRegistry + }), + address(accessControl) + ); + moduleRegistry.registerProtocolModule(ModuleRegistryKeys.REGISTRATION_MODULE, registrationModule); // Create Relationship Module relationshipModule = new RelationshipModule( BaseModule.ModuleConstruction({ ipaRegistry: registry, moduleRegistry: moduleRegistry, - licenseRegistry: address(123) + licenseRegistry: licenseRegistry }), address(accessControl) ); moduleRegistry.registerProtocolModule(ModuleRegistryKeys.RELATIONSHIP_MODULE, relationshipModule); - - // Create Licensing Module - // address licensingImplementation = address(new LicensingModule(address(ipAssetOrgFactory))); - // licensingModule = LicensingModule( - // _deployUUPSProxy( - // licensingImplementation, - // abi.encodeWithSelector( - // bytes4(keccak256(bytes("initialize(address,string)"))), - // address(accessControl), NON_COMMERCIAL_LICENSE_URI - // ) - // ) - // ); + defaultCollectNftImpl = _deployCollectNFTImpl(); collectModule = ICollectModule(_deployCollectModule(defaultCollectNftImpl)); @@ -127,41 +126,11 @@ contract BaseTest is BaseTestUtils, ProxyHelper, AccessControlHelper { ipAssetOrgParams.symbol )); - // licenseRegistry = ILicenseRegistry(ipOrg.getLicenseRegistry()); - - // Configure Licensing for IPOrg - // nonCommercialTermsProcessor = new MockTermsProcessor(); - // commercialTermsProcessor = new MockTermsProcessor(); - // licensingModule.configureIpOrgLicensing(address(ipOrg), _getLicensingConfig()); vm.stopPrank(); } - // function _getLicensingConfig() view internal returns (Licensing.IPOrgConfig memory) { - // return Licensing.IPOrgConfig({ - // nonCommercialConfig: Licensing.IpAssetConfig({ - // canSublicense: true, - // ipAssetOrgRootLicenseId: 0 - // }), - // nonCommercialTerms: Licensing.TermsProcessorConfig({ - // processor: address(0), //nonCommercialTermsProcessor, - // data: abi.encode("nonCommercial") - // }), - // commercialConfig: Licensing.IpAssetConfig({ - // canSublicense: false, - // ipAssetOrgRootLicenseId: 0 - // }), - // commercialTerms: Licensing.TermsProcessorConfig({ - // processor: address(0),// commercialTermsProcessor, - // data: abi.encode("commercial") - // }), - // rootIpAssetHasCommercialRights: false, - // revoker: revoker, - // commercialLicenseUri: "uriuri" - // }); - // } - function _deployCollectNFTImpl() internal virtual returns (address) { return address(new MockCollectNFT()); }