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

Add More Validations to Group IPA #358

Merged
merged 2 commits into from
Dec 12, 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
48 changes: 48 additions & 0 deletions contracts/lib/Errors.sol
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,32 @@ library Errors {
/// @notice The empty group cannot mint license token.
error LicenseRegistry__EmptyGroupCannotMintLicenseToken(address groupId);

/// @notice The group can only attach one license terms which is common for all members.
error LicenseRegistry__GroupIpAlreadyHasLicenseTerms(address groupId);

/// @notice The license template cannot be Zero address.
error LicenseRegistry__LicenseTemplateCannotBeZeroAddress();

/// @notice license minting fee configured in IP must be identical to the group minting fee.
error LicenseRegistry__IpMintingFeeNotMatchWithGroup(address ipId, uint256 mintingFee, uint256 groupMintingFee);

/// @notice licensing hook configured in IP must be identical to the group licensing hook.
error LicenseRegistry__IpLicensingHookNotMatchWithGroup(
address ipId,
address licensingHook,
address groupLicensingHook
);

/// @notice licensing hook data configured in IP must be identical to the group licensing hook data.
error LicenseRegistry__IpLicensingHookDataNotMatchWithGroup(address ipId, bytes hookData, bytes groupHookData);

/// @notice commercial revenue share configured in group must be NOT less than the IP commercial revenue share.
error LicenseRegistry__GroupIpCommercialRevShareConfigMustNotLessThanIp(
address groupId,
uint32 ipCommercialRevShare,
uint32 groupCommercialRevShare
);

////////////////////////////////////////////////////////////////////////////
// License Token //
////////////////////////////////////////////////////////////////////////////
Expand Down Expand Up @@ -402,6 +428,28 @@ library Errors {
/// @notice register derivative require all parent IP to have the same royalty policy.
error LicensingModule__RoyaltyPolicyMismatch(address royaltyPolicy, address anotherRoyaltyPolicy);

/// @notice The group IP cannot enable/disable the licensing configuration once it has members.
error LicensingModule__GroupIpCannotChangeIsSet(address groupId);

/// @notice The group IP cannot change minting fee once it has members.
error LicensingModule__GroupIpCannotChangeMintingFee(address groupId);

/// @notice The group IP cannot change licensing hook once it has members.
error LicensingModule__GroupIpCannotChangeLicensingHook(address groupId);

/// @notice The group IP cannot change hook data once it has members.
error LicensingModule__GroupIpCannotChangeHookData(address groupId);

/// @notice The group Ip cannot specify expect group reward pool, as a group cannot be added to another group.
error LicensingModule__GroupIpCannotSetExpectGroupRewardPool(address groupId);

/// @notice GroupIP cannot decrease the royalty percentage.
error LicensingModule__GroupIpCannotDecreaseRoyalty(
address groupId,
uint32 newRoyaltyPercent,
uint32 oldRoyaltyPercent
);

////////////////////////////////////////////////////////////////////////////
// Dispute Module //
////////////////////////////////////////////////////////////////////////////
Expand Down
52 changes: 52 additions & 0 deletions contracts/modules/licensing/LicensingModule.sol
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { IIPAccount } from "../../interfaces/IIPAccount.sol";
import { IModule } from "../../interfaces/modules/base/IModule.sol";
import { ILicensingModule } from "../../interfaces/modules/licensing/ILicensingModule.sol";
import { IIPAssetRegistry } from "../../interfaces/registries/IIPAssetRegistry.sol";
import { IGroupIPAssetRegistry } from "../../interfaces/registries/IGroupIPAssetRegistry.sol";
import { IDisputeModule } from "../../interfaces/modules/dispute/IDisputeModule.sol";
import { ILicenseRegistry } from "../../interfaces/registries/ILicenseRegistry.sol";
import { Errors } from "../../lib/Errors.sol";
Expand Down Expand Up @@ -403,6 +404,10 @@ contract LicensingModule is
revert Errors.LicensingModule__LicenseTemplateCannotBeZeroAddressToOverrideRoyaltyPercent();
}

if (IGroupIPAssetRegistry(address(IP_ASSET_REGISTRY)).isRegisteredGroup(ipId)) {
_verifyGroupIpConfig(ipId, licenseTemplate, licenseTermsId, licensingConfig);
}

if (licensingConfig.commercialRevShare != 0) {
ILicenseTemplate lct = ILicenseTemplate(licenseTemplate);
if (!LICENSE_REGISTRY.isRegisteredLicenseTemplate(licenseTemplate)) {
Expand Down Expand Up @@ -726,6 +731,53 @@ contract LicensingModule is
}
}

/// @dev Verifies the group IP licensing configuration
function _verifyGroupIpConfig(
address groupId,
address licenseTemplate,
uint256 licenseTermsId,
Licensing.LicensingConfig memory licensingConfig
) private {
if (licenseTemplate == address(0)) {
revert Errors.LicenseRegistry__LicenseTemplateCannotBeZeroAddress();
}
if (licensingConfig.expectGroupRewardPool != address(0)) {
revert Errors.LicensingModule__GroupIpCannotSetExpectGroupRewardPool(groupId);
}
// Some configuration cannot be changed once the group has members
if (IGroupIPAssetRegistry(address(IP_ASSET_REGISTRY)).totalMembers(groupId) == 0) {
return;
}
Licensing.LicensingConfig memory oldLicensingConfig = LICENSE_REGISTRY.getLicensingConfig(
groupId,
licenseTemplate,
licenseTermsId
);
if (oldLicensingConfig.isSet != licensingConfig.isSet) {
revert Errors.LicensingModule__GroupIpCannotChangeIsSet(groupId);
}
if (oldLicensingConfig.mintingFee != licensingConfig.mintingFee) {
revert Errors.LicensingModule__GroupIpCannotChangeMintingFee(groupId);
}
if (oldLicensingConfig.licensingHook != licensingConfig.licensingHook) {
revert Errors.LicensingModule__GroupIpCannotChangeLicensingHook(groupId);
}
// check hood data are the same
if (
oldLicensingConfig.hookData.length != licensingConfig.hookData.length ||
keccak256(oldLicensingConfig.hookData) != keccak256(licensingConfig.hookData)
) {
revert Errors.LicensingModule__GroupIpCannotChangeHookData(groupId);
}
if (licensingConfig.commercialRevShare < oldLicensingConfig.commercialRevShare) {
revert Errors.LicensingModule__GroupIpCannotDecreaseRoyalty(
groupId,
licensingConfig.commercialRevShare,
oldLicensingConfig.commercialRevShare
);
}
}

/// @dev Hook to authorize the upgrade according to UUPSUpgradeable
/// @param newImplementation The address of the new implementation
function _authorizeUpgrade(address newImplementation) internal override restricted {}
Expand Down
41 changes: 41 additions & 0 deletions contracts/registries/LicenseRegistry.sol
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,13 @@ contract LicenseRegistry is ILicenseRegistry, AccessManagedUpgradeable, UUPSUpgr
if (!_exists(licenseTemplate, licenseTermsId)) {
revert Errors.LicensingModule__LicenseTermsNotFound(licenseTemplate, licenseTermsId);
}
// The group can only attach one license terms which is common for all members.
if (
GROUP_IP_ASSET_REGISTRY.isRegisteredGroup(ipId) &&
_getLicenseRegistryStorage().attachedLicenseTerms[ipId].length() > 0
) {
revert Errors.LicenseRegistry__GroupIpAlreadyHasLicenseTerms(ipId);
}

if (_isExpiredNow(ipId)) {
revert Errors.LicenseRegistry__IpExpired(ipId);
Expand Down Expand Up @@ -326,6 +333,7 @@ contract LicenseRegistry is ILicenseRegistry, AccessManagedUpgradeable, UUPSUpgr
/// @param groupLicenseTermsId The ID of the license terms attached to the group.
/// the IP must have this license terms.
/// @return ipLicensingConfig The configuration for license attached to the IP.
// solhint-disable code-complexity
function verifyGroupAddIp(
address groupId,
address groupRewardPool,
Expand Down Expand Up @@ -356,6 +364,39 @@ contract LicenseRegistry is ILicenseRegistry, AccessManagedUpgradeable, UUPSUpgr
if (_getExpireTime(ipId) != 0) {
revert Errors.LicenseRegistry__CannotAddIpWithExpirationToGroup(ipId);
}
// ipId must have the same license config items with group IP
Licensing.LicensingConfig memory groupLct = _getLicensingConfig(
groupId,
groupLicenseTemplate,
groupLicenseTermsId
);
// minting fee must be the same
if (lct.mintingFee != groupLct.mintingFee) {
revert Errors.LicenseRegistry__IpMintingFeeNotMatchWithGroup(ipId, lct.mintingFee, groupLct.mintingFee);
}
// hook must be the same
if (lct.licensingHook != groupLct.licensingHook) {
revert Errors.LicenseRegistry__IpLicensingHookNotMatchWithGroup(
ipId,
lct.licensingHook,
groupLct.licensingHook
);
}
// hook data must be the same
if (
lct.hookData.length != groupLct.hookData.length || keccak256(lct.hookData) != keccak256(groupLct.hookData)
) {
revert Errors.LicenseRegistry__IpLicensingHookDataNotMatchWithGroup(ipId, lct.hookData, groupLct.hookData);
}
// group commercial revenue share must be greater than or equal to IP commercial revenue share
if (groupLct.commercialRevShare < lct.commercialRevShare) {
revert Errors.LicenseRegistry__GroupIpCommercialRevShareConfigMustNotLessThanIp(
ipId,
lct.commercialRevShare,
groupLct.commercialRevShare
);
}

ipLicensingConfig = lct;
}

Expand Down
5 changes: 5 additions & 0 deletions test/foundry/integration/flows/grouping/Grouping.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -69,13 +69,18 @@ contract Flows_Integration_Grouping is BaseIntegration, ERC721Holder {
expectGroupRewardPool: address(evenSplitGroupPool)
});

licensingConfig.expectGroupRewardPool = address(0);

{
vm.startPrank(groupOwner);
groupId = groupingModule.registerGroup(address(evenSplitGroupPool));
vm.label(groupId, "Group1");
licensingModule.attachLicenseTerms(groupId, address(pilTemplate), commRemixTermsId);
licensingModule.setLicensingConfig(groupId, address(pilTemplate), commRemixTermsId, licensingConfig);
vm.stopPrank();
}

licensingConfig.expectGroupRewardPool = address(evenSplitGroupPool);
{
vm.startPrank(u.alice);
ipAcct[1] = registerIpAccount(mockNFT, 1, u.alice);
Expand Down
33 changes: 24 additions & 9 deletions test/foundry/modules/grouping/GroupingModule.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,8 @@ contract GroupingModuleTest is BaseTest, ERC721Holder {

vm.startPrank(alice);
licensingModule.attachLicenseTerms(groupId, address(pilTemplate), termsId);
licensingConfig.expectGroupRewardPool = address(0);
licensingModule.setLicensingConfig(groupId, address(pilTemplate), termsId, licensingConfig);
address[] memory ipIds = new address[](2);
ipIds[0] = ipId1;
ipIds[1] = ipId2;
Expand Down Expand Up @@ -204,8 +206,10 @@ contract GroupingModuleTest is BaseTest, ERC721Holder {
licensingModule.setLicensingConfig(ipId2, address(pilTemplate), termsId, licensingConfig);
vm.stopPrank();

licensingConfig.expectGroupRewardPool = address(0);
vm.startPrank(alice);
licensingModule.attachLicenseTerms(groupId, address(pilTemplate), termsId);
licensingModule.setLicensingConfig(groupId, address(pilTemplate), termsId, licensingConfig);
address[] memory ipIds = new address[](2);
ipIds[0] = ipId1;
ipIds[1] = ipId2;
Expand Down Expand Up @@ -254,8 +258,10 @@ contract GroupingModuleTest is BaseTest, ERC721Holder {
licensingModule.mintLicenseTokens(ipId2, address(pilTemplate), termsId, 1, address(this), "", 0);
vm.stopPrank();

licensingConfig.expectGroupRewardPool = address(0);
vm.startPrank(alice);
licensingModule.attachLicenseTerms(groupId, address(pilTemplate), termsId);
licensingModule.setLicensingConfig(groupId, address(pilTemplate), termsId, licensingConfig);
address[] memory ipIds = new address[](2);
ipIds[0] = ipId1;
ipIds[1] = ipId2;
Expand Down Expand Up @@ -332,8 +338,10 @@ contract GroupingModuleTest is BaseTest, ERC721Holder {
licensingModule.mintLicenseTokens(ipId2, address(pilTemplate), termsId, 1, address(this), "", 0);
vm.stopPrank();

licensingConfig.expectGroupRewardPool = address(0);
vm.startPrank(alice);
licensingModule.attachLicenseTerms(groupId, address(pilTemplate), termsId);
licensingModule.setLicensingConfig(groupId, address(pilTemplate), termsId, licensingConfig);
address[] memory ipIds = new address[](2);
ipIds[0] = ipId1;
ipIds[1] = ipId2;
Expand Down Expand Up @@ -650,10 +658,6 @@ contract GroupingModuleTest is BaseTest, ERC721Holder {
royaltyPolicy: address(royaltyPolicyLAP)
})
);
vm.startPrank(alice);
address groupId1 = groupingModule.registerGroup(address(rewardPool));
licensingModule.attachLicenseTerms(groupId1, address(pilTemplate), termsId);
vm.stopPrank();

Licensing.LicensingConfig memory licensingConfig = Licensing.LicensingConfig({
isSet: true,
Expand All @@ -676,6 +680,13 @@ contract GroupingModuleTest is BaseTest, ERC721Holder {
licensingModule.setLicensingConfig(ipId2, address(pilTemplate), termsId, licensingConfig);
vm.stopPrank();

licensingConfig.expectGroupRewardPool = address(0);
vm.startPrank(alice);
address groupId1 = groupingModule.registerGroup(address(rewardPool));
licensingModule.attachLicenseTerms(groupId1, address(pilTemplate), termsId);
licensingModule.setLicensingConfig(groupId1, address(pilTemplate), termsId, licensingConfig);
vm.stopPrank();

address[] memory ipIds = new address[](1);
ipIds[0] = ipId1;
vm.prank(alice);
Expand Down Expand Up @@ -760,11 +771,6 @@ contract GroupingModuleTest is BaseTest, ERC721Holder {
})
);

vm.startPrank(alice);
address groupId = groupingModule.registerGroup(address(rewardPool));
licensingModule.attachLicenseTerms(groupId, address(pilTemplate), termsId);
vm.stopPrank();

vm.prank(ipOwner1);
licensingModule.attachLicenseTerms(ipId1, address(pilTemplate), termsId);
vm.prank(ipOwner2);
Expand All @@ -783,6 +789,13 @@ contract GroupingModuleTest is BaseTest, ERC721Holder {
vm.prank(ipOwner1);
licensingModule.setLicensingConfig(ipId1, address(pilTemplate), termsId, licensingConfig);

licensingConfig.expectGroupRewardPool = address(0);
vm.startPrank(alice);
address groupId = groupingModule.registerGroup(address(rewardPool));
licensingModule.attachLicenseTerms(groupId, address(pilTemplate), termsId);
licensingModule.setLicensingConfig(groupId, address(pilTemplate), termsId, licensingConfig);
vm.stopPrank();

address[] memory ipIds = new address[](1);
ipIds[0] = ipId1;
vm.prank(alice);
Expand Down Expand Up @@ -922,9 +935,11 @@ contract GroupingModuleTest is BaseTest, ERC721Holder {
licensingModule.setLicensingConfig(ipId2, address(pilTemplate), termsId, licensingConfig);
vm.stopPrank();

licensingConfig.expectGroupRewardPool = address(0);
vm.startPrank(alice);
address groupId = groupingModule.registerGroup(address(rewardPool));
licensingModule.attachLicenseTerms(groupId, address(pilTemplate), termsId);
licensingModule.setLicensingConfig(groupId, address(pilTemplate), termsId, licensingConfig);
address[] memory ipIds = new address[](2);
ipIds[0] = ipId1;
ipIds[1] = ipId2;
Expand Down
Loading