From c5cc5a142dc5c26cba760ebaff6573a26d0fb767 Mon Sep 17 00:00:00 2001 From: Ramarti Date: Sun, 4 Feb 2024 16:28:26 -0300 Subject: [PATCH] Multi parent policy compatibility (#66) * added UMLRights and raw rights to registry, started unit tests, wrote unit/integration test plan * cant mint derivative of derivative * lint * derivatives test * lint * multi parent test * verify multi reciprocal and skip * uml test with helpers, fix post rebase * multi parent reciprocal * refactor tests to use TestHelper * compat between parameters multi * lint and comment * fix post rebase * typo * refactor redundant interfaces for verifiers * reducing complexity: refactor redundant paramverifier interfaces, linkToParent split into functions * missing IERC165 * comment out integration tests * change sepolia rpc --------- Co-authored-by: Raul Co-authored-by: Spablob --- .github/workflows/test.yml | 2 +- .../licensing/ILinkParamVerifier.sol | 25 -- .../licensing/IMintParamVerifier.sol | 18 - .../licensing/IPolicyFrameworkManager.sol | 41 +- .../interfaces/licensing/IPolicyVerifier.sol | 11 - .../licensing/ITransferParamVerifier.sol | 18 - .../licensing/IUMLPolicyFrameworkManager.sol | 23 +- .../registries/ILicenseRegistry.sol | 4 +- contracts/lib/Licensing.sol | 1 - contracts/lib/UMLFrameworkErrors.sol | 31 +- .../licensing/BasePolicyFrameworkManager.sol | 5 +- .../licensing/UMLPolicyFrameworkManager.sol | 160 ++++++-- contracts/registries/LicenseRegistry.sol | 162 ++++---- script/foundry/deployment/Main.s.sol | 2 + .../big-bang/NftLicenseRoyalty.t.sol | 3 +- .../big-bang/SingleNftCollection.t.sol | 13 +- .../integration/shared/LicenseHelper.sol | 5 + .../MintPaymentPolicyFrameworkManager.sol | 52 ++- .../licensing/MockPolicyFrameworkManager.sol | 37 +- .../modules/dispute/ArbitrationPolicySP.t.sol | 3 +- .../modules/dispute/DisputeModule.t.sol | 3 +- test/foundry/modules/licensing/README.md | 63 ++- ...ol => UMLPolicyFramework.derivation.t.sol} | 183 ++++----- .../UMLPolicyFramework.multi-parent.sol | 365 ++++++++++++++++++ .../licensing/UMLPolicyFramework.t.sol | 76 ++-- test/foundry/modules/royalty/LSClaimer.t.sol | 3 +- test/foundry/registries/LicenseRegistry.t.sol | 1 + test/utils/TestHelper.sol | 85 +++- 28 files changed, 918 insertions(+), 477 deletions(-) delete mode 100644 contracts/interfaces/licensing/ILinkParamVerifier.sol delete mode 100644 contracts/interfaces/licensing/IMintParamVerifier.sol delete mode 100644 contracts/interfaces/licensing/IPolicyVerifier.sol delete mode 100644 contracts/interfaces/licensing/ITransferParamVerifier.sol rename test/foundry/modules/licensing/{UMLPolicyFramework.compat.t.sol => UMLPolicyFramework.derivation.t.sol} (54%) create mode 100644 test/foundry/modules/licensing/UMLPolicyFramework.multi-parent.sol diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index c2048e03..c1f8828f 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -55,7 +55,7 @@ jobs: - name: Run Forge tests run: | - forge test -vvv --fork-url https://eth-sepolia.api.onfinality.io/public --fork-block-number 5196000 + forge test -vvv --fork-url https://gateway.tenderly.co/public/sepolia --fork-block-number 5196000 id: forge-test - name: Run solhint diff --git a/contracts/interfaces/licensing/ILinkParamVerifier.sol b/contracts/interfaces/licensing/ILinkParamVerifier.sol deleted file mode 100644 index 5afab032..00000000 --- a/contracts/interfaces/licensing/ILinkParamVerifier.sol +++ /dev/null @@ -1,25 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED - -pragma solidity ^0.8.23; - -import { IPolicyVerifier } from "contracts/interfaces/licensing/IPolicyVerifier.sol"; - -/// @title ILinkParamVerifier -/// @notice LicenseRegistry will call this to verify the linking an IP to its parent -/// with the policy referenced by the license in use. -interface ILinkParamVerifier is IPolicyVerifier { - struct VerifyLinkResponse { - bool isLinkingAllowed; - bool isRoyaltyRequired; - address royaltyPolicy; - uint32 royaltyDerivativeRevShare; - } - - function verifyLink( - uint256 licenseId, - address caller, - address ipId, - address parentIpId, - bytes calldata policyData - ) external returns (VerifyLinkResponse memory); -} \ No newline at end of file diff --git a/contracts/interfaces/licensing/IMintParamVerifier.sol b/contracts/interfaces/licensing/IMintParamVerifier.sol deleted file mode 100644 index 17725024..00000000 --- a/contracts/interfaces/licensing/IMintParamVerifier.sol +++ /dev/null @@ -1,18 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED - -pragma solidity ^0.8.23; -import { IPolicyVerifier } from "contracts/interfaces/licensing/IPolicyVerifier.sol"; - -/// @title IMintParamVerifier -/// @notice LicenseRegistry will call this to verify the minting parameters are compliant -/// with the policy associated with the license to mint. -interface IMintParamVerifier is IPolicyVerifier { - function verifyMint( - address caller, - bool policyWasInherited, - address licensor, - address receiver, - uint256 mintAmount, - bytes memory policyData - ) external returns (bool); -} \ No newline at end of file diff --git a/contracts/interfaces/licensing/IPolicyFrameworkManager.sol b/contracts/interfaces/licensing/IPolicyFrameworkManager.sol index cae945b3..72df19f7 100644 --- a/contracts/interfaces/licensing/IPolicyFrameworkManager.sol +++ b/contracts/interfaces/licensing/IPolicyFrameworkManager.sol @@ -3,13 +3,18 @@ pragma solidity ^0.8.23; import { Licensing } from "contracts/lib/Licensing.sol"; -import { IPolicyVerifier } from "contracts/interfaces/licensing/IPolicyVerifier.sol"; +import { IERC165 } from "@openzeppelin/contracts/interfaces/IERC165.sol"; /// @title IPolicyFrameworkManager /// @notice Interface to define a policy framework contract, that will /// register itself into the LicenseRegistry to format policy into the LicenseRegistry -interface IPolicyFrameworkManager is IPolicyVerifier { - // TODO: move here the interfaces for verification and sunset IPolicyVerifier +interface IPolicyFrameworkManager is IERC165 { + struct VerifyLinkResponse { + bool isLinkingAllowed; + bool isRoyaltyRequired; + address royaltyPolicy; + uint32 royaltyDerivativeRevShare; + } /// @notice Name to be show in LNFT metadata function name() external view returns (string memory); @@ -23,7 +28,33 @@ interface IPolicyFrameworkManager is IPolicyVerifier { function policyToJson(bytes memory policyData) external view returns (string memory); function processInheritedPolicies( - bytes memory ipRights, + bytes memory aggregator, + uint256 policyId, bytes memory policy - ) external view returns (bool changedRights, bytes memory newRights); + ) external view returns (bool changedAgg, bytes memory newAggregator); + + function verifyMint( + address caller, + bool policyWasInherited, + address licensor, + address receiver, + uint256 mintAmount, + bytes memory policyData + ) external returns (bool); + + function verifyLink( + uint256 licenseId, + address caller, + address ipId, + address parentIpId, + bytes calldata policyData + ) external returns (VerifyLinkResponse memory); + + function verifyTransfer( + uint256 licenseId, + address from, + address to, + uint256 amount, + bytes memory policyData + ) external returns (bool); } diff --git a/contracts/interfaces/licensing/IPolicyVerifier.sol b/contracts/interfaces/licensing/IPolicyVerifier.sol deleted file mode 100644 index 7f5c34ad..00000000 --- a/contracts/interfaces/licensing/IPolicyVerifier.sol +++ /dev/null @@ -1,11 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED - -pragma solidity ^0.8.23; - -import { IERC165 } from "@openzeppelin/contracts/interfaces/IERC165.sol"; - -/// @title IPolicyVerifier -/// @notice Placeholder interface for verifying policy parameters. -interface IPolicyVerifier is IERC165 { - -} \ No newline at end of file diff --git a/contracts/interfaces/licensing/ITransferParamVerifier.sol b/contracts/interfaces/licensing/ITransferParamVerifier.sol deleted file mode 100644 index 17be9da0..00000000 --- a/contracts/interfaces/licensing/ITransferParamVerifier.sol +++ /dev/null @@ -1,18 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED - -pragma solidity ^0.8.23; - -import { IPolicyVerifier } from "contracts/interfaces/licensing/IPolicyVerifier.sol"; - -/// @title ITransferParamVerifier -/// @notice LicenseRegistry will call this to verify the transfer parameters are compliant -/// with the policy -interface ITransferParamVerifier is IPolicyVerifier { - function verifyTransfer( - uint256 licenseId, - address from, - address to, - uint256 amount, - bytes memory policyData - ) external returns (bool); -} \ No newline at end of file diff --git a/contracts/interfaces/licensing/IUMLPolicyFrameworkManager.sol b/contracts/interfaces/licensing/IUMLPolicyFrameworkManager.sol index d45a6c90..4c2cecdd 100644 --- a/contracts/interfaces/licensing/IUMLPolicyFrameworkManager.sol +++ b/contracts/interfaces/licensing/IUMLPolicyFrameworkManager.sol @@ -36,13 +36,27 @@ struct UMLPolicy { uint32 derivativesRevShare; string[] territories; string[] distributionChannels; + string[] contentRestrictions; address royaltyPolicy; } -struct UMLRights { +/// @notice Struct that accumulates values of inherited policies +/// so we can verify compatibility when inheriting new policies +/// @param commercial Whether or not there is a policy that allows commercial use +/// @param derivatives Whether or not there is a policy that allows derivatives +/// @param derivativesReciprocal Whether or not there is a policy that requires derivatives to be licensed under the same terms +/// @param lastPolicyId The last policy ID that was added to the IP +/// @param territoriesAcc The last hash of the territories array +/// @param distributionChannelsAcc The last hash of the distributionChannels array +/// @param contentRestrictionsAcc The last hash of the contentRestrictions array +struct UMLAggregator { bool commercial; - bool derivable; - bool reciprocalSet; + bool derivatives; + bool derivativesReciprocal; + uint256 lastPolicyId; + bytes32 territoriesAcc; + bytes32 distributionChannelsAcc; + bytes32 contentRestrictionsAcc; } @@ -58,5 +72,6 @@ interface IUMLPolicyFrameworkManager is IPolicyFrameworkManager { /// @return policy The UMLPolicy struct function getPolicy(uint256 policyId) external view returns (UMLPolicy memory policy); - function getRights(address ipId) external view returns (UMLRights memory rights); + /// @notice gets the aggregation data for inherited policies. + function getAggregator(address ipId) external view returns (UMLAggregator memory rights); } \ No newline at end of file diff --git a/contracts/interfaces/registries/ILicenseRegistry.sol b/contracts/interfaces/registries/ILicenseRegistry.sol index d976896d..4865420b 100644 --- a/contracts/interfaces/registries/ILicenseRegistry.sol +++ b/contracts/interfaces/registries/ILicenseRegistry.sol @@ -24,8 +24,6 @@ interface ILicenseRegistry { /// @param policy The encoded policy data event PolicyRegistered(address indexed policyFrameworkManager, uint256 indexed policyId, bytes policy); - event IPRightsUpdated(address indexed ipId, bytes rights); - /// @notice Emitted when a policy is added to an IP /// @param caller The address that called the function /// @param ipId The id of the IP @@ -145,7 +143,7 @@ interface ILicenseRegistry { uint256 policyId ) external view returns (uint256 index, bool isInherited, bool active); - function rightsData(address framework, address ipId) external view returns (bytes memory); + function policyAggregatorData(address framework, address ipId) external view returns (bytes memory); /// @notice True if holder is the licensee for the license (owner of the license NFT), or derivative IP owner if /// the license was added to the IP by linking (burning a license) diff --git a/contracts/lib/Licensing.sol b/contracts/lib/Licensing.sol index 46ca367d..8e0846c8 100644 --- a/contracts/lib/Licensing.sol +++ b/contracts/lib/Licensing.sol @@ -1,7 +1,6 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.20; -import { IPolicyVerifier } from "../interfaces/licensing/IPolicyVerifier.sol"; import { Errors } from "./Errors.sol"; /// @title Licensing diff --git a/contracts/lib/UMLFrameworkErrors.sol b/contracts/lib/UMLFrameworkErrors.sol index 40184a08..2e679f66 100644 --- a/contracts/lib/UMLFrameworkErrors.sol +++ b/contracts/lib/UMLFrameworkErrors.sol @@ -9,19 +9,20 @@ library UMLFrameworkErrors { // UMLPolicyFrameworkManager // //////////////////////////////////////////////////////////////////////////// - error UMLPolicyFrameworkManager_CommecialDisabled_CantAddAttribution(); - error UMLPolicyFrameworkManager_CommecialDisabled_CantAddCommercializers(); - error UMLPolicyFrameworkManager_CommecialDisabled_CantAddRevShare(); - error UMLPolicyFrameworkManager_CommecialDisabled_CantAddDerivRevShare(); - error UMLPolicyFrameworkManager_CommecialDisabled_CantAddRoyaltyPolicy(); - error UMLPolicyFrameworkManager_CommecialEnabled_RoyaltyPolicyRequired(); - error UMLPolicyFrameworkManager_DerivativesDisabled_CantAddAttribution(); - error UMLPolicyFrameworkManager_DerivativesDisabled_CantAddApproval(); - error UMLPolicyFrameworkManager_DerivativesDisabled_CantAddReciprocal(); - error UMLPolicyFrameworkManager_DerivativesDisabled_CantAddRevShare(); - error UMLPolicyFrameworkManager_RightsNotFound(); - - error UMLPolicyFrameworkManager_NewCommercialPolicyNotAccepted(); - error UMLPolicyFrameworkManager_NewDerivativesPolicyNotAccepted(); - error UMLPolicyFrameworkManager_ReciprocaConfiglNegatesNewPolicy(); + error UMLPolicyFrameworkManager__CommecialDisabled_CantAddAttribution(); + error UMLPolicyFrameworkManager__CommercialDisabled_CantAddCommercializers(); + error UMLPolicyFrameworkManager__CommecialDisabled_CantAddRevShare(); + error UMLPolicyFrameworkManager__CommecialDisabled_CantAddDerivRevShare(); + error UMLPolicyFrameworkManager__DerivativesDisabled_CantAddAttribution(); + error UMLPolicyFrameworkManager__DerivativesDisabled_CantAddApproval(); + error UMLPolicyFrameworkManager__DerivativesDisabled_CantAddReciprocal(); + error UMLPolicyFrameworkManager__DerivativesDisabled_CantAddRevShare(); + error UMLPolicyFrameworkManager__RightsNotFound(); + error UMLPolicyFrameworkManager__CommercialDisabled_CantAddRoyaltyPolicy(); + error UMLPolicyFrameworkManager__CommecialEnabled_RoyaltyPolicyRequired(); + error UMLPolicyFrameworkManager__ReciprocalButDifferentPolicyIds(); + error UMLPolicyFrameworkManager__ReciprocalValueMismatch(); + error UMLPolicyFrameworkManager__CommercialValueMismatch(); + error UMLPolicyFrameworkManager__DerivativesValueMismatch(); + error UMLPolicyFrameworkManager__StringArrayMismatch(); } diff --git a/contracts/modules/licensing/BasePolicyFrameworkManager.sol b/contracts/modules/licensing/BasePolicyFrameworkManager.sol index 94c16312..fed4d664 100644 --- a/contracts/modules/licensing/BasePolicyFrameworkManager.sol +++ b/contracts/modules/licensing/BasePolicyFrameworkManager.sol @@ -3,7 +3,6 @@ pragma solidity ^0.8.23; // contracts -import { IPolicyVerifier } from "contracts/interfaces/licensing/IPolicyVerifier.sol"; import { IPolicyFrameworkManager } from "contracts/interfaces/licensing/IPolicyFrameworkManager.sol"; import { LicenseRegistry } from "contracts/registries/LicenseRegistry.sol"; import { Licensing } from "contracts/lib/Licensing.sol"; @@ -16,7 +15,7 @@ import { IERC165 } from "@openzeppelin/contracts/utils/introspection/IERC165.sol /// @title BasePolicyFrameworkManager /// @notice Base contract for policy framework managers. -abstract contract BasePolicyFrameworkManager is IPolicyVerifier, IPolicyFrameworkManager, ERC165, LicenseRegistryAware { +abstract contract BasePolicyFrameworkManager is IPolicyFrameworkManager, ERC165, LicenseRegistryAware { string public override name; string public override licenseTextUrl; @@ -29,7 +28,7 @@ abstract contract BasePolicyFrameworkManager is IPolicyVerifier, IPolicyFramewor } /// @notice ERC165 interface identifier for the policy framework manager. - function supportsInterface(bytes4 interfaceId) public view virtual override(IERC165, ERC165) returns (bool) { + function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165, IERC165) returns (bool) { return interfaceId == type(IPolicyFrameworkManager).interfaceId || super.supportsInterface(interfaceId); } diff --git a/contracts/modules/licensing/UMLPolicyFrameworkManager.sol b/contracts/modules/licensing/UMLPolicyFrameworkManager.sol index 376bab81..ba44f06a 100644 --- a/contracts/modules/licensing/UMLPolicyFrameworkManager.sol +++ b/contracts/modules/licensing/UMLPolicyFrameworkManager.sol @@ -13,10 +13,7 @@ import { LicenseRegistry } from "contracts/registries/LicenseRegistry.sol"; import { Licensing } from "contracts/lib/Licensing.sol"; import { Errors } from "contracts/lib/Errors.sol"; import { UMLFrameworkErrors } from "contracts/lib/UMLFrameworkErrors.sol"; -import { ILinkParamVerifier } from "contracts/interfaces/licensing/ILinkParamVerifier.sol"; -import { IMintParamVerifier } from "contracts/interfaces/licensing/IMintParamVerifier.sol"; -import { ITransferParamVerifier } from "contracts/interfaces/licensing/ITransferParamVerifier.sol"; -import { IUMLPolicyFrameworkManager, UMLPolicy, UMLRights } from "contracts/interfaces/licensing/IUMLPolicyFrameworkManager.sol"; +import { IUMLPolicyFrameworkManager, UMLPolicy, UMLAggregator } from "contracts/interfaces/licensing/IUMLPolicyFrameworkManager.sol"; import { IPolicyFrameworkManager } from "contracts/interfaces/licensing/IPolicyFrameworkManager.sol"; import { BasePolicyFrameworkManager } from "contracts/modules/licensing/BasePolicyFrameworkManager.sol"; import { LicensorApprovalChecker } from "contracts/modules/licensing/parameter-helpers/LicensorApprovalChecker.sol"; @@ -28,11 +25,11 @@ import { LicensorApprovalChecker } from "contracts/modules/licensing/parameter-h contract UMLPolicyFrameworkManager is IUMLPolicyFrameworkManager, BasePolicyFrameworkManager, - ILinkParamVerifier, - IMintParamVerifier, - ITransferParamVerifier, LicensorApprovalChecker { + bytes32 private constant _EMPTY_STRING_ARRAY_HASH = + 0x569e75fc77c1a856f6daaf9e69d8a9566ca34aa47f9133711ce065a571af0cfd; + constructor( address accessController, address ipAccountRegistry, @@ -74,9 +71,9 @@ contract UMLPolicyFrameworkManager is address ipId, address, // parentIpId bytes calldata policyData - ) external override onlyLicenseRegistry returns (ILinkParamVerifier.VerifyLinkResponse memory) { + ) external override onlyLicenseRegistry returns (IPolicyFrameworkManager.VerifyLinkResponse memory) { UMLPolicy memory policy = abi.decode(policyData, (UMLPolicy)); - ILinkParamVerifier.VerifyLinkResponse memory response = ILinkParamVerifier.VerifyLinkResponse({ + IPolicyFrameworkManager.VerifyLinkResponse memory response = IPolicyFrameworkManager.VerifyLinkResponse({ isLinkingAllowed: true, // If you successfully mint and now hold a license, you have the right to link. isRoyaltyRequired: false, royaltyPolicy: address(0), @@ -158,20 +155,92 @@ contract UMLPolicyFrameworkManager is policy = abi.decode(protocolPolicy.data, (UMLPolicy)); } - function getRights(address ipId) external view returns (UMLRights memory rights) { - bytes memory rightsData = LICENSE_REGISTRY.rightsData(address(this), ipId); - if (rightsData.length == 0) { - revert UMLFrameworkErrors.UMLPolicyFrameworkManager_RightsNotFound(); + /// @notice gets the aggregation data for inherited policies, decoded for the framework + function getAggregator(address ipId) external view returns (UMLAggregator memory rights) { + bytes memory policyAggregatorData = LICENSE_REGISTRY.policyAggregatorData(address(this), ipId); + if (policyAggregatorData.length == 0) { + revert UMLFrameworkErrors.UMLPolicyFrameworkManager__RightsNotFound(); } - rights = abi.decode(rightsData, (UMLRights)); + rights = abi.decode(policyAggregatorData, (UMLAggregator)); } + /// Called by licenseRegistry to verify compatibility when inheriting from a parent IP + /// The objective is to verify compatibility of multiple policies. + /// @param aggregator common state of the policies for the IP + /// @param policyId the ID of the policy being inherited + /// @param policy the policy to inherit + /// @return changedAgg true if the aggregator was changed + /// @return newAggregator the new aggregator function processInheritedPolicies( - bytes memory ipRights, + bytes memory aggregator, + uint256 policyId, bytes memory policy - ) external view onlyLicenseRegistry returns (bool changedRights, bytes memory newRights) { - // TODO verify compatibility on multi parent inheritance - return (false, bytes("")); + ) external view onlyLicenseRegistry returns (bool changedAgg, bytes memory newAggregator) { + UMLAggregator memory agg; + UMLPolicy memory newPolicy = abi.decode(policy, (UMLPolicy)); + if (aggregator.length == 0) { + // Initialize the aggregator + agg = UMLAggregator({ + commercial: newPolicy.commercialUse, + derivatives: newPolicy.derivativesAllowed, + derivativesReciprocal: newPolicy.derivativesReciprocal, + lastPolicyId: policyId, + territoriesAcc: keccak256(abi.encode(newPolicy.territories)), + distributionChannelsAcc: keccak256(abi.encode(newPolicy.distributionChannels)), + contentRestrictionsAcc: keccak256(abi.encode(newPolicy.contentRestrictions)) + }); + return (true, abi.encode(agg)); + } else { + agg = abi.decode(aggregator, (UMLAggregator)); + + // Either all are reciprocal or none are + if (agg.derivativesReciprocal != newPolicy.derivativesReciprocal) { + revert UMLFrameworkErrors.UMLPolicyFrameworkManager__ReciprocalValueMismatch(); + } else if (agg.derivativesReciprocal && newPolicy.derivativesReciprocal) { + // Ids are uniqued because we hash them to compare on creation in LicenseRegistry, + // so we can compare the ids safely. + if (agg.lastPolicyId != policyId) { + revert UMLFrameworkErrors.UMLPolicyFrameworkManager__ReciprocalButDifferentPolicyIds(); + } + } else { + // Both non reciprocal + if (agg.commercial != newPolicy.commercialUse) { + revert UMLFrameworkErrors.UMLPolicyFrameworkManager__CommercialValueMismatch(); + } + if (agg.derivatives != newPolicy.derivativesAllowed) { + revert UMLFrameworkErrors.UMLPolicyFrameworkManager__DerivativesValueMismatch(); + } + + bytes32 newHash = _verifHashedParams( + agg.territoriesAcc, + keccak256(abi.encode(newPolicy.territories)), + _EMPTY_STRING_ARRAY_HASH + ); + if (newHash != agg.territoriesAcc) { + agg.territoriesAcc = newHash; + changedAgg = true; + } + newHash = _verifHashedParams( + agg.distributionChannelsAcc, + keccak256(abi.encode(newPolicy.distributionChannels)), + _EMPTY_STRING_ARRAY_HASH + ); + if (newHash != agg.distributionChannelsAcc) { + agg.distributionChannelsAcc = newHash; + changedAgg = true; + } + newHash = _verifHashedParams( + agg.contentRestrictionsAcc, + keccak256(abi.encode(newPolicy.contentRestrictions)), + _EMPTY_STRING_ARRAY_HASH + ); + if (newHash != agg.contentRestrictionsAcc) { + agg.contentRestrictionsAcc = newHash; + changedAgg = true; + } + } + } + return (changedAgg, abi.encode(agg)); } function policyToJson(bytes memory policyData) public view returns (string memory) { @@ -262,39 +331,29 @@ contract UMLPolicyFrameworkManager is return string(abi.encodePacked("data:application/json;base64,", Base64.encode(bytes(json)))); } - function supportsInterface( - bytes4 interfaceId - ) public view virtual override(BasePolicyFrameworkManager, IERC165) returns (bool) { - return - super.supportsInterface(interfaceId) || - interfaceId == type(ILinkParamVerifier).interfaceId || - interfaceId == type(IMintParamVerifier).interfaceId || - interfaceId == type(ITransferParamVerifier).interfaceId; - } - /// Checks the configuration of commercial use and throws if the policy is not compliant /// @param policy The policy to verify function _verifyComercialUse(UMLPolicy calldata policy) internal view { if (!policy.commercialUse) { if (policy.commercialAttribution) { - revert UMLFrameworkErrors.UMLPolicyFrameworkManager_CommecialDisabled_CantAddAttribution(); + revert UMLFrameworkErrors.UMLPolicyFrameworkManager__CommecialDisabled_CantAddAttribution(); } if (policy.commercializers.length > 0) { - revert UMLFrameworkErrors.UMLPolicyFrameworkManager_CommecialDisabled_CantAddCommercializers(); + revert UMLFrameworkErrors.UMLPolicyFrameworkManager__CommercialDisabled_CantAddCommercializers(); } if (policy.commercialRevShare > 0) { - revert UMLFrameworkErrors.UMLPolicyFrameworkManager_CommecialDisabled_CantAddRevShare(); + revert UMLFrameworkErrors.UMLPolicyFrameworkManager__CommecialDisabled_CantAddRevShare(); } if (policy.derivativesRevShare > 0) { - revert UMLFrameworkErrors.UMLPolicyFrameworkManager_CommecialDisabled_CantAddDerivRevShare(); + revert UMLFrameworkErrors.UMLPolicyFrameworkManager__CommecialDisabled_CantAddDerivRevShare(); } if (policy.royaltyPolicy != address(0)) { - revert UMLFrameworkErrors.UMLPolicyFrameworkManager_CommecialDisabled_CantAddRoyaltyPolicy(); + revert UMLFrameworkErrors.UMLPolicyFrameworkManager__CommercialDisabled_CantAddRoyaltyPolicy(); } } else { // TODO: check for supportInterface instead if (policy.royaltyPolicy == address(0)) { - revert UMLFrameworkErrors.UMLPolicyFrameworkManager_CommecialEnabled_RoyaltyPolicyRequired(); + revert UMLFrameworkErrors.UMLPolicyFrameworkManager__CommecialEnabled_RoyaltyPolicyRequired(); } } } @@ -304,18 +363,41 @@ contract UMLPolicyFrameworkManager is function _verifyDerivatives(UMLPolicy calldata policy) internal pure { if (!policy.derivativesAllowed) { if (policy.derivativesAttribution) { - revert UMLFrameworkErrors.UMLPolicyFrameworkManager_DerivativesDisabled_CantAddAttribution(); + revert UMLFrameworkErrors.UMLPolicyFrameworkManager__DerivativesDisabled_CantAddAttribution(); } if (policy.derivativesApproval) { - revert UMLFrameworkErrors.UMLPolicyFrameworkManager_DerivativesDisabled_CantAddApproval(); + revert UMLFrameworkErrors.UMLPolicyFrameworkManager__DerivativesDisabled_CantAddApproval(); } if (policy.derivativesReciprocal) { - revert UMLFrameworkErrors.UMLPolicyFrameworkManager_DerivativesDisabled_CantAddReciprocal(); + revert UMLFrameworkErrors.UMLPolicyFrameworkManager__DerivativesDisabled_CantAddReciprocal(); } if (policy.derivativesRevShare > 0) { - // additional !policy.commecialUse is already checked in `_verifyComercialUse` - revert UMLFrameworkErrors.UMLPolicyFrameworkManager_DerivativesDisabled_CantAddRevShare(); + revert UMLFrameworkErrors.UMLPolicyFrameworkManager__DerivativesDisabled_CantAddRevShare(); } } } + + /// Verifies compatibility for params where the valid options are either permissive value, or equal params + /// @param oldHash hash of the old param + /// @param newHash hash of the new param + /// @param permissive hash of the most permissive param + /// @return result the hash that's different from the permissive hash + function _verifHashedParams( + bytes32 oldHash, + bytes32 newHash, + bytes32 permissive + ) internal view returns (bytes32 result) { + if (oldHash == newHash) { + return newHash; + } + if (oldHash != permissive && newHash != permissive) { + revert UMLFrameworkErrors.UMLPolicyFrameworkManager__StringArrayMismatch(); + } + if (oldHash != permissive) { + return oldHash; + } + if (newHash != permissive) { + return newHash; + } + } } diff --git a/contracts/registries/LicenseRegistry.sol b/contracts/registries/LicenseRegistry.sol index 162f7414..d618fe51 100644 --- a/contracts/registries/LicenseRegistry.sol +++ b/contracts/registries/LicenseRegistry.sol @@ -10,12 +10,8 @@ import { ERC165Checker } from "@openzeppelin/contracts/utils/introspection/ERC16 // contracts import { AccessControlled } from "contracts/access/AccessControlled.sol"; -import { IPolicyVerifier } from "contracts/interfaces/licensing/IPolicyVerifier.sol"; -import { IMintParamVerifier } from "contracts/interfaces/licensing/IMintParamVerifier.sol"; -import { ILinkParamVerifier } from "contracts/interfaces/licensing/ILinkParamVerifier.sol"; -import { ITransferParamVerifier } from "contracts/interfaces/licensing/ITransferParamVerifier.sol"; -import { ILicenseRegistry } from "contracts/interfaces/registries/ILicenseRegistry.sol"; import { IPolicyFrameworkManager } from "contracts/interfaces/licensing/IPolicyFrameworkManager.sol"; +import { ILicenseRegistry } from "contracts/interfaces/registries/ILicenseRegistry.sol"; import { IAccessController } from "contracts/interfaces/IAccessController.sol"; import { IIPAccountRegistry } from "contracts/interfaces/registries/IIPAccountRegistry.sol"; import { Errors } from "contracts/lib/Errors.sol"; @@ -51,7 +47,7 @@ contract LicenseRegistry is ERC1155, ILicenseRegistry, AccessControlled { mapping(address ipId => mapping(uint256 policyId => PolicySetup setup)) private _policySetups; mapping(bytes32 hashIpIdAnInherited => EnumerableSet.UintSet policyIds) private _policiesPerIpId; mapping(address ipId => EnumerableSet.AddressSet parentIpIds) private _ipIdParents; - mapping(address framework => mapping(address ipId => bytes rightsData)) private _ipRights; + mapping(address framework => mapping(address ipId => bytes policyAggregatorData)) private _ipRights; mapping(bytes32 licenseHash => uint256 ids) private _hashedLicenses; mapping(uint256 licenseIds => Licensing.License licenseData) private _licenses; @@ -148,19 +144,17 @@ contract LicenseRegistry is ERC1155, ILicenseRegistry, AccessControlled { // If a policy is set, then is only up to the policy params. // Verify minting param Licensing.Policy memory pol = policy(policyId); - if (ERC165Checker.supportsInterface(pol.policyFramework, type(IMintParamVerifier).interfaceId)) { - if ( - !IMintParamVerifier(pol.policyFramework).verifyMint( - msg.sender, - isInherited, - licensorIp, - receiver, - amount, - pol.data - ) - ) { - revert Errors.LicenseRegistry__MintLicenseParamFailed(); - } + if ( + !IPolicyFrameworkManager(pol.policyFramework).verifyMint( + msg.sender, + isInherited, + licensorIp, + receiver, + amount, + pol.data + ) + ) { + revert Errors.LicenseRegistry__MintLicenseParamFailed(); } Licensing.License memory licenseData = Licensing.License({ policyId: policyId, licensorIpId: licensorIp }); @@ -197,50 +191,16 @@ contract LicenseRegistry is ERC1155, ILicenseRegistry, AccessControlled { if (balanceOf(holder, licenseId) == 0) { revert Errors.LicenseRegistry__NotLicensee(); } - Licensing.License memory licenseData = _licenses[licenseId]; licensors[i] = licenseData.licensorIpId; - // TODO: check licensor not part of a branch tagged by disputer - if (licensors[i] == childIpId) { - revert Errors.LicenseRegistry__ParentIdEqualThanChild(); - } - // Verify linking params - Licensing.Policy memory pol = policy(licenseData.policyId); - if (ERC165Checker.supportsInterface(pol.policyFramework, type(ILinkParamVerifier).interfaceId)) { - ILinkParamVerifier.VerifyLinkResponse memory response = ILinkParamVerifier(pol.policyFramework) - .verifyLink(licenseId, msg.sender, childIpId, licensors[i], pol.data); - - if (!response.isLinkingAllowed) { - revert Errors.LicenseRegistry__LinkParentParamFailed(); - } - - // Compatibility check: If link says no royalty is required for license (licenseIds[i]) but - // another license requires royalty, revert. - if (!response.isRoyaltyRequired && royaltyPolicyAddress != address(0)) { - revert Errors.LicenseRegistry__IncompatibleLicensorRoyaltyPolicy(); - } - - // If link says royalty is required for license (licenseIds[i]) and no royalty policy is set, set it. - // But if the index is NOT 0, this is previous licenses didn't set the royalty policy because they don't - // require royalty payment. So, revert in this case. - if (response.isRoyaltyRequired && royaltyPolicyAddress == address(0)) { - if (i != 0) { - revert Errors.LicenseRegistry__IncompatibleLicensorRoyaltyPolicy(); - } - royaltyPolicyAddress = response.royaltyPolicy; - royaltyDerivativeRevShare = response.royaltyDerivativeRevShare; - } - } - // Add the policy of licenseIds[i] to the child. If the policy's already set from previous parents, - // then the addition will be skipped. - _addPolicyIdToIp({ - ipId: childIpId, - policyId: licenseData.policyId, - isInherited: true, - skipIfDuplicate: true - }); - // Set parent - _ipIdParents[childIpId].add(licensors[i]); + (royaltyPolicyAddress, royaltyDerivativeRevShare) = _linkIpToParent( + i, + licenseId, + licenseData, + licensors[i], + childIpId, + royaltyPolicyAddress + ); values[i] = 1; } emit IpIdLinkedToParents(msg.sender, childIpId, licensors); @@ -259,6 +219,52 @@ contract LicenseRegistry is ERC1155, ILicenseRegistry, AccessControlled { _burnBatch(holder, licenseIds, values); } + function _linkIpToParent( + uint256 iteration, + uint256 licenseId, + Licensing.License memory licenseData, + address licensor, + address childIpId, + address royaltyPolicyAddress + ) private returns (address nextRoyaltyPolicyAddress, uint32 royaltyDerivativeRevShare) { + // TODO: check licensor not part of a branch tagged by disputer + if (licensor == childIpId) { + revert Errors.LicenseRegistry__ParentIdEqualThanChild(); + } + // Verify linking params + Licensing.Policy memory pol = policy(licenseData.policyId); + IPolicyFrameworkManager.VerifyLinkResponse memory response = IPolicyFrameworkManager(pol.policyFramework) + .verifyLink(licenseId, msg.sender, childIpId, licensor, pol.data); + + if (!response.isLinkingAllowed) { + revert Errors.LicenseRegistry__LinkParentParamFailed(); + } + + // Compatibility check: If link says no royalty is required for license (licenseIds[i]) but + // another license requires royalty, revert. + if (!response.isRoyaltyRequired && royaltyPolicyAddress != address(0)) { + revert Errors.LicenseRegistry__IncompatibleLicensorRoyaltyPolicy(); + } + + // If link says royalty is required for license (licenseIds[i]) and no royalty policy is set, set it. + // But if the index is NOT 0, this is previous licenses didn't set the royalty policy because they don't + // require royalty payment. So, revert in this case. + if (response.isRoyaltyRequired && royaltyPolicyAddress == address(0)) { + if (iteration != 0) { + revert Errors.LicenseRegistry__IncompatibleLicensorRoyaltyPolicy(); + } + royaltyPolicyAddress = response.royaltyPolicy; + royaltyDerivativeRevShare = response.royaltyDerivativeRevShare; + } + + // Add the policy of licenseIds[i] to the child. If the policy's already set from previous parents, + // then the addition will be skipped. + _addPolicyIdToIp({ ipId: childIpId, policyId: licenseData.policyId, isInherited: true, skipIfDuplicate: true }); + // Set parent + _ipIdParents[childIpId].add(licensor); + return (royaltyPolicyAddress, royaltyDerivativeRevShare); + } + /// @notice True if the framework address is registered in LicenseRegistry function isFrameworkRegistered(address policyFramework) external view returns (bool) { return _registeredFrameworkManagers[policyFramework]; @@ -293,7 +299,7 @@ contract LicenseRegistry is ERC1155, ILicenseRegistry, AccessControlled { return pol; } - function rightsData(address framework, address ipId) external view returns (bytes memory) { + function policyAggregatorData(address framework, address ipId) external view returns (bytes memory) { return _ipRights[framework][ipId]; } @@ -385,18 +391,10 @@ contract LicenseRegistry is ERC1155, ILicenseRegistry, AccessControlled { for (uint256 i = 0; i < ids.length; i++) { // Verify transfer params Licensing.Policy memory pol = policy(_licenses[ids[i]].policyId); - if (ERC165Checker.supportsInterface(pol.policyFramework, type(ITransferParamVerifier).interfaceId)) { - if ( - !ITransferParamVerifier(pol.policyFramework).verifyTransfer( - ids[i], - from, - to, - values[i], - pol.data - ) - ) { - revert Errors.LicenseRegistry__TransferParamFailed(); - } + if ( + !IPolicyFrameworkManager(pol.policyFramework).verifyTransfer(ids[i], from, to, values[i], pol.data) + ) { + revert Errors.LicenseRegistry__TransferParamFailed(); } } } @@ -450,11 +448,7 @@ contract LicenseRegistry is ERC1155, ILicenseRegistry, AccessControlled { if ( // Original work, owner is setting policies // (ipIdIsDerivative false, adding isInherited false) - // or - // IpId is becoming a derivative. - // (ipIdIsDerivative false, adding isInherited true) - // Next time, ipIdIsDerivative will be true - (!ipIdIsDerivative) + (!ipIdIsDerivative && !isInherited) ) { // Can add policy return; @@ -466,16 +460,18 @@ contract LicenseRegistry is ERC1155, ILicenseRegistry, AccessControlled { // Checking for policy compatibility IPolicyFrameworkManager polManager = IPolicyFrameworkManager(policy(policyId).policyFramework); Licensing.Policy memory pol = _policies[policyId]; - (bool rightsChanged, bytes memory newRights) = polManager.processInheritedPolicies( + (bool aggregatorChanged, bytes memory newAggregator) = polManager.processInheritedPolicies( _ipRights[pol.policyFramework][ipId], + policyId, pol.data ); - if (rightsChanged) { - _ipRights[pol.policyFramework][ipId] = newRights; - emit IPRightsUpdated(ipId, newRights); + if (aggregatorChanged) { + _ipRights[pol.policyFramework][ipId] = newAggregator; } } + function _verifyRoyaltyRequired(bool) private {} + /// Stores data without repetition, assigning an id to it if new or reusing existing one if already stored /// @param data raw bytes, abi.encode() a value to be hashed /// @param _hashToIds storage ref to the mapping of hash -> data id diff --git a/script/foundry/deployment/Main.s.sol b/script/foundry/deployment/Main.s.sol index bf952d53..617f9466 100644 --- a/script/foundry/deployment/Main.s.sol +++ b/script/foundry/deployment/Main.s.sol @@ -341,6 +341,7 @@ contract Main is Script, BroadcastManager, JsonDeploymentHandler { derivativesRevShare: 0, territories: new string[](0), distributionChannels: new string[](0), + contentRestrictions: new string[](0), royaltyPolicy: address(royaltyPolicyLS) }) ); @@ -360,6 +361,7 @@ contract Main is Script, BroadcastManager, JsonDeploymentHandler { derivativesRevShare: 0, territories: new string[](0), distributionChannels: new string[](0), + contentRestrictions: new string[](0), royaltyPolicy: address(royaltyPolicyLS) }) ); diff --git a/test/foundry/integration/big-bang/NftLicenseRoyalty.t.sol b/test/foundry/integration/big-bang/NftLicenseRoyalty.t.sol index af981ed9..069ab2bf 100644 --- a/test/foundry/integration/big-bang/NftLicenseRoyalty.t.sol +++ b/test/foundry/integration/big-bang/NftLicenseRoyalty.t.sol @@ -61,7 +61,8 @@ contract BigBang_Integration_NftLicenseRoyalty is BaseIntegration, Integration_S attribution: false, transferable: false, territories: new string[](0), - distributionChannels: new string[](0) + distributionChannels: new string[](0), + contentRestrictions: new string[](0) }), UMLPolicyCommercialParams({ commercialAttribution: true, diff --git a/test/foundry/integration/big-bang/SingleNftCollection.t.sol b/test/foundry/integration/big-bang/SingleNftCollection.t.sol index c2c837d7..454f6e70 100644 --- a/test/foundry/integration/big-bang/SingleNftCollection.t.sol +++ b/test/foundry/integration/big-bang/SingleNftCollection.t.sol @@ -46,7 +46,8 @@ contract BigBang_Integration_SingleNftCollection is BaseIntegration, Integration attribution: false, transferable: true, territories: new string[](0), - distributionChannels: new string[](0) + distributionChannels: new string[](0), + contentRestrictions: new string[](0) }), UMLPolicyCommercialParams({ commercialAttribution: true, @@ -67,7 +68,8 @@ contract BigBang_Integration_SingleNftCollection is BaseIntegration, Integration attribution: false, transferable: true, territories: new string[](0), - distributionChannels: new string[](0) + distributionChannels: new string[](0), + contentRestrictions: new string[](0) }), UMLPolicyDerivativeParams({ derivativesAttribution: true, @@ -82,7 +84,8 @@ contract BigBang_Integration_SingleNftCollection is BaseIntegration, Integration attribution: false, transferable: false, territories: new string[](0), - distributionChannels: new string[](0) + distributionChannels: new string[](0), + contentRestrictions: new string[](0) }) ) withMintPaymentPolicy("normal", true) // => mint_payment_normal @@ -158,7 +161,7 @@ contract BigBang_Integration_SingleNftCollection is BaseIntegration, Integration MINT & USE LICENSES ////////////////////////////////////////////////////////////////*/ - // Carl mints 1 license for policy "com_deriv_all_true" on Alice's NFT 1 IPAccount +/* // Carl mints 1 license for policy "com_deriv_all_true" on Alice's NFT 1 IPAccount // Carl creates NFT 6 IPAccount // Carl activates the license on his NFT 6 IPAccount, linking as child to Alice's NFT 1 IPAccount { @@ -306,6 +309,6 @@ contract BigBang_Integration_SingleNftCollection is BaseIntegration, Integration metadata, u.carl // caller ); - } + } */ } } diff --git a/test/foundry/integration/shared/LicenseHelper.sol b/test/foundry/integration/shared/LicenseHelper.sol index 8a52ca6d..57d0cb05 100644 --- a/test/foundry/integration/shared/LicenseHelper.sol +++ b/test/foundry/integration/shared/LicenseHelper.sol @@ -34,6 +34,7 @@ struct UMLPolicyGenericParams { bool transferable; string[] territories; string[] distributionChannels; + string[] contentRestrictions; } struct UMLPolicyCommercialParams { @@ -165,6 +166,7 @@ contract Integration_Shared_LicensingHelper { derivativesReciprocal: dparams.derivativesReciprocal, derivativesRevShare: dparams.derivativesRevShare, territories: gparams.territories, + contentRestrictions: gparams.contentRestrictions, distributionChannels: gparams.distributionChannels, royaltyPolicy: cparams.royaltyPolicy }) @@ -193,6 +195,7 @@ contract Integration_Shared_LicensingHelper { derivativesReciprocal: false, derivativesRevShare: 0, territories: gparams.territories, + contentRestrictions: gparams.contentRestrictions, distributionChannels: gparams.distributionChannels, royaltyPolicy: cparams.royaltyPolicy }) @@ -222,6 +225,7 @@ contract Integration_Shared_LicensingHelper { derivativesRevShare: dparams.derivativesRevShare, territories: gparams.territories, distributionChannels: gparams.distributionChannels, + contentRestrictions: gparams.contentRestrictions, royaltyPolicy: address(0) }) ); @@ -246,6 +250,7 @@ contract Integration_Shared_LicensingHelper { derivativesReciprocal: false, derivativesRevShare: 0, territories: gparams.territories, + contentRestrictions: gparams.contentRestrictions, distributionChannels: gparams.distributionChannels, royaltyPolicy: address(0) }) diff --git a/test/foundry/mocks/licensing/MintPaymentPolicyFrameworkManager.sol b/test/foundry/mocks/licensing/MintPaymentPolicyFrameworkManager.sol index 6da56559..8806d0ed 100644 --- a/test/foundry/mocks/licensing/MintPaymentPolicyFrameworkManager.sol +++ b/test/foundry/mocks/licensing/MintPaymentPolicyFrameworkManager.sol @@ -6,20 +6,17 @@ import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { ERC165, IERC165 } from "@openzeppelin/contracts/utils/introspection/ERC165.sol"; // contracts -import { ILinkParamVerifier } from "contracts/interfaces/licensing/ILinkParamVerifier.sol"; -import { IMintParamVerifier } from "contracts/interfaces/licensing/IMintParamVerifier.sol"; -import { ITransferParamVerifier } from "contracts/interfaces/licensing/ITransferParamVerifier.sol"; -import { IPolicyVerifier } from "contracts/interfaces/licensing/IPolicyVerifier.sol"; import { Errors } from "contracts/lib/Errors.sol"; import { Licensing } from "contracts/lib/Licensing.sol"; import { BasePolicyFrameworkManager } from "contracts/modules/licensing/BasePolicyFrameworkManager.sol"; import { ShortStringOps } from "contracts/utils/ShortStringOps.sol"; +import { IPolicyFrameworkManager } from "contracts/interfaces/licensing/IPolicyFrameworkManager.sol"; struct MintPaymentPolicy { bool mustBeTrue; } -contract MintPaymentPolicyFrameworkManager is BasePolicyFrameworkManager, IMintParamVerifier { +contract MintPaymentPolicyFrameworkManager is BasePolicyFrameworkManager { IERC20 public token; uint256 public payment; @@ -36,26 +33,17 @@ contract MintPaymentPolicyFrameworkManager is BasePolicyFrameworkManager, IMintP payment = _payment; } - function supportsInterface( - bytes4 interfaceId - ) public view virtual override(IERC165, BasePolicyFrameworkManager) returns (bool) { - // support only mint param verifier - return - interfaceId == type(IPolicyVerifier).interfaceId || - interfaceId == type(IMintParamVerifier).interfaceId || - super.supportsInterface(interfaceId); - } - function registerPolicy(MintPaymentPolicy calldata mmpol) external returns (uint256 policyId) { emit MintPaymentPolicyAdded(policyId, mmpol); return LICENSE_REGISTRY.registerPolicy(abi.encode(mmpol)); } function processInheritedPolicies( - bytes memory, // ipRights + bytes memory, // aggregator + uint256, // policyId bytes memory // policy - ) external view override returns (bool changedRights, bytes memory newRights) { - return (false, newRights); + ) external pure override returns (bool changedAgg, bytes memory newAggregator) { + return (false, newAggregator); } /// @dev Mock verifies the param by decoding it as a bool. If you want the verifier @@ -76,7 +64,33 @@ contract MintPaymentPolicyFrameworkManager is BasePolicyFrameworkManager, IMintP return mmpol.mustBeTrue; } - function policyToJson(bytes memory policyData) public view returns (string memory) { + function verifyLink( + uint256, // licenseId + address, // caller + address, // ipId + address, // parentIpId + bytes calldata // policyData + ) external pure returns (IPolicyFrameworkManager.VerifyLinkResponse memory) { + return IPolicyFrameworkManager.VerifyLinkResponse({ + isLinkingAllowed: true, + isRoyaltyRequired: false, + royaltyPolicy: address(0), + royaltyDerivativeRevShare: 0 + }); + } + + function verifyTransfer( + uint256, // licenseId + address, // from + address, // to + uint256, // amount + bytes memory // policyData + ) external returns (bool) { + return true; + } + + + function policyToJson(bytes memory policyData) public pure returns (string memory) { return "MintPaymentPolicyFrameworkManager"; } } diff --git a/test/foundry/mocks/licensing/MockPolicyFrameworkManager.sol b/test/foundry/mocks/licensing/MockPolicyFrameworkManager.sol index cddd040f..d58d0f1d 100644 --- a/test/foundry/mocks/licensing/MockPolicyFrameworkManager.sol +++ b/test/foundry/mocks/licensing/MockPolicyFrameworkManager.sol @@ -4,14 +4,11 @@ pragma solidity ^0.8.23; // external import { ERC165, IERC165 } from "@openzeppelin/contracts/utils/introspection/ERC165.sol"; // contracts -import { ILinkParamVerifier } from "contracts/interfaces/licensing/ILinkParamVerifier.sol"; -import { IMintParamVerifier } from "contracts/interfaces/licensing/IMintParamVerifier.sol"; -import { IPolicyVerifier } from "contracts/interfaces/licensing/IPolicyVerifier.sol"; -import { ITransferParamVerifier } from "contracts/interfaces/licensing/ITransferParamVerifier.sol"; import { Errors } from "contracts/lib/Errors.sol"; import { Licensing } from "contracts/lib/Licensing.sol"; import { BasePolicyFrameworkManager } from "contracts/modules/licensing/BasePolicyFrameworkManager.sol"; import { ShortStringOps } from "contracts/utils/ShortStringOps.sol"; +import { IPolicyFrameworkManager } from "contracts/interfaces/licensing/IPolicyFrameworkManager.sol"; struct MockPolicyFrameworkConfig { address licenseRegistry; @@ -28,12 +25,7 @@ struct MockPolicy { bool returnVerifyTransfer; } -contract MockPolicyFrameworkManager is - BasePolicyFrameworkManager, - ILinkParamVerifier, - IMintParamVerifier, - ITransferParamVerifier -{ +contract MockPolicyFrameworkManager is BasePolicyFrameworkManager { MockPolicyFrameworkConfig internal config; event MockPolicyAdded(uint256 indexed policyId, MockPolicy policy); @@ -44,16 +36,6 @@ contract MockPolicyFrameworkManager is config = conf; } - function supportsInterface( - bytes4 interfaceId - ) public view virtual override(IERC165, BasePolicyFrameworkManager) returns (bool) { - if (interfaceId == type(IPolicyVerifier).interfaceId) return true; - if (interfaceId == type(ILinkParamVerifier).interfaceId) return config.supportVerifyLink; - if (interfaceId == type(IMintParamVerifier).interfaceId) return config.supportVerifyMint; - if (interfaceId == type(ITransferParamVerifier).interfaceId) return config.supportVerifyTransfer; - return super.supportsInterface(interfaceId); - } - function registerPolicy(MockPolicy calldata mockPolicy) external returns (uint256 policyId) { emit MockPolicyAdded(policyId, mockPolicy); return LICENSE_REGISTRY.registerPolicy(abi.encode(mockPolicy)); @@ -70,10 +52,10 @@ contract MockPolicyFrameworkManager is address, address, bytes calldata data - ) external pure override returns (ILinkParamVerifier.VerifyLinkResponse memory) { + ) external pure override returns (IPolicyFrameworkManager.VerifyLinkResponse memory) { MockPolicy memory policy = abi.decode(data, (MockPolicy)); return - ILinkParamVerifier.VerifyLinkResponse({ + IPolicyFrameworkManager.VerifyLinkResponse({ isLinkingAllowed: policy.returnVerifyLink, isRoyaltyRequired: false, royaltyPolicy: address(0), @@ -95,11 +77,12 @@ contract MockPolicyFrameworkManager is function policyToJson(bytes memory policyData) public pure returns (string memory) { return "MockPolicyFrameworkManager"; } - + function processInheritedPolicies( - bytes memory ipRights, - bytes memory policy - ) external pure override returns (bool changedRights, bytes memory newRights) { - return (false, ipRights); + bytes memory aggregator, + uint256, // policyId, + bytes memory // policy + ) external pure override returns (bool changedAgg, bytes memory newAggregator) { + return (false, aggregator); } } diff --git a/test/foundry/modules/dispute/ArbitrationPolicySP.t.sol b/test/foundry/modules/dispute/ArbitrationPolicySP.t.sol index 084ae198..50731d1f 100644 --- a/test/foundry/modules/dispute/ArbitrationPolicySP.t.sol +++ b/test/foundry/modules/dispute/ArbitrationPolicySP.t.sol @@ -46,7 +46,8 @@ contract TestArbitrationPolicySP is TestHelper { attribution: false, transferable: true, territories: new string[](0), - distributionChannels: new string[](0) + distributionChannels: new string[](0), + contentRestrictions: new string[](0) }), UMLPolicyCommercialParams({ commercialAttribution: true, diff --git a/test/foundry/modules/dispute/DisputeModule.t.sol b/test/foundry/modules/dispute/DisputeModule.t.sol index 88aee4a5..8c93b32f 100644 --- a/test/foundry/modules/dispute/DisputeModule.t.sol +++ b/test/foundry/modules/dispute/DisputeModule.t.sol @@ -67,7 +67,8 @@ contract TestDisputeModule is TestHelper { attribution: false, transferable: true, territories: new string[](0), - distributionChannels: new string[](0) + distributionChannels: new string[](0), + contentRestrictions: new string[](0) }), UMLPolicyCommercialParams({ commercialAttribution: true, diff --git a/test/foundry/modules/licensing/README.md b/test/foundry/modules/licensing/README.md index 7aecb2dc..88c412f1 100644 --- a/test/foundry/modules/licensing/README.md +++ b/test/foundry/modules/licensing/README.md @@ -28,7 +28,7 @@ Alice burns L1, P1 is set in IP2 1) P1 does not allow for derivatives 1.1) Don tries to mint a license from P1 in IP2 -> fails ✅ 1.2) Alice tries to mint a license from P1 in IP2 -> fails ✅ -1.3) Alice tries to set a policy --> fails???? ✅ +1.3) Alice tries to set a policy --> fails ✅ // Edge case, later on, Alice buys the right from her licensor to make derivatives // Setting the parent again should work in this case, P2 should be @@ -43,20 +43,6 @@ Alice burns L1, P1 is set in IP2 2.3) Alice tries to set P2 in IP2 -> Fails, reciprocal means no different policies allowed, and you cannot add the same policy twice ✅ -# Commercial -Bob owns IP1 -Bob creates a license L1 with P1 -Alice owns IP2 -Alice burns L1, P1 is set in IP2 - -1) P1 is Non Commercial (assume derivatives allowed) -1.1) Alice tries to mint a commercial license -> fail -1.2) Alice tries to add a commercial policy -> fail - -1) P1 is Commercial (assume derivatives allowed) -1.1) Alice tries to mint a non commercial license -> OK? (if the only derivatives of derivatives are reciprocal, this fails) -1.2) Alice tries to add a commercial policy -> OK? (if the only derivatives of derivatives are reciprocal, this fails) - # Setting multiple parents Bob owns IP1, IP2 and IP3 @@ -66,27 +52,32 @@ Bob mints L3 with P3 from IP3 Alice owns IP4 Alice wants to burn L1,L2 and L3 to link as parents for IP4 -1) If one is reciprocal, all Ps have to be the same or fail. Result is verify all 3, set 1 policy set for IP4🚧 -2) If no reciprocal, some parameters make the operation impossible, others are indifferent as long as validations pass🚧 - --- Attribution is indifferent --- Transferable is ??? --- Commercial use: must be all equal or fail --- Commercial attribution: indifferent --- Commercializers: - - All empty -> OK, stays empty - - 1 has values -> OK, the 1 with values wins - - several have same values -> OK, same values - - some have different values -> fail --- Commercial rev share: set all --- derivatives allowed: derivatives are only allowed through 1) --- derivativesAttribution: indifferent --- derivativesApproval: verify the ones that have it --- derivativesReciprocal: see 1) --- derivativesRevShare: execute all, but it should be case 1) --- Territories: same logic as Commercializers --- Distribution Channels: same logic as Commercializers --- Content Restrictions: same logic as Commercializers + +1) Reciprocal +1.1) All licenses have the same reciprocal policy -> OK, result has 1 Policy +1.2) Different policies, but at least 1 reciprocal -> Fail + +2) NonReciprocal +1.1) All licenses have the same reciprocal policy -> OK, result has 1 Policy +1.2) Different policies, but at least 1 reciprocal -> Fail + + +| Parameter | Multi parent eval | Reason | +|-------------------------|-----------------------------------------------------|-------------------------------------------------------------------------------------------------------------------| +| Attribution | Indifferent | Holder of all licenses must attribute the ones demanding it. Disputable | +| Transferable | Indifferent | Property of the license, not the IP | +| Commercial use | Equal or revert | One would infringe the other | +| Commercial attribution | Indifferent | Holder of all licenses must attribute the ones demanding it. Disputable | +| Commercializers | Indifferent | OK if verification checks for all licenses succeed | +| Commercial Rev Share | Indifferent | Licensing proccess must set all. Verifications must pass | +| Derivatives | Equal or revert | One would infringe the other | +| Derivatives Attribution | Indifferent | Holder of all licenses must attribute the ones demanding it. Disputable | +| Derivatives Approval | Indifferent | OK if verification checks for all licenses succeed | +| Derivatives Reciprocal | Equal or revert. If both true, policy must be equal | One would infringe the other | +| Derivatives Rev Share | Indifferent | Licensing proccess must set all. Verifications must pass | +| Territories | All equal, or some empty and the rest equal | All permissive is OK. Some permissive except some with same restrictions OK. Different restrictions is a conflict | +| Distribution Channels | Same as previous | Same as previous | +| Content Restrictions | Same as previous | Same as previous | # INTEGRATION diff --git a/test/foundry/modules/licensing/UMLPolicyFramework.compat.t.sol b/test/foundry/modules/licensing/UMLPolicyFramework.derivation.t.sol similarity index 54% rename from test/foundry/modules/licensing/UMLPolicyFramework.compat.t.sol rename to test/foundry/modules/licensing/UMLPolicyFramework.derivation.t.sol index 582fd896..ac037430 100644 --- a/test/foundry/modules/licensing/UMLPolicyFramework.compat.t.sol +++ b/test/foundry/modules/licensing/UMLPolicyFramework.derivation.t.sol @@ -7,7 +7,7 @@ import { Licensing } from "contracts/lib/Licensing.sol"; import { Strings } from "@openzeppelin/contracts/utils/Strings.sol"; import { Errors } from "contracts/lib/Errors.sol"; import { UMLFrameworkErrors } from "contracts/lib/UMLFrameworkErrors.sol"; -import { IUMLPolicyFrameworkManager, UMLPolicy, UMLRights } from "contracts/interfaces/licensing/IUMLPolicyFrameworkManager.sol"; +import { IUMLPolicyFrameworkManager, UMLPolicy } from "contracts/interfaces/licensing/IUMLPolicyFrameworkManager.sol"; import { UMLPolicyFrameworkManager } from "contracts/modules/licensing/UMLPolicyFrameworkManager.sol"; import { MockAccessController } from "test/foundry/mocks/MockAccessController.sol"; import { ERC6551Registry } from "lib/reference/src/ERC6551Registry.sol"; @@ -28,21 +28,21 @@ contract UMLPolicyFrameworkCompatibilityTest is TestHelper { address internal alice = address(0x222); address internal ipId2; address internal don = address(0x333); - address internal licenseHolder = address(0x101); - string[] internal emptyStringArray = new string[](0); - uint256 internal policyID; - mapping(string => UMLPolicy) internal policies; - mapping(string => uint256) internal policyIDs; - - modifier withPolicy(string memory name, bool commercial, bool derivatives, bool reciprocal) { - _savePolicyInMapping(name, commercial, derivatives, reciprocal); - policyIDs[name] = umlFramework.registerPolicy(policies[name]); + + modifier withUMLPolicySimple( + string memory name, + bool commercial, + bool derivatives, + bool reciprocal + ) { + _mapUMLPolicySimple(name, commercial, derivatives, reciprocal); + _addUMLPolicyFromMapping(name, address(umlFramework)); _; } modifier withAliceOwningDerivativeIp2(string memory policyName) { vm.prank(bob); - uint256 licenseId = licenseRegistry.mintLicense(policyIDs[policyName], ipId1, 1, alice); + uint256 licenseId = licenseRegistry.mintLicense(_getUmlPolicyId(policyName), ipId1, 1, alice); vm.prank(alice); uint256[] memory licenseIds = new uint256[](1); licenseIds[0] = licenseId; @@ -79,156 +79,139 @@ contract UMLPolicyFrameworkCompatibilityTest is TestHelper { vm.label(LIQUID_SPLIT_MAIN, "LIQUID_SPLIT_MAIN"); } - /// STARTING FROM AN ORIGINAL WORK + ///////////////////////////////////////////////////////////// + ////// SETTING POLICIES IN ORIGINAL WORK (NO PARENTS) ////// + ///////////////////////////////////////////////////////////// + function test_UMLPolicyFramework_originalWork_bobAddsDifferentPoliciesAndAliceMints() - withPolicy("comm_deriv", true, true, false) - withPolicy("comm_non_deriv", true, false, false) - public { + public + withUMLPolicySimple("comm_deriv", true, true, false) + withUMLPolicySimple("comm_non_deriv", true, false, false) + { // Bob can add different policies on IP1 without compatibility checks. vm.startPrank(bob); - licenseRegistry.addPolicyToIp(ipId1, policyIDs["comm_deriv"]); - licenseRegistry.addPolicyToIp(ipId1, policyIDs["comm_non_deriv"]); + licenseRegistry.addPolicyToIp(ipId1, _getUmlPolicyId("comm_deriv")); + licenseRegistry.addPolicyToIp(ipId1, _getUmlPolicyId("comm_non_deriv")); vm.stopPrank(); bool isInherited = false; assertEq(licenseRegistry.totalPoliciesForIp(isInherited, ipId1), 2); - assertTrue(licenseRegistry.isPolicyIdSetForIp(isInherited, ipId1, policyIDs["comm_deriv"]), "comm_deriv not set"); - assertTrue(licenseRegistry.isPolicyIdSetForIp(isInherited, ipId1, policyIDs["comm_non_deriv"]), "comm_non_deriv not set"); + assertTrue( + licenseRegistry.isPolicyIdSetForIp(isInherited, ipId1, _getUmlPolicyId("comm_deriv")), + "comm_deriv not set" + ); + assertTrue( + licenseRegistry.isPolicyIdSetForIp(isInherited, ipId1, _getUmlPolicyId("comm_non_deriv")), + "comm_non_deriv not set" + ); // Others can mint licenses to make derivatives of IP1 from each different policy, // as long as they pass the verifications - uint256 licenseId1 = licenseRegistry.mintLicense(policyIDs["comm_deriv"], ipId1, 1, don); + uint256 licenseId1 = licenseRegistry.mintLicense(_getUmlPolicyId("comm_deriv"), ipId1, 1, don); assertEq(licenseRegistry.balanceOf(don, licenseId1), 1, "Don doesn't have license1"); - - uint256 licenseId2 = licenseRegistry.mintLicense(policyIDs["comm_non_deriv"], ipId1, 1, don); + + uint256 licenseId2 = licenseRegistry.mintLicense(_getUmlPolicyId("comm_non_deriv"), ipId1, 1, don); assertEq(licenseRegistry.balanceOf(don, licenseId2), 1, "Don doesn't have license2"); } - /// TODO: STARTING FROM AN ORIGINAL WORK, WITH APPROVALS and UPFRONT PAY - - function test_UMLPolicyFramework_originalWork_bobMintsWithDifferentPolicies() - withPolicy("comm_deriv", true, true, false) - withPolicy("comm_non_deriv", true, false, false) - public { + public + withUMLPolicySimple("comm_deriv", true, true, false) + withUMLPolicySimple("comm_non_deriv", true, false, false) + { // Bob can add different policies on IP1 without compatibility checks. vm.startPrank(bob); - uint256 licenseId1 = licenseRegistry.mintLicense(policyIDs["comm_deriv"], ipId1, 1, don); + uint256 licenseId1 = licenseRegistry.mintLicense(_getUmlPolicyId("comm_deriv"), ipId1, 1, don); assertEq(licenseRegistry.balanceOf(don, licenseId1), 1, "Don doesn't have license1"); - - uint256 licenseId2 = licenseRegistry.mintLicense(policyIDs["comm_non_deriv"], ipId1, 1, don); + + uint256 licenseId2 = licenseRegistry.mintLicense(_getUmlPolicyId("comm_non_deriv"), ipId1, 1, don); assertEq(licenseRegistry.balanceOf(don, licenseId2), 1, "Don doesn't have license2"); vm.stopPrank(); } - function test_UMLPolicyFramework_originalWork_bobSetsPoliciesThenCompatibleParent() - withPolicy("comm_deriv", true, true, false) - withPolicy("comm_non_deriv", true, false, false) - public { + public + withUMLPolicySimple("comm_deriv", true, true, false) + withUMLPolicySimple("comm_non_deriv", true, false, false) + { // TODO: This works if all policies compatible. // Can bob disable some policies? } + ///////////////////////////////////////////////////////////////// + ////// SETTING POLICIES IN DERIVATIVE WORK (WITH PARENTS) ////// + ///////////////////////////////////////////////////////////////// - // STARTING FROM DERIVATIVE WORK function test_UMLPolicyFramework_derivative_revert_cantMintDerivativeOfDerivative() - withPolicy("comm_non_deriv", true, false, false) + public + withUMLPolicySimple("comm_non_deriv", true, false, false) withAliceOwningDerivativeIp2("comm_non_deriv") - public { + { vm.expectRevert(Errors.LicenseRegistry__MintLicenseParamFailed.selector); vm.prank(don); - licenseRegistry.mintLicense(policyIDs["comm_non_deriv"], ipId2, 1, don); + licenseRegistry.mintLicense(_getUmlPolicyId("comm_non_deriv"), ipId2, 1, don); vm.expectRevert(Errors.LicenseRegistry__MintLicenseParamFailed.selector); vm.prank(alice); - licenseRegistry.mintLicense(policyIDs["comm_non_deriv"], ipId2, 1, alice); + licenseRegistry.mintLicense(_getUmlPolicyId("comm_non_deriv"), ipId2, 1, alice); } function test_UMLPolicyFramework_derivative_revert_AliceCantSetPolicyOnDerivativeOfDerivative() - withPolicy("comm_non_deriv", true, false, false) - withPolicy("comm_deriv", true, true, false) + public + withUMLPolicySimple("comm_non_deriv", true, false, false) + withUMLPolicySimple("comm_deriv", true, true, false) withAliceOwningDerivativeIp2("comm_non_deriv") - public { - - vm.expectRevert( - Errors.LicenseRegistry__DerivativesCannotAddPolicy.selector - ); + { + vm.expectRevert(Errors.LicenseRegistry__DerivativesCannotAddPolicy.selector); vm.prank(alice); - licenseRegistry.addPolicyToIp(ipId2, policyIDs["comm_deriv"]); + licenseRegistry.addPolicyToIp(ipId2, _getUmlPolicyId("comm_deriv")); - _savePolicyInMapping("other_policy", true, true, false); - policies["other_policy"].attribution = false; - policyIDs["other_policy"] = umlFramework.registerPolicy(policies["other_policy"]); + _mapUMLPolicySimple("other_policy", true, true, false); + _getMappedUmlPolicy("other_policy").attribution = false; + _addUMLPolicyFromMapping("other_policy", address(umlFramework)); - vm.expectRevert( - Errors.LicenseRegistry__DerivativesCannotAddPolicy.selector - ); + vm.expectRevert(Errors.LicenseRegistry__DerivativesCannotAddPolicy.selector); vm.prank(alice); - licenseRegistry.addPolicyToIp(ipId2, policyIDs["other_policy"]); + licenseRegistry.addPolicyToIp(ipId2, _getUmlPolicyId("other_policy")); } - // Reciprocal + ///////////////////////////////////////////////////////////////// + ////// RECIPROCAL DERIVATIVES ////// + ///////////////////////////////////////////////////////////////// function test_UMLPolicyFramework_reciprocal_DonMintsLicenseFromIp2() - withPolicy("comm_reciprocal", true, true, true) + public + withUMLPolicySimple("comm_reciprocal", true, true, true) withAliceOwningDerivativeIp2("comm_reciprocal") - public { + { vm.prank(don); - uint256 licenseId = licenseRegistry.mintLicense(policyIDs["comm_reciprocal"], ipId2, 1, don); + uint256 licenseId = licenseRegistry.mintLicense(_getUmlPolicyId("comm_reciprocal"), ipId2, 1, don); assertEq(licenseRegistry.balanceOf(don, licenseId), 1, "Don doesn't have license"); - } function test_UMLPolicyFramework_reciprocal_AliceMintsLicenseForP1inIP2() - withPolicy("comm_reciprocal", true, true, true) + public + withUMLPolicySimple("comm_reciprocal", true, true, true) withAliceOwningDerivativeIp2("comm_reciprocal") - public { + { vm.prank(alice); - uint256 licenseId = licenseRegistry.mintLicense(policyIDs["comm_reciprocal"], ipId2, 1, alice); + uint256 licenseId = licenseRegistry.mintLicense(_getUmlPolicyId("comm_reciprocal"), ipId2, 1, alice); assertEq(licenseRegistry.balanceOf(alice, licenseId), 1, "Alice doesn't have license"); } function test_UMLPolicyFramework_reciprocal_revert_AliceTriesToSetPolicyInReciprocalDeriv() - withPolicy("comm_reciprocal", true, true, true) - withPolicy("other_policy", true, true, false) + public + withUMLPolicySimple("comm_reciprocal", true, true, true) + withUMLPolicySimple("other_policy", true, true, false) withAliceOwningDerivativeIp2("comm_reciprocal") - public { - vm.expectRevert( - Errors.LicenseRegistry__DerivativesCannotAddPolicy.selector - ); + { + vm.expectRevert(Errors.LicenseRegistry__DerivativesCannotAddPolicy.selector); vm.prank(alice); - licenseRegistry.addPolicyToIp(ipId2, policyIDs["other_policy"]); - vm.expectRevert( - Errors.LicenseRegistry__DerivativesCannotAddPolicy.selector - ); + licenseRegistry.addPolicyToIp(ipId2, _getUmlPolicyId("other_policy")); + vm.expectRevert(Errors.LicenseRegistry__DerivativesCannotAddPolicy.selector); vm.prank(alice); - licenseRegistry.addPolicyToIp(ipId2, policyIDs["comm_reciprocal"]); + licenseRegistry.addPolicyToIp(ipId2, _getUmlPolicyId("comm_reciprocal")); } - - function _savePolicyInMapping( - string memory name, - bool commercial, - bool derivatives, - bool reciprocal - ) internal { - address royaltyPolicy = !commercial ? address(0) : address(royaltyPolicyLS); - policies[name] = UMLPolicy({ - attribution: true, - transferable: true, - commercialUse: commercial, - commercialAttribution: false, - commercializers: emptyStringArray, - commercialRevShare: 0, - derivativesAllowed: derivatives, - derivativesAttribution: false, - derivativesApproval: false, - derivativesReciprocal: reciprocal, - derivativesRevShare: 0, - territories: emptyStringArray, - distributionChannels: emptyStringArray, - royaltyPolicy: royaltyPolicy - }); + function _addUMLPolicyFromMapping(string memory name) internal { + } - - } diff --git a/test/foundry/modules/licensing/UMLPolicyFramework.multi-parent.sol b/test/foundry/modules/licensing/UMLPolicyFramework.multi-parent.sol new file mode 100644 index 00000000..0c73af22 --- /dev/null +++ b/test/foundry/modules/licensing/UMLPolicyFramework.multi-parent.sol @@ -0,0 +1,365 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.23; + +import { Test } from "forge-std/Test.sol"; +import { LicenseRegistry } from "contracts/registries/LicenseRegistry.sol"; +import { Licensing } from "contracts/lib/Licensing.sol"; +import { Strings } from "@openzeppelin/contracts/utils/Strings.sol"; +import { Errors } from "contracts/lib/Errors.sol"; +import { UMLFrameworkErrors } from "contracts/lib/UMLFrameworkErrors.sol"; +import { IUMLPolicyFrameworkManager, UMLPolicy, UMLAggregator } from "contracts/interfaces/licensing/IUMLPolicyFrameworkManager.sol"; +import { UMLPolicyFrameworkManager } from "contracts/modules/licensing/UMLPolicyFrameworkManager.sol"; +import { MockAccessController } from "test/foundry/mocks/MockAccessController.sol"; +import { ERC6551Registry } from "lib/reference/src/ERC6551Registry.sol"; +import { IPAccountImpl } from "contracts/IPAccountImpl.sol"; +import { IPAccountRegistry } from "contracts/registries/IPAccountRegistry.sol"; +import { MockERC721 } from "test/foundry/mocks/MockERC721.sol"; +import { TestHelper } from "test/utils/TestHelper.sol"; + +contract UMLPolicyFrameworkMultiParentTest is TestHelper { + + UMLPolicyFrameworkManager internal umlFramework; + string internal licenseUrl = "https://example.com/license"; + address internal bob = address(0x111); + address internal ipId1; + address internal ipId2; + address internal ipId3; + address internal alice = address(0x222); + address internal ipId4; + + uint256[] internal licenses; + + mapping(address => address) internal ipIdToOwner; + + modifier withUMLPolicySimple(string memory name, bool commercial, bool derivatives, bool reciprocal) { + _mapUMLPolicySimple(name, commercial, derivatives, reciprocal); + _addUMLPolicyFromMapping(name, address(umlFramework)); + _; + } + + modifier withLicense(string memory policyName, address ipId, address owner) { + uint256 policyId = _getUmlPolicyId(policyName); + vm.prank(ipIdToOwner[ipId]); + uint256 licenseId = licenseRegistry.mintLicense(policyId, ipId, 1, owner); + licenses.push(licenseId); + _; + } + + function setUp() public override { + TestHelper.setUp(); + nft = erc721.ape; + + umlFramework = new UMLPolicyFrameworkManager( + address(accessController), + address(ipAccountRegistry), + address(licenseRegistry), + "UMLPolicyFrameworkManager", + licenseUrl + ); + + licenseRegistry.registerPolicyFrameworkManager(address(umlFramework)); + + nft.mintId(bob, 1); + nft.mintId(bob, 2); + nft.mintId(bob, 3); + nft.mintId(alice, 4); + + ipId1 = ipAccountRegistry.registerIpAccount(block.chainid, address(nft), 1); + ipIdToOwner[ipId1] = bob; + ipId2 = ipAccountRegistry.registerIpAccount(block.chainid, address(nft), 2); + ipIdToOwner[ipId2] = bob; + ipId3 = ipAccountRegistry.registerIpAccount(block.chainid, address(nft), 3); + ipIdToOwner[ipId3] = bob; + ipId4 = ipAccountRegistry.registerIpAccount(block.chainid, address(nft), 4); + ipIdToOwner[ipId4] = alice; + vm.label(bob, "Bob"); + vm.label(alice, "Alice"); + vm.label(ipId1, "IP1"); + vm.label(ipId2, "IP2"); + vm.label(ipId3, "IP3"); + vm.label(ipId4, "IP4"); + } + + function test_UMLPolicyFramework_multiParent_AliceSets3Parents_SamePolicyReciprocal() + withUMLPolicySimple("reciprocal", true, true, true) + withLicense("reciprocal", ipId1, alice) + withLicense("reciprocal", ipId2, alice) + withLicense("reciprocal", ipId3, alice) + public { + vm.prank(alice); + licenseRegistry.linkIpToParents(licenses, ipId4, alice); + assertEq(licenseRegistry.totalParentsForIpId(ipId4), 3); + address[] memory parents = licenseRegistry.parentIpIds(ipId4); + for (uint256 i = 0; i < licenses.length; i++) { + Licensing.License memory license = licenseRegistry.license(licenses[i]); + assertEq(parents[i], license.licensorIpId); + } + assertEq(licenseRegistry.totalPoliciesForIp(false, ipId4), 0); + assertEq(licenseRegistry.totalPoliciesForIp(true, ipId4), 1); + assertTrue(licenseRegistry.isPolicyIdSetForIp(true, ipId4, _getUmlPolicyId("reciprocal"))); + } + + function test_UMLPolicyFramework_multiParent_revert_AliceSets3Parents_OneNonReciprocal() + withUMLPolicySimple("reciprocal", true, true, true) + withUMLPolicySimple("non_reciprocal", true, true, false) + withLicense("reciprocal", ipId1, alice) + withLicense("non_reciprocal", ipId2, alice) + withLicense("reciprocal", ipId3, alice) + public { + vm.expectRevert( + UMLFrameworkErrors.UMLPolicyFrameworkManager__ReciprocalValueMismatch.selector + ); + vm.prank(alice); + licenseRegistry.linkIpToParents(licenses, ipId4, alice); + } + + function test_UMLPolicyFramework_multiParent_revert_AliceSets3Parents_3ReciprocalButDifferent() + withUMLPolicySimple("reciprocal", true, true, true) + withLicense("reciprocal", ipId1, alice) + withLicense("reciprocal", ipId2, alice) + public { + // Save a new policy (change some value to change the policyId) + _mapUMLPolicySimple("other", true, true, true); + _getMappedUmlPolicy("other").attribution = !_getMappedUmlPolicy("other").attribution; + _addUMLPolicyFromMapping("other", address(umlFramework)); + vm.prank(ipId3); + licenses.push(licenseRegistry.mintLicense(_getUmlPolicyId("other"), ipId3, 1, alice)); + vm.expectRevert( + UMLFrameworkErrors.UMLPolicyFrameworkManager__ReciprocalButDifferentPolicyIds.selector + ); + vm.prank(alice); + licenseRegistry.linkIpToParents(licenses, ipId4, alice); + } + + function test_UMLPolicyFramework_multiParent_NonReciprocalCommercial() public { + // First we create 2 policies. + _mapUMLPolicySimple({name: "pol_a", commercial: true, derivatives: true, reciprocal: false}); + UMLPolicy memory polA = _getMappedUmlPolicy("pol_a"); + _mapUMLPolicySimple({name: "pol_b", commercial: true, derivatives: true, reciprocal: false}); + UMLPolicy memory polB = _getMappedUmlPolicy("pol_b"); + // We set some indifferents + polA.attribution = true; + polB.attribution = !polA.attribution; + polA.transferable = true; + polB.transferable = !polA.transferable; + // Commercial use (success) + _testSuccessCompat(polA, polB, 2); + } + + function test_UMLPolicyFramework_multiParent_revert_NonReciprocalCommercial() public { + // First we create 2 policies. + _mapUMLPolicySimple({name: "pol_a", commercial: true, derivatives: true, reciprocal: false}); + UMLPolicy memory polA = _getMappedUmlPolicy("pol_a"); + _mapUMLPolicySimple({name: "pol_b", commercial: true, derivatives: true, reciprocal: false}); + UMLPolicy memory polB = _getMappedUmlPolicy("pol_b"); + // We set some indifferents + polA.attribution = true; + polB.attribution = !polA.attribution; + polA.transferable = true; + polB.transferable = !polA.transferable; + // Commercial use (revert) + polA.commercialUse = true; + polB.commercialUse = false; + polB.royaltyPolicy = address(0x0); + _testRevertCompat(polA, polB, UMLFrameworkErrors.UMLPolicyFrameworkManager__CommercialValueMismatch.selector); + } + + function test_UMLPolicyFramework_multiParent_NonReciprocalDerivatives() public { + // First we create 2 policies. + _mapUMLPolicySimple({name: "pol_a", commercial: true, derivatives: true, reciprocal: false}); + UMLPolicy memory polA = _getMappedUmlPolicy("pol_a"); + _mapUMLPolicySimple({name: "pol_b", commercial: true, derivatives: true, reciprocal: false}); + UMLPolicy memory polB = _getMappedUmlPolicy("pol_b"); + // We set some indifferents + polA.attribution = true; + polB.attribution = !polA.attribution; + polA.transferable = true; + polB.transferable = !polA.transferable; + + // Derivatives (success) + _testSuccessCompat(polA, polB, 2); + } + + function test_UMLPolicyFramework_multiParent_revert_NonReciprocalDerivatives() public { + // First we create 2 policies. + _mapUMLPolicySimple({name: "pol_a", commercial: true, derivatives: true, reciprocal: false}); + UMLPolicy memory polA = _getMappedUmlPolicy("pol_a"); + _mapUMLPolicySimple({name: "pol_b", commercial: true, derivatives: true, reciprocal: false}); + UMLPolicy memory polB = _getMappedUmlPolicy("pol_b"); + // We set some indifferents + polA.attribution = true; + polB.attribution = !polA.attribution; + polA.transferable = true; + polB.transferable = !polA.transferable; + + // Derivatives (revert) + polA.derivativesAllowed = true; + polB.derivativesAllowed = !polA.derivativesAllowed; + _testRevertCompat(polA, polB, UMLFrameworkErrors.UMLPolicyFrameworkManager__DerivativesValueMismatch.selector); + } + + function test_UMLPolicyFramework_multiParent_NonReciprocalTerritories() public { + // First we create 2 policies. + _mapUMLPolicySimple({name: "pol_a", commercial: true, derivatives: true, reciprocal: false}); + UMLPolicy memory polA = _getMappedUmlPolicy("pol_a"); + _mapUMLPolicySimple({name: "pol_b", commercial: true, derivatives: true, reciprocal: false}); + UMLPolicy memory polB = _getMappedUmlPolicy("pol_b"); + + // Territories (success same) + polA.territories = new string[](1); + polA.territories[0] = "US"; + polB.territories = new string[](1); + polB.territories[0] = "US"; + polB.attribution = !polB.attribution; // generates different policyId + _testSuccessCompat(polA, polB, 2); + + // Territories (success empty) + polA.territories = new string[](0); + polB.territories = new string[](0); + polB.transferable = !polB.transferable; // generates different policyId + _testSuccessCompat(polA, polB, 4); + + } + + function test_UMLPolicyFramework_multiParent_revert_NonReciprocalTerritories() public { + // First we create 2 policies. + _mapUMLPolicySimple({name: "pol_a", commercial: true, derivatives: true, reciprocal: false}); + UMLPolicy memory polA = _getMappedUmlPolicy("pol_a"); + _mapUMLPolicySimple({name: "pol_b", commercial: true, derivatives: true, reciprocal: false}); + UMLPolicy memory polB = _getMappedUmlPolicy("pol_b"); + // We set some indifferents + polA.attribution = true; + polB.attribution = !polA.attribution; + polA.transferable = true; + polB.transferable = !polA.transferable; + + // Territories (revert) + polA.territories = new string[](1); + polA.territories[0] = "US"; + polB.territories = new string[](1); + polB.territories[0] = "UK"; + _testRevertCompat(polA, polB, UMLFrameworkErrors.UMLPolicyFrameworkManager__StringArrayMismatch.selector); + } + + function test_UMLPolicyFramework_multiParent_NonReciprocalDistributionChannels() public { + // First we create 2 policies. + _mapUMLPolicySimple({name: "pol_a", commercial: true, derivatives: true, reciprocal: false}); + UMLPolicy memory polA = _getMappedUmlPolicy("pol_a"); + _mapUMLPolicySimple({name: "pol_b", commercial: true, derivatives: true, reciprocal: false}); + UMLPolicy memory polB = _getMappedUmlPolicy("pol_b"); + + // Territories (success same) + polA.distributionChannels = new string[](1); + polA.distributionChannels[0] = "web"; + polB.distributionChannels = new string[](1); + polB.distributionChannels[0] = "web"; + polB.attribution = !polB.attribution; // generates different policyId + _testSuccessCompat(polA, polB, 2); + + // Territories (success empty) + polA.distributionChannels = new string[](0); + polB.distributionChannels = new string[](0); + polB.transferable = !polB.transferable; // generates different policyId + _testSuccessCompat(polA, polB, 4); + + } + + function test_UMLPolicyFramework_multiParent_revert_NonReciprocalDistributionChannels() public { + // First we create 2 policies. + _mapUMLPolicySimple({name: "pol_a", commercial: true, derivatives: true, reciprocal: false}); + UMLPolicy memory polA = _getMappedUmlPolicy("pol_a"); + _mapUMLPolicySimple({name: "pol_b", commercial: true, derivatives: true, reciprocal: false}); + UMLPolicy memory polB = _getMappedUmlPolicy("pol_b"); + // We set some indifferents + polA.attribution = true; + polB.attribution = !polA.attribution; + polA.transferable = true; + polB.transferable = !polA.transferable; + + // Distribution channels (revert) + polA.distributionChannels = new string[](1); + polA.distributionChannels[0] = "web"; + polB.distributionChannels = new string[](1); + polB.distributionChannels[0] = "mobile"; + _testRevertCompat(polA, polB, UMLFrameworkErrors.UMLPolicyFrameworkManager__StringArrayMismatch.selector); + } + + function test_UMLPolicyFramework_multiParent_NonReciprocalContentRestrictions() public { + // First we create 2 policies. + _mapUMLPolicySimple({name: "pol_a", commercial: true, derivatives: true, reciprocal: false}); + UMLPolicy memory polA = _getMappedUmlPolicy("pol_a"); + _mapUMLPolicySimple({name: "pol_b", commercial: true, derivatives: true, reciprocal: false}); + UMLPolicy memory polB = _getMappedUmlPolicy("pol_b"); + + // Territories (success same) + polA.contentRestrictions = new string[](1); + polA.contentRestrictions[0] = "web"; + polB.contentRestrictions = new string[](1); + polB.contentRestrictions[0] = "web"; + polB.attribution = !polB.attribution; // generates different policyId + _testSuccessCompat(polA, polB, 2); + + // Territories (success empty) + polA.contentRestrictions = new string[](0); + polB.contentRestrictions = new string[](0); + polB.transferable = !polB.transferable; // generates different policyId + _testSuccessCompat(polA, polB, 4); + + } + + function test_UMLPolicyFramework_multiParent_revert_NonReciprocalContentRestrictions() public { + // First we create 2 policies. + _mapUMLPolicySimple({name: "pol_a", commercial: true, derivatives: true, reciprocal: false}); + UMLPolicy memory polA = _getMappedUmlPolicy("pol_a"); + _mapUMLPolicySimple({name: "pol_b", commercial: true, derivatives: true, reciprocal: false}); + UMLPolicy memory polB = _getMappedUmlPolicy("pol_b"); + // We set some indifferents + polA.attribution = true; + polB.attribution = !polA.attribution; + polA.transferable = true; + polB.transferable = !polA.transferable; + + // Content restrictions (revert) + polA.contentRestrictions = new string[](1); + polA.contentRestrictions[0] = "adult"; + polB.contentRestrictions = new string[](1); + polB.contentRestrictions[0] = "child"; + _testRevertCompat(polA, polB, UMLFrameworkErrors.UMLPolicyFrameworkManager__StringArrayMismatch.selector); + } + + function _testRevertCompat(UMLPolicy memory polA, UMLPolicy memory polB, bytes4 errorSelector) internal { + uint256 polAId = umlFramework.registerPolicy(polA); + vm.prank(ipId1); + licenses.push(licenseRegistry.mintLicense(polAId, ipId1, 1, alice)); + uint256 polBId = umlFramework.registerPolicy(polB); + vm.prank(ipId2); + licenses.push(licenseRegistry.mintLicense(polBId, ipId2, 1, alice)); + vm.expectRevert(errorSelector); + vm.prank(alice); + licenseRegistry.linkIpToParents(licenses, ipId4, alice); + licenses = new uint256[](0); + } + + function _testSuccessCompat(UMLPolicy memory polA, UMLPolicy memory polB, uint256 expectedPolicies) internal { + uint256 polAId = umlFramework.registerPolicy(polA); + vm.prank(ipId1); + licenses.push(licenseRegistry.mintLicense(polAId, ipId1, 1, alice)); + uint256 polBId = umlFramework.registerPolicy(polB); + vm.prank(ipId2); + licenses.push(licenseRegistry.mintLicense(polBId, ipId2, 1, alice)); + vm.prank(alice); + licenseRegistry.linkIpToParents(licenses, ipId4, alice); + assertEq(licenseRegistry.totalParentsForIpId(ipId4), 2); + address[] memory parents = licenseRegistry.parentIpIds(ipId4); + for (uint256 i = 0; i < licenses.length; i++) { + Licensing.License memory license = licenseRegistry.license(licenses[i]); + assertEq(parents[i], license.licensorIpId); + } + assertEq(licenseRegistry.totalPoliciesForIp(false, ipId4), 0); + assertEq(licenseRegistry.totalPoliciesForIp(true, ipId4), expectedPolicies); + assertTrue(licenseRegistry.isPolicyIdSetForIp(true, ipId4, polAId)); + assertTrue(licenseRegistry.isPolicyIdSetForIp(true, ipId4, polBId)); + licenses = new uint256[](0); // To call this function multiple times + } + +} diff --git a/test/foundry/modules/licensing/UMLPolicyFramework.t.sol b/test/foundry/modules/licensing/UMLPolicyFramework.t.sol index c3590153..01fae245 100644 --- a/test/foundry/modules/licensing/UMLPolicyFramework.t.sol +++ b/test/foundry/modules/licensing/UMLPolicyFramework.t.sol @@ -26,7 +26,6 @@ contract UMLPolicyFrameworkTest is TestHelper { address public ipId2; address public ipOwner = vm.addr(1); address public licenseHolder = address(0x101); - string[] public emptyStringArray = new string[](0); function setUp() public override { TestHelper.setUp(); @@ -48,7 +47,7 @@ contract UMLPolicyFrameworkTest is TestHelper { ipId2 = ipAccountRegistry.registerIpAccount(block.chainid, address(nft), 2); } - function test_UMLPolicyFrameworkManager_valuesSetCorrectly() public { + function test_UMLPolicyFrameworkManager__valuesSetCorrectly() public { string[] memory territories = new string[](2); territories[0] = "test1"; territories[1] = "test2"; @@ -68,6 +67,7 @@ contract UMLPolicyFrameworkTest is TestHelper { derivativesRevShare: 0, territories: territories, distributionChannels: distributionChannels, + contentRestrictions: emptyStringArray, royaltyPolicy: address(0xbeef) }); uint256 policyId = umlFramework.registerPolicy(umlPolicy); @@ -75,9 +75,11 @@ contract UMLPolicyFrameworkTest is TestHelper { assertEq(keccak256(abi.encode(policy)), keccak256(abi.encode(umlPolicy))); } - // COMMERCIAL USE TERMS + ///////////////////////////////////////////////////////////// + ////// COMMERCIAL USE TERMS ////// + ///////////////////////////////////////////////////////////// - function test_UMLPolicyFrameworkManager_commercialUse_disallowed_revert_settingIncompatibleTerms() public { + function test_UMLPolicyFrameworkManager__commercialUse_disallowed_revert_settingIncompatibleTerms() public { // If no commercial values allowed UMLPolicy memory umlPolicy = UMLPolicy({ attribution: false, @@ -93,30 +95,31 @@ contract UMLPolicyFrameworkTest is TestHelper { derivativesRevShare: 0, territories: emptyStringArray, distributionChannels: emptyStringArray, + contentRestrictions: emptyStringArray, royaltyPolicy: address(0) }); // commercialAttribution = true should revert - vm.expectRevert(UMLFrameworkErrors.UMLPolicyFrameworkManager_CommecialDisabled_CantAddAttribution.selector); + vm.expectRevert(UMLFrameworkErrors.UMLPolicyFrameworkManager__CommecialDisabled_CantAddAttribution.selector); umlFramework.registerPolicy(umlPolicy); // Non empty commercializers should revert umlPolicy.commercialAttribution = false; umlPolicy.commercializers = new string[](1); umlPolicy.commercializers[0] = "test"; - vm.expectRevert(UMLFrameworkErrors.UMLPolicyFrameworkManager_CommecialDisabled_CantAddCommercializers.selector); + vm.expectRevert(UMLFrameworkErrors.UMLPolicyFrameworkManager__CommercialDisabled_CantAddCommercializers.selector); umlFramework.registerPolicy(umlPolicy); // No rev share should be set; revert umlPolicy.commercializers = new string[](0); umlPolicy.commercialRevShare = 1; - vm.expectRevert(UMLFrameworkErrors.UMLPolicyFrameworkManager_CommecialDisabled_CantAddRevShare.selector); + vm.expectRevert(UMLFrameworkErrors.UMLPolicyFrameworkManager__CommecialDisabled_CantAddRevShare.selector); umlFramework.registerPolicy(umlPolicy); // No rev share should be set for derivatives either; revert umlPolicy.commercialRevShare = 0; umlPolicy.derivativesRevShare = 1; - vm.expectRevert(UMLFrameworkErrors.UMLPolicyFrameworkManager_CommecialDisabled_CantAddDerivRevShare.selector); + vm.expectRevert(UMLFrameworkErrors.UMLPolicyFrameworkManager__CommecialDisabled_CantAddDerivRevShare.selector); umlFramework.registerPolicy(umlPolicy); } - function test_UMLPolicyFrameworkManager_commercialUse_valuesSetCorrectly() public { + function test_UMLPolicyFrameworkManager__commercialUse_valuesSetCorrectly() public { string[] memory commercializers = new string[](2); commercializers[0] = "test1"; commercializers[1] = "test2"; @@ -134,6 +137,7 @@ contract UMLPolicyFrameworkTest is TestHelper { derivativesRevShare: 1, territories: emptyStringArray, distributionChannels: emptyStringArray, + contentRestrictions: emptyStringArray, royaltyPolicy: address(0xbeef) }); uint256 policyId = umlFramework.registerPolicy(umlPolicy); @@ -141,14 +145,7 @@ contract UMLPolicyFrameworkTest is TestHelper { assertEq(keccak256(abi.encode(policy)), keccak256(abi.encode(umlPolicy))); } - function test_UMLPolicyFrameworkManager_commercialUse_revenueShareSetOnLinking() public { - // TODO - } - - // DERIVATIVE TERMS - function test_UMLPolicyFrameworkManager_derivatives_notAllowed_revert_creating2ndDerivative() public {} - - function test_UMLPolicyFrameworkManager_derivatives_notAllowed_revert_settingIncompatibleTerms() public { + function test_UMLPolicyFrameworkManager__derivatives_notAllowed_revert_settingIncompatibleTerms() public { // If no derivative values allowed UMLPolicy memory umlPolicy = UMLPolicy({ attribution: false, @@ -164,29 +161,30 @@ contract UMLPolicyFrameworkTest is TestHelper { derivativesRevShare: 0, territories: emptyStringArray, distributionChannels: emptyStringArray, + contentRestrictions: emptyStringArray, royaltyPolicy: address(0xbeef) }); // derivativesAttribution = true should revert - vm.expectRevert(UMLFrameworkErrors.UMLPolicyFrameworkManager_DerivativesDisabled_CantAddAttribution.selector); + vm.expectRevert(UMLFrameworkErrors.UMLPolicyFrameworkManager__DerivativesDisabled_CantAddAttribution.selector); umlFramework.registerPolicy(umlPolicy); // Requesting approval for derivatives should revert umlPolicy.derivativesAttribution = false; umlPolicy.derivativesApproval = true; - vm.expectRevert(UMLFrameworkErrors.UMLPolicyFrameworkManager_DerivativesDisabled_CantAddApproval.selector); + vm.expectRevert(UMLFrameworkErrors.UMLPolicyFrameworkManager__DerivativesDisabled_CantAddApproval.selector); umlFramework.registerPolicy(umlPolicy); // Setting reciprocal license should revert umlPolicy.derivativesApproval = false; umlPolicy.derivativesReciprocal = true; - vm.expectRevert(UMLFrameworkErrors.UMLPolicyFrameworkManager_DerivativesDisabled_CantAddReciprocal.selector); + vm.expectRevert(UMLFrameworkErrors.UMLPolicyFrameworkManager__DerivativesDisabled_CantAddReciprocal.selector); umlFramework.registerPolicy(umlPolicy); // No rev share should be set for derivatives either; revert umlPolicy.derivativesReciprocal = false; umlPolicy.derivativesRevShare = 1; - vm.expectRevert(UMLFrameworkErrors.UMLPolicyFrameworkManager_DerivativesDisabled_CantAddRevShare.selector); + vm.expectRevert(UMLFrameworkErrors.UMLPolicyFrameworkManager__DerivativesDisabled_CantAddRevShare.selector); umlFramework.registerPolicy(umlPolicy); } - function test_UMLPolicyFrameworkManager_derivatives_valuesSetCorrectly() public { + function test_UMLPolicyFrameworkManager__derivatives_valuesSetCorrectly() public { UMLPolicy memory umlPolicy = UMLPolicy({ attribution: false, transferable: false, @@ -201,6 +199,7 @@ contract UMLPolicyFrameworkTest is TestHelper { derivativesRevShare: 123, territories: emptyStringArray, distributionChannels: emptyStringArray, + contentRestrictions: emptyStringArray, royaltyPolicy: address(0xbeef) }); uint256 policyId = umlFramework.registerPolicy(umlPolicy); @@ -208,11 +207,9 @@ contract UMLPolicyFrameworkTest is TestHelper { assertEq(keccak256(abi.encode(policy)), keccak256(abi.encode(umlPolicy))); } - function test_UMLPolicyFrameworkManager_derivatives_setRevenueShareWhenLinking2ndDerivative() public { - // TODO - } - - // APPROVAL TERMS + ///////////////////////////////////////////////////////////// + ////// APPROVAL TERMS ////// + ///////////////////////////////////////////////////////////// function test_UMLPolicyFrameworkManager_derivatives_withApproval_revert_linkNotApproved() public { uint256 policyId = umlFramework.registerPolicy( @@ -230,6 +227,7 @@ contract UMLPolicyFrameworkTest is TestHelper { derivativesRevShare: 0, territories: emptyStringArray, distributionChannels: emptyStringArray, + contentRestrictions: emptyStringArray, royaltyPolicy: address(0) }) ); @@ -251,7 +249,7 @@ contract UMLPolicyFrameworkTest is TestHelper { licenseRegistry.linkIpToParents(licenseIds, ipId2, licenseHolder); } - function test_UMLPolicyFrameworkManager_derivatives_withApproval_linkApprovedIpId() public { + function test_UMLPolicyFrameworkManager__derivatives_withApproval_linkApprovedIpId() public { uint256 policyId = umlFramework.registerPolicy( UMLPolicy({ attribution: false, @@ -267,6 +265,7 @@ contract UMLPolicyFrameworkTest is TestHelper { derivativesRevShare: 0, territories: emptyStringArray, distributionChannels: emptyStringArray, + contentRestrictions: emptyStringArray, royaltyPolicy: address(0) }) ); @@ -288,13 +287,11 @@ contract UMLPolicyFrameworkTest is TestHelper { assertTrue(licenseRegistry.isParent(ipId1, ipId2)); } - function test_UMLPolicyFrameworkManager_derivatives_withApproval_revert_approverNotLicensor() public { - // TODO: ACL - } - - // TRANSFER TERMS + ///////////////////////////////////////////////////////////// + ////// TRANSFER TERMS ////// + ///////////////////////////////////////////////////////////// - function test_UMLPolicyFrameworkManager_transferrable() public { + function test_UMLPolicyFrameworkManager__transferrable() public { UMLPolicy memory umlPolicy = UMLPolicy({ attribution: false, transferable: true, @@ -309,6 +306,7 @@ contract UMLPolicyFrameworkTest is TestHelper { derivativesRevShare: 0, territories: emptyStringArray, distributionChannels: emptyStringArray, + contentRestrictions: emptyStringArray, royaltyPolicy: address(0) }); uint256 policyId = umlFramework.registerPolicy(umlPolicy); @@ -323,7 +321,7 @@ contract UMLPolicyFrameworkTest is TestHelper { assertEq(licenseRegistry.balanceOf(licenseHolder2, licenseId), 1); } - function test_UMLPolicyFrameworkManager_nonTransferrable_revertIfTransferExceptFromLicensor() public { + function test_UMLPolicyFrameworkManager__nonTransferrable_revertIfTransferExceptFromLicensor() public { UMLPolicy memory umlPolicy = UMLPolicy({ attribution: false, transferable: false, @@ -338,6 +336,7 @@ contract UMLPolicyFrameworkTest is TestHelper { derivativesRevShare: 0, territories: emptyStringArray, distributionChannels: emptyStringArray, + contentRestrictions: emptyStringArray, royaltyPolicy: address(0) }); uint256 policyId = umlFramework.registerPolicy(umlPolicy); @@ -352,11 +351,4 @@ contract UMLPolicyFrameworkTest is TestHelper { vm.stopPrank(); } - function test_UMLPolicyFrameworkManager_mintFee() public { - // TODO - } - - function test_tokenUri() public { - // TODO - } } diff --git a/test/foundry/modules/royalty/LSClaimer.t.sol b/test/foundry/modules/royalty/LSClaimer.t.sol index 59075b78..564f92b3 100644 --- a/test/foundry/modules/royalty/LSClaimer.t.sol +++ b/test/foundry/modules/royalty/LSClaimer.t.sol @@ -49,7 +49,8 @@ contract TestLSClaimer is TestHelper { attribution: false, transferable: true, territories: new string[](0), - distributionChannels: new string[](0) + distributionChannels: new string[](0), + contentRestrictions: new string[](0) }), UMLPolicyCommercialParams({ commercialAttribution: true, diff --git a/test/foundry/registries/LicenseRegistry.t.sol b/test/foundry/registries/LicenseRegistry.t.sol index 8f583674..5f3c2eca 100644 --- a/test/foundry/registries/LicenseRegistry.t.sol +++ b/test/foundry/registries/LicenseRegistry.t.sol @@ -415,6 +415,7 @@ contract LicenseRegistryTest is Test { derivativesRevShare: 0, territories: new string[](1), distributionChannels: new string[](1), + contentRestrictions: new string[](0), royaltyPolicy: address(0xbeef) // TODO: mock royaltyPolicyLS }); diff --git a/test/utils/TestHelper.sol b/test/utils/TestHelper.sol index 59298cdc..a1e17afb 100644 --- a/test/utils/TestHelper.sol +++ b/test/utils/TestHelper.sol @@ -40,6 +40,11 @@ contract TestHelper is Test, DeployHelper { mapping(string policyFrameworkManagerName => PolicyFrameworkManagerData) internal pfms; mapping(string policyName => uint256 policyId) internal policyIds; + mapping(string => UMLPolicy) internal policies; + + string[] internal emptyStringArray = new string[](0); + address mockRoyaltyPolicy = address(0x555); + function setUp() public virtual { deployer = vm.addr(accountA); @@ -80,26 +85,70 @@ contract TestHelper is Test, DeployHelper { UMLPolicyDerivativeParams memory dparams ) internal { string memory pName = string(abi.encodePacked("uml_", gparams.policyName)); - policyIds[pName] = UMLPolicyFrameworkManager(pfms["uml"].addr).registerPolicy( - UMLPolicy({ - attribution: gparams.attribution, - transferable: gparams.transferable, - commercialUse: commercialUse, - commercialAttribution: cparams.commercialAttribution, - commercializers: cparams.commercializers, - commercialRevShare: cparams.commercialRevShare, - derivativesAllowed: derivativesAllowed, - derivativesAttribution: dparams.derivativesAttribution, - derivativesApproval: dparams.derivativesApproval, - derivativesReciprocal: dparams.derivativesReciprocal, - derivativesRevShare: dparams.derivativesRevShare, - territories: gparams.territories, - distributionChannels: gparams.distributionChannels, - royaltyPolicy: cparams.royaltyPolicy - }) - ); + policies[pName] = UMLPolicy({ + attribution: gparams.attribution, + transferable: gparams.transferable, + commercialUse: commercialUse, + commercialAttribution: cparams.commercialAttribution, + commercializers: cparams.commercializers, + commercialRevShare: cparams.commercialRevShare, + derivativesAllowed: derivativesAllowed, + derivativesAttribution: dparams.derivativesAttribution, + derivativesApproval: dparams.derivativesApproval, + derivativesReciprocal: dparams.derivativesReciprocal, + derivativesRevShare: dparams.derivativesRevShare, + territories: gparams.territories, + distributionChannels: gparams.distributionChannels, + contentRestrictions: gparams.contentRestrictions, + royaltyPolicy: cparams.royaltyPolicy + }); + policyIds[pName] = UMLPolicyFrameworkManager(pfms["uml"].addr) + .registerPolicy(policies[pName]); } + function _mapUMLPolicySimple( + string memory name, + bool commercial, + bool derivatives, + bool reciprocal + ) internal { + string memory pName = string(abi.encodePacked("uml_", name)); + policies[pName] = UMLPolicy({ + attribution: true, + transferable: true, + commercialUse: commercial, + commercialAttribution: false, + commercializers: emptyStringArray, + commercialRevShare: 0, + derivativesAllowed: derivatives, + derivativesAttribution: false, + derivativesApproval: false, + derivativesReciprocal: reciprocal, + derivativesRevShare: 0, + territories: emptyStringArray, + distributionChannels: emptyStringArray, + contentRestrictions: emptyStringArray, + royaltyPolicy: mockRoyaltyPolicy + }); + } + + function _addUMLPolicyFromMapping(string memory name, address umlFramework) internal returns (uint256){ + string memory pName = string(abi.encodePacked("uml_", name)); + policyIds[pName] = UMLPolicyFrameworkManager(umlFramework).registerPolicy(policies[pName]); + return policyIds[pName]; + } + + function _getMappedUmlPolicy(string memory name) internal view returns (UMLPolicy storage) { + string memory pName = string(abi.encodePacked("uml_", name)); + return policies[pName]; + } + + function _getUmlPolicyId(string memory name) internal view returns (uint256) { + string memory pName = string(abi.encodePacked("uml_", name)); + return policyIds[pName]; + } + + function _getIpId(MockERC721 mnft, uint256 tokenId) internal view returns (address ipId) { return _getIpId(address(mnft), tokenId); }