Skip to content

Commit

Permalink
Add Function to Predict License Minting Fee in Licensing Module (stor…
Browse files Browse the repository at this point in the history
  • Loading branch information
kingster-will authored Sep 11, 2024
1 parent 9cbc013 commit 1d28bf8
Show file tree
Hide file tree
Showing 5 changed files with 279 additions and 0 deletions.
23 changes: 23 additions & 0 deletions contracts/interfaces/modules/licensing/ILicensingHook.sol
Original file line number Diff line number Diff line change
Expand Up @@ -51,4 +51,27 @@ interface ILicensingHook is IModule {
uint256 licenseTermsId,
bytes calldata hookData
) external returns (uint256 mintingFee);

/// @notice This function is called when the LicensingModule calculates/predict the minting fee for license tokens.
/// @dev The hook should guarantee the minting fee calculation is correct and return the minting fee which is
/// the exact same amount with returned by beforeMintLicenseTokens().
/// The hook should revert if the minting fee calculation is not allowed.
/// @param caller The address of the caller who calling the mintLicenseTokens() function.
/// @param licensorIpId The ID of licensor IP from which issue the license tokens.
/// @param licenseTemplate The address of the license template.
/// @param licenseTermsId The ID of the license terms within the license template,
/// which is used to mint license tokens.
/// @param amount The amount of license tokens to mint.
/// @param receiver The address of the receiver who receive the license tokens.
/// @param hookData The data to be used by the licensing hook.
/// @return totalMintingFee The total minting fee to be paid when minting amount of license tokens.
function calculateMintingFee(
address caller,
address licensorIpId,
address licenseTemplate,
uint256 licenseTermsId,
uint256 amount,
address receiver,
bytes calldata hookData
) external view returns (uint256 totalMintingFee);
}
19 changes: 19 additions & 0 deletions contracts/interfaces/modules/licensing/ILicensingModule.sol
Original file line number Diff line number Diff line change
Expand Up @@ -138,4 +138,23 @@ interface ILicensingModule is IModule {
uint256 licenseTermsId,
Licensing.LicensingConfig memory licensingConfig
) external;

/// @notice pre-compute the minting license fee for the given IP and license terms.
/// the function can be used to calculate the minting license fee before minting license tokens.
/// @param licensorIpId The IP ID of the licensor.
/// @param licenseTemplate The address of the license template.
/// @param licenseTermsId The ID of the license terms.
/// @param amount The amount of license tokens to mint.
/// @param receiver The address of the receiver.
/// @param royaltyContext The context of the royalty.
/// @return currencyToken The address of the ERC20 token used for minting license fee.
/// @return tokenAmount The amount of the currency token to be paid for minting license tokens.
function predictMintingLicenseFee(
address licensorIpId,
address licenseTemplate,
uint256 licenseTermsId,
uint256 amount,
address receiver,
bytes calldata royaltyContext
) external view returns (address currencyToken, uint256 tokenAmount);
}
57 changes: 57 additions & 0 deletions contracts/modules/licensing/LicensingModule.sol
Original file line number Diff line number Diff line change
Expand Up @@ -382,6 +382,63 @@ contract LicensingModule is
}
}

