Skip to content

Commit

Permalink
Introduce maxMintingFee when minting licensing (#276)
Browse files Browse the repository at this point in the history
  • Loading branch information
kingster-will authored Oct 17, 2024
1 parent 1efaf9e commit 73bcb0f
Show file tree
Hide file tree
Showing 17 changed files with 385 additions and 113 deletions.
8 changes: 6 additions & 2 deletions contracts/interfaces/modules/licensing/ILicensingModule.sol
Original file line number Diff line number Diff line change
Expand Up @@ -82,14 +82,16 @@ interface ILicensingModule is IModule {
/// @param amount The amount of license tokens to mint.
/// @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.
/// @return startLicenseTokenId The start ID of the minted license tokens.
function mintLicenseTokens(
address licensorIpId,
address licenseTemplate,
uint256 licenseTermsId,
uint256 amount,
address receiver,
bytes calldata royaltyContext
bytes calldata royaltyContext,
uint256 maxMintingFee
) external returns (uint256 startLicenseTokenId);

/// @notice Registers a derivative directly with parent IP's license terms, without needing license tokens,
Expand All @@ -102,12 +104,14 @@ interface ILicensingModule is IModule {
/// @param licenseTermsIds The IDs of the license terms that the parent IP supports.
/// @param licenseTemplate The address of the license template of the license terms Ids.
/// @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.
function registerDerivative(
address childIpId,
address[] calldata parentIpIds,
uint256[] calldata licenseTermsIds,
address licenseTemplate,
bytes calldata royaltyContext
bytes calldata royaltyContext,
uint256 maxMintingFee
) external;

/// @notice Registers a derivative with license tokens.
Expand Down
3 changes: 3 additions & 0 deletions contracts/lib/Errors.sol
Original file line number Diff line number Diff line change
Expand Up @@ -365,6 +365,9 @@ library Errors {
/// @notice Grouping Module is zero address.
error LicensingModule__ZeroGroupingModule();

/// @notice licensing minting fee is above the maximum minting fee.
error LicensingModule__MintingFeeExceedMaxMintingFee(uint256 mintingFee, uint256 maxMintingFee);

////////////////////////////////////////////////////////////////////////////
// Dispute Module //
////////////////////////////////////////////////////////////////////////////
Expand Down
92 changes: 75 additions & 17 deletions contracts/modules/licensing/LicensingModule.sol
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,13 @@ contract LicensingModule is
using Strings for *;
using IPAccountStorageOps for IIPAccount;

struct RoyaltyPolicyInfo {
address royaltyPolicy;
uint32 royaltyPercent;
uint256 mintingFeeByLicense;
address currencyToken;
}

/// @inheritdoc IModule
string public constant override name = LICENSING_MODULE_KEY;

Expand Down Expand Up @@ -148,14 +155,16 @@ contract LicensingModule is
/// @param amount The amount of license tokens to mint.
/// @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.
/// @return startLicenseTokenId The start ID of the minted license tokens.
function mintLicenseTokens(
address licensorIpId,
address licenseTemplate,
uint256 licenseTermsId,
uint256 amount,
address receiver,
bytes calldata royaltyContext
bytes calldata royaltyContext,
uint256 maxMintingFee
) external whenNotPaused nonReentrant returns (uint256 startLicenseTokenId) {
if (amount == 0) {
revert Errors.LicensingModule__MintAmountZero();
Expand Down Expand Up @@ -186,7 +195,16 @@ contract LicensingModule is
);
}

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

if (!ILicenseTemplate(licenseTemplate).verifyMintLicenseToken(licenseTermsId, receiver, licensorIpId, amount)) {
revert Errors.LicensingModule__LicenseDenyMintLicenseToken(licenseTemplate, licenseTermsId, licensorIpId);
Expand Down Expand Up @@ -223,12 +241,14 @@ contract LicensingModule is
/// @param licenseTermsIds The IDs of the license terms that the parent IP supports.
/// @param licenseTemplate The address of the license template of the license terms Ids.
/// @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.
function registerDerivative(
address childIpId,
address[] calldata parentIpIds,
uint256[] calldata licenseTermsIds,
address licenseTemplate,
bytes calldata royaltyContext
bytes calldata royaltyContext,
uint256 maxMintingFee
) external whenNotPaused nonReentrant verifyPermission(childIpId) {
if (parentIpIds.length != licenseTermsIds.length) {
revert Errors.LicensingModule__LicenseTermsLengthMismatch(parentIpIds.length, licenseTermsIds.length);
Expand All @@ -243,8 +263,14 @@ contract LicensingModule is
// All license terms must be compatible with each other.
// Verify that the derivative IP is permitted under all license terms from the parent IPs.
address childIpOwner = IIPAccount(payable(childIpId)).owner();
ILicenseTemplate lct = ILicenseTemplate(licenseTemplate);
if (!lct.verifyRegisterDerivativeForAllParents(childIpId, parentIpIds, licenseTermsIds, childIpOwner)) {
if (
!ILicenseTemplate(licenseTemplate).verifyRegisterDerivativeForAllParents(
childIpId,
parentIpIds,
licenseTermsIds,
childIpOwner
)
) {
revert Errors.LicensingModule__LicenseNotCompatibleForDerivative(childIpId);
}

Expand All @@ -266,7 +292,8 @@ contract LicensingModule is
licenseTermsIds,
licenseTemplate,
childIpOwner,
royaltyContext
royaltyContext,
maxMintingFee
);
// emit event
emit DerivativeRegistered(
Expand Down Expand Up @@ -475,7 +502,8 @@ contract LicensingModule is
uint256[] calldata licenseTermsIds,
address licenseTemplate,
address childIpOwner,
bytes calldata royaltyContext
bytes calldata royaltyContext,
uint256 maxMintingFee
) private returns (address[] memory royaltyPolicies, uint32[] memory royaltyPercents) {
royaltyPolicies = new address[](licenseTermsIds.length);
royaltyPercents = new uint32[](licenseTermsIds.length);
Expand All @@ -487,7 +515,8 @@ contract LicensingModule is
licenseTemplate,
licenseTermsIds[i],
childIpOwner,
royaltyContext
royaltyContext,
maxMintingFee
);
royaltyPolicies[i] = royaltyPolicy;
royaltyPercents[i] = royaltyPercent;
Expand All @@ -500,7 +529,8 @@ contract LicensingModule is
address licenseTemplate,
uint256 licenseTermsId,
address childIpOwner,
bytes calldata royaltyContext
bytes calldata royaltyContext,
uint256 maxMintingFee
) private returns (address royaltyPolicy, uint32 royaltyPercent) {
Licensing.LicensingConfig memory lsc = LICENSE_REGISTRY.getLicensingConfig(
parentIpId,
Expand All @@ -526,7 +556,8 @@ contract LicensingModule is
1,
royaltyContext,
lsc,
mintingFeeByHook
mintingFeeByHook,
maxMintingFee
);
}

Expand All @@ -540,6 +571,8 @@ contract LicensingModule is
/// @param amount The amount of license tokens to mint.
/// @param royaltyContext The context of the royalty.
/// @param licensingConfig The minting license config
/// @param mintingFeeByHook The minting fee set by the hook.
/// @param maxMintingFee The maximum minting fee that the caller is willing to pay.
/// @return royaltyPolicy The address of the royalty policy.
/// @return royaltyPercent The license royalty percentage
function _payMintingFee(
Expand All @@ -549,26 +582,51 @@ contract LicensingModule is
uint256 amount,
bytes calldata royaltyContext,
Licensing.LicensingConfig memory licensingConfig,
uint256 mintingFeeByHook
uint256 mintingFeeByHook,
uint256 maxMintingFee
) private returns (address royaltyPolicy, uint32 royaltyPercent) {
ILicenseTemplate lct = ILicenseTemplate(licenseTemplate);
uint256 mintingFeeByLicense = 0;
address currencyToken = address(0);
(royaltyPolicy, royaltyPercent, mintingFeeByLicense, currencyToken) = lct.getRoyaltyPolicy(licenseTermsId);
RoyaltyPolicyInfo memory royaltyInfo = _getRoyaltyPolicyInfo(licenseTemplate, licenseTermsId);
royaltyPolicy = royaltyInfo.royaltyPolicy;
royaltyPercent = royaltyInfo.royaltyPercent;
// override royalty percent if it is set in licensing config
if (licensingConfig.isSet && licensingConfig.commercialRevShare != 0) {
royaltyPercent = licensingConfig.commercialRevShare;
}
if (royaltyPolicy != address(0)) {
ROYALTY_MODULE.onLicenseMinting(parentIpId, royaltyPolicy, royaltyPercent, royaltyContext);
uint256 tmf = _getTotalMintingFee(licensingConfig, mintingFeeByHook, mintingFeeByLicense, amount);
uint256 tmf = _getTotalMintingFee(
licensingConfig,
mintingFeeByHook,
royaltyInfo.mintingFeeByLicense,
amount
);
if (maxMintingFee != 0 && tmf > maxMintingFee) {
revert Errors.LicensingModule__MintingFeeExceedMaxMintingFee(tmf, maxMintingFee);
}
// pay minting fee
if (tmf > 0) {
ROYALTY_MODULE.payLicenseMintingFee(parentIpId, msg.sender, currencyToken, tmf);
ROYALTY_MODULE.payLicenseMintingFee(parentIpId, msg.sender, royaltyInfo.currencyToken, tmf);
}
}
}

/// @dev get royalty policy info from given license terms
/// @param licenseTemplate The address of the license template.
/// @param licenseTermsId The ID of the license terms.
/// @return RoyaltyPolicyInfo The royalty policy info
function _getRoyaltyPolicyInfo(
address licenseTemplate,
uint256 licenseTermsId
) private view returns (RoyaltyPolicyInfo memory) {
(
address royaltyPolicy,
uint32 royaltyPercent,
uint256 mintingFeeByLicense,
address currencyToken
) = ILicenseTemplate(licenseTemplate).getRoyaltyPolicy(licenseTermsId);
return RoyaltyPolicyInfo(royaltyPolicy, royaltyPercent, mintingFeeByLicense, currencyToken);
}

/// @dev get total minting fee
/// There are 3 places to get the minting fee: license terms, MintingLicenseConfig, MintingFeeModule
/// The order of priority is MintingFeeModule > MintingLicenseConfig > > license terms
Expand Down
18 changes: 12 additions & 6 deletions test/foundry/integration/big-bang/SingleNftCollection.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,8 @@ contract BigBang_Integration_SingleNftCollection is BaseIntegration {
licenseTermsId: commDerivTermsId,
amount: 1,
receiver: u.carl,
royaltyContext: ""
royaltyContext: "",
maxMintingFee: 0
});

ipAcct[6] = registerIpAccount(mockNFT, 6, u.carl);
Expand All @@ -178,7 +179,8 @@ contract BigBang_Integration_SingleNftCollection is BaseIntegration {
licenseTermsId: ncSocialRemixTermsId,
amount: 1,
receiver: u.carl,
royaltyContext: ""
royaltyContext: "",
maxMintingFee: 0
});

ipAcct[tokenId] = registerIpAccount(address(mockNFT), tokenId, u.carl);
Expand Down Expand Up @@ -207,7 +209,8 @@ contract BigBang_Integration_SingleNftCollection is BaseIntegration {
licenseTermsId: commDerivTermsId,
amount: 2,
receiver: u.alice,
royaltyContext: ""
royaltyContext: "",
maxMintingFee: 0
}); // ID 0 (first license)

ipAcct[2] = registerIpAccount(mockNFT, 2, u.alice);
Expand Down Expand Up @@ -243,7 +246,8 @@ contract BigBang_Integration_SingleNftCollection is BaseIntegration {
licenseTermsId: commDerivTermsId,
amount: license0_mintAmount,
receiver: u.carl,
royaltyContext: ""
royaltyContext: "",
maxMintingFee: 0
});

// NC Social Remix license
Expand All @@ -253,7 +257,8 @@ contract BigBang_Integration_SingleNftCollection is BaseIntegration {
licenseTermsId: ncSocialRemixTermsId, // ipAcct[3] has this policy attached
amount: 1,
receiver: u.carl,
royaltyContext: ""
royaltyContext: "",
maxMintingFee: 0
});

ipAcct[tokenId] = registerIpAccount(address(mockNFT), tokenId, u.carl);
Expand All @@ -278,7 +283,8 @@ contract BigBang_Integration_SingleNftCollection is BaseIntegration {
licenseTermsId: commDerivTermsId,
amount: license1_mintAmount,
receiver: u.carl,
royaltyContext: ""
royaltyContext: "",
maxMintingFee: 0
});
carl_licenses[1] = carl_licenses[1] + license1_mintAmount - 1; // use last license ID minted from above

