From bad91824f852586e016fd7b7799f351010c317c4 Mon Sep 17 00:00:00 2001 From: Raul Date: Sun, 12 Nov 2023 03:39:15 -0300 Subject: [PATCH] refactored configuration to be purely terms based, testing creating licenses --- contracts/IPAssetRegistry.sol | 8 +- contracts/StoryProtocol.sol | 30 +++ contracts/hooks/licensing/TermsHook.sol | 4 +- contracts/lib/Errors.sol | 7 +- contracts/lib/modules/Licensing.sol | 34 +-- .../lib/modules/ProtocolRelationships.sol | 25 +-- .../licensing/LicenseCreatorModule.sol | 201 ++++++++++++------ .../modules/licensing/LicenseRegistry.sol | 27 +-- .../modules/licensing/TermsRepository.sol | 33 ++- .../relationships/RelationshipModule.sol | 4 + .../licensing/LicenseCreatorModule.Terms.sol | 53 +++++ .../LicensingCreatorModule.Config.t.sol | 163 ++++++++++++++ .../LicensingCreatorModule.Licensing.sol | 121 +++++++++++ 13 files changed, 577 insertions(+), 133 deletions(-) create mode 100644 test/foundry/modules/licensing/LicenseCreatorModule.Terms.sol create mode 100644 test/foundry/modules/licensing/LicensingCreatorModule.Config.t.sol create mode 100644 test/foundry/modules/licensing/LicensingCreatorModule.Licensing.sol diff --git a/contracts/IPAssetRegistry.sol b/contracts/IPAssetRegistry.sol index c36fe3c1..42bb0411 100644 --- a/contracts/IPAssetRegistry.sol +++ b/contracts/IPAssetRegistry.sol @@ -38,13 +38,13 @@ contract IPAssetRegistry is IIPAssetRegistry { // TODO(ramarti): Add registration authorization via registration module. // TODO(ramarti): Include module parameters and interfacing to registration. function register(IPAsset.RegisterIpAssetParams calldata params_) public returns (uint256) { - uint256 ipAssetId = numIPAssets++; + uint256 ipAssetId = ++numIPAssets; uint64 registrationDate = uint64(block.timestamp); ipAssets[ipAssetId] = IPAsset.IPA({ name: params_.name, ipAssetType: params_.ipAssetType, - status: 0, // TODO(ramarti): Define status types. + status: 1, // TODO(ramarti): Define status types. owner: params_.owner, initialRegistrant: params_.owner, ipOrg: params_.ipOrg, @@ -105,4 +105,8 @@ contract IPAssetRegistry is IIPAssetRegistry { return ipAssets[ipAssetId_].ipOrg; } + function getIpAsset(uint256 ipAssetId_) external view returns (IPAsset.IPA memory) { + return ipAssets[ipAssetId_]; + } + } diff --git a/contracts/StoryProtocol.sol b/contracts/StoryProtocol.sol index 456196b9..e7f28ea7 100644 --- a/contracts/StoryProtocol.sol +++ b/contracts/StoryProtocol.sol @@ -97,4 +97,34 @@ contract StoryProtocol { ); } + // Create a sublicense for an existing IPA (or license?) + // This can be: + // To make merch (tradeable, no IPA as result) + // To adapt/remix/extend (tradeable, IPA will be result) + // This method will set Activation terms (approvals, kyc, etc) + // How do we allow marketplaces for permissionless license? + // How can we differentiate a request for license, from a license? + // Something has to check that you have licenses for all the related characters to give you commercial rights (terms to check for a set of relationships) + // IpOrgs can set hooks on creation (token gating, etc), for commercial or non commercial + function createLicense( + address ipOrg_, + Licensing.LicenseCreationParams calldata params_, + bytes[] calldata preHooksData_, + bytes[] calldata postHooksData_ + ) external returns (uint256) { + return abi.decode( + MODULE_REGISTRY.execute( + IIPOrg(ipOrg_), + msg.sender, + ModuleRegistryKeys.LICENSING_MODULE, + abi.encode(params_), + preHooksData_, + postHooksData_ + ), + (uint256) + ); + } + + + } \ No newline at end of file diff --git a/contracts/hooks/licensing/TermsHook.sol b/contracts/hooks/licensing/TermsHook.sol index 723ea2e2..9ca6b82a 100644 --- a/contracts/hooks/licensing/TermsHook.sol +++ b/contracts/hooks/licensing/TermsHook.sol @@ -20,7 +20,7 @@ contract TermsHook is SyncBaseHook { // abi.decode still cannot be try/catched, so we cannot return meaningful errors. // If config is correct, this will not revert // See https://github.com/ethereum/solidity/issues/13869 - if (ShortStringOps._equal(TermIds.SHARE_ALIKE, config.termsId)) { + if (ShortStringOps._equal(TermIds.SHARE_ALIKE, config.termId)) { abi.decode(config.data, (TermsHooks.ShareAlike)); } revert Errors.TermsHook_UnsupportedTermsId(); @@ -31,7 +31,7 @@ contract TermsHook is SyncBaseHook { bytes memory hookParams_ ) internal virtual override returns (bytes memory) { Licensing.TermsConfig memory config = abi.decode(hookConfig_, (Licensing.TermsConfig)); - if (ShortStringOps._equal(TermIds.SHARE_ALIKE, config.termsId)) { + if (ShortStringOps._equal(TermIds.SHARE_ALIKE, config.termId)) { abi.decode(config.data, (TermsHooks.ShareAlike)); } return ""; diff --git a/contracts/lib/Errors.sol b/contracts/lib/Errors.sol index 6e4a095d..4a9c9bad 100644 --- a/contracts/lib/Errors.sol +++ b/contracts/lib/Errors.sol @@ -237,9 +237,14 @@ library Errors { error LicensingModule_NonExistentIPOrg(); error LicensingModule_CallerNotIpOrgOwner(); error LicensingModule_InvalidConfigType(); - error LicensingModule_CommercialTermNotAllowed(); + error LicensingModule_InvalidTermCommercialStatus(); error LicensingModule_IpOrgFrameworkAlreadySet(); error LicensingModule_DuplicateTermId(); + error LicensingModule_InvalidIntent(); + error LicensingModule_IPANotActive(); + error LicensingModule_CommercialLicenseNotAllowed(); + error LicensingModule_NonCommercialTermsRequired(); + error LicensingModule_IpOrgNotConfigured(); //////////////////////////////////////////////////////////////////////////// // RelationshipModule // diff --git a/contracts/lib/modules/Licensing.sol b/contracts/lib/modules/Licensing.sol index 21d782a9..c5650449 100644 --- a/contracts/lib/modules/Licensing.sol +++ b/contracts/lib/modules/Licensing.sol @@ -9,9 +9,24 @@ library Licensing { struct License { bool isCommercial; - bytes[] terms; - address[] activationHooks; - bytes[] activationHookParams; + address licensor; + address revoker; + TermsConfig[] termsConfig; + // + + } + + struct LicenseCreationParams { + uint256 parentLicenseId; + bool isCommercial; + uint256 ipaId; + // Intent intent; + } + + enum Intent { + RootIpa, // No parent license + DerivativeIpa,// Parent license id needed, will become untradeable after completion + OffchainDerivative // Parent license id needed, need to log back } enum CommercialStatus { @@ -28,21 +43,16 @@ library Licensing { } struct TermsConfig { - ShortString termsId; + ShortString termId; bytes data; } struct FrameworkConfig { - bool isCommercialAllowed; - TermsConfig[] termsConfig; + TermsConfig[] comTermsConfig; + TermsConfig[] nonComTermsConfig; } - // Available categories -> IPORg wide - // Excluded categories (IPORg wide? IPA wide?) - // Pure text terms - // Share alike == sublicensing on/off - // - // Attribution should point to a relationship Type + bytes32 constant LICENSING_FRAMEWORK_CONFIG = keccak256("LICENSING_FRAMEWORK_CONFIG"); } diff --git a/contracts/lib/modules/ProtocolRelationships.sol b/contracts/lib/modules/ProtocolRelationships.sol index f48b5763..9daabc58 100644 --- a/contracts/lib/modules/ProtocolRelationships.sol +++ b/contracts/lib/modules/ProtocolRelationships.sol @@ -5,30 +5,13 @@ import { LibRelationship } from "contracts/lib/modules/LibRelationship.sol"; library ProtocolRelationships { - string constant public IPORG_TERMS_REL_TYPE = "IPORG_TERMS"; - - function _getIpOrgTermsAddRelParams() - internal pure - returns (LibRelationship.AddRelationshipTypeParams memory) { - return LibRelationship.AddRelationshipTypeParams({ - relType: IPORG_TERMS_REL_TYPE, - ipOrg: LibRelationship.PROTOCOL_LEVEL_RELATIONSHIP, - allowedElements: LibRelationship.RelatedElements({ - src: LibRelationship.Relatables.ADDRESS, - dst: LibRelationship.Relatables.LICENSE - }), - allowedSrcs: new uint8[](0), - allowedDsts: new uint8[](0) - }); - } - - string constant public IPA_LICENSE_REL_TYPE = "IPA_LICENSE"; + string constant public IPA_LICENSE = "IPA_LICENSE"; function _getIpLicenseAddRelPArams() internal pure returns (LibRelationship.AddRelationshipTypeParams memory) { return LibRelationship.AddRelationshipTypeParams({ - relType: IPA_LICENSE_REL_TYPE, + relType: IPA_LICENSE, ipOrg: LibRelationship.PROTOCOL_LEVEL_RELATIONSHIP, allowedElements: LibRelationship.RelatedElements({ src: LibRelationship.Relatables.IPA, @@ -40,13 +23,13 @@ library ProtocolRelationships { } - string constant public SUBLICENSE_REL_TYPE = "SUBLICENSE"; + string constant public SUBLICENSE_OF = "SUBLICENSE_OF"; function _getSublicenseAddRelParams() internal pure returns (LibRelationship.AddRelationshipTypeParams memory) { return LibRelationship.AddRelationshipTypeParams({ - relType: SUBLICENSE_REL_TYPE, + relType: SUBLICENSE_OF, ipOrg: LibRelationship.PROTOCOL_LEVEL_RELATIONSHIP, allowedElements: LibRelationship.RelatedElements({ src: LibRelationship.Relatables.LICENSE, diff --git a/contracts/modules/licensing/LicenseCreatorModule.sol b/contracts/modules/licensing/LicenseCreatorModule.sol index 4cf6ab90..2ef11fed 100644 --- a/contracts/modules/licensing/LicenseCreatorModule.sol +++ b/contracts/modules/licensing/LicenseCreatorModule.sol @@ -13,6 +13,7 @@ import { TermsRepository } from "./TermsRepository.sol"; import { ProtocolTermsHelper } from "./ProtocolTermsHelper.sol"; import { ShortString, ShortStrings } from "@openzeppelin/contracts/utils/ShortStrings.sol"; import { EnumerableSet } from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; +import { IPAsset } from "contracts/lib/IPAsset.sol"; import "forge-std/console.sol"; @@ -20,12 +21,13 @@ contract LicenseCreatorModule is BaseModule, TermsRepository, ProtocolTermsHelpe using ShortStrings for *; using EnumerableSet for EnumerableSet.Bytes32Set; - event LicensingFrameworkSet(address ipOrg_, bytes32[] termIds, bytes[] termData); + event IpOrgTermsSet(address indexed ipOrg, bool commercial, bytes32[] termIds, bytes[] termData); RelationshipModule public immutable RELATIONSHIP_MODULE; - mapping(address => bool) private _ipOrgAllowsCommercial; - mapping(address => EnumerableSet.Bytes32Set) private _ipOrgTermIds; - mapping(address => bytes[]) private _ipOrgTermData; + mapping(address => EnumerableSet.Bytes32Set) private _comIpOrgTermIds; + mapping(address => bytes[]) private _comIpOrgTermData; + mapping(address => EnumerableSet.Bytes32Set) private _nonComIpOrgTermIds; + mapping(address => bytes[]) private _nonComIpOrgTermData; constructor(ModuleConstruction memory params_) BaseModule(params_) { RELATIONSHIP_MODULE = RelationshipModule( @@ -37,15 +39,22 @@ contract LicenseCreatorModule is BaseModule, TermsRepository, ProtocolTermsHelpe ); } - function isIpOrgCommercial(address ipOrg_) public view returns (bool) { - return _ipOrgAllowsCommercial[ipOrg_]; + function ipOrgAllowsCommercial(address ipOrg_) public view returns (bool) { + return _comIpOrgTermIds[ipOrg_].length() > 0; } - function getIpOrgTerms(address ipOrg_) public view returns (bytes32[] memory, bytes[] memory) { - return ( - _ipOrgTermIds[ipOrg_].values(), - _ipOrgTermData[ipOrg_] - ); + function getIpOrgTerms(bool commercial, address ipOrg_) public view returns (bytes32[] memory, bytes[] memory) { + if (commercial) { + return ( + _comIpOrgTermIds[ipOrg_].values(), + _comIpOrgTermData[ipOrg_] + ); + } else { + return ( + _nonComIpOrgTermIds[ipOrg_].values(), + _nonComIpOrgTermData[ipOrg_] + ); + } } function _hookRegistryAdmin() @@ -63,7 +72,33 @@ contract LicenseCreatorModule is BaseModule, TermsRepository, ProtocolTermsHelpe address caller_, bytes calldata params_ ) virtual internal override { + Licensing.LicenseCreationParams memory lParams = abi.decode( + params_, + (Licensing.LicenseCreationParams) + ); + // If creating license for root IPA: + if (lParams.parentLicenseId == 0) { + if (ipOrg_.owner() != caller_) { + revert Errors.LicensingModule_CallerNotIpOrgOwner(); + } + } else { + // TODO: Check if license is active + + } + if (_comIpOrgTermIds[address(ipOrg_)].length() == 0) { + revert Errors.LicensingModule_IpOrgNotConfigured(); + } + if (!ipOrgAllowsCommercial(address(ipOrg_)) && lParams.isCommercial) { + revert Errors.LicensingModule_CommercialLicenseNotAllowed(); + } + if (lParams.ipaId != 0) { + // Todo: define status types + if (IPA_REGISTRY.ipAssetStatus(lParams.ipaId) != 1) { + revert Errors.LicensingModule_IPANotActive(); + } + //TODO Check if IPA has a license already + } } function _performAction( @@ -71,7 +106,44 @@ contract LicenseCreatorModule is BaseModule, TermsRepository, ProtocolTermsHelpe address caller_, bytes calldata params_ ) virtual internal override returns (bytes memory result) { - ( + Licensing.LicenseCreationParams memory lParams = abi.decode( + params_, + (Licensing.LicenseCreationParams) + ); + IPAsset.IPA memory ipa = IPA_REGISTRY.getIpAsset(lParams.ipaId); + + Licensing.License memory license = Licensing.License({ + isCommercial: lParams.isCommercial, + licensor: _getLicensor( + ipOrg_.owner(), + ipa.owner, + LICENSE_REGISTRY.getLicenseOwner(lParams.parentLicenseId) + ), + revoker: _getRevoker(address(ipOrg_)), + termsConfig: new Licensing.TermsConfig[](0) + }); + return abi.encode(1); + } + + function _getLicensor( + address ipOrgOwner, + address ipaOwner, + address parentLicenseOwner + ) private view returns (address) { + // TODO: Check for Licensor term in terms registry. + if (parentLicenseOwner != address(0)) { + return parentLicenseOwner; + } + return ipaOwner; + } + + function _getRevoker(address ipOrg) private view returns (address) { + // TODO: Check Revoker term in terms registry. + return address(0); + } + + /* + ( uint256 parentLicenseId, bytes[] memory additionalTerms, uint256 newIpaId @@ -85,7 +157,7 @@ contract LicenseCreatorModule is BaseModule, TermsRepository, ProtocolTermsHelpe uint256 sublicenseRelId = _createRelationship( LibRelationship.CreateRelationshipParams({ - relType: ProtocolRelationships.SUBLICENSE_REL_TYPE, + relType: ProtocolRelationships.SUBLICENSE_OF, srcAddress: address(LICENSE_REGISTRY), srcId: parentLicenseId, srcType: 0, @@ -98,7 +170,7 @@ contract LicenseCreatorModule is BaseModule, TermsRepository, ProtocolTermsHelpe if (newIpaId > 0) { uint256 ipaI = _createRelationship( LibRelationship.CreateRelationshipParams({ - relType: ProtocolRelationships.IPA_LICENSE_REL_TYPE, + relType: ProtocolRelationships.IPA_LICENSE, srcAddress: address(ipOrg_), srcId: parentLicenseId, srcType: 0, @@ -116,21 +188,7 @@ contract LicenseCreatorModule is BaseModule, TermsRepository, ProtocolTermsHelpe params_, (LibRelationship.CreateRelationshipParams) ); - - - } - - function isIpOrgLicense(uint256 licenseId_, address ipOrg_) public view returns (bool) { - return RELATIONSHIP_MODULE.relationshipExists( - LibRelationship.Relationship({ - relType: ProtocolRelationships.IPORG_TERMS_REL_TYPE, - srcAddress: ipOrg_, - srcId: 0, - dstAddress: address(LICENSE_REGISTRY), - dstId: licenseId_ - }) - ); - } + */ function _configure( @@ -161,47 +219,66 @@ contract LicenseCreatorModule is BaseModule, TermsRepository, ProtocolTermsHelpe if (ipOrg_.owner() != caller_) { revert Errors.LicensingModule_CallerNotIpOrgOwner(); } + console.log("Setting ipOrg framework"); Licensing.FrameworkConfig memory framework = abi.decode(params_, (Licensing.FrameworkConfig)); - _verifyFrameworkConfig(framework); - // Solidity doesn't allow to store arrays of structs (TermsConfig[]) from memory to storage, - // so we store them separately. - // On the bright side, we can use EnumerableSet to check for duplicates - EnumerableSet.Bytes32Set storage termIds = _ipOrgTermIds[address(ipOrg_)]; - if (termIds.length() > 0) { + console.log("got config"); + if (framework.nonComTermsConfig.length == 0) { + revert Errors.LicensingModule_NonCommercialTermsRequired(); + } + address ipOrgAddress = address(ipOrg_); + console.log("setting commercial"); + // Set non-commercial terms + bytes[] storage nonComTermData = _nonComIpOrgTermData[ipOrgAddress]; + EnumerableSet.Bytes32Set storage nonComTermIds = _nonComIpOrgTermIds[ipOrgAddress]; + if (nonComTermIds.length() > 0) { + // We assume ipOrg config is immutable, licensing changes in an ipOrg imply a fork revert Errors.LicensingModule_IpOrgFrameworkAlreadySet(); } + console.log("bla"); + _setTerms(false, framework.nonComTermsConfig, nonComTermIds, nonComTermData); + emit IpOrgTermsSet(ipOrgAddress, true, nonComTermIds.values(), nonComTermData); + console.log("setting non commercial"); + // Set commercial terms + bytes[] storage comTermData = _comIpOrgTermData[ipOrgAddress]; + EnumerableSet.Bytes32Set storage comTermIds = _comIpOrgTermIds[ipOrgAddress]; + _setTerms(true, framework.comTermsConfig, comTermIds, comTermData); + emit IpOrgTermsSet(ipOrgAddress, true, comTermIds.values(), comTermData); + + return ""; + } - _ipOrgAllowsCommercial[address(ipOrg_)] = framework.isCommercialAllowed; - uint256 termsLength = framework.termsConfig.length; - bytes[] storage termData = _ipOrgTermData[address(ipOrg_)]; + function _setTerms( + bool commercial, + Licensing.TermsConfig[] memory termsConfig_, + EnumerableSet.Bytes32Set storage termIds_, + bytes[] storage ipOrgTermData_ + ) internal { + // Solidity doesn't allow to store arrays of structs (TermsConfig[]) from memory to storage, + // so we store them separately. + // On the bright side, we can use EnumerableSet to check for termId duplicates + uint256 termsLength = termsConfig_.length; + console.log(termsLength); for (uint256 i = 0; i < termsLength; i++) { - Licensing.TermsConfig memory config = framework.termsConfig[i]; - bytes32 termsId = ShortString.unwrap(config.termsId); - if (termIds.contains(termsId)) { + Licensing.TermsConfig memory config = termsConfig_[i]; + bytes32 termId = ShortString.unwrap(config.termId); + console.log("termId"); + console.logBytes32(termId); + if (termIds_.contains(termId)) { revert Errors.LicensingModule_DuplicateTermId(); } - termIds.add(termsId); - termData.push(config.data); - } - emit LicensingFrameworkSet(address(ipOrg_), termIds.values(), termData); - return ""; - } - - function _verifyFrameworkConfig( - Licensing.FrameworkConfig memory framework_ - ) private view { - uint256 length = framework_.termsConfig.length; - for (uint256 i = 0; i < length; i++) { - Licensing.TermsConfig memory config = framework_.termsConfig[i]; - Licensing.LicensingTerm memory term = getTerm(config.termsId); - if ( - !framework_.isCommercialAllowed && - term.comStatus == Licensing.CommercialStatus.Commercial - ) { - revert Errors.LicensingModule_CommercialTermNotAllowed(); + Licensing.LicensingTerm memory term = getTerm(config.termId); + console.log(address(term.hook)); + if (commercial && term.comStatus == Licensing.CommercialStatus.NonCommercial) { + // We assume that CommercialStatus.Unset is not possible, since + // TermsRepository checks for that + revert Errors.LicensingModule_InvalidTermCommercialStatus(); + } + if (address(term.hook) != address(0)) { + // Reverts if decoding fails + term.hook.validateConfig(abi.encode(config)); } - // Reverts if decoding fails - term.hook.validateConfig(abi.encode(config)); + termIds_.add(termId); + ipOrgTermData_.push(config.data); } } diff --git a/contracts/modules/licensing/LicenseRegistry.sol b/contracts/modules/licensing/LicenseRegistry.sol index 6d2107b9..fa656adf 100644 --- a/contracts/modules/licensing/LicenseRegistry.sol +++ b/contracts/modules/licensing/LicenseRegistry.sol @@ -8,7 +8,6 @@ contract LicenseRegistry { mapping(uint256 => Licensing.License) private _licenses; uint256 private _licenseCount = 0; - mapping(address => uint256) private _ipOrgRootIpLicense; function addLicense(Licensing.License calldata license_) external returns (uint256) { _licenseCount++; @@ -20,33 +19,11 @@ contract LicenseRegistry { return _licenses[id_]; } - function getRootIpLicenseId(address ipOrg_) external view returns (uint256) { - return _ipOrgRootIpLicense[ipOrg_]; - } - - function getRootIpLicense(address ipOrg_) external view returns (Licensing.License memory) { - return _licenses[_ipOrgRootIpLicense[ipOrg_]]; - } - - function isLicenseActive(uint256 id_) public view returns (bool) { - return false; - } - - function canSublicense( - uint256 parentLicenseId_, - bytes memory termsData_ - ) external view returns (bool) { - return false; - } - - function createLicenseFrom( - uint256 parentLicenseId_ - ) external returns (uint256) { - return _licenseCount++; + function getLicenseOwner(uint256 id_) external view returns (address) { + return address(123); } function addTerms(uint256 licenseId, bytes[] memory terms) external { - _licenses[licenseId].terms = terms; } function makeTradeable(uint256 licenseId_) external { diff --git a/contracts/modules/licensing/TermsRepository.sol b/contracts/modules/licensing/TermsRepository.sol index e0d03d39..74cec4a2 100644 --- a/contracts/modules/licensing/TermsRepository.sol +++ b/contracts/modules/licensing/TermsRepository.sol @@ -6,6 +6,7 @@ import { EnumerableSet } from "@openzeppelin/contracts/utils/structs/EnumerableS 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"; contract TermsRepository is Multicall { using EnumerableSet for EnumerableSet.Bytes32Set; @@ -24,6 +25,21 @@ contract TermsRepository is Multicall { // 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 addTermCategory(string calldata category_) public { _termCategories.add(ShortString.unwrap(category_.toShortString())); emit TermCategoryAdded(category_); @@ -85,20 +101,21 @@ contract TermsRepository is Multicall { function getTerm( ShortString termId_ - ) public view returns (Licensing.LicensingTerm memory) { - if (_terms[termId_].comStatus == Licensing.CommercialStatus.Unset) { - revert Errors.TermsRegistry_UnsupportedTerm(); - } + ) 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 returns (Licensing.LicensingTerm memory) { + ) public view onlyValidTermString(termId_) returns (Licensing.LicensingTerm memory) { ShortString termId = termId_.toShortString(); - if (_terms[termId].comStatus == Licensing.CommercialStatus.Unset) { - revert Errors.TermsRegistry_UnsupportedTerm(); - } return _terms[termId]; } diff --git a/contracts/modules/relationships/RelationshipModule.sol b/contracts/modules/relationships/RelationshipModule.sol index aa349f85..89cc5889 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 diff --git a/test/foundry/modules/licensing/LicenseCreatorModule.Terms.sol b/test/foundry/modules/licensing/LicenseCreatorModule.Terms.sol new file mode 100644 index 00000000..7953c099 --- /dev/null +++ b/test/foundry/modules/licensing/LicenseCreatorModule.Terms.sol @@ -0,0 +1,53 @@ +// 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, TermCategories, TermIds } from "contracts/lib/modules/Licensing.sol"; +import { OffChain } from "contracts/lib/OffChain.sol"; +import { IHook } from "contracts/interfaces/hooks/base/IHook.sol"; + +/// Test for particular terms +contract LicensingCreatorModuleTermsTest is BaseTest { + using ShortStrings for *; + ShortString public textTermId = "text_term_id".toShortString(); + + function setUp() override public { + super.setUp(); + licensingModule.addTermCategory("test_category"); + licensingModule.addTerm( + "test_category", + "text_term_id", + Licensing.LicensingTerm({ + comStatus: Licensing.CommercialStatus.Both, + text: OffChain.Content({ + url: "https://example.com" + }), + hook: IHook(address(0)) + } + )); + } + + + function test_LicensingModule_configIpOrg_availableCategoriesCanBeSet() public { + + } + + function test_LicensingModule_licensing_revert_categoryExcluded() public { + // TODO that term + } + + + function test_LicensingModule_licensing_shouldAskForLicensor() public { + + } + + function test_LicensingModule_licensing_licensorPreviousLicenseHolder() public { + + } + +} + 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..e21fa830 --- /dev/null +++ b/test/foundry/modules/licensing/LicensingCreatorModule.Config.t.sol @@ -0,0 +1,163 @@ +// 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, TermCategories, TermIds } from "contracts/lib/modules/Licensing.sol"; +import { OffChain } from "contracts/lib/OffChain.sol"; +import { IHook } from "contracts/interfaces/hooks/base/IHook.sol"; + +contract LicensingCreatorModuleConfigTest is BaseTest { + using ShortStrings for *; + + ShortString public textTermId = "text_term_id".toShortString(); + + function setUp() override public { + super.setUp(); + licensingModule.addTermCategory("test_category"); + licensingModule.addTerm( + "test_category", + "text_term_id", + Licensing.LicensingTerm({ + comStatus: Licensing.CommercialStatus.Both, + text: OffChain.Content({ + url: "https://example.com" + }), + hook: IHook(address(0)) + } + )); + } + + function test_LicensingModule_configIpOrg_revertIfNotIpOrgOwner() public { + vm.expectRevert(Errors.LicensingModule_CallerNotIpOrgOwner.selector); + spg.configureIpOrgLicensing( + address(ipOrg), + Licensing.FrameworkConfig({ + comTermsConfig: new Licensing.TermsConfig[](0), + nonComTermsConfig: new Licensing.TermsConfig[](1) + }) + ); + } + function test_LicensingModule_configIpOrg_ipOrgWithNoCommercialTermsIsNonCommercial() public { + Licensing.TermsConfig[] memory nonComTerms = new Licensing.TermsConfig[](1); + nonComTerms[0] = Licensing.TermsConfig({ + termId: textTermId, + data: "" + }); + + vm.prank(ipOrg.owner()); + spg.configureIpOrgLicensing( + address(ipOrg), + Licensing.FrameworkConfig({ + comTermsConfig: new Licensing.TermsConfig[](0), + nonComTermsConfig: nonComTerms + }) + ); + assertFalse(licensingModule.ipOrgAllowsCommercial(address(ipOrg))); + (bytes32[] memory nonComTermIds, bytes[] memory nonComTermData) = licensingModule.getIpOrgTerms(false, address(ipOrg)); + assertTrue(ShortStringOps._equal(nonComTermIds[0], textTermId)); + (bytes32[] memory termIds, bytes[] memory termsData) = licensingModule.getIpOrgTerms(true, address(ipOrg)); + assertTrue(termIds.length == 0); + } + + function test_LicensingModule_configIpOrg_ipOrgWithCommercialTermsIsCommercial() public { + Licensing.TermsConfig[] memory nonComTerms = new Licensing.TermsConfig[](1); + nonComTerms[0] = Licensing.TermsConfig({ + termId: textTermId, + data: "" + }); + Licensing.TermsConfig[] memory comTerms = nonComTerms; + vm.prank(ipOrg.owner()); + spg.configureIpOrgLicensing( + address(ipOrg), + Licensing.FrameworkConfig({ + comTermsConfig: comTerms, + nonComTermsConfig: nonComTerms + }) + ); + assertTrue(licensingModule.ipOrgAllowsCommercial(address(ipOrg))); + (bytes32[] memory nonComTermIds, bytes[] memory nonComTermData) = licensingModule.getIpOrgTerms(false, address(ipOrg)); + assertTrue(ShortStringOps._equal(nonComTermIds[0], textTermId)); + (bytes32[] memory termIds, bytes[] memory termsData) = licensingModule.getIpOrgTerms(true, address(ipOrg)); + assertTrue(ShortStringOps._equal(termIds[0], textTermId)); + } + + function test_LicensingModule_configIpOrg_revert_noEmptyNonCommercialTerms() public { + vm.startPrank(ipOrg.owner()); + vm.expectRevert(Errors.LicensingModule_NonCommercialTermsRequired.selector); + spg.configureIpOrgLicensing( + address(ipOrg), + Licensing.FrameworkConfig({ + comTermsConfig: new Licensing.TermsConfig[](0), + nonComTermsConfig: new Licensing.TermsConfig[](0) + }) + ); + vm.stopPrank(); + } + + function test_LicensingModule_configIpOrg_revertIfWrongTermCommercialStatus() public { + licensingModule.addTerm( + "test_category", + "term_id", + Licensing.LicensingTerm({ + comStatus: Licensing.CommercialStatus.Commercial, + text: OffChain.Content({ + url: "https://example.com" + }), + hook: IHook(address(0)) + } + )); + ShortString termId = "term_id".toShortString(); + Licensing.TermsConfig[] memory nonComTerms = new Licensing.TermsConfig[](1); + nonComTerms[0] = Licensing.TermsConfig({ + termId: textTermId, + data: "" + }); + Licensing.TermsConfig[] memory comTerms = nonComTerms; + vm.startPrank(ipOrg.owner()); + vm.expectRevert(Errors.LicensingModule_InvalidTermCommercialStatus.selector); + spg.configureIpOrgLicensing( + address(ipOrg), + Licensing.FrameworkConfig({ + comTermsConfig: comTerms, + nonComTermsConfig: nonComTerms + }) + ); + 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 + } + + + function test_LicensingModule_licensing_createsCommercialLicense() public { + + } + + + function test_LicensingModule_licensing_createsNonCommercialLicense() public { + + } +} + diff --git a/test/foundry/modules/licensing/LicensingCreatorModule.Licensing.sol b/test/foundry/modules/licensing/LicensingCreatorModule.Licensing.sol new file mode 100644 index 00000000..076bd467 --- /dev/null +++ b/test/foundry/modules/licensing/LicensingCreatorModule.Licensing.sol @@ -0,0 +1,121 @@ +// 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, TermCategories, TermIds } from "contracts/lib/modules/Licensing.sol"; +import { OffChain } from "contracts/lib/OffChain.sol"; +import { IHook } from "contracts/interfaces/hooks/base/IHook.sol"; +import { IPAsset } from "contracts/lib/IPAsset.sol"; + +contract LicensingCreatorModuleConfigTest is BaseTest { + using ShortStrings for *; + + ShortString public textTermId = "text_term_id".toShortString(); + address ipaOwner = address(0x13333); + + function setUp() override public { + super.setUp(); + licensingModule.addTermCategory("test_category"); + licensingModule.addTerm( + "test_category", + "text_term_id", + Licensing.LicensingTerm({ + comStatus: Licensing.CommercialStatus.Both, + text: OffChain.Content({ + url: "https://example.com" + }), + hook: IHook(address(0)) + } + )); + Licensing.TermsConfig[] memory nonComTerms = new Licensing.TermsConfig[](1); + nonComTerms[0] = Licensing.TermsConfig({ + termId: textTermId, + data: "" + }); + Licensing.TermsConfig[] memory comTerms = nonComTerms; + vm.prank(ipOrg.owner()); + spg.configureIpOrgLicensing( + address(ipOrg), + Licensing.FrameworkConfig({ + comTermsConfig: comTerms, + nonComTermsConfig: nonComTerms + }) + ); + registry.register(IPAsset.RegisterIpAssetParams({ + name: "test", + ipAssetType: 2, + owner: ipaOwner, + ipOrg: (address(ipOrg)), + hash: keccak256("test"), + url: "https://example.com", + data: "" + })); + + } + + function getTerms(uint256 length) view private returns (Licensing.TermsConfig[] memory ) { + Licensing.TermsConfig[] memory terms = new Licensing.TermsConfig[](length); + terms[0] = Licensing.TermsConfig({ + termId: textTermId, + data: "" + }); + return terms; + } + + function test_LicensingModule_configIpOrg_commercialLicenseActivationHooksCanBeSet() public { + // TODO + } + + function test_LicensingModule_configIpOrg_nonCommercialLicenseActivationHooksCanBeSet() public { + // TODO + } + + + function test_LicensingModule_licensing_createsCommercialLicense() public { + } + + function test_LicensingModule_licensing_createsNonCommercialLicense() public { + } + + function test_LicensingModule_licensing_createRootLicense() public { + vm.prank(ipOrg.owner()); + uint256 lId = spg.createLicense( + address(ipOrg), + Licensing.LicenseCreationParams({ + parentLicenseId: 0, + isCommercial: false, + ipaId: 1 + }), + new bytes[](0), + new bytes[](0) + ); + // Non Commercial + Licensing.License memory license = licenseRegistry.getLicense(lId); + assertTrue(license.isCommercial); + + + assertTrue( + relationshipModule.relationshipExists( + LibRelationship.Relationship({ + relType: ProtocolRelationships.IPA_LICENSE, + srcAddress: address(registry), + dstAddress: address(licenseRegistry), + srcId: 1, + dstId: 1 + }) + ) + ); + //Licensing.License memory license = licenseReg + // Commercial + + + + + } + +} +