/// @notice pre-compute the minting license fee for the given IP and license terms.
/// the function can be used to calculate the minting license fee before minting license tokens.
/// @param licensorIpId The IP ID of the licensor.
/// @param licenseTemplate The address of the license template.
/// @param licenseTermsId The ID of the license terms.
/// @param amount The amount of license tokens to mint.
/// @param receiver The address of the receiver.
/// @param royaltyContext The context of the royalty.
/// @return currencyToken The address of the ERC20 token used for minting license fee.
/// @return tokenAmount The amount of the currency token to be paid for minting license tokens.
function predictMintingLicenseFee(
address licensorIpId,
address licenseTemplate,
uint256 licenseTermsId,
uint256 amount,
address receiver,
bytes calldata royaltyContext
) external view returns (address currencyToken, uint256 tokenAmount) {
tokenAmount = 0;
if (amount == 0) {
revert Errors.LicensingModule__MintAmountZero();
}
if (receiver == address(0)) {
revert Errors.LicensingModule__ReceiverZeroAddress();
}
if (!IP_ACCOUNT_REGISTRY.isIpAccount(licensorIpId)) {
revert Errors.LicensingModule__LicensorIpNotRegistered();
}
Licensing.LicensingConfig memory lsc = LICENSE_REGISTRY.verifyMintLicenseToken(
licensorIpId,
licenseTemplate,
licenseTermsId,
_hasPermission(licensorIpId)
);
uint256 mintingFeeByHook = 0;
if (lsc.isSet && lsc.licensingHook != address(0)) {
mintingFeeByHook = ILicensingHook(lsc.licensingHook).calculateMintingFee(
msg.sender,
licensorIpId,
licenseTemplate,
licenseTermsId,
amount,
receiver,
lsc.hookData
);
}

ILicenseTemplate lct = ILicenseTemplate(licenseTemplate);
uint256 mintingFeeByLicense = 0;
address royaltyPolicy = address(0);
(royaltyPolicy, , mintingFeeByLicense, currencyToken) = lct.getRoyaltyPolicy(licenseTermsId);

if (royaltyPolicy != address(0)) {
tokenAmount = _getTotalMintingFee(lsc, mintingFeeByHook, mintingFeeByLicense, amount);
}
}

/// @dev pay minting fee for all parent IPs
/// This function is called by registerDerivative
/// It pays the minting fee for all parent IPs through the royalty module
Expand Down
15 changes: 15 additions & 0 deletions test/foundry/mocks/module/MockLicensingHook.sol
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,21 @@ contract MockLicensingHook is BaseModule, ILicensingHook {
return 100;
}

function calculateMintingFee(
address caller,
address licensorIpId,
address licenseTemplate,
uint256 licenseTermsId,
uint256 amount,
address receiver,
bytes calldata hookData
) external view returns (uint256 totalMintingFee) {
address unqualifiedAddress = abi.decode(hookData, (address));
if (caller == unqualifiedAddress) revert("MockLicensingHook: caller is invalid");
if (receiver == unqualifiedAddress) revert("MockLicensingHook: receiver is invalid");
return amount * 100;
}

function supportsInterface(bytes4 interfaceId) public view virtual override(BaseModule, IERC165) returns (bool) {
return interfaceId == type(ILicensingHook).interfaceId || super.supportsInterface(interfaceId);
}
Expand Down
165 changes: 165 additions & 0 deletions test/foundry/modules/licensing/LicensingModule.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -1671,6 +1671,171 @@ contract LicensingModuleTest is BaseTest {
});
}

function test_LicensingModule_calculatingMintingFee_withMintingFeeFromHook() public {
uint256 termsId = pilTemplate.registerLicenseTerms(
PILFlavors.commercialRemix({
mintingFee: 999,
commercialRevShare: 10,
currencyToken: address(erc20),
royaltyPolicy: address(royaltyPolicyLAP)
})
);

MockLicensingHook licensingHook = new MockLicensingHook();
vm.prank(admin);
moduleRegistry.registerModule("MockLicensingHook", address(licensingHook));
Licensing.LicensingConfig memory licensingConfig = Licensing.LicensingConfig({
isSet: true,
mintingFee: 999999,
licensingHook: address(licensingHook),
hookData: abi.encode(address(0x123))
});
vm.prank(ipOwner1);
licensingModule.setLicensingConfig(ipId1, address(pilTemplate), termsId, licensingConfig);

vm.prank(ipOwner1);
licensingModule.attachLicenseTerms(ipId1, address(pilTemplate), termsId);

address receiver = address(0x111);
(address token, uint256 mintingFee) = licensingModule.predictMintingLicenseFee(
ipId1,
address(pilTemplate),
termsId,
5,
receiver,
""
);
assertEq(mintingFee, 100 * 5);
assertEq(token, address(erc20));

address minter = vm.addr(777);
vm.startPrank(minter);

erc20.mint(minter, 1000);
erc20.approve(address(royaltyModule), 100 * 5);

vm.expectEmit();
emit ILicensingModule.LicenseTokensMinted(minter, ipId1, address(pilTemplate), termsId, 5, receiver, 0);

uint256 lcTokenId = licensingModule.mintLicenseTokens({
licensorIpId: ipId1,
licenseTemplate: address(pilTemplate),
licenseTermsId: termsId,
amount: 5,
receiver: receiver,
royaltyContext: ""
});
vm.stopPrank();

assertEq(erc20.balanceOf(minter), 500);
assertEq(licenseToken.ownerOf(lcTokenId), receiver);
}