Expand Down
18 changes: 12 additions & 6 deletions test/foundry/integration/flows/disputes/Disputes.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,8 @@ contract Flows_Integration_Disputes is BaseIntegration {
licenseTermsId: ncSocialRemixTermsId,
amount: 1,
receiver: u.carl,
royaltyContext: ""
royaltyContext: "",
maxMintingFee: 0
});
assertEq(licenseToken.balanceOf(u.carl), 1);

Expand All @@ -69,7 +70,8 @@ contract Flows_Integration_Disputes is BaseIntegration {
licenseTermsId: ncSocialRemixTermsId,
amount: 1,
receiver: u.carl,
royaltyContext: ""
royaltyContext: "",
maxMintingFee: 0
});
}

Expand All @@ -83,7 +85,8 @@ contract Flows_Integration_Disputes is BaseIntegration {
licenseTermsId: ncSocialRemixTermsId,
amount: 1,
receiver: u.carl,
royaltyContext: ""
royaltyContext: "",
maxMintingFee: 0
});
assertEq(licenseToken.balanceOf(u.carl), 1);

Expand Down Expand Up @@ -113,7 +116,8 @@ contract Flows_Integration_Disputes is BaseIntegration {
parentIpIds: parentIpIds,
licenseTermsIds: licenseTermsIds,
licenseTemplate: address(pilTemplate),
royaltyContext: ""
royaltyContext: "",
maxMintingFee: 0
});
}

