Skip to content

Commit

Permalink
Add More Validations to Group IPA (storyprotocol#358)
Browse files Browse the repository at this point in the history
  • Loading branch information
kingster-will authored Dec 12, 2024
1 parent 0f3a4a7 commit d2281f5
Show file tree
Hide file tree
Showing 5 changed files with 170 additions and 9 deletions.
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

0 comments on commit d2281f5

Please sign in to comment.