Skip to content
This repository has been archived by the owner on Apr 30, 2024. It is now read-only.

Extracting royalty policy specific logic out of Licensing Module #87

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ interface ILicensingModule is IModule {
/// verified by the policy framework manager.
/// @param isLicenseTransferable True if the license is transferable
/// @param data The policy data
function registerPolicy(bool isLicenseTransferable, bytes memory data) external returns (uint256 policyId);
function registerPolicy(bool isLicenseTransferable, bool isCommercial, Licensing.RoyaltyConfig calldata royaltyConfig, bytes calldata data) external returns (uint256 policyId);

/// @notice Adds a policy to an IP policy list
/// @param ipId The id of the IP
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,13 @@ import { IPolicyFrameworkManager } from "../../../interfaces/modules/licensing/I
/// @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.
/// @param royaltyPolicy Address of a royalty policy contract (e.g. RoyaltyPolicyLS) that will handle royalty payments
struct UMLPolicy {
bool transferable;
bool transferable; // This should be removed as is now in Policy
bool attribution;
bool commercialUse;
bool commercialUse; // This should be removed as is now in Policy
bool commercialAttribution;
string[] commercializers;
uint32 commercialRevShare;
Expand Down
11 changes: 8 additions & 3 deletions contracts/interfaces/modules/royalty/policies/IRoyaltyPolicy.sol
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,12 @@ interface IRoyaltyPolicy {
/// @param amount The amount to pay
function onRoyaltyPayment(address caller, address ipId, address token, uint256 amount) external;

/// @notice Returns the minimum royalty the IPAccount expects from descendants
/// @param ipId The ipId
function minRoyaltyFromDescendants(address ipId) external view returns (uint32);
function verifyParamsMatch(bytes memory data) external view;
function verifyMultiParentLinking(
uint256 iteration,
bytes memory accumulator,
bytes memory data
) external view returns (bytes memory accData);
function childRoyaltyData(bytes memory accumulator, bytes calldata childInput) external pure returns (bytes memory output);

}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@ import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";

import { IRoyaltyPolicy } from "../../../../interfaces/modules/royalty/policies/IRoyaltyPolicy.sol";

struct LSParams {
uint256 selfRevShare;
uint256 derivRevShare;
}

/// @title RoyaltyPolicy interface
interface IRoyaltyPolicyLS is IRoyaltyPolicy {
/// @notice Gets the royalty data
Expand Down
7 changes: 7 additions & 0 deletions contracts/lib/Licensing.sol
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,13 @@ library Licensing {
struct Policy {
address policyFramework;
bool isLicenseTransferable;
bool isCommercial;
RoyaltyConfig royaltyConfig;
bytes data;
}

struct RoyaltyConfig {
address rPolicy;
bytes data;
}

Expand Down
158 changes: 62 additions & 96 deletions contracts/modules/licensing/LicensingModule.sol

Large diffs are not rendered by default.

38 changes: 21 additions & 17 deletions contracts/modules/licensing/UMLPolicyFrameworkManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { IUMLPolicyFrameworkManager, UMLPolicy, UMLAggregator } from "../../inte
import { IPolicyFrameworkManager } from "../../interfaces/modules/licensing/IPolicyFrameworkManager.sol";
import { BasePolicyFrameworkManager } from "../../modules/licensing/BasePolicyFrameworkManager.sol";
import { LicensorApprovalChecker } from "../../modules/licensing/parameter-helpers/LicensorApprovalChecker.sol";
import { LSParams } from "../../interfaces/modules/royalty/policies/IRoyaltyPolicyLS.sol";

/// @title UMLPolicyFrameworkManager
/// @notice This is the UML Policy Framework Manager, which implements the UML Policy Framework
Expand Down Expand Up @@ -49,9 +50,23 @@ contract UMLPolicyFrameworkManager is
function registerPolicy(UMLPolicy calldata umlPolicy) external returns (uint256 policyId) {
_verifyComercialUse(umlPolicy);
_verifyDerivatives(umlPolicy);
// TODO if royaltyPolicy not supported, revert? or just set royaltyPolicy in UMLPolicyFrameworkManager
// constructor


Licensing.RoyaltyConfig memory rConfig = Licensing.RoyaltyConfig({
rPolicy: umlPolicy.royaltyPolicy,
data: abi.encode(
LSParams({
selfRevShare: umlPolicy.commercialRevShare,
derivRevShare: umlPolicy.derivativesRevShare
})
)
});

// TODO: either split UMLPolicy in smaller structs, or dont store the whole thing encoded in module
// No need to emit here, as the LicensingModule will emit the event

return LICENSING_MODULE.registerPolicy(umlPolicy.transferable, abi.encode(umlPolicy));
return LICENSING_MODULE.registerPolicy(umlPolicy.transferable, umlPolicy.commercialUse, abi.encode(umlPolicy));
}

/// Called by licenseRegistry to verify policy parameters for linking an IP
Expand All @@ -65,28 +80,17 @@ contract UMLPolicyFrameworkManager is
address ipId,
address, // parentIpId
bytes calldata policyData
) external override onlyLicensingModule returns (IPolicyFrameworkManager.VerifyLinkResponse memory) {
) external override onlyLicensingModule returns (bool) {
UMLPolicy memory policy = abi.decode(policyData, (UMLPolicy));
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),
royaltyDerivativeRevShare: 0
});

if (policy.commercialUse) {
response.isRoyaltyRequired = true;
response.royaltyPolicy = policy.royaltyPolicy;
response.royaltyDerivativeRevShare = policy.derivativesRevShare;
}
bool isLinkingAllowed = true;

// If the policy defines the licensor must approve derivatives, check if the
// derivative is approved by the licensor
if (policy.derivativesApproval) {
response.isLinkingAllowed = response.isLinkingAllowed && isDerivativeApproved(licenseId, ipId);
isLinkingAllowed == isLinkingAllowed && isLinkingAllowed && isDerivativeApproved(licenseId, ipId);
}

return response;
return isLinkingAllowed;
}