function test_LicensingModule_calculatingMintingFee_withMintingFeeFromLicenseConfig() public {
uint256 termsId = pilTemplate.registerLicenseTerms(
PILFlavors.commercialRemix({
mintingFee: 999,
commercialRevShare: 10,
currencyToken: address(erc20),
royaltyPolicy: address(royaltyPolicyLAP)
})
);

Licensing.LicensingConfig memory licensingConfig = Licensing.LicensingConfig({
isSet: true,
mintingFee: 1000,
licensingHook: address(0),
hookData: abi.encode(address(0x123))
});
vm.prank(ipOwner1);
licensingModule.setLicensingConfig(ipId1, address(pilTemplate), termsId, licensingConfig);

vm.prank(ipOwner1);
licensingModule.attachLicenseTerms(ipId1, address(pilTemplate), termsId);

address receiver = address(0x111);
(address token, uint256 mintingFee) = licensingModule.predictMintingLicenseFee(
ipId1,
address(pilTemplate),
termsId,
5,
receiver,
""
);
assertEq(mintingFee, 1000 * 5);
assertEq(token, address(erc20));

address minter = vm.addr(777);
vm.startPrank(minter);

erc20.mint(minter, 5000);
erc20.approve(address(royaltyModule), 1000 * 5);

vm.expectEmit();
emit ILicensingModule.LicenseTokensMinted(minter, ipId1, address(pilTemplate), termsId, 5, receiver, 0);

uint256 lcTokenId = licensingModule.mintLicenseTokens({
licensorIpId: ipId1,
licenseTemplate: address(pilTemplate),
licenseTermsId: termsId,
amount: 5,
receiver: receiver,
royaltyContext: ""
});
vm.stopPrank();

assertEq(erc20.balanceOf(minter), 0);
assertEq(licenseToken.ownerOf(lcTokenId), receiver);
}

function test_LicensingModule_calculatingMintingFee_withMintingFeeFromLicense() public {
uint256 termsId = pilTemplate.registerLicenseTerms(
PILFlavors.commercialRemix({
mintingFee: 10000,
commercialRevShare: 10,
currencyToken: address(erc20),
royaltyPolicy: address(royaltyPolicyLAP)
})
);

vm.prank(ipOwner1);
licensingModule.attachLicenseTerms(ipId1, address(pilTemplate), termsId);

address receiver = address(0x111);
(address token, uint256 mintingFee) = licensingModule.predictMintingLicenseFee(
ipId1,
address(pilTemplate),
termsId,
5,
receiver,
""
);
assertEq(mintingFee, 10000 * 5);
assertEq(token, address(erc20));

address minter = vm.addr(777);
vm.startPrank(minter);

erc20.mint(minter, 50000);
erc20.approve(address(royaltyModule), 10000 * 5);

vm.expectEmit();
emit ILicensingModule.LicenseTokensMinted(minter, ipId1, address(pilTemplate), termsId, 5, receiver, 0);

uint256 lcTokenId = licensingModule.mintLicenseTokens({
licensorIpId: ipId1,
licenseTemplate: address(pilTemplate),
licenseTermsId: termsId,
amount: 5,
receiver: receiver,
royaltyContext: ""
});
vm.stopPrank();

assertEq(erc20.balanceOf(minter), 0);
assertEq(licenseToken.ownerOf(lcTokenId), receiver);
}

function test_LicensingModule_mintLicenseTokens_withMintingFeeFromHook() public {
uint256 termsId = pilTemplate.registerLicenseTerms(
PILFlavors.commercialRemix({
Expand Down

0 comments on commit 1d28bf8

Please sign in to comment.