Skip to content

Commit

Permalink
Introduce maxRevenueShare for Minting License Tokens and Registering …
Browse files Browse the repository at this point in the history
…Derivatives (#364)
  • Loading branch information
kingster-will authored Dec 14, 2024
1 parent 1505d79 commit 34c5f2b
Show file tree
Hide file tree
Showing 22 changed files with 430 additions and 163 deletions.
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

0 comments on commit 34c5f2b

Please sign in to comment.