From 73e45f9d70307911af9dc3d101e2e6147c11f6a0 Mon Sep 17 00:00:00 2001 From: Raul Date: Mon, 29 Jan 2024 18:30:52 -0300 Subject: [PATCH] comments and linting --- contracts/IPAccountImpl.sol | 1 - contracts/governance/Governable.sol | 3 +- .../licensing/ILinkParamVerifier.sol | 3 + .../licensing/IMintParamVerifier.sol | 5 +- .../interfaces/licensing/IParamVerifier.sol | 2 + .../licensing/ITransferParamVerifier.sol | 5 +- .../licensing/IUMLPolicyFrameworkManager.sol | 56 +++++++++++ .../registries/ILicenseRegistry.sol | 94 +++++++++++++++---- contracts/lib/Errors.sol | 4 +- contracts/lib/GovernanceLib.sol | 7 +- contracts/lib/Licensing.sol | 25 ++--- .../licensing/BasePolicyFrameworkManager.sol | 9 +- .../licensing/LicenseRegistryAware.sol | 2 + .../licensing/UMLPolicyFrameworkManager.sol | 75 ++++++++------- .../LicensorApprovalManager.sol | 19 +++- contracts/registries/LicenseRegistry.sol | 71 +++++++------- contracts/utils/ShortStringOps.sol | 2 +- script/foundry/deployment/Main.s.sol | 7 +- test/foundry/integration/Integration.t.sol | 2 +- .../MintPaymentPolicyFrameworkManager.sol | 2 +- .../licensing/UMLPolicyFramework.t.sol | 28 +++--- 21 files changed, 290 insertions(+), 132 deletions(-) create mode 100644 contracts/interfaces/licensing/IUMLPolicyFrameworkManager.sol diff --git a/contracts/IPAccountImpl.sol b/contracts/IPAccountImpl.sol index 84c6683a8..5446fb777 100644 --- a/contracts/IPAccountImpl.sol +++ b/contracts/IPAccountImpl.sol @@ -185,5 +185,4 @@ contract IPAccountImpl is IERC165, IIPAccount { } } } - } diff --git a/contracts/governance/Governable.sol b/contracts/governance/Governable.sol index 214784aac..439918864 100644 --- a/contracts/governance/Governable.sol +++ b/contracts/governance/Governable.sol @@ -7,6 +7,7 @@ import { IGovernance } from "contracts/interfaces/governance/IGovernance.sol"; import { IGovernable } from "../interfaces/governance/IGovernable.sol"; import { ERC165Checker } from "@openzeppelin/contracts/utils/introspection/ERC165Checker.sol"; import { GovernanceLib } from "contracts/lib/GovernanceLib.sol"; + /// @title Governable /// @dev All contracts managed by governance should inherit from this contract. abstract contract Governable is IGovernable { @@ -15,7 +16,7 @@ abstract contract Governable is IGovernable { /// @dev Ensures that the function is called by the protocol admin. modifier onlyProtocolAdmin() { - if(!IGovernance(governance).hasRole(GovernanceLib.PROTOCOL_ADMIN, msg.sender)) { + if (!IGovernance(governance).hasRole(GovernanceLib.PROTOCOL_ADMIN, msg.sender)) { revert Errors.Governance__OnlyProtocolAdmin(); } _; diff --git a/contracts/interfaces/licensing/ILinkParamVerifier.sol b/contracts/interfaces/licensing/ILinkParamVerifier.sol index af2885651..09f106e1e 100644 --- a/contracts/interfaces/licensing/ILinkParamVerifier.sol +++ b/contracts/interfaces/licensing/ILinkParamVerifier.sol @@ -4,6 +4,9 @@ pragma solidity ^0.8.23; import { IParamVerifier } from "contracts/interfaces/licensing/IParamVerifier.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 IParamVerifier { function verifyLink( uint256 licenseId, diff --git a/contracts/interfaces/licensing/IMintParamVerifier.sol b/contracts/interfaces/licensing/IMintParamVerifier.sol index 9abbf22d3..4bd9864de 100644 --- a/contracts/interfaces/licensing/IMintParamVerifier.sol +++ b/contracts/interfaces/licensing/IMintParamVerifier.sol @@ -3,10 +3,13 @@ pragma solidity ^0.8.23; import { IParamVerifier } from "contracts/interfaces/licensing/IParamVerifier.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 IParamVerifier { function verifyMint( address caller, - bool policyAddedByLinking, + bool policyWasInherited, address licensors, address receiver, uint256 mintAmount, diff --git a/contracts/interfaces/licensing/IParamVerifier.sol b/contracts/interfaces/licensing/IParamVerifier.sol index 0b7081436..534ec5872 100644 --- a/contracts/interfaces/licensing/IParamVerifier.sol +++ b/contracts/interfaces/licensing/IParamVerifier.sol @@ -4,6 +4,8 @@ pragma solidity ^0.8.23; import { IERC165 } from "@openzeppelin/contracts/interfaces/IERC165.sol"; +/// @title IParamVerifier +/// @notice Placeholder interface for verifying policy parameters. interface IParamVerifier is IERC165 { } \ No newline at end of file diff --git a/contracts/interfaces/licensing/ITransferParamVerifier.sol b/contracts/interfaces/licensing/ITransferParamVerifier.sol index 1edb2c4d0..3f2e19d4e 100644 --- a/contracts/interfaces/licensing/ITransferParamVerifier.sol +++ b/contracts/interfaces/licensing/ITransferParamVerifier.sol @@ -4,12 +4,15 @@ pragma solidity ^0.8.23; import { IParamVerifier } from "contracts/interfaces/licensing/IParamVerifier.sol"; +/// @title ITransferParamVerifier +/// @notice LicenseRegistry will call this to verify the transfer parameters are compliant +/// with the policy interface ITransferParamVerifier is IParamVerifier { function verifyTransfer( uint256 licenseId, address from, address to, uint256 amount, - bytes memory data + 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 new file mode 100644 index 000000000..2e01b5821 --- /dev/null +++ b/contracts/interfaces/licensing/IUMLPolicyFrameworkManager.sol @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: UNLICENSED + +pragma solidity ^0.8.23; + +import { Licensing } from "contracts/lib/Licensing.sol"; +import { IPolicyFrameworkManager } from "contracts/interfaces/licensing/IPolicyFrameworkManager.sol"; + +/// @notice Licensing parameters for the UML standard +/// @param attribution Whether or not attribution is required when reproducing the work +/// @param transferable Whether or not the license is transferable +/// @param commercialUse Whether or not the work can be used commercially +/// @param commercialAttribution Whether or not attribution is required when reproducing the work commercially +/// @param commercializers List of commericializers that are allowed to commercially exploit the work. If empty +/// then no restrictions. +/// @param commercialRevShare Percentage of revenue that must be shared with the licensor +/// @param derivativesAllowed Whether or not the licensee can create derivatives of his work +/// @param derivativesAttribution Whether or not attribution is required for derivatives of the work +/// @param derivativesApproval Whether or not the licensor must approve derivatives of the work before they can be +/// linked to the licensor IP ID +/// @param derivativesReciprocal Whether or not the licensee must license derivatives of the work under the same terms. +/// @param derivativesRevShare Percentage of revenue that must be shared with the licensor for derivatives of the work +/// @param territories List of territories where the license is valid. If empty, global. +/// @param distributionChannels List of distribution channels where the license is valid. Empty if no restrictions. +struct UMLPolicy { + bool attribution; + bool transferable; + bool commercialUse; + bool commercialAttribution; + string[] commercializers; + uint256 commercialRevShare; + bool derivativesAllowed; + bool derivativesAttribution; + bool derivativesApproval; + bool derivativesReciprocal; + uint256 derivativesRevShare; + string[] territories; + string[] distributionChannels; +} + + +/// @title IUMLPolicyFrameworkManager +/// @notice Defines the interface for a Policy Framework Manager compliant with the UML standard +interface IUMLPolicyFrameworkManager is IPolicyFrameworkManager { + + /// @notice Emitted when a new policy is added to the registry + event UMLPolicyAdded(uint256 indexed policyId, UMLPolicy policy); + + /// @notice Adds a new policy to the registry + /// @dev Must encode the policy into bytes to be stored in the LicenseRegistry + /// @param umlPolicy UMLPolicy compliant licensing term values + function addPolicy(UMLPolicy calldata umlPolicy) external returns (uint256 policyId); + /// @notice Fetchs a policy from the registry, decoding the raw bytes into a UMLPolicy struct + /// @param policyId The ID of the policy to fetch + /// @return policy The UMLPolicy struct + function getPolicy(uint256 policyId) external view returns (UMLPolicy memory policy); +} \ No newline at end of file diff --git a/contracts/interfaces/registries/ILicenseRegistry.sol b/contracts/interfaces/registries/ILicenseRegistry.sol index 6376e5540..a66bcf603 100644 --- a/contracts/interfaces/registries/ILicenseRegistry.sol +++ b/contracts/interfaces/registries/ILicenseRegistry.sol @@ -3,15 +3,39 @@ pragma solidity ^0.8.23; import { Licensing } from "contracts/lib/Licensing.sol"; +/// @title ILicenseRegistry +/// @notice Interface for the LicenseRegistry contract, which is the main entry point for the licensing system. +/// It is responsible for: +/// - Registering policy frameworks +/// - Registering policies +/// - Minting licenses +/// - Linking IP to its parent +/// - Verifying transfer parameters (through the ITransferParamVerifier interface implementation by the policy framework) +/// - Verifying linking parameters (through the ILinkParamVerifier interface implementation by the policy framework) +/// - Verifying policy parameters (through the IParamVerifier interface implementation by the policy framework) interface ILicenseRegistry { + + /// @notice Emitted when a policy framework is created by registering a policy framework manager + /// @param creator The address that created the policy framework + /// @param frameworkId The id of the policy framework + /// @param framework The policy framework data event PolicyFrameworkCreated( address indexed creator, uint256 indexed frameworkId, Licensing.PolicyFramework framework ); - + /// @notice Emitted when a policy is added to the contract. + /// @param creator The address that created the policy + /// @param policyId The id of the policy + /// @param policy The policy data event PolicyCreated(address indexed creator, uint256 indexed policyId, Licensing.Policy policy); + /// @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 + /// @param policyId The id of the policy + /// @param index The index of the policy in the IP's policy list + /// @param inheritedPolicy Whether the policy was inherited from a parent IP (linking) or set by IP owner event PolicyAddedToIpId( address indexed caller, address indexed ipId, @@ -20,6 +44,12 @@ interface ILicenseRegistry { bool inheritedPolicy ); + /// @notice Emitted when a license is minted + /// @param creator The address that created the license + /// @param receiver The address that received the license + /// @param licenseId The id of the license + /// @param amount The amount of licenses minted + /// @param licenseData The license data event LicenseMinted( address indexed creator, address indexed receiver, @@ -28,14 +58,33 @@ interface ILicenseRegistry { Licensing.License licenseData ); + /// @notice Emitted when an IP is linked to its parent by burning a license + /// @param caller The address that called the function + /// @param ipId The id of the IP + /// @param parentIpId The id of the parent IP event IpIdLinkedToParent(address indexed caller, address indexed ipId, address indexed parentIpId); + /// @notice registers a policy framework into the contract + /// @param fw The policy framework data + /// @return frameworkId The id of the policy framework function addPolicyFramework(Licensing.PolicyFramework calldata fw) external returns (uint256 frameworkId); - function addPolicyToIp(address ipId, uint256 polId) external returns (uint256 indexOnIpId); - + /// @notice registers a policy into the contract + /// @param pol The policy data + /// @return policyId The id of the policy function addPolicy(Licensing.Policy memory pol) external returns (uint256 policyId); + /// @notice adds a policy to an IP policy list + /// @param ipId The id of the IP + /// @param polId The id of the policy + /// @return indexOnIpId The index of the policy in the IP's policy list + function addPolicyToIp(address ipId, uint256 polId) external returns (uint256 indexOnIpId); + + /// @notice mints a license to create derivative IP + /// @param policyId The id of the policy with the licensing parameters + /// @param licensorIpId The id of the licensor IP + /// @param amount The amount of licenses to mint + /// @param receiver The address that will receive the license function mintLicense( uint256 policyId, address licensorIpId, @@ -43,44 +92,53 @@ interface ILicenseRegistry { address receiver ) external returns (uint256 licenseId); + /// @notice links an IP to its parent IP, burning the license NFT and the policy allows it + /// @param licenseId The id of the license to burn + /// @param childIpId The id of the child IP + /// @param holder The address that holds the license function linkIpToParent(uint256 licenseId, address childIpId, address holder) external; /// /// Getters /// + /// @notice gets total number of policy frameworks in the contract function totalFrameworks() external view returns (uint256); - + /// @notice gets policy framework data by id function framework(uint256 frameworkId) external view returns (Licensing.PolicyFramework memory); + /// @notice gets policy framework license template URL by id function frameworkUrl(uint256 frameworkId) external view returns (string memory); - + /// @notice gets total number of policies (framework parameter configurations) in the contract function totalPolicies() external view returns (uint256); - + /// @notice gets policy data by id function policy(uint256 policyId) external view returns (Licensing.Policy memory pol); - + /// @notice true if policy is defined in the contract function isPolicyDefined(uint256 policyId) external view returns (bool); - + /// @notice gets the policy ids for an IP function policyIdsForIp(address ipId) external view returns (uint256[] memory policyIds); - + /// @notice gets total number of policies for an IP function totalPoliciesForIp(address ipId) external view returns (uint256); - + /// @notice true if policy is part of an IP's policy list function isPolicyIdSetForIp(address ipId, uint256 policyId) external view returns (bool); - + /// @notice gets the policy ID for an IP by index on the IP's policy list function policyIdForIpAtIndex(address ipId, uint256 index) external view returns (uint256 policyId); - + /// @notice gets the policy for an IP by index on the IP's policy list function policyForIpAtIndex(address ipId, uint256 index) external view returns (Licensing.Policy memory); - + /// @notice gets the index of a policy in an IP's policy list function indexOfPolicyForIp(address ipId, uint256 policyId) external view returns (uint256 index); - + /// @notice true if the license was added to the IP by linking (burning a license) function isPolicyInherited(address ipId, uint256 policyId) external view returns (bool); - + /// @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) function isLicensee(uint256 licenseId, address holder) external view returns (bool); - + /// @notice IP ID of the licensor for the license (parent IP) function licensorIpId(uint256 licenseId) external view returns (address); + /// @notice license data (licensor, policy...) for the license id function license(uint256 licenseId) external view returns (Licensing.License memory); + /// @notice true if an IP is a derivative of another IP function isParent(address parentIpId, address childIpId) external view returns (bool); - + /// @notice returns the parent IP IDs for an IP ID function parentIpIds(address ipId) external view returns (address[] memory); - + /// @notice total number of parents for an IP ID function totalParentsForIpId(address ipId) external view returns (uint256); } \ No newline at end of file diff --git a/contracts/lib/Errors.sol b/contracts/lib/Errors.sol index 649d4c264..27c0b0307 100644 --- a/contracts/lib/Errors.sol +++ b/contracts/lib/Errors.sol @@ -22,7 +22,6 @@ library Errors { error IPAccount__InvalidSignature(); error IPAccount__ExpiredSignature(); - //////////////////////////////////////////////////////////////////////////// // Module // //////////////////////////////////////////////////////////////////////////// @@ -70,7 +69,6 @@ library Errors { // LicenseRegistry // //////////////////////////////////////////////////////////////////////////// - /// @notice Error thrown when a policy is already set for an IP ID. error LicenseRegistry__PolicyAlreadySetForIpId(); error LicenseRegistry__FrameworkNotFound(); error LicenseRegistry__EmptyLicenseUrl(); @@ -109,7 +107,7 @@ library Errors { error UMLPolicyFrameworkManager_DerivativesDisabled_CantAddReciprocal(); error UMLPolicyFrameworkManager_DerivativesDisabled_CantAddRevShare(); error UMLPolicyFrameworkManager_FrameworkNotYetRegistered(); - + //////////////////////////////////////////////////////////////////////////// // LicensorApprovalManager // //////////////////////////////////////////////////////////////////////////// diff --git a/contracts/lib/GovernanceLib.sol b/contracts/lib/GovernanceLib.sol index ae01de17e..f51c987bc 100644 --- a/contracts/lib/GovernanceLib.sol +++ b/contracts/lib/GovernanceLib.sol @@ -5,12 +5,11 @@ pragma solidity ^0.8.23; /// @title Governance /// @dev This library provides types for Story Protocol Governance. library GovernanceLib { - bytes32 public constant PROTOCOL_ADMIN = bytes32(0); - /// @notice An enum containing the different states the protocol can be in. - /// @param Unpaused The unpaused state. - /// @param Paused The paused state. + /// @notice An enum containing the different states the protocol can be in. + /// @param Unpaused The unpaused state. + /// @param Paused The paused state. enum ProtocolState { Unpaused, Paused diff --git a/contracts/lib/Licensing.sol b/contracts/lib/Licensing.sol index 4c2e7d818..433f9ab31 100644 --- a/contracts/lib/Licensing.sol +++ b/contracts/lib/Licensing.sol @@ -4,32 +4,35 @@ pragma solidity ^0.8.20; import { IParamVerifier } from "../interfaces/licensing/IParamVerifier.sol"; import { Errors } from "./Errors.sol"; +/// @title Licensing +/// @notice Types and constants used by the licensing related contracts library Licensing { - - /// Describes a licensing framework, which is a set of licensing terms (parameters) + /// @notice Describes a license policy framework, which is a set of licensing terms (parameters) /// that come into effect in different moments of the licensing life cycle. /// Must correspond to human (or at least lawyer) readable text describing them in licenseUrl. - /// To be valid in Story Protocol, the parameters described in the text must express default values - /// corresponding to those of each Parameter struct + /// To be valid in Story Protocol, the policy framework must be registered in the LicenseRegistry. + /// @param policyFramework Address of the contract implementing the policy framework encoding and logic + /// @param licenseUrl URL to the file containing the legal text for the license agreement struct PolicyFramework { address policyFramework; - /// @notice URL to the file containing the legal text for the license agreement string licenseUrl; } - /// A particular configuration of a Licensing PolicyFramework, setting (or not) values for the licensing + /// @notice A particular configuration (flavor) of a Policy Framework, setting values for the licensing /// terms (parameters) of the framework. + /// @param policyFrameworkId Id of the policy framework this policy is based on + /// @param policyData Encoded data for the policy, specific to the policy framework struct Policy { - /// Id of a Licensing PolicyFramework uint256 frameworkId; bytes data; } - /// Data that define a License Agreement NFT + /// @notice Data that define a License Agreement NFT + /// @param policyId Id of the policy this license is based on, which will be set in the derivative + /// IP when the license is burnt + /// @param licensorIpId Id of the IP this license is for struct License { - /// the id for the Policy this License will set to the desired derivative IP after being burned. uint256 policyId; - /// Id for the licensor of the Ip Id address licensorIpId; } -} \ No newline at end of file +} diff --git a/contracts/modules/licensing/BasePolicyFrameworkManager.sol b/contracts/modules/licensing/BasePolicyFrameworkManager.sol index 2479bb9c2..8893c8d73 100644 --- a/contracts/modules/licensing/BasePolicyFrameworkManager.sol +++ b/contracts/modules/licensing/BasePolicyFrameworkManager.sol @@ -14,17 +14,24 @@ import { LicenseRegistryAware } from "contracts/modules/licensing/LicenseRegistr import { ERC165 } from "@openzeppelin/contracts/utils/introspection/ERC165.sol"; import { IERC165 } from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; +/// @title BasePolicyFrameworkManager +/// @notice Base contract for policy framework managers. abstract contract BasePolicyFrameworkManager is IParamVerifier, IPolicyFrameworkManager, ERC165, LicenseRegistryAware { string public licenseUrl; uint256 public frameworkId; - /// @notice Initializes the base module contract. + /// @notice Initializes the base contract. /// @param registry The address of the license registry. + /// @param templateUrl The URL for the license template. constructor(address registry, string memory templateUrl) LicenseRegistryAware(registry) { licenseUrl = templateUrl; } + /// @notice Registers this policy framework manager within the license registry, to be able + /// to add policies into the license registry. + /// @dev save the frameworkId in this PolicyFrameworkManager + /// @return The ID of the policy framework. function register() external returns (uint256) { Licensing.PolicyFramework memory framework = Licensing.PolicyFramework({ policyFramework: address(this), diff --git a/contracts/modules/licensing/LicenseRegistryAware.sol b/contracts/modules/licensing/LicenseRegistryAware.sol index 5b231ca97..74d511916 100644 --- a/contracts/modules/licensing/LicenseRegistryAware.sol +++ b/contracts/modules/licensing/LicenseRegistryAware.sol @@ -6,6 +6,8 @@ pragma solidity ^0.8.23; import { LicenseRegistry } from "contracts/registries/LicenseRegistry.sol"; import { Errors } from "contracts/lib/Errors.sol"; +/// @title LicenseRegistryAware +/// @notice Base contract to be inherited by modules that need to access the license registry. abstract contract LicenseRegistryAware { /// @notice Gets the protocol-wide license registry. diff --git a/contracts/modules/licensing/UMLPolicyFrameworkManager.sol b/contracts/modules/licensing/UMLPolicyFrameworkManager.sol index 9876f8964..c78bd66be 100644 --- a/contracts/modules/licensing/UMLPolicyFrameworkManager.sol +++ b/contracts/modules/licensing/UMLPolicyFrameworkManager.sol @@ -11,44 +11,38 @@ import { Errors } from "contracts/lib/Errors.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 } from "contracts/interfaces/licensing/IUMLPolicyFrameworkManager.sol"; +import { IPolicyFrameworkManager } from "contracts/interfaces/licensing/IPolicyFrameworkManager.sol"; import { BasePolicyFrameworkManager } from "contracts/modules/licensing/BasePolicyFrameworkManager.sol"; import { LicensorApprovalManager } from "contracts/modules/licensing/parameter-helpers/LicensorApprovalManager.sol"; // external import { IERC165 } from "@openzeppelin/contracts/utils/introspection/ERC165.sol"; -struct UMLv1Policy { - bool attribution; - bool transferable; - bool commercialUse; - bool commercialAttribution; - string[] commercializers; - uint256 commercialRevShare; - bool derivativesAllowed; - bool derivativesAttribution; - bool derivativesApproval; - bool derivativesReciprocal; - uint256 derivativesRevShare; - string[] territories; - string[] distributionChannels; -} - +/// @title UMLPolicyFrameworkManager +/// @notice This is the UML Policy Framework Manager, which implements the UML Policy Framework +/// logic for encoding and decoding UML policies into the LicenseRegistry and verifying +/// the licensing parameters for linking, minting, and transferring. contract UMLPolicyFrameworkManager is + IUMLPolicyFrameworkManager, BasePolicyFrameworkManager, ILinkParamVerifier, IMintParamVerifier, ITransferParamVerifier, LicensorApprovalManager { - event UMLv1PolicyAdded(uint256 indexed policyId, UMLv1Policy policy); + constructor(address licRegistry, string memory licenseUrl) BasePolicyFrameworkManager(licRegistry, licenseUrl) {} - function licenseRegistry() external view override returns (address) { + function licenseRegistry() external view override(BasePolicyFrameworkManager, IPolicyFrameworkManager) returns (address) { return address(LICENSE_REGISTRY); } - function addPolicy(UMLv1Policy calldata umlPolicy) external returns (uint256 policyId) { + /// @notice Adds a new policy to the registry + /// @dev Must encode the policy into bytes to be stored in the LicenseRegistry + /// @param umlPolicy UMLPolicy compliant licensing term values + function addPolicy(UMLPolicy calldata umlPolicy) external returns (uint256 policyId) { if (frameworkId == 0) { revert Errors.UMLPolicyFrameworkManager_FrameworkNotYetRegistered(); } @@ -58,16 +52,19 @@ contract UMLPolicyFrameworkManager is frameworkId: frameworkId, data: abi.encode(umlPolicy) }); - emit UMLv1PolicyAdded(policyId, umlPolicy); + emit UMLPolicyAdded(policyId, umlPolicy); return LICENSE_REGISTRY.addPolicy(protocolPolicy); } - function getPolicy(uint256 policyId) public view returns (UMLv1Policy memory policy) { + /// @notice Fetchs a policy from the registry, decoding the raw bytes into a UMLPolicy struct + /// @param policyId The ID of the policy to fetch + /// @return policy The UMLPolicy struct + function getPolicy(uint256 policyId) public view returns (UMLPolicy memory policy) { Licensing.Policy memory protocolPolicy = LICENSE_REGISTRY.policy(policyId); if (protocolPolicy.frameworkId != frameworkId) { revert Errors.LicenseRegistry__FrameworkNotFound(); } - policy = abi.decode(protocolPolicy.data, (UMLv1Policy)); + policy = abi.decode(protocolPolicy.data, (UMLPolicy)); } function policyToJson(bytes memory) public view returns (string memory) { @@ -91,14 +88,20 @@ contract UMLPolicyFrameworkManager is address, bytes calldata policyData ) external override onlyLicenseRegistry returns (bool) { - UMLv1Policy memory policy = abi.decode(policyData, (UMLv1Policy)); + UMLPolicy memory policy = abi.decode(policyData, (UMLPolicy)); bool linkingOK = true; + // If the policy defines commercial revenue sharing, call the royalty module + // to set it for the licensor if (policy.commercialRevShare > 0) { // RoyaltyModule.setRevShare() } + // If the policy defines derivative revenue sharing, call the royalty module + // to set it for the licensor in future derivatives if (policy.derivativesRevShare > 0) { // RoyaltyModule.setRevShareForDerivatives() } + // If the policy defines the licensor must approve derivatives, check if the + // derivative is approved by the licensor if (policy.derivativesApproval) { linkingOK = linkingOK && isDerivativeApproved(licenseId, ipId); } @@ -107,15 +110,17 @@ contract UMLPolicyFrameworkManager is function verifyMint( address, - bool policyAddedByLinking, + bool policyWasInherited, address, address, uint256, bytes memory policyData - ) external returns (bool) { - UMLv1Policy memory policy = abi.decode(policyData, (UMLv1Policy)); - if (!policy.derivativesAllowed && policyAddedByLinking) { - // Parent said no derivatives, but child is trying to mint + ) external onlyLicenseRegistry returns (bool) { + UMLPolicy memory policy = abi.decode(policyData, (UMLPolicy)); + // TODO: + // If the policy defines no derivative is allowed, and policy was inherited, + // we don't allow minting + if (!policy.derivativesAllowed && policyWasInherited) { return false; } return true; @@ -127,8 +132,10 @@ contract UMLPolicyFrameworkManager is address, uint256, bytes memory policyData - ) external returns (bool) { - UMLv1Policy memory policy = abi.decode(policyData, (UMLv1Policy)); + ) external onlyLicenseRegistry returns (bool) { + UMLPolicy memory policy = abi.decode(policyData, (UMLPolicy)); + // If license is non-transferable, only the licensor can transfer out a license + // (or be directly minted to someone else) if (!policy.transferable) { // True if from == licensor return from == LICENSE_REGISTRY.licensorIpId(licenseId); @@ -136,7 +143,9 @@ contract UMLPolicyFrameworkManager is return true; } - function _verifyComercialUse(UMLv1Policy calldata policy) internal view { + /// 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 Errors.UMLPolicyFrameworkManager_CommecialDisabled_CantAddAttribution(); @@ -153,7 +162,9 @@ contract UMLPolicyFrameworkManager is } } - function _verifyDerivatives(UMLv1Policy calldata policy) internal pure { + /// Checks the configuration of derivative parameters and throws if the policy is not compliant + /// @param policy The policy to verify + function _verifyDerivatives(UMLPolicy calldata policy) internal pure { if (!policy.derivativesAllowed) { if (policy.derivativesAttribution) { revert Errors.UMLPolicyFrameworkManager_DerivativesDisabled_CantAddAttribution(); diff --git a/contracts/modules/licensing/parameter-helpers/LicensorApprovalManager.sol b/contracts/modules/licensing/parameter-helpers/LicensorApprovalManager.sol index 00515a86b..ca3ff95d4 100644 --- a/contracts/modules/licensing/parameter-helpers/LicensorApprovalManager.sol +++ b/contracts/modules/licensing/parameter-helpers/LicensorApprovalManager.sol @@ -5,18 +5,30 @@ pragma solidity ^0.8.23; import { Errors } from "contracts/lib/Errors.sol"; import { LicenseRegistryAware } from "contracts/modules/licensing/LicenseRegistryAware.sol"; -// NOTE: this could be a standalone contract or part of a licensing module +/// @title LicensorApprovalManager +/// @notice Manages the approval of derivative IP accounts by the licensor. Used to verify +/// licensing terms like "Derivatives With Approval" in UML. abstract contract LicensorApprovalManager is LicenseRegistryAware { + /// Emits when a derivative IP account is approved by the licensor. + /// @param licenseId id of the license waiting for approval + /// @param ipId id of the derivative IP to be approved + /// @param caller executor of the approval + /// @param approved result of the approval event DerivativeApproved(uint256 indexed licenseId, address indexed ipId, address indexed caller, bool approved); - // License Id => licensor => childIpId => approved + /// @notice Approvals for derivative IP. + /// @dev License Id => licensor => childIpId => approved mapping(uint256 => mapping(address => mapping(address => bool))) private _approvals; - // TODO: meta tx version? + /// @notice Approves or disapproves a derivative IP account. + /// @param licenseId id of the license waiting for approval + /// @param childIpId id of the derivative IP to be approved + /// @param approved result of the approval function setApproval(uint256 licenseId, address childIpId, bool approved) external { address licensorIpId = LICENSE_REGISTRY.licensorIpId(licenseId); // TODO: ACL + // TODO: meta tx version? bool callerIsLicensor = true; // msg.sender == IPAccountRegistry(licensorIpId).owner() or IP Account itself; if (!callerIsLicensor) { revert Errors.LicensorApprovalManager__Unauthorized(); @@ -25,6 +37,7 @@ abstract contract LicensorApprovalManager is LicenseRegistryAware { emit DerivativeApproved(licenseId, licensorIpId, msg.sender, approved); } + /// @notice Checks if a derivative IP account is approved by the licensor. function isDerivativeApproved(uint256 licenseId, address childIpId) public view returns (bool) { address licensorIpId = LICENSE_REGISTRY.licensorIpId(licenseId); return _approvals[licenseId][licensorIpId][childIpId]; diff --git a/contracts/registries/LicenseRegistry.sol b/contracts/registries/LicenseRegistry.sol index 737851949..6e8e7f10b 100644 --- a/contracts/registries/LicenseRegistry.sol +++ b/contracts/registries/LicenseRegistry.sol @@ -19,7 +19,6 @@ import { Errors } from "contracts/lib/Errors.sol"; import { Licensing } from "contracts/lib/Licensing.sol"; import { IPolicyFrameworkManager } from "contracts/interfaces/licensing/IPolicyFrameworkManager.sol"; - // TODO: consider disabling operators/approvals on creation contract LicenseRegistry is ERC1155, ILicenseRegistry { using EnumerableSet for EnumerableSet.UintSet; @@ -70,9 +69,7 @@ contract LicenseRegistry is ERC1155, ILicenseRegistry { /// Must be called by protocol admin /// @param fwCreation framework parameters /// @return frameworkId identifier for framework, starting in 1 - function addPolicyFramework( - Licensing.PolicyFramework calldata fwCreation - ) external returns (uint256 frameworkId) { + function addPolicyFramework(Licensing.PolicyFramework calldata fwCreation) external returns (uint256 frameworkId) { // TODO: check protocol auth if (bytes(fwCreation.licenseUrl).length == 0 || fwCreation.licenseUrl.equal("")) { revert Errors.LicenseRegistry__EmptyLicenseUrl(); @@ -150,7 +147,7 @@ contract LicenseRegistry is ERC1155, ILicenseRegistry { } /// Adds a particular configuration of license terms to the protocol. - /// Must be called by a PolicyFramework, which is responsible for verifying the parameters + /// Must be called by a PolicyFramework, which is responsible for verifying the parameters /// are valid and the configuration makes sense. /// @param pol policy data /// @return policyId if policy data was in the contract, policyId is reused, if it's new, id will be new. @@ -280,18 +277,20 @@ contract LicenseRegistry is ERC1155, ILicenseRegistry { bool inheritedPolicy = _policySetups[licensorIp][policyId].inheritedPolicy; if (ERC165Checker.supportsInterface(fw.policyFramework, type(IMintParamVerifier).interfaceId)) { - if(!IMintParamVerifier(fw.policyFramework).verifyMint( - msg.sender, - inheritedPolicy, - licensorIp, - receiver, - amount, - pol.data - )) { + if ( + !IMintParamVerifier(fw.policyFramework).verifyMint( + msg.sender, + inheritedPolicy, + licensorIp, + receiver, + amount, + pol.data + ) + ) { revert Errors.LicenseRegistry__MintLicenseParamFailed(); } } - + Licensing.License memory licenseData = Licensing.License({ policyId: policyId, licensorIpId: licensorIp }); bool isNew; (licenseId, isNew) = _addIdOrGetExisting(abi.encode(licenseData), _hashedLicenses, _totalLicenses); @@ -344,13 +343,15 @@ contract LicenseRegistry is ERC1155, ILicenseRegistry { Licensing.PolicyFramework storage fw = _framework(pol.frameworkId); if (ERC165Checker.supportsInterface(fw.policyFramework, type(ILinkParamVerifier).interfaceId)) { - if(!ILinkParamVerifier(fw.policyFramework).verifyLink( - licenseId, - msg.sender, - childIpId, - parentIpId, - pol.data - )) { + if ( + !ILinkParamVerifier(fw.policyFramework).verifyLink( + licenseId, + msg.sender, + childIpId, + parentIpId, + pol.data + ) + ) { revert Errors.LicenseRegistry__LinkParentParamFailed(); } } @@ -392,26 +393,24 @@ contract LicenseRegistry is ERC1155, ILicenseRegistry { uint256[] memory ids, uint256[] memory values ) internal virtual override { - // We are interested in transfers, minting and burning are checked in mintLicense and linkIpToParent respectively. + // We are interested in transfers, minting and burning are checked in mintLicense and + // linkIpToParent respectively. if (from != address(0) && to != address(0)) { for (uint256 i = 0; i < ids.length; i++) { // Verify transfer params Licensing.Policy memory pol = policy(_licenses[ids[i]].policyId); Licensing.PolicyFramework storage fw = _framework(pol.frameworkId); - if ( - ERC165Checker.supportsInterface( - fw.policyFramework, - type(ITransferParamVerifier).interfaceId - ) - ) { - if(!ITransferParamVerifier(fw.policyFramework).verifyTransfer( - ids[i], - from, - to, - values[i], - pol.data - )) { + if (ERC165Checker.supportsInterface(fw.policyFramework, type(ITransferParamVerifier).interfaceId)) { + if ( + !ITransferParamVerifier(fw.policyFramework).verifyTransfer( + ids[i], + from, + to, + values[i], + pol.data + ) + ) { revert Errors.LicenseRegistry__TransferParamFailed(); } } @@ -426,4 +425,4 @@ contract LicenseRegistry is ERC1155, ILicenseRegistry { Licensing.PolicyFramework storage fw = _framework(pol.frameworkId); return IPolicyFrameworkManager(fw.policyFramework).policyToJson(pol.data); } -} \ No newline at end of file +} diff --git a/contracts/utils/ShortStringOps.sol b/contracts/utils/ShortStringOps.sol index ebf7f01c7..f0a19565c 100644 --- a/contracts/utils/ShortStringOps.sol +++ b/contracts/utils/ShortStringOps.sol @@ -45,4 +45,4 @@ library ShortStringOps { function bytes32ToString(bytes32 b) internal pure returns (string memory) { return ShortString.wrap(b).toString(); } -} \ No newline at end of file +} diff --git a/script/foundry/deployment/Main.s.sol b/script/foundry/deployment/Main.s.sol index fc137b172..930a33776 100644 --- a/script/foundry/deployment/Main.s.sol +++ b/script/foundry/deployment/Main.s.sol @@ -28,7 +28,8 @@ import { RoyaltyModule } from "contracts/modules/royalty-module/RoyaltyModule.so import { DisputeModule } from "contracts/modules/dispute-module/DisputeModule.sol"; import { IPResolver } from "contracts/resolvers/IPResolver.sol"; import { Governance } from "contracts/governance/Governance.sol"; -import { UMLPolicyFrameworkManager, UMLv1Policy } from "contracts/modules/licensing/UMLPolicyFrameworkManager.sol"; +import { IUMLPolicyFrameworkManager, UMLPolicy } from "contracts/interfaces/licensing/IUMLPolicyFrameworkManager.sol"; +import { UMLPolicyFrameworkManager } from "contracts/modules/licensing/UMLPolicyFrameworkManager.sol"; // test import { MockERC721 } from "test/foundry/mocks/MockERC721.sol"; @@ -265,7 +266,7 @@ contract Main is Script, BroadcastManager, JsonDeploymentHandler { // ////////////////////////////////////////////////////////////////*/ policyIds["test_true"] = umlAllTrue.addPolicy( - UMLv1Policy({ + UMLPolicy({ attribution: true, transferable: true, commercialUse: true, @@ -283,7 +284,7 @@ contract Main is Script, BroadcastManager, JsonDeploymentHandler { ); policyIds["expensive_mint"] = umlAllTrue.addPolicy( - UMLv1Policy({ + UMLPolicy({ attribution: true, transferable: true, commercialUse: true, diff --git a/test/foundry/integration/Integration.t.sol b/test/foundry/integration/Integration.t.sol index 3f9ef7b37..24c3b403b 100644 --- a/test/foundry/integration/Integration.t.sol +++ b/test/foundry/integration/Integration.t.sol @@ -17,7 +17,7 @@ import { RoyaltyModule } from "contracts/modules/royalty-module/RoyaltyModule.so import { RoyaltyPolicyLS } from "contracts/modules/royalty-module/policies/RoyaltyPolicyLS.sol"; import { IPAccountRegistry } from "contracts/registries/IPAccountRegistry.sol"; import { LicenseRegistry } from "contracts/registries/LicenseRegistry.sol"; -import { UMLPolicyFrameworkManager, UMLv1Policy } from "contracts/modules/licensing/UMLPolicyFrameworkManager.sol"; +import { UMLPolicyFrameworkManager } from "contracts/modules/licensing/UMLPolicyFrameworkManager.sol"; import { MockAccessController } from "test/foundry/mocks/MockAccessController.sol"; import { MockERC20 } from "test/foundry/mocks/MockERC20.sol"; diff --git a/test/foundry/mocks/licensing/MintPaymentPolicyFrameworkManager.sol b/test/foundry/mocks/licensing/MintPaymentPolicyFrameworkManager.sol index cbf372f7f..bc24b947b 100644 --- a/test/foundry/mocks/licensing/MintPaymentPolicyFrameworkManager.sol +++ b/test/foundry/mocks/licensing/MintPaymentPolicyFrameworkManager.sol @@ -64,7 +64,7 @@ contract MintPaymentPolicyFrameworkManager is BasePolicyFrameworkManager, IMintP /// to return true, pass in abi.encode(true) as the value. function verifyMint( address caller, - bool policyAddedByLinking, + bool policyWasInherited, address licensors, address receiver, uint256 mintAmount, diff --git a/test/foundry/modules/licensing/UMLPolicyFramework.t.sol b/test/foundry/modules/licensing/UMLPolicyFramework.t.sol index 0f55e640a..1092fbb19 100644 --- a/test/foundry/modules/licensing/UMLPolicyFramework.t.sol +++ b/test/foundry/modules/licensing/UMLPolicyFramework.t.sol @@ -6,8 +6,8 @@ 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 { UMLPolicyFrameworkManager, UMLv1Policy } from "contracts/modules/licensing/UMLPolicyFrameworkManager.sol"; +import { IUMLPolicyFrameworkManager, UMLPolicy } from "contracts/interfaces/licensing/IUMLPolicyFrameworkManager.sol"; +import { UMLPolicyFrameworkManager } from "contracts/modules/licensing/UMLPolicyFrameworkManager.sol"; import "forge-std/console2.sol"; @@ -37,7 +37,7 @@ contract UMLPolicyFrameworkTest is Test { territories[1] = "test2"; string[] memory distributionChannels = new string[](1); distributionChannels[0] = "test3"; - UMLv1Policy memory umlPolicy = UMLv1Policy({ + UMLPolicy memory umlPolicy = UMLPolicy({ attribution: true, transferable: false, commercialUse: true, @@ -53,7 +53,7 @@ contract UMLPolicyFrameworkTest is Test { distributionChannels: distributionChannels }); uint256 policyId = umlFramework.addPolicy(umlPolicy); - UMLv1Policy memory policy = umlFramework.getPolicy(policyId); + UMLPolicy memory policy = umlFramework.getPolicy(policyId); assertEq(keccak256(abi.encode(policy)), keccak256(abi.encode(umlPolicy))); } @@ -61,7 +61,7 @@ contract UMLPolicyFrameworkTest is Test { function test_UMLPolicyFrameworkManager_commercialUse_disallowed_revert_settingIncompatibleTerms() public { // If no commercial values allowed - UMLv1Policy memory umlPolicy = UMLv1Policy({ + UMLPolicy memory umlPolicy = UMLPolicy({ attribution: false, transferable: false, commercialUse: false, @@ -110,7 +110,7 @@ contract UMLPolicyFrameworkTest is Test { string[] memory commercializers = new string[](2); commercializers[0] = "test1"; commercializers[1] = "test2"; - UMLv1Policy memory umlPolicy = UMLv1Policy({ + UMLPolicy memory umlPolicy = UMLPolicy({ attribution: false, transferable: false, commercialUse: true, @@ -126,7 +126,7 @@ contract UMLPolicyFrameworkTest is Test { distributionChannels: emptyStringArray }); uint256 policyId = umlFramework.addPolicy(umlPolicy); - UMLv1Policy memory policy = umlFramework.getPolicy(policyId); + UMLPolicy memory policy = umlFramework.getPolicy(policyId); assertEq(keccak256(abi.encode(policy)), keccak256(abi.encode(umlPolicy))); } @@ -141,7 +141,7 @@ contract UMLPolicyFrameworkTest is Test { function test_UMLPolicyFrameworkManager_derivatives_notAllowed_revert_settingIncompatibleTerms() public { // If no derivative values allowed - UMLv1Policy memory umlPolicy = UMLv1Policy({ + UMLPolicy memory umlPolicy = UMLPolicy({ attribution: false, transferable: false, commercialUse: true, // So derivativesRevShare doesn't revert for this @@ -185,7 +185,7 @@ contract UMLPolicyFrameworkTest is Test { } function test_UMLPolicyFrameworkManager_derivatives_valuesSetCorrectly() public { - UMLv1Policy memory umlPolicy = UMLv1Policy({ + UMLPolicy memory umlPolicy = UMLPolicy({ attribution: false, transferable: false, commercialUse: true, // If false, derivativesRevShare should revert @@ -201,7 +201,7 @@ contract UMLPolicyFrameworkTest is Test { distributionChannels: emptyStringArray }); uint256 policyId = umlFramework.addPolicy(umlPolicy); - UMLv1Policy memory policy = umlFramework.getPolicy(policyId); + UMLPolicy memory policy = umlFramework.getPolicy(policyId); assertEq(keccak256(abi.encode(policy)), keccak256(abi.encode(umlPolicy))); } @@ -212,7 +212,7 @@ contract UMLPolicyFrameworkTest is Test { // APPROVAL TERMS function test_UMLPolicyFrameworkManager_derivativesWithApproval_revert_linkNotApproved() public { - uint256 policyId = umlFramework.addPolicy(UMLv1Policy({ + uint256 policyId = umlFramework.addPolicy(UMLPolicy({ attribution: false, transferable: false, commercialUse: false, @@ -239,7 +239,7 @@ contract UMLPolicyFrameworkTest is Test { } function test_UMLPolicyFrameworkManager_derivatives_withApproval_linkApprovedIpId() public { - uint256 policyId = umlFramework.addPolicy(UMLv1Policy({ + uint256 policyId = umlFramework.addPolicy(UMLPolicy({ attribution: false, transferable: false, commercialUse: false, @@ -272,7 +272,7 @@ contract UMLPolicyFrameworkTest is Test { // TRANSFER TERMS function test_UMLPolicyFrameworkManager_transferrable() public { - UMLv1Policy memory umlPolicy = UMLv1Policy({ + UMLPolicy memory umlPolicy = UMLPolicy({ attribution: false, transferable: true, commercialUse: false, @@ -299,7 +299,7 @@ contract UMLPolicyFrameworkTest is Test { } function test_UMLPolicyFrameworkManager_nonTransferrable_revertIfTransferExceptFromLicensor() public { - UMLv1Policy memory umlPolicy = UMLv1Policy({ + UMLPolicy memory umlPolicy = UMLPolicy({ attribution: false, transferable: false, commercialUse: false,