From e77ef9a8c8e9782b23b298eeaa9a3024106c53fb Mon Sep 17 00:00:00 2001 From: Raul Date: Thu, 1 Feb 2024 00:33:19 -0300 Subject: [PATCH 1/4] refactor tests to multiple licenses --- .../modules/IRegistrationModule.sol | 4 +- .../registries/ILicenseRegistry.sol | 13 +- contracts/modules/RegistrationModule.sol | 11 +- contracts/registries/LicenseRegistry.sol | 103 +++++------ script/foundry/deployment/Main.s.sol | 8 +- test/foundry/integration/BaseIntegration.sol | 173 +++++++++++------- .../licensing/UMLPolicyFramework.t.sol | 8 +- test/foundry/registries/LicenseRegistry.t.sol | 9 +- 8 files changed, 184 insertions(+), 145 deletions(-) diff --git a/contracts/interfaces/modules/IRegistrationModule.sol b/contracts/interfaces/modules/IRegistrationModule.sol index b57d2f34..e94e6e0a 100644 --- a/contracts/interfaces/modules/IRegistrationModule.sol +++ b/contracts/interfaces/modules/IRegistrationModule.sol @@ -4,7 +4,7 @@ pragma solidity ^0.8.23; interface IRegistrationModule { event RootIPRegistered(address indexed caller, address indexed ipId, uint256 indexed policyId); - event DerivativeIPRegistered(address indexed caller, address indexed ipId, uint256 licenseId); + event DerivativeIPRegistered(address indexed caller, address indexed ipId, uint256[] licenseIds); function registerRootIp( uint256 policyId, @@ -14,7 +14,7 @@ interface IRegistrationModule { ) external returns (address); function registerDerivativeIp( - uint256 licenseId, + uint256[] calldata licenseIds, address tokenContract, uint256 tokenId, string memory ipName, diff --git a/contracts/interfaces/registries/ILicenseRegistry.sol b/contracts/interfaces/registries/ILicenseRegistry.sol index 56324f1b..20478024 100644 --- a/contracts/interfaces/registries/ILicenseRegistry.sol +++ b/contracts/interfaces/registries/ILicenseRegistry.sol @@ -63,8 +63,8 @@ interface ILicenseRegistry { /// @notice Emitted when an IP is linked to its parent by burning a license /// @param caller The address that called the function /// @param ipId The id of the IP - /// @param parentIpId The id of the parent IP - event IpIdLinkedToParent(address indexed caller, address indexed ipId, address indexed parentIpId); + /// @param parentIpIds The ids of the parent IP + event IpIdLinkedToParents(address indexed caller, address indexed ipId, address[] indexed parentIpIds); /// @notice Registers a policy framework manager into the contract, so it can add policy data for /// licenses. @@ -95,11 +95,12 @@ interface ILicenseRegistry { address receiver ) external returns (uint256 licenseId); - /// @notice Links an IP to its parent IP, burning the license NFT and the policy allows it - /// @param licenseId The id of the license to burn - /// @param childIpId The id of the child IP + /// @notice Links an IP to the licensors (parent IP IDs) listed in the License NFTs, if their policies allow it, + /// burning the NFTs in the proccess. The caller must be the owner of the NFTs and the IP owner. + /// @param licenseIds The id of the licenses to burn + /// @param childIpId The id of the child IP to be linked /// @param holder The address that holds the license - function linkIpToParent(uint256 licenseId, address childIpId, address holder) external; + function linkIpToParents(uint256[] calldata licenseIds, address childIpId, address holder) external; /// /// Getters diff --git a/contracts/modules/RegistrationModule.sol b/contracts/modules/RegistrationModule.sol index a4fde5cf..68419e33 100644 --- a/contracts/modules/RegistrationModule.sol +++ b/contracts/modules/RegistrationModule.sol @@ -82,8 +82,8 @@ contract RegistrationModule is BaseModule, IRegistrationModule { return ipId; } - /// @notice Registers an IP derivative into the protocol. - /// @param licenseId The license to incorporate for the new IP. + /// @notice Registers IP derivatives into the protocol. + /// @param licenseIds The licenses to incorporate for the new IP. /// @param tokenContract The address of the NFT bound to the derivative IP. /// @param tokenId The token id of the NFT bound to the derivative IP. /// @param ipName The name assigned to the new IP. @@ -92,7 +92,7 @@ contract RegistrationModule is BaseModule, IRegistrationModule { /// TODO: Replace all metadata with a generic bytes parameter type, and do /// encoding on the periphery contract level instead. function registerDerivativeIp( - uint256 licenseId, + uint256[] calldata licenseIds, address tokenContract, uint256 tokenId, string memory ipName, @@ -125,10 +125,9 @@ contract RegistrationModule is BaseModule, IRegistrationModule { ); // Perform core IP derivative licensing - the license must be owned by the caller. - // TODO: return resulting policy index - LICENSE_REGISTRY.linkIpToParent(licenseId, ipId, msg.sender); + LICENSE_REGISTRY.linkIpToParents(licenseIds, ipId, msg.sender); - emit DerivativeIPRegistered(msg.sender, ipId, licenseId); + emit DerivativeIPRegistered(msg.sender, ipId, licenseIds); } /// @notice Gets the protocol-wide module identifier for this module. diff --git a/contracts/registries/LicenseRegistry.sol b/contracts/registries/LicenseRegistry.sol index 18ea21f4..79fcd82b 100644 --- a/contracts/registries/LicenseRegistry.sol +++ b/contracts/registries/LicenseRegistry.sol @@ -58,14 +58,6 @@ contract LicenseRegistry is ERC1155, ILicenseRegistry { /// This tracks the number of licenses registered in the protocol, it will not decrease when a license is burnt. uint256 private _totalLicenses; - modifier onlyLicensee(uint256 licenseId, address holder) { - // Should ERC1155 operator count? IMO is a security risk. Better use ACL - if (balanceOf(holder, licenseId) == 0) { - revert Errors.LicenseRegistry__NotLicensee(); - } - _; - } - constructor(address accessController, address ipAccountRegistry) ERC1155("") { ACCESS_CONTROLLER = IAccessController(accessController); IP_ACCOUNT_REGISTRY = IIPAccountRegistry(ipAccountRegistry); @@ -106,7 +98,7 @@ contract LicenseRegistry is ERC1155, ILicenseRegistry { if (!isPolicyDefined(polId)) { revert Errors.LicenseRegistry__PolicyNotFound(); } - return _addPolictyIdToIp(ipId, polId, false); + return _addPolicyIdToIp(ipId, polId, false); } /// @notice Registers a policy into the contract. MUST be called by a registered @@ -191,58 +183,61 @@ contract LicenseRegistry is ERC1155, ILicenseRegistry { return licenseId; } - /// Relates an IP ID with its parents (licensors), by burning the License NFT the holder owns - /// Licensing parameters related to linking IPAs must be verified in order to succeed, reverts otherwise. - /// The child IP ID will have the policy that the license represent added to it's own, if it's compatible with - /// existing child policies. - /// The child IP ID will be linked to the parent (if it wasn't before). - /// @param licenseId license NFT to be burned - /// @param childIpId that will receive the policy defined by licenseId - /// @param holder of the license NFT - function linkIpToParent( - uint256 licenseId, + /// @notice Links an IP to the licensors (parent IP IDs) listed in the License NFTs, if their policies allow it, + /// burning the NFTs in the proccess. The caller must be the owner of the NFTs and the IP owner. + /// @param licenseIds The id of the licenses to burn + /// @param childIpId The id of the child IP to be linked + /// @param holder The address that holds the license + function linkIpToParents( + uint256[] calldata licenseIds, address childIpId, address holder - ) external onlyLicensee(licenseId, holder) { + ) external { // check the caller is owner or authorized by the childIp if ( msg.sender != childIpId && - !ACCESS_CONTROLLER.checkPermission(childIpId, msg.sender, address(this), this.linkIpToParent.selector) + !ACCESS_CONTROLLER.checkPermission(childIpId, msg.sender, address(this), this.linkIpToParents.selector) ) { revert Errors.LicenseRegistry__UnauthorizedAccess(); } - // TODO: check if childIpId exists and is owned by holder - Licensing.License memory licenseData = _licenses[licenseId]; - address parentIpId = licenseData.licensorIpId; - if (parentIpId == childIpId) { - revert Errors.LicenseRegistry__ParentIdEqualThanChild(); - } - // TODO: check licensor exist - // TODO: check licensor not part of a branch tagged by disputer - - // Verify linking params - Licensing.Policy memory pol = policy(licenseData.policyId); - if (ERC165Checker.supportsInterface(pol.policyFramework, type(ILinkParamVerifier).interfaceId)) { - if ( - !ILinkParamVerifier(pol.policyFramework).verifyLink( - licenseId, - msg.sender, - childIpId, - parentIpId, - pol.data - ) - ) { - revert Errors.LicenseRegistry__LinkParentParamFailed(); + uint256 licenses = licenseIds.length; + address[] memory licensors = new address[](licenses); + uint256[] memory values = new uint256[](licenses); + for (uint256 i = 0; i < licenses; i++) { + uint256 licenseId = licenseIds[i]; + if (balanceOf(holder, licenseId) == 0) { + revert Errors.LicenseRegistry__NotLicensee(); + } + Licensing.License memory licenseData = _licenses[licenseId]; + licensors[i] = licenseData.licensorIpId; + // TODO: check licensor not part of a branch tagged by disputer + if (licensors[i] == childIpId) { + revert Errors.LicenseRegistry__ParentIdEqualThanChild(); } + // Verify linking params + Licensing.Policy memory pol = policy(licenseData.policyId); + if (ERC165Checker.supportsInterface(pol.policyFramework, type(ILinkParamVerifier).interfaceId)) { + if ( + !ILinkParamVerifier(pol.policyFramework).verifyLink( + licenseId, + msg.sender, + childIpId, + licensors[i], + pol.data + ) + ) { + revert Errors.LicenseRegistry__LinkParentParamFailed(); + } + } + // Add policy to kid + _addPolicyIdToIp(childIpId, licenseData.policyId, true); + // Set parent + _ipIdParents[childIpId].add(licensors[i]); + values[i] = 1; } - // Add policy to kid - _addPolictyIdToIp(childIpId, licenseData.policyId, true); - // Set parent - _ipIdParents[childIpId].add(parentIpId); - emit IpIdLinkedToParent(msg.sender, childIpId, parentIpId); - - // Burn license - _burn(holder, licenseId, 1); + emit IpIdLinkedToParents(msg.sender, childIpId, licensors); + // Burn licenses + _burnBatch(holder, licenseIds, values); } /// @notice True if the framework address is registered in LicenseRegistry @@ -373,7 +368,7 @@ contract LicenseRegistry is ERC1155, ILicenseRegistry { super._update(from, to, ids, values); } - function _verifyRegisteredFramework(address policyFramework) internal view returns (address) { + function _verifyRegisteredFramework(address policyFramework) internal view { if (!_registeredFrameworkManagers[policyFramework]) { revert Errors.LicenseRegistry__FrameworkNotFound(); } @@ -385,7 +380,7 @@ contract LicenseRegistry is ERC1155, ILicenseRegistry { /// @param policyId id of the policy data /// @param inheritedPolicy true if set in linkIpToParent, false otherwise /// @return index of the policy added to the set - function _addPolictyIdToIp(address ipId, uint256 policyId, bool inheritedPolicy) internal returns (uint256 index) { + function _addPolicyIdToIp(address ipId, uint256 policyId, bool inheritedPolicy) internal returns (uint256 index) { EnumerableSet.UintSet storage _pols = _policiesPerIpId[ipId]; if (!_pols.add(policyId)) { revert Errors.LicenseRegistry__PolicyAlreadySetForIpId(); @@ -431,7 +426,7 @@ contract LicenseRegistry is ERC1155, ILicenseRegistry { return (id, true); } - function _verifyPolicy(Licensing.Policy memory pol) private view { + function _verifyPolicy(Licensing.Policy memory pol) private pure { if (pol.policyFramework == address(0)) { revert Errors.LicenseRegistry__PolicyNotFound(); } diff --git a/script/foundry/deployment/Main.s.sol b/script/foundry/deployment/Main.s.sol index 3b89af52..bdb085c7 100644 --- a/script/foundry/deployment/Main.s.sol +++ b/script/foundry/deployment/Main.s.sol @@ -355,9 +355,11 @@ contract Main is Script, BroadcastManager, JsonDeploymentHandler { 2, deployer ); + uint256[] memory licenseIds = new uint256[](1); + licenseIds[0] = licenseId1; registrationModule.registerDerivativeIp( - licenseId1, + licenseIds, address(mockNft), nftIds[3], "best derivative ip", @@ -368,8 +370,8 @@ contract Main is Script, BroadcastManager, JsonDeploymentHandler { // /*/////////////////////////////////////////////////////////////// // LINK IPACCOUNTS TO PARENTS USING LICENSES // ////////////////////////////////////////////////////////////////*/ - - licenseRegistry.linkIpToParent(licenseId1, getIpId(mockNft, nftIds[4]), deployer); + + licenseRegistry.linkIpToParents(licenseIds, getIpId(mockNft, nftIds[4]), deployer); } function getIpId(MockERC721 mnft, uint256 tokenId) public view returns (address ipId) { diff --git a/test/foundry/integration/BaseIntegration.sol b/test/foundry/integration/BaseIntegration.sol index 86f4d74c..cc062944 100644 --- a/test/foundry/integration/BaseIntegration.sol +++ b/test/foundry/integration/BaseIntegration.sol @@ -180,7 +180,7 @@ contract BaseIntegration is Test { accessController.setGlobalPermission( address(registrationModule), address(licenseRegistry), - bytes4(licenseRegistry.linkIpToParent.selector), + bytes4(licenseRegistry.linkIpToParents.selector), 1 // AccessPermission.ALLOW ); } @@ -268,8 +268,8 @@ contract BaseIntegration is Test { return registerIpAccount(address(nft), tokenId, caller); } - function registerDerivativeIp( - uint256 licenseId, + function registerDerivativeIps( + uint256[] calldata licenseIds, address nft, uint256 tokenId, IP.MetadataV1 memory metadata, @@ -285,10 +285,15 @@ contract BaseIntegration is Test { ); vm.label(expectedAddr, string(abi.encodePacked("IPAccount", Strings.toString(tokenId)))); - - uint256 policyId = licenseRegistry.policyIdForLicense(licenseId); - address parentIpId = licenseRegistry.licensorIpId(licenseId); - uint256 newPolicyIndex = licenseRegistry.totalPoliciesForIp(expectedAddr); + + uint256[] memory policyIds = new uint256[](licenseIds.length); + address[] memory parentIpIds = new address[](licenseIds.length); + uint256[] memory newPolicyIndexes = new uint256[](licenseIds.length); + for (uint256 i = 0; i < licenseIds.length; i++) { + policyIds[i] = licenseRegistry.policyIdForLicense(licenseIds[i]); + parentIpIds[i] = licenseRegistry.licensorIpId(licenseIds[i]); + newPolicyIndexes[i] = licenseRegistry.totalPoliciesForIp(expectedAddr); + } vm.expectEmit(); emit IERC6551Registry.ERC6551AccountCreated({ @@ -334,84 +339,116 @@ contract BaseIntegration is Test { }); // Note that below events are emitted in function that's called by the registration module. - + for (uint256 i = 0; i < licenseIds.length; i++) { + vm.expectEmit(); + emit ILicenseRegistry.PolicyAddedToIpId({ + caller: address(registrationModule), + ipId: expectedAddr, + policyId: policyIds[i], + index: newPolicyIndexes[i], + inheritedPolicy: true + }); + } vm.expectEmit(); - emit ILicenseRegistry.PolicyAddedToIpId({ + emit ILicenseRegistry.IpIdLinkedToParents({ caller: address(registrationModule), ipId: expectedAddr, - policyId: policyId, - index: newPolicyIndex, - inheritedPolicy: true + parentIpIds: parentIpIds }); - vm.expectEmit(); - emit ILicenseRegistry.IpIdLinkedToParent({ - caller: address(registrationModule), - ipId: expectedAddr, - parentIpId: parentIpId - }); - - vm.expectEmit(); - emit IERC1155.TransferSingle({ - operator: address(registrationModule), - from: caller, - to: address(0), // burn addr - id: licenseId, - value: 1 - }); + if (licenseIds.length == 1) { + vm.expectEmit(); + emit IERC1155.TransferSingle({ + operator: address(registrationModule), + from: caller, + to: address(0), // burn addr + id: licenseIds[0], + value: 1 + }); + } else { + vm.expectEmit(); + emit IERC1155.TransferBatch({ + operator: address(registrationModule), + from: caller, + to: address(0), // burn addr + ids: licenseIds, + values: new uint256[](licenseIds.length) + }); + } vm.expectEmit(); - emit IRegistrationModule.DerivativeIPRegistered({ caller: caller, ipId: expectedAddr, licenseId: licenseId }); + emit IRegistrationModule.DerivativeIPRegistered({ caller: caller, ipId: expectedAddr, licenseIds: licenseIds }); vm.startPrank(caller); registrationModule.registerDerivativeIp(licenseId, nft, tokenId, metadata.name, metadata.hash, metadata.uri); return expectedAddr; } - function linkIpToParent(uint256 licenseId, address ipId, address caller) internal { - uint256 policyId = licenseRegistry.policyIdForLicense(licenseId); - address parentIpId = licenseRegistry.licensorIpId(licenseId); - uint256 newPolicyIndex = licenseRegistry.totalPoliciesForIp(ipId); - - uint256 prevLicenseAmount = licenseRegistry.balanceOf(caller, licenseId); + function linkIpToParents(uint256[] calldata licenseIds, address ipId, address caller) internal { + uint256[] memory policyIds = new uint256[](licenseIds.length); + address[] memory parentIpIds = new address[](licenseIds.length); + uint256[] memory newPolicyIndexes = new uint256[](licenseIds.length); + uint256[] memory prevLicenseAmounts = new uint256[](licenseIds.length); + uint256[] memory values = new uint256[](licenseIds.length); + + for (uint256 i = 0; i < licenseIds.length; i++) { + policyIds[i] = licenseRegistry.policyIdForLicense(licenseIds[i]); + parentIpIds[i] = licenseRegistry.licensorIpId(licenseIds[i]); + newPolicyIndexes[i] = licenseRegistry.totalPoliciesForIp(ipId); + prevLicenseAmounts[i] = licenseRegistry.balanceOf(caller, licenseIds[i]); + values[i] = 1; + vm.expectEmit(); + emit ILicenseRegistry.PolicyAddedToIpId({ + caller: caller, + ipId: ipId, + policyId: policyIds[i], + index: newPolicyIndexes[i], + inheritedPolicy: true + }); + } vm.expectEmit(); - emit ILicenseRegistry.PolicyAddedToIpId({ - caller: caller, - ipId: ipId, - policyId: policyId, - index: newPolicyIndex, - inheritedPolicy: true - }); - - vm.expectEmit(); - emit ILicenseRegistry.IpIdLinkedToParent({ caller: caller, ipId: ipId, parentIpId: parentIpId }); - - vm.expectEmit(); - emit IERC1155.TransferSingle({ - operator: caller, - from: caller, - to: address(0), // burn addr - id: licenseId, - value: 1 - }); + emit ILicenseRegistry.IpIdLinkedToParents({ caller: caller, ipId: ipId, parentIpIds: parentIpIds }); + + if (licenseIds.length == 1) { + vm.expectEmit(); + emit IERC1155.TransferSingle({ + operator: caller, + from: caller, + to: address(0), // burn addr + id: licenseIds[0], + value: 1 + }); + } else { + vm.expectEmit(); + emit IERC1155.TransferBatch({ + operator: caller, + from: caller, + to: address(0), // burn addr + ids: licenseIds, + values: values + }); + } vm.startPrank(caller); - licenseRegistry.linkIpToParent(licenseId, ipId, caller); - - assertEq(licenseRegistry.balanceOf(caller, licenseId), prevLicenseAmount - 1, "license not burnt on linking"); - assertTrue(licenseRegistry.isParent(parentIpId, ipId), "parent IP account is not parent"); - assertEq( - keccak256( - abi.encode( - licenseRegistry.policyForIpAtIndex( - parentIpId, - licenseRegistry.indexOfPolicyForIp(parentIpId, policyId) + licenseRegistry.linkIpToParents(licenseIds, ipId, caller); + + for (uint256 i = 0; i < licenseIds.length; i++) { + assertEq(licenseRegistry.balanceOf(caller, licenseIds[i]), prevLicenseAmounts[i] - 1, "license not burnt on linking"); + assertTrue(licenseRegistry.isParent(parentIpIds[i], ipId), "parent IP account is not parent"); + assertEq( + keccak256( + abi.encode( + licenseRegistry.policyForIpAtIndex( + parentIpIds[i], + licenseRegistry.indexOfPolicyForIp(parentIpIds[i], policyIds[i]) + ) ) - ) - ), - keccak256(abi.encode(licenseRegistry.policyForIpAtIndex(ipId, newPolicyIndex))), - "policy not the same in parent to child" - ); + ), + keccak256(abi.encode(licenseRegistry.policyForIpAtIndex(ipId, newPolicyIndexes[i]))), + "policy not the same in parent to child" + ); + } } + } diff --git a/test/foundry/modules/licensing/UMLPolicyFramework.t.sol b/test/foundry/modules/licensing/UMLPolicyFramework.t.sol index 46e0d6c0..885e426f 100644 --- a/test/foundry/modules/licensing/UMLPolicyFramework.t.sol +++ b/test/foundry/modules/licensing/UMLPolicyFramework.t.sol @@ -243,9 +243,11 @@ contract UMLPolicyFrameworkTest is Test { umlFramework.setApproval(licenseId, ipId2, false); assertFalse(umlFramework.isDerivativeApproved(licenseId, ipId2)); + uint256[] memory licenseIds = new uint256[](1); + licenseIds[0] = licenseId; vm.expectRevert(Errors.LicenseRegistry__LinkParentParamFailed.selector); vm.prank(ipOwner); - registry.linkIpToParent(licenseId, ipId2, licenseHolder); + registry.linkIpToParents(licenseIds, ipId2, licenseHolder); } function test_UMLPolicyFrameworkManager_derivatives_withApproval_linkApprovedIpId() public { @@ -274,9 +276,11 @@ contract UMLPolicyFrameworkTest is Test { vm.prank(ipOwner); umlFramework.setApproval(licenseId, ipId2, true); assertTrue(umlFramework.isDerivativeApproved(licenseId, ipId2)); + uint256[] memory licenseIds = new uint256[](1); + licenseIds[0] = licenseId; vm.prank(ipOwner); - registry.linkIpToParent(licenseId, ipId2, licenseHolder); + registry.linkIpToParents(licenseIds, ipId2, licenseHolder); assertTrue(registry.isParent(ipId1, ipId2)); } diff --git a/test/foundry/registries/LicenseRegistry.t.sol b/test/foundry/registries/LicenseRegistry.t.sol index e8562219..df89acfd 100644 --- a/test/foundry/registries/LicenseRegistry.t.sol +++ b/test/foundry/registries/LicenseRegistry.t.sol @@ -99,7 +99,7 @@ contract LicenseRegistryTest is Test { function test_LicenseRegistry_registerPolicy_revert_policyAlreadyAdded() public { registry.registerPolicyFrameworkManager(address(module1)); vm.startPrank(address(module1)); - uint256 polId = registry.registerPolicy(_createPolicy()); + registry.registerPolicy(_createPolicy()); vm.expectRevert(Errors.LicenseRegistry__PolicyAlreadyAdded.selector); registry.registerPolicy(_createPolicy()); vm.stopPrank(); @@ -208,11 +208,12 @@ contract LicenseRegistryTest is Test { return licenseId; } - function test_LicenseRegistry_linkIpToParent() public { - // TODO: something cleaner than this + function test_LicenseRegistry_linkIpToParents_single_parent() public { uint256 licenseId = test_LicenseRegistry_mintLicense(); + uint256[] memory licenseIds = new uint256[](1); + licenseIds[0] = licenseId; vm.prank(ipOwner); - registry.linkIpToParent(licenseId, ipId2, licenseHolder); + registry.linkIpToParents(licenseIds, ipId2, licenseHolder); assertEq(registry.balanceOf(licenseHolder, licenseId), 1, "not burnt"); assertEq(registry.isParent(ipId1, ipId2), true, "not parent"); assertEq( From 1f0ddccac1a4a7da8f54fbb2bc70710604f7a9a4 Mon Sep 17 00:00:00 2001 From: Jongwon Park Date: Wed, 31 Jan 2024 21:34:10 -0800 Subject: [PATCH 2/4] test: Fix for multi-parent licensing --- test/foundry/integration/BaseIntegration.sol | 6 +++--- .../integration/big-bang/SingleNftCollection.t.sol | 12 +++++++----- test/foundry/modules/royalty/LSClaimer.t.sol | 3 ++- test/utils/DeployHelper.sol | 2 +- 4 files changed, 13 insertions(+), 10 deletions(-) diff --git a/test/foundry/integration/BaseIntegration.sol b/test/foundry/integration/BaseIntegration.sol index cc062944..6aab9390 100644 --- a/test/foundry/integration/BaseIntegration.sol +++ b/test/foundry/integration/BaseIntegration.sol @@ -269,7 +269,7 @@ contract BaseIntegration is Test { } function registerDerivativeIps( - uint256[] calldata licenseIds, + uint256[] memory licenseIds, address nft, uint256 tokenId, IP.MetadataV1 memory metadata, @@ -380,11 +380,11 @@ contract BaseIntegration is Test { emit IRegistrationModule.DerivativeIPRegistered({ caller: caller, ipId: expectedAddr, licenseIds: licenseIds }); vm.startPrank(caller); - registrationModule.registerDerivativeIp(licenseId, nft, tokenId, metadata.name, metadata.hash, metadata.uri); + registrationModule.registerDerivativeIp(licenseIds, nft, tokenId, metadata.name, metadata.hash, metadata.uri); return expectedAddr; } - function linkIpToParents(uint256[] calldata licenseIds, address ipId, address caller) internal { + function linkIpToParents(uint256[] memory licenseIds, address ipId, address caller) internal { uint256[] memory policyIds = new uint256[](licenseIds.length); address[] memory parentIpIds = new address[](licenseIds.length); uint256[] memory newPolicyIndexes = new uint256[](licenseIds.length); diff --git a/test/foundry/integration/big-bang/SingleNftCollection.t.sol b/test/foundry/integration/big-bang/SingleNftCollection.t.sol index 3300f18f..58b74ad5 100644 --- a/test/foundry/integration/big-bang/SingleNftCollection.t.sol +++ b/test/foundry/integration/big-bang/SingleNftCollection.t.sol @@ -136,7 +136,8 @@ contract BigBang_Integration_SingleNftCollection is BaseIntegration, Integration // Carl activates the license on his NFT 6 IPAccount, linking as child to Alice's NFT 1 IPAccount { vm.startPrank(u.carl); - uint256 carl_license_from_root_alice = licenseRegistry.mintLicense( + uint256[] memory carl_license_from_root_alice = new uint256[](1); + carl_license_from_root_alice[0] = licenseRegistry.mintLicense( policyIds["uml_com_deriv_cheap_flexible"], ipAcct[1], 1, @@ -144,7 +145,7 @@ contract BigBang_Integration_SingleNftCollection is BaseIntegration, Integration ); ipAcct[6] = registerIpAccount(nft, 6, u.carl); - linkIpToParent(carl_license_from_root_alice, ipAcct[6], u.carl); + linkIpToParents(carl_license_from_root_alice, ipAcct[6], u.carl); } // Alice mints 2 license for policy "noncom_deriv_mint_payment" on Bob's NFT 3 IPAccount @@ -162,7 +163,8 @@ contract BigBang_Integration_SingleNftCollection is BaseIntegration, Integration uint256 aliceTokenBalance = erc20.balanceOf(u.alice); uint256 pfmTokenBalance = erc20.balanceOf(pfm["mint_payment"].addr); - uint256 alice_license_from_root_bob = licenseRegistry.mintLicense( + uint256[] memory alice_license_from_root_bob = new uint256[](1); + alice_license_from_root_bob[0] = licenseRegistry.mintLicense( policyIds["mint_payment_normal"], ipAcct[3], mintAmount, @@ -181,12 +183,12 @@ contract BigBang_Integration_SingleNftCollection is BaseIntegration, Integration ); ipAcct[2] = registerIpAccount(nft, 2, u.alice); - linkIpToParent(alice_license_from_root_bob, ipAcct[2], u.alice); + linkIpToParents(alice_license_from_root_bob, ipAcct[2], u.alice); uint256 tokenId = 99999999; nft.mintId(u.alice, tokenId); - ipAcct[tokenId] = registerDerivativeIp( + ipAcct[tokenId] = registerDerivativeIps( alice_license_from_root_bob, address(nft), tokenId, diff --git a/test/foundry/modules/royalty/LSClaimer.t.sol b/test/foundry/modules/royalty/LSClaimer.t.sol index 276a6972..9569d056 100644 --- a/test/foundry/modules/royalty/LSClaimer.t.sol +++ b/test/foundry/modules/royalty/LSClaimer.t.sol @@ -99,8 +99,9 @@ contract TestLSClaimer is TestHelper { ); vm.label(ipAddr, string(abi.encodePacked("IPAccount", Strings.toString(nftIds[0])))); + uint256[] memory licenseId = new uint256[](1); for (uint256 i = 0; i < 99; i++) { - uint256 licenseId = licenseRegistry.mintLicense( + licenseId[0] = licenseRegistry.mintLicense( policyIds["uml_cheap_flexible"], _getIpId(nft, nftIds[i]), 2, diff --git a/test/utils/DeployHelper.sol b/test/utils/DeployHelper.sol index e268edb1..b21cdd91 100644 --- a/test/utils/DeployHelper.sol +++ b/test/utils/DeployHelper.sol @@ -195,7 +195,7 @@ contract DeployHelper is Test { accessController.setGlobalPermission( address(registrationModule), address(licenseRegistry), - bytes4(licenseRegistry.linkIpToParent.selector), + bytes4(licenseRegistry.linkIpToParents.selector), 1 // AccessPermission.ALLOW ); From bdf0ba8e9c30be5562b41ba74d8acf715bda805f Mon Sep 17 00:00:00 2001 From: Jongwon Park Date: Wed, 31 Jan 2024 21:45:24 -0800 Subject: [PATCH 3/4] test: Multi-parent licensors, Event emits --- test/foundry/integration/BaseIntegration.sol | 21 +++++++--- .../big-bang/SingleNftCollection.t.sol | 42 +++++++++++++++++++ 2 files changed, 57 insertions(+), 6 deletions(-) diff --git a/test/foundry/integration/BaseIntegration.sol b/test/foundry/integration/BaseIntegration.sol index 6aab9390..52413412 100644 --- a/test/foundry/integration/BaseIntegration.sol +++ b/test/foundry/integration/BaseIntegration.sol @@ -285,14 +285,14 @@ contract BaseIntegration is Test { ); vm.label(expectedAddr, string(abi.encodePacked("IPAccount", Strings.toString(tokenId)))); - + uint256[] memory policyIds = new uint256[](licenseIds.length); address[] memory parentIpIds = new address[](licenseIds.length); uint256[] memory newPolicyIndexes = new uint256[](licenseIds.length); for (uint256 i = 0; i < licenseIds.length; i++) { policyIds[i] = licenseRegistry.policyIdForLicense(licenseIds[i]); parentIpIds[i] = licenseRegistry.licensorIpId(licenseIds[i]); - newPolicyIndexes[i] = licenseRegistry.totalPoliciesForIp(expectedAddr); + newPolicyIndexes[i] = licenseRegistry.totalPoliciesForIp(expectedAddr) + i; } vm.expectEmit(); @@ -349,6 +349,7 @@ contract BaseIntegration is Test { inheritedPolicy: true }); } + vm.expectEmit(); emit ILicenseRegistry.IpIdLinkedToParents({ caller: address(registrationModule), @@ -366,13 +367,18 @@ contract BaseIntegration is Test { value: 1 }); } else { + uint256[] memory values = new uint256[](licenseIds.length); + for (uint256 i = 0; i < licenseIds.length; ++i) { + values[i] = 1; + } + vm.expectEmit(); emit IERC1155.TransferBatch({ operator: address(registrationModule), from: caller, to: address(0), // burn addr ids: licenseIds, - values: new uint256[](licenseIds.length) + values: values }); } @@ -432,9 +438,13 @@ contract BaseIntegration is Test { vm.startPrank(caller); licenseRegistry.linkIpToParents(licenseIds, ipId, caller); - + for (uint256 i = 0; i < licenseIds.length; i++) { - assertEq(licenseRegistry.balanceOf(caller, licenseIds[i]), prevLicenseAmounts[i] - 1, "license not burnt on linking"); + assertEq( + licenseRegistry.balanceOf(caller, licenseIds[i]), + prevLicenseAmounts[i] - 1, + "license not burnt on linking" + ); assertTrue(licenseRegistry.isParent(parentIpIds[i], ipId), "parent IP account is not parent"); assertEq( keccak256( @@ -450,5 +460,4 @@ contract BaseIntegration is Test { ); } } - } diff --git a/test/foundry/integration/big-bang/SingleNftCollection.t.sol b/test/foundry/integration/big-bang/SingleNftCollection.t.sol index 58b74ad5..25b969c1 100644 --- a/test/foundry/integration/big-bang/SingleNftCollection.t.sol +++ b/test/foundry/integration/big-bang/SingleNftCollection.t.sol @@ -209,5 +209,47 @@ contract BigBang_Integration_SingleNftCollection is BaseIntegration, Integration taggingModule.setTag("sequel", ipAcct[99999999]); assertTrue(taggingModule.isTagged("sequel", ipAcct[99999999])); } + + // Carl mints licenses and linkts to multiple parents + // Carl creates NFT 6 IPAccount + // Carl activates the license on his NFT 6 IPAccount, linking as child to Alice's NFT 1 IPAccount + { + vm.startPrank(u.carl); + + uint256 paymentPerMint = MintPaymentPolicyFrameworkManager(pfm["mint_payment"].addr).payment(); + + uint256 tokenId = 7; + nft.mintId(u.carl, tokenId); + + erc20.approve(pfm["mint_payment"].addr, 1 * paymentPerMint); + + uint256[] memory carl_licenses = new uint256[](2); + carl_licenses[0] = licenseRegistry.mintLicense( + policyIds["uml_com_deriv_cheap_flexible"], // ipAcct[1] has this policy attached + ipAcct[1], + 1, + u.carl + ); + carl_licenses[1] = licenseRegistry.mintLicense( + policyIds["mint_payment_normal"], // ipAcct[3] has this policy attached + ipAcct[3], + 1, + u.carl + ); + + registerDerivativeIps( + carl_licenses, + address(nft), + tokenId, + IP.MetadataV1({ + name: "IP NAME", + hash: bytes32("hash"), + registrationDate: uint64(block.timestamp), + registrant: u.carl, // caller + uri: "external URL" + }), + u.carl // caller + ); + } } } From 71e17957c6204b46d6bceafedde7532e556ff3ea Mon Sep 17 00:00:00 2001 From: Raul Date: Thu, 1 Feb 2024 11:44:12 -0300 Subject: [PATCH 4/4] lint --- contracts/registries/IPAssetRegistry.sol | 11 +++-------- contracts/registries/LicenseRegistry.sol | 6 +----- 2 files changed, 4 insertions(+), 13 deletions(-) diff --git a/contracts/registries/IPAssetRegistry.sol b/contracts/registries/IPAssetRegistry.sol index ef492546..a1049568 100644 --- a/contracts/registries/IPAssetRegistry.sol +++ b/contracts/registries/IPAssetRegistry.sol @@ -43,7 +43,7 @@ contract IPAssetRegistry is IIPAssetRegistry, IPAccountRegistry { IMetadataProviderMigratable internal _metadataProvider; /// @notice Ensures only protocol governance owner may call a function. - modifier onlyOwner { + modifier onlyOwner() { if (msg.sender != owner) { revert Errors.IPAssetRegistry__Unauthorized(); } @@ -82,11 +82,7 @@ contract IPAssetRegistry is IIPAssetRegistry, IPAccountRegistry { revert Errors.IPAssetRegistry__AlreadyRegistered(); } - if ( - id.code.length == 0 && - createAccount && - id != registerIpAccount(chainId, tokenContract, tokenId) - ) { + if (id.code.length == 0 && createAccount && id != registerIpAccount(chainId, tokenContract, tokenId)) { revert Errors.IPAssetRegistry__InvalidAccount(); } _setResolver(id, resolverAddr); @@ -155,7 +151,7 @@ contract IPAssetRegistry is IIPAssetRegistry, IPAccountRegistry { /// @param id The canonical ID of the IP. /// @param data Canonical metadata to associate with the IP. function setMetadata(address id, address provider, bytes calldata data) external { - // Metadata is set on registration and immutable thereafter, with new fields + // Metadata is set on registration and immutable thereafter, with new fields // only added during a migration to new protocol-approved metadata provider. if (address(_records[id].metadataProvider) != msg.sender) { revert Errors.IPAssetRegistry__Unauthorized(); @@ -195,5 +191,4 @@ contract IPAssetRegistry is IIPAssetRegistry, IPAccountRegistry { provider.setMetadata(id, data); emit MetadataSet(id, address(provider), data); } - } diff --git a/contracts/registries/LicenseRegistry.sol b/contracts/registries/LicenseRegistry.sol index 79fcd82b..6cc2b591 100644 --- a/contracts/registries/LicenseRegistry.sol +++ b/contracts/registries/LicenseRegistry.sol @@ -188,11 +188,7 @@ contract LicenseRegistry is ERC1155, ILicenseRegistry { /// @param licenseIds The id of the licenses to burn /// @param childIpId The id of the child IP to be linked /// @param holder The address that holds the license - function linkIpToParents( - uint256[] calldata licenseIds, - address childIpId, - address holder - ) external { + function linkIpToParents(uint256[] calldata licenseIds, address childIpId, address holder) external { // check the caller is owner or authorized by the childIp if ( msg.sender != childIpId &&