/// Called by licenseRegistry to verify policy parameters for minting a license
Expand Down
20 changes: 12 additions & 8 deletions contracts/modules/royalty-module/RoyaltyModule.sol
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,9 @@ contract RoyaltyModule is IRoyaltyModule, Governable, ReentrancyGuard {
address[] calldata _parentIpIds,
bytes calldata _data
) external nonReentrant onlyLicensingModule {
if (isRoyaltyPolicyImmutable[_ipId]) revert Errors.RoyaltyModule__AlreadySetRoyaltyPolicy();
if (isRoyaltyPolicyImmutable[_ipId]) {
revert Errors.RoyaltyModule__AlreadySetRoyaltyPolicy();
}
if (!isWhitelistedRoyaltyPolicy[_royaltyPolicy]) revert Errors.RoyaltyModule__NotWhitelistedRoyaltyPolicy();

if (_parentIpIds.length > 0) isRoyaltyPolicyImmutable[_ipId] = true;
Expand All @@ -100,15 +102,17 @@ contract RoyaltyModule is IRoyaltyModule, Governable, ReentrancyGuard {
emit RoyaltyPolicySet(_ipId, _royaltyPolicy, _data);
}

function setRoyaltyPolicyImmutable(address _ipId) external onlyLicensingModule {
isRoyaltyPolicyImmutable[_ipId] = true;
function verifyCanMintWihtImmutable(address licensorIp, address royaltyPolicy, bytes memory data) external view nonReentrant onlyLicensingModule {
// If the royalty policy is immutable, we allow minting license on a private policy if and only
// if this policyId's royalty policy and min royalty is the same as the current setting.
if (royaltyPolicy != royaltyPolicies[licensorIp]) {
revert Errors.LicensingModule__MismatchBetweenRoyaltyPolicy();
}
IRoyaltyPolicy(royaltyPolicy).verifyParamsMatch(data);
}

function minRoyaltyFromDescendants(address _ipId) external view returns (uint256) {
address royaltyPolicy = royaltyPolicies[_ipId];
if (royaltyPolicy == address(0)) revert Errors.RoyaltyModule__NoRoyaltyPolicySet();

return IRoyaltyPolicy(royaltyPolicy).minRoyaltyFromDescendants(_ipId);
function setRoyaltyPolicyImmutable(address _ipId) external onlyLicensingModule {
isRoyaltyPolicyImmutable[_ipId] = true;
}

/// @notice Allows a sender to to pay royalties on behalf of an ipId
Expand Down
61 changes: 59 additions & 2 deletions contracts/modules/royalty-module/policies/RoyaltyPolicyLS.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { LSClaimer } from "../../../modules/royalty-module/policies/LSClaimer.so
import { ILiquidSplitClone } from "../../../interfaces/modules/royalty/policies/ILiquidSplitClone.sol";
import { ILiquidSplitFactory } from "../../../interfaces/modules/royalty/policies/ILiquidSplitFactory.sol";
import { ILiquidSplitMain } from "../../../interfaces/modules/royalty/policies/ILiquidSplitMain.sol";
import { IRoyaltyPolicyLS } from "../../../interfaces/modules/royalty/policies/IRoyaltyPolicyLS.sol";
import { IRoyaltyPolicyLS, LSParams } from "../../../interfaces/modules/royalty/policies/IRoyaltyPolicyLS.sol";
import { Errors } from "../../../lib/Errors.sol";

/// @title Liquid Split Royalty Policy
Expand All @@ -26,6 +26,11 @@ contract RoyaltyPolicyLS is IRoyaltyPolicyLS, ERC1155Holder {
uint32 minRoyalty; // minimum royalty the ipId will receive from its children and grandchildren (number between 0 and 1000)
}

struct LSRoyaltyAccumulator {
uint32 royaltyDerivativeRevShare;
uint32 derivativeRevShareSum;
}

/// @notice Percentage scale - 1000 rnfts represents 100%
uint32 public constant TOTAL_RNFT_SUPPLY = 1000;

Expand Down Expand Up @@ -114,10 +119,62 @@ contract RoyaltyPolicyLS is IRoyaltyPolicyLS, ERC1155Holder {

/// @notice Returns the minimum royalty the IPAccount expects from descendants
/// @param _ipId The ipId
function minRoyaltyFromDescendants(address _ipId) external view override returns (uint32) {
function minRoyaltyFromDescendants(address _ipId) public view override returns (uint32) {
return royaltyData[_ipId].minRoyalty;
}

function verifyParamsMatch(bytes memory data, address ipId) external view {
uint256 newMinRoyalty = abi.decode(data, (uint256));
uint256 minRoyalty = minRoyaltyFromDescendants(ipId);
if (newMinRoyalty != minRoyalty) {
revert Errors.LicensingModule__MismatchBetweenCommercialRevenueShareAndMinRoyalty();
}
}

function verifyMultiParentLinking(
uint256 iteration,
bytes memory accumulator,
bytes memory data
) external pure returns (bytes memory accData) {
LSRoyaltyAccumulator memory acc;
if (accumulator.length == 0) {
acc = LSRoyaltyAccumulator({
royaltyDerivativeRevShare: 0,
derivativeRevShareSum: 0
});
} else {
acc = abi.decode(accumulator, (LSRoyaltyAccumulator));
}
LSParams memory policyRData = abi.decode(data, (LSParams));
// TODO: Unit test.
// If the previous license's derivativeRevShare is different from that of the current license, revert.
// For iteration == 0, this check is skipped as `royaltyDerivativeRevShare` param is at 0.
if (iteration > 0 && acc.royaltyDerivativeRevShare != policyRData.derivRevShare) {
revert Errors.LicensingModule__IncompatibleRoyaltyPolicyDerivativeRevShare();
}

// TODO: Read max RNFT supply instead of hardcoding the expected max supply
// TODO: Do we need safe check?
// TODO: Test this in unit test.
if (acc.derivativeRevShareSum + policyRData.derivRevShare > 1000) {
revert Errors.LicensingModule__DerivativeRevShareSumExceedsMaxRNFTSupply();
}
acc.royaltyDerivativeRevShare = policyRData.derivRevShare;
acc.derivativeRevShareSum += policyRData.derivRevShare;
return abi.encode(acc);
}

function childRoyaltyData(bytes memory accumulator, bytes calldata childInput) external pure returns (bytes memory output) {
// TODO: currently, `royaltyDerivativeRevShare` is the derivative rev share value of the last license.
LSRoyaltyAccumulator memory acc = abi.decode(accumulator, (LSRoyaltyAccumulator));
// If the parent licenses specify the `derivativeRevShare` value to non-zero, use the value.
// Otherwise, the child IPAccount has the freedom to set the value.
uint256 minRoyalty = abi.decode(childInput, (uint256));
return abi.encode(
acc.royaltyDerivativeRevShare > 0 ? acc.royaltyDerivativeRevShare : minRoyalty
);
}

/// @notice Distributes funds to the accounts in the LiquidSplitClone contract
/// @param _ipId The ipId
/// @param _token The token to distribute
Expand Down