From a6f5935b59bcfc81b6d9bb19ade86fbe9ed56288 Mon Sep 17 00:00:00 2001 From: Kingter <83567446+kingster-will@users.noreply.github.com> Date: Wed, 18 Dec 2024 00:18:01 -0800 Subject: [PATCH] Ensure IP Cannot Be Registered as Derivative After Minting Private License Token (#366) --- contracts/LicenseToken.sol | 9 +- .../registries/ILicenseRegistry.sol | 3 + contracts/lib/Errors.sol | 6 ++ .../modules/licensing/LicensingModule.sol | 4 +- contracts/registries/LicenseRegistry.sol | 5 + .../modules/licensing/LicensingModule.t.sol | 97 +++++++++++++++++++ 6 files changed, 122 insertions(+), 2 deletions(-) diff --git a/contracts/LicenseToken.sol b/contracts/LicenseToken.sol index a458ac67..4b9b80f1 100644 --- a/contracts/LicenseToken.sol +++ b/contracts/LicenseToken.sol @@ -126,7 +126,9 @@ contract LicenseToken is ILicenseToken, ERC721EnumerableUpgradeable, AccessManag LicenseTokenStorage storage $ = _getLicenseTokenStorage(); startLicenseTokenId = $.totalMintedTokens; $.totalMintedTokens += amount; - $.licensorIpTotalTokens[licensorIpId] += amount; + if (!LICENSE_REGISTRY.isDefaultLicense(licenseTemplate, licenseTermsId)) { + $.licensorIpTotalTokens[licensorIpId] += amount; + } for (uint256 i = 0; i < amount; i++) { uint256 tokenId = startLicenseTokenId + i; $.licenseTokenMetadatas[tokenId] = ltm; @@ -170,6 +172,11 @@ contract LicenseToken is ILicenseToken, ERC721EnumerableUpgradeable, AccessManag ) { LicenseTokenStorage storage $ = _getLicenseTokenStorage(); + + if ($.licensorIpTotalTokens[childIpId] != 0) { + revert Errors.LicenseToken__ChildIPAlreadyHasBeenMintedLicenseTokens(childIpId); + } + licenseTemplate = $.licenseTokenMetadatas[tokenIds[0]].licenseTemplate; licensorIpIds = new address[](tokenIds.length); licenseTermsIds = new uint256[](tokenIds.length); diff --git a/contracts/interfaces/registries/ILicenseRegistry.sol b/contracts/interfaces/registries/ILicenseRegistry.sol index 926a7a15..7099c8ca 100644 --- a/contracts/interfaces/registries/ILicenseRegistry.sol +++ b/contracts/interfaces/registries/ILicenseRegistry.sol @@ -37,6 +37,9 @@ interface ILicenseRegistry { /// @notice Returns the default license terms. function getDefaultLicenseTerms() external view returns (address licenseTemplate, uint256 licenseTermsId); + /// @notice Checks if the license terms are the default license terms. + function isDefaultLicense(address licenseTemplate, uint256 licenseTermsId) external view returns (bool); + /// @notice Registers a new license template in the Story Protocol. /// @param licenseTemplate The address of the license template to register. function registerLicenseTemplate(address licenseTemplate) external; diff --git a/contracts/lib/Errors.sol b/contracts/lib/Errors.sol index 3c6ac37c..67f4cc95 100644 --- a/contracts/lib/Errors.sol +++ b/contracts/lib/Errors.sol @@ -345,6 +345,9 @@ library Errors { address licenseTemplate, uint256 licenseTermsId ); + + /// @notice There are non-default license tokens have already been minted from the child Ip. + error LicenseToken__ChildIPAlreadyHasBeenMintedLicenseTokens(address childIpId); //////////////////////////////////////////////////////////////////////////// // Licensing Module // //////////////////////////////////////////////////////////////////////////// @@ -385,6 +388,9 @@ library Errors { /// @notice Derivative IP cannot add license terms. error LicensingModule__DerivativesCannotAddLicenseTerms(); + /// @notice there are non-default license tokens have already been minted from the child Ip. + error LicensingModule__DerivativeAlreadyHasBeenMintedLicenseTokens(address childIpId); + /// @notice IP list and license terms list length mismatch. error LicensingModule__LicenseTermsLengthMismatch(uint256 ipLength, uint256 licenseTermsLength); diff --git a/contracts/modules/licensing/LicensingModule.sol b/contracts/modules/licensing/LicensingModule.sol index ca052428..eaa105d8 100644 --- a/contracts/modules/licensing/LicensingModule.sol +++ b/contracts/modules/licensing/LicensingModule.sol @@ -257,7 +257,9 @@ contract LicensingModule is ) { revert Errors.LicensingModule__LicenseNotCompatibleForDerivative(childIpId); } - + if (LICENSE_NFT.getTotalTokensByLicensor(childIpId) != 0) { + revert Errors.LicensingModule__DerivativeAlreadyHasBeenMintedLicenseTokens(childIpId); + } // Ensure none of the parent IPs have expired. // Confirm that each parent IP has the license terms attached as specified by 'licenseTermsIds' // or default license terms. diff --git a/contracts/registries/LicenseRegistry.sol b/contracts/registries/LicenseRegistry.sol index e1c67fc1..5fc9f653 100644 --- a/contracts/registries/LicenseRegistry.sol +++ b/contracts/registries/LicenseRegistry.sol @@ -563,6 +563,11 @@ contract LicenseRegistry is ILicenseRegistry, AccessManagedUpgradeable, UUPSUpgr return ($.defaultLicenseTemplate, $.defaultLicenseTermsId); } + /// @notice Checks if the license terms are the default license terms. + function isDefaultLicense(address licenseTemplate, uint256 licenseTermsId) external view returns (bool) { + LicenseRegistryStorage storage $ = _getLicenseRegistryStorage(); + return $.defaultLicenseTemplate == licenseTemplate && $.defaultLicenseTermsId == licenseTermsId; + } /// @notice Returns the license terms through which a child IP links to a parent IP. /// @param childIpId The address of the child IP. /// @param parentIpId The address of the parent IP. diff --git a/test/foundry/modules/licensing/LicensingModule.t.sol b/test/foundry/modules/licensing/LicensingModule.t.sol index d70596f4..d1a43904 100644 --- a/test/foundry/modules/licensing/LicensingModule.t.sol +++ b/test/foundry/modules/licensing/LicensingModule.t.sol @@ -1330,6 +1330,59 @@ contract LicensingModuleTest is BaseTest { licensingModule.registerDerivativeWithLicenseTokens(ipId3, licenseTokens, "", 100e6); } + function test_LicensingModule_registerDerivativeWithLicenseTokens_revert_mintPrivateLicense() public { + uint256 commRemixTermsId = pilTemplate.registerLicenseTerms( + PILFlavors.commercialRemix({ + mintingFee: 0, + commercialRevShare: 10_000_000, + royaltyPolicy: address(royaltyPolicyLRP), + currencyToken: address(erc20) + }) + ); + uint256 commUseTermsId = pilTemplate.registerLicenseTerms( + PILFlavors.commercialUse({ + mintingFee: 0, + currencyToken: address(erc20), + royaltyPolicy: address(royaltyPolicyLAP) + }) + ); + + vm.prank(ipOwner1); + licensingModule.attachLicenseTerms(ipId1, address(pilTemplate), commRemixTermsId); + + uint256 lcTokenId = licensingModule.mintLicenseTokens( + ipId1, + address(pilTemplate), + commRemixTermsId, + 1, + ipOwner2, + "", + 0, + 0 + ); + + vm.prank(ipOwner2); + licensingModule.mintLicenseTokens({ + licensorIpId: ipId2, + licenseTemplate: address(pilTemplate), + licenseTermsId: commUseTermsId, + amount: 1, + receiver: ipOwner3, + royaltyContext: "", + maxMintingFee: 0, + maxRevenueShare: 0 + }); + + uint256[] memory licenseTokens = new uint256[](1); + licenseTokens[0] = lcTokenId; + + vm.expectRevert( + abi.encodeWithSelector(Errors.LicenseToken__ChildIPAlreadyHasBeenMintedLicenseTokens.selector, ipId2) + ); + vm.prank(ipOwner2); + licensingModule.registerDerivativeWithLicenseTokens(ipId2, licenseTokens, "", 100e6); + } + function test_LicensingModule_registerDerivative_revert_royaltyPolicyMismatch() public { PILTerms memory terms = PILFlavors.commercialRemix({ mintingFee: 0, @@ -1749,6 +1802,50 @@ contract LicensingModuleTest is BaseTest { licensingModule.registerDerivative(ipId3, parentIpIds, licenseTermsIds, address(pilTemplate), "", 0, 100e6, 0); } + function test_LicensingModule_registerDerivative_revert_mintPrivateLicense() public { + uint256 commRemixTermsId = pilTemplate.registerLicenseTerms( + PILFlavors.commercialRemix({ + mintingFee: 0, + commercialRevShare: 10_000_000, + royaltyPolicy: address(royaltyPolicyLRP), + currencyToken: address(erc20) + }) + ); + uint256 commUseTermsId = pilTemplate.registerLicenseTerms( + PILFlavors.commercialUse({ + mintingFee: 0, + currencyToken: address(erc20), + royaltyPolicy: address(royaltyPolicyLAP) + }) + ); + + vm.prank(ipOwner1); + licensingModule.attachLicenseTerms(ipId1, address(pilTemplate), commRemixTermsId); + vm.prank(ipOwner2); + licensingModule.mintLicenseTokens({ + licensorIpId: ipId2, + licenseTemplate: address(pilTemplate), + licenseTermsId: commUseTermsId, + amount: 1, + receiver: ipOwner3, + royaltyContext: "", + maxMintingFee: 0, + maxRevenueShare: 0 + }); + + address[] memory parentIpIds = new address[](1); + parentIpIds[0] = ipId1; + + uint256[] memory licenseTermsIds = new uint256[](1); + licenseTermsIds[0] = commRemixTermsId; + + vm.expectRevert( + abi.encodeWithSelector(Errors.LicensingModule__DerivativeAlreadyHasBeenMintedLicenseTokens.selector, ipId2) + ); + vm.prank(ipOwner2); + licensingModule.registerDerivative(ipId2, parentIpIds, licenseTermsIds, address(pilTemplate), "", 0, 100e6, 0); + } + function test_LicensingModule_registerDerivative_revert_CommercialUseOnlyLicense() public { (, uint256 defaultTermsId) = licenseRegistry.getDefaultLicenseTerms(); uint256 commUseTermsId = pilTemplate.registerLicenseTerms(