Expand All @@ -127,7 +131,8 @@ contract Flows_Integration_Disputes is BaseIntegration {
licenseTermsId: ncSocialRemixTermsId,
amount: 1,
receiver: u.carl,
royaltyContext: ""
royaltyContext: "",
maxMintingFee: 0
});
assertEq(licenseToken.balanceOf(u.carl), 1);

Expand All @@ -154,7 +159,8 @@ contract Flows_Integration_Disputes is BaseIntegration {
licenseTermsId: ncSocialRemixTermsId,
amount: 1,
receiver: u.carl,
royaltyContext: ""
royaltyContext: "",
maxMintingFee: 0
});
assertEq(licenseToken.balanceOf(u.carl), 1);
}
Expand Down
6 changes: 3 additions & 3 deletions test/foundry/integration/flows/grouping/Grouping.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,8 @@ contract Flows_Integration_Grouping is BaseIntegration {
vm.stopPrank();
}

licensingModule.mintLicenseTokens(ipAcct[1], address(pilTemplate), commRemixTermsId, 1, address(this), "");
licensingModule.mintLicenseTokens(ipAcct[2], address(pilTemplate), commRemixTermsId, 1, address(this), "");
licensingModule.mintLicenseTokens(ipAcct[1], address(pilTemplate), commRemixTermsId, 1, address(this), "", 0);
licensingModule.mintLicenseTokens(ipAcct[2], address(pilTemplate), commRemixTermsId, 1, address(this), "", 0);
{
address[] memory ipIds = new address[](2);
ipIds[0] = ipAcct[1];
Expand All @@ -98,7 +98,7 @@ contract Flows_Integration_Grouping is BaseIntegration {
parentIpIds[0] = groupId;
uint256[] memory licenseIds = new uint256[](1);
licenseIds[0] = commRemixTermsId;
licensingModule.registerDerivative(ipAcct[3], parentIpIds, licenseIds, address(pilTemplate), "");
licensingModule.registerDerivative(ipAcct[3], parentIpIds, licenseIds, address(pilTemplate), "", 0);
vm.stopPrank();
}

Expand Down
Loading

0 comments on commit 73bcb0f

Please sign in to comment.