Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce maxRevenueShare for Minting License Tokens and Registering Derivatives #364

Merged
merged 1 commit into from
Dec 14, 2024
Merged
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
14 changes: 12 additions & 2 deletions contracts/LicenseToken.sol
Original file line number Diff line number Diff line change
Expand Up @@ -88,14 +88,16 @@ contract LicenseToken is ILicenseToken, ERC721EnumerableUpgradeable, AccessManag
/// @param amount The amount of License Tokens to mint.
/// @param minter The address of the minter.
/// @param receiver The address of the receiver of the minted License Tokens.
/// @param maxRevenueShare The maximum revenue share percentage allowed for minting the License Tokens.
/// @return startLicenseTokenId The start ID of the minted License Tokens.
function mintLicenseTokens(
address licensorIpId,
address licenseTemplate,
uint256 licenseTermsId,
uint256 amount, // mint amount
address minter,
address receiver
address receiver,
uint32 maxRevenueShare
) external onlyLicensingModule returns (uint256 startLicenseTokenId) {
LicenseTokenMetadata memory ltm = LicenseTokenMetadata({
licensorIpId: licensorIpId,
Expand All @@ -104,7 +106,15 @@ contract LicenseToken is ILicenseToken, ERC721EnumerableUpgradeable, AccessManag
transferable: ILicenseTemplate(licenseTemplate).isLicenseTransferable(licenseTermsId),
commercialRevShare: LICENSE_REGISTRY.getRoyaltyPercent(licensorIpId, licenseTemplate, licenseTermsId)
});

if (maxRevenueShare != 0 && ltm.commercialRevShare > maxRevenueShare) {
revert Errors.LicenseToken__CommercialRevenueShareExceedMaxRevenueShare(
ltm.commercialRevShare,
maxRevenueShare,
licensorIpId,
licenseTemplate,
licenseTermsId
);
}
if (ltm.commercialRevShare > MAX_COMMERCIAL_REVENUE_SHARE) {
revert Errors.LicenseToken__InvalidRoyaltyPercent(
ltm.commercialRevShare,
Expand Down
4 changes: 3 additions & 1 deletion contracts/interfaces/ILicenseToken.sol
Original file line number Diff line number Diff line change
Expand Up @@ -41,14 +41,16 @@ interface ILicenseToken is IERC721Metadata, IERC721Enumerable {
/// @param amount The amount of License Tokens to mint.
/// @param minter The address of the minter.
/// @param receiver The address of the receiver of the minted License Tokens.
/// @param maxRevenueShare The maximum revenue share percentage allowed for minting the License Tokens.
/// @return startLicenseTokenId The start ID of the minted License Tokens.
function mintLicenseTokens(
address licensorIpId,
address licenseTemplate,
uint256 licenseTermsId,
uint256 amount, // mint amount
address minter,
address receiver
address receiver,
uint32 maxRevenueShare
) external returns (uint256 startLicenseTokenId);

/// @notice Burns specified License Tokens.
Expand Down
8 changes: 6 additions & 2 deletions contracts/interfaces/modules/licensing/ILicensingModule.sol
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ interface ILicensingModule is IModule {
/// @param receiver The address of the receiver.
/// @param royaltyContext The context of the royalty.
/// @param maxMintingFee The maximum minting fee that the caller is willing to pay. if set to 0 then no limit.
/// @param maxRevenueShare The maximum revenue share percentage allowed for minting the License Tokens.
/// @return startLicenseTokenId The start ID of the minted license tokens.
function mintLicenseTokens(
address licensorIpId,
Expand All @@ -91,7 +92,8 @@ interface ILicensingModule is IModule {
uint256 amount,
address receiver,
bytes calldata royaltyContext,
uint256 maxMintingFee
uint256 maxMintingFee,
uint32 maxRevenueShare
) external returns (uint256 startLicenseTokenId);

/// @notice Registers a derivative directly with parent IP's license terms, without needing license tokens,
Expand All @@ -106,14 +108,16 @@ interface ILicensingModule is IModule {
/// @param royaltyContext The context of the royalty.
/// @param maxMintingFee The maximum minting fee that the caller is willing to pay. if set to 0 then no limit.
/// @param maxRts The maximum number of royalty tokens that can be distributed to the external royalty policies.
/// @param maxRevenueShare The maximum revenue share percentage allowed for minting the License Tokens.
function registerDerivative(
address childIpId,
address[] calldata parentIpIds,
uint256[] calldata licenseTermsIds,
address licenseTemplate,
bytes calldata royaltyContext,
uint256 maxMintingFee,
uint32 maxRts
uint32 maxRts,
uint32 maxRevenueShare
) external;

/// @notice Registers a derivative with license tokens.
Expand Down
18 changes: 17 additions & 1 deletion contracts/lib/Errors.sol
Original file line number Diff line number Diff line change
Expand Up @@ -337,7 +337,14 @@ library Errors {
address licenseTemplate,
uint256 licenseTermsId
);

/// @notice Commercial revenue share exceeds the maximum revenue share set by the minter of license token.
error LicenseToken__CommercialRevenueShareExceedMaxRevenueShare(
uint32 commercialRevenueShare,
uint32 maxRevenueShare,
address ipId,
address licenseTemplate,
uint256 licenseTermsId
);
////////////////////////////////////////////////////////////////////////////
// Licensing Module //
////////////////////////////////////////////////////////////////////////////
Expand Down Expand Up @@ -450,6 +457,15 @@ library Errors {
uint32 oldRoyaltyPercent
);

/// @notice Parent IP Royalty percentage is above the maximum royalty percentage.
error LicensingModule__ExceedMaxRevenueShare(
address ipId,
address licenseTemplate,
uint256 licenseTermsId,
uint32 revenueShare,
uint32 maxRevenueShare
);

////////////////////////////////////////////////////////////////////////////
// Dispute Module //
////////////////////////////////////////////////////////////////////////////
Expand Down
117 changes: 81 additions & 36 deletions contracts/modules/licensing/LicensingModule.sol
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ contract LicensingModule is
/// @param receiver The address of the receiver.
/// @param royaltyContext The context of the royalty.
/// @param maxMintingFee The maximum minting fee that the caller is willing to pay. if set to 0 then no limit.
/// @param maxRevenueShare The maximum revenue share percentage allowed for minting the License Tokens.
/// @return startLicenseTokenId The start ID of the minted license tokens.
function mintLicenseTokens(
address licensorIpId,
Expand All @@ -165,7 +166,8 @@ contract LicensingModule is
uint256 amount,
address receiver,
bytes calldata royaltyContext,
uint256 maxMintingFee
uint256 maxMintingFee,
uint32 maxRevenueShare
) external nonReentrant whenNotPaused returns (uint256 startLicenseTokenId) {
if (amount == 0) {
revert Errors.LicensingModule__MintAmountZero();
Expand All @@ -177,52 +179,24 @@ contract LicensingModule is
revert Errors.LicensingModule__LicensorIpNotRegistered();
}
_verifyIpNotDisputed(licensorIpId);
Licensing.LicensingConfig memory lsc = LICENSE_REGISTRY.verifyMintLicenseToken(
licensorIpId,
licenseTemplate,
licenseTermsId,
_hasPermission(licensorIpId)
);

if (lsc.isSet && lsc.disabled) {
revert Errors.LicensingModule__LicenseDisabled(licensorIpId, licenseTemplate, licenseTermsId);
}

uint256 mintingFeeByHook = 0;
if (lsc.isSet && lsc.licensingHook != address(0)) {
mintingFeeByHook = ILicensingHook(lsc.licensingHook).beforeMintLicenseTokens(
msg.sender,
licensorIpId,
licenseTemplate,
licenseTermsId,
amount,
receiver,
lsc.hookData
);
}

_payMintingFee(
_verifyAndPayMintingFee(
licensorIpId,
licenseTemplate,
licenseTermsId,
amount,
receiver,
royaltyContext,
lsc,
mintingFeeByHook,
maxMintingFee
);

if (!ILicenseTemplate(licenseTemplate).verifyMintLicenseToken(licenseTermsId, receiver, licensorIpId, amount)) {
revert Errors.LicensingModule__LicenseDenyMintLicenseToken(licenseTemplate, licenseTermsId, licensorIpId);
}

startLicenseTokenId = LICENSE_NFT.mintLicenseTokens(
licensorIpId,
licenseTemplate,
licenseTermsId,
amount,
msg.sender,
receiver
receiver,
maxRevenueShare
);

emit LicenseTokensMinted(
Expand All @@ -249,14 +223,16 @@ contract LicensingModule is
/// @param royaltyContext The context of the royalty.
/// @param maxMintingFee The maximum minting fee that the caller is willing to pay. if set to 0 then no limit.
/// @param maxRts The maximum number of royalty tokens that can be distributed to the external royalty policies.
/// @param maxRevenueShare The maximum revenue share percentage allowed for minting the License Tokens.
function registerDerivative(
address childIpId,
address[] calldata parentIpIds,
uint256[] calldata licenseTermsIds,
address licenseTemplate,
bytes calldata royaltyContext,
uint256 maxMintingFee,
uint32 maxRts
uint32 maxRts,
uint32 maxRevenueShare
) external nonReentrant whenNotPaused verifyPermission(childIpId) {
if (parentIpIds.length != licenseTermsIds.length) {
revert Errors.LicensingModule__LicenseTermsLengthMismatch(parentIpIds.length, licenseTermsIds.length);
Expand Down Expand Up @@ -300,7 +276,8 @@ contract LicensingModule is
licenseTemplate,
royaltyContext,
maxMintingFee,
maxRts
maxRts,
maxRevenueShare
);

emit DerivativeRegistered(
Expand Down Expand Up @@ -503,7 +480,8 @@ contract LicensingModule is
address licenseTemplate,
bytes calldata royaltyContext,
uint256 maxMintingFee,
uint32 maxRts
uint32 maxRts,
uint32 maxRevenueShare
) private {
// Process the payment for the minting fee.
(address[] memory royaltyPolicies, uint32[] memory royaltyPercents) = _payMintingFeeForAllParentIps(
Expand All @@ -515,6 +493,23 @@ contract LicensingModule is
maxMintingFee
);

for (uint256 i = 0; i < parentIpIds.length; i++) {
royaltyPercents[i] = LICENSE_REGISTRY.getRoyaltyPercent(
parentIpIds[i],
licenseTemplate,
licenseTermsIds[i]
);
if (maxRevenueShare != 0 && royaltyPercents[i] > maxRevenueShare) {
revert Errors.LicensingModule__ExceedMaxRevenueShare(
parentIpIds[i],
licenseTemplate,
licenseTermsIds[i],
royaltyPercents[i],
maxRevenueShare
);
}
}

if (royaltyPolicies.length == 0 || royaltyPolicies[0] == address(0)) return;
ROYALTY_MODULE.onLinkToParents(
childIpId,
Expand Down Expand Up @@ -640,6 +635,56 @@ contract LicensingModule is
);
}

/// @dev verify minting license token and pay minting fee
function _verifyAndPayMintingFee(
address licensorIpId,
address licenseTemplate,
uint256 licenseTermsId,
uint256 amount,
address receiver,
bytes calldata royaltyContext,
uint256 maxMintingFee
) private {
Licensing.LicensingConfig memory lsc = LICENSE_REGISTRY.verifyMintLicenseToken(
licensorIpId,
licenseTemplate,
licenseTermsId,
_hasPermission(licensorIpId)
);

if (lsc.isSet && lsc.disabled) {
revert Errors.LicensingModule__LicenseDisabled(licensorIpId, licenseTemplate, licenseTermsId);
}

uint256 mintingFeeByHook = 0;
if (lsc.isSet && lsc.licensingHook != address(0)) {
mintingFeeByHook = ILicensingHook(lsc.licensingHook).beforeMintLicenseTokens(
msg.sender,
licensorIpId,
licenseTemplate,
licenseTermsId,
amount,
receiver,
lsc.hookData
);
}

_payMintingFee(
licensorIpId,
licenseTemplate,
licenseTermsId,
amount,
royaltyContext,
lsc,
mintingFeeByHook,
maxMintingFee
);

if (!ILicenseTemplate(licenseTemplate).verifyMintLicenseToken(licenseTermsId, receiver, licensorIpId, amount)) {
revert Errors.LicensingModule__LicenseDenyMintLicenseToken(licenseTemplate, licenseTermsId, licensorIpId);
}
}

/// @dev pay minting fee for an parent IP
/// This function is called by mintLicenseTokens and registerDerivative
/// It initialize royalty module and pays the minting fee for the parent IP through the royalty module
Expand Down
21 changes: 14 additions & 7 deletions test/foundry/LicenseToken.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,8 @@ contract LicenseTokenTest is BaseTest {
licenseTermsId: commTermsId,
amount: mintAmount,
minter: ipOwner[1],
receiver: ipOwner[1]
receiver: ipOwner[1],
maxRevenueShare: 0
});

for (uint256 i = 0; i < mintAmount; i++) {
Expand All @@ -89,7 +90,8 @@ contract LicenseTokenTest is BaseTest {
licenseTermsId: commTermsId,
amount: 1,
minter: ipOwner[1],
receiver: ipOwner[1]
receiver: ipOwner[1],
maxRevenueShare: 0
});

vm.prank(ipOwner[1]);
Expand Down Expand Up @@ -137,7 +139,8 @@ contract LicenseTokenTest is BaseTest {
licenseTermsId: licenseTermsId,
amount: 1,
minter: ipOwner[1],
receiver: ipOwner[1]
receiver: ipOwner[1],
maxRevenueShare: 0
});

vm.expectRevert(Errors.LicenseToken__NotTransferable.selector);
Expand All @@ -157,7 +160,8 @@ contract LicenseTokenTest is BaseTest {
licenseTermsId: licenseTermsId,
amount: 1,
minter: ipOwner[1],
receiver: ipOwner[1]
receiver: ipOwner[1],
maxRevenueShare: 0
});

string memory tokenURI = licenseToken.tokenURI(licenseTokenId);
Expand Down Expand Up @@ -196,7 +200,8 @@ contract LicenseTokenTest is BaseTest {
licenseTermsId: licenseTermsId,
amount: 1,
minter: ipOwner[1],
receiver: ipOwner[1]
receiver: ipOwner[1],
maxRevenueShare: 0
});

ILicenseToken.LicenseTokenMetadata memory lmt = licenseToken.getLicenseTokenMetadata(licenseTokenId);
Expand Down Expand Up @@ -233,7 +238,8 @@ contract LicenseTokenTest is BaseTest {
licenseTermsId: licenseTermsId,
amount: 1,
minter: ipOwner[1],
receiver: ipOwner[1]
receiver: ipOwner[1],
maxRevenueShare: 0
});

ILicenseToken.LicenseTokenMetadata memory lmt = licenseToken.getLicenseTokenMetadata(licenseTokenId);
Expand Down Expand Up @@ -280,7 +286,8 @@ contract LicenseTokenTest is BaseTest {
licenseTermsId: licenseTermsId,
amount: 1,
minter: ipOwner[1],
receiver: ipOwner[1]
receiver: ipOwner[1],
maxRevenueShare: 0
});
}
}
Loading
Loading