diff --git a/contracts/interfaces/modules/licensing/IPolicyFrameworkManager.sol b/contracts/interfaces/modules/licensing/IPolicyFrameworkManager.sol index c5e92ce0..67bd9fb9 100644 --- a/contracts/interfaces/modules/licensing/IPolicyFrameworkManager.sol +++ b/contracts/interfaces/modules/licensing/IPolicyFrameworkManager.sol @@ -37,7 +37,7 @@ interface IPolicyFrameworkManager is IERC165 { /// @notice Verify policy parameters for minting a license. /// @dev Enforced to be only callable by LicenseRegistry - /// @param caller the address executing the mint + /// @param licensee the address that holds the license and is executing the mint /// @param mintingFromADerivative true if we verify minting a license from a derivative IP ID /// @param receiver the address receiving the license /// @param licensorIpId the IP id of the licensor @@ -45,7 +45,7 @@ interface IPolicyFrameworkManager is IERC165 { /// @param policyData the encoded framework policy data to verify /// @return verified True if the link is verified function verifyMint( - address caller, + address licensee, bool mintingFromADerivative, address licensorIpId, address receiver, @@ -56,14 +56,14 @@ interface IPolicyFrameworkManager is IERC165 { /// @notice Verify policy parameters for linking a child IP to a parent IP (licensor) by burning a license NFT. /// @dev Enforced to be only callable by LicenseRegistry /// @param licenseId the license id to burn - /// @param caller the address executing the link + /// @param licensee the address that holds the license and is executing the link /// @param ipId the IP id of the IP being linked /// @param parentIpId the IP id of the parent IP /// @param policyData the encoded framework policy data to verify /// @return verified True if the link is verified function verifyLink( uint256 licenseId, - address caller, + address licensee, address ipId, address parentIpId, bytes calldata policyData diff --git a/contracts/modules/licensing/LicensingModule.sol b/contracts/modules/licensing/LicensingModule.sol index dc0f385e..e69ef570 100644 --- a/contracts/modules/licensing/LicensingModule.sol +++ b/contracts/modules/licensing/LicensingModule.sol @@ -225,8 +225,9 @@ contract LicensingModule is AccessControlled, ILicensingModule, BaseModule, Reen } // If a policy is set, then is only up to the policy params. - // Verify minting param - if (!pfm.verifyMint(msg.sender, isInherited, licensorIpId, receiver, amount, pol.frameworkData)) { + // When verifying mint via PFM, pass in `receiver` as the `licensee` since the receiver is the one who will own + // the license NFT after minting. + if (!pfm.verifyMint(receiver, isInherited, licensorIpId, receiver, amount, pol.frameworkData)) { revert Errors.LicensingModule__MintLicenseParamFailed(); } @@ -298,7 +299,7 @@ contract LicensingModule is AccessControlled, ILicensingModule, BaseModule, Reen revert Errors.LicensingModule__IncompatibleLicensorCommercialPolicy(); } - _linkIpToParent(i, licenseId, licenseData.policyId, pol, licenseData.licensorIpId, childIpId); + _linkIpToParent(i, licenseId, licenseData.policyId, pol, licenseData.licensorIpId, childIpId, holder); return (licenseData.licensorIpId, pol.royaltyPolicy, pol.royaltyData); } @@ -485,7 +486,8 @@ contract LicensingModule is AccessControlled, ILicensingModule, BaseModule, Reen uint256 policyId, Licensing.Policy memory pol, address licensor, - address childIpId + address childIpId, + address licensee ) private { // TODO: check licensor not part of a branch tagged by disputer if (licensor == childIpId) { @@ -495,7 +497,7 @@ contract LicensingModule is AccessControlled, ILicensingModule, BaseModule, Reen if ( !IPolicyFrameworkManager(pol.policyFramework).verifyLink( licenseId, - msg.sender, + licensee, childIpId, licensor, pol.frameworkData diff --git a/contracts/modules/licensing/PILPolicyFrameworkManager.sol b/contracts/modules/licensing/PILPolicyFrameworkManager.sol index 0f76c6fe..beca396f 100644 --- a/contracts/modules/licensing/PILPolicyFrameworkManager.sol +++ b/contracts/modules/licensing/PILPolicyFrameworkManager.sol @@ -76,14 +76,14 @@ contract PILPolicyFrameworkManager is /// @notice Verify policy parameters for linking a child IP to a parent IP (licensor) by burning a license NFT. /// @dev Enforced to be only callable by LicenseRegistry /// @param licenseId the license id to burn - /// @param caller the address executing the link + /// @param licensee the address that holds the license and is executing the linking /// @param ipId the IP id of the IP being linked /// @param parentIpId the IP id of the parent IP /// @param policyData the encoded framework policy data to verify /// @return verified True if the link is verified function verifyLink( uint256 licenseId, - address caller, + address licensee, address ipId, address parentIpId, bytes calldata policyData @@ -104,7 +104,7 @@ contract PILPolicyFrameworkManager is if (policy.commercializerChecker != address(0)) { // No need to check if the commercializerChecker supports the IHookModule interface, as it was checked // when the policy was registered. - if (!IHookModule(policy.commercializerChecker).verify(caller, policy.commercializerCheckerData)) { + if (!IHookModule(policy.commercializerChecker).verify(licensee, policy.commercializerCheckerData)) { return false; } } @@ -113,7 +113,7 @@ contract PILPolicyFrameworkManager is /// @notice Verify policy parameters for minting a license. /// @dev Enforced to be only callable by LicenseRegistry - /// @param caller the address executing the mint + /// @param licensee the address that holds the license and is executing the mint /// @param mintingFromADerivative true if the license is minting from a derivative IPA /// @param licensorIpId the IP id of the licensor /// @param receiver the address receiving the license @@ -121,7 +121,7 @@ contract PILPolicyFrameworkManager is /// @param policyData the encoded framework policy data to verify /// @return verified True if the link is verified function verifyMint( - address caller, + address licensee, bool mintingFromADerivative, address licensorIpId, address receiver, @@ -138,7 +138,7 @@ contract PILPolicyFrameworkManager is if (policy.commercializerChecker != address(0)) { // No need to check if the commercializerChecker supports the IHookModule interface, as it was checked // when the policy was registered. - if (!IHookModule(policy.commercializerChecker).verify(caller, policy.commercializerCheckerData)) { + if (!IHookModule(policy.commercializerChecker).verify(licensee, policy.commercializerCheckerData)) { return false; } } diff --git a/test/foundry/integration/big-bang/SingleNftCollection.t.sol b/test/foundry/integration/big-bang/SingleNftCollection.t.sol index 2119716c..f6284f52 100644 --- a/test/foundry/integration/big-bang/SingleNftCollection.t.sol +++ b/test/foundry/integration/big-bang/SingleNftCollection.t.sol @@ -13,10 +13,16 @@ import { IRoyaltyPolicyLAP } from "../../../../contracts/interfaces/modules/roya // test import { BaseIntegration } from "../BaseIntegration.t.sol"; +import { MockTokenGatedHook } from "../../mocks/MockTokenGatedHook.sol"; +import { MockERC721 } from "../../mocks/token/MockERC721.sol"; contract BigBang_Integration_SingleNftCollection is BaseIntegration { using EnumerableSet for EnumerableSet.UintSet; + MockTokenGatedHook internal mockTokenGatedHook; + + MockERC721 internal mockGatedNft; + mapping(uint256 tokenId => address ipAccount) internal ipAcct; mapping(string name => uint256 licenseId) internal licenseIds; @@ -28,6 +34,9 @@ contract BigBang_Integration_SingleNftCollection is BaseIntegration { function setUp() public override { super.setUp(); + mockTokenGatedHook = new MockTokenGatedHook(); + mockGatedNft = new MockERC721("MockGatedNft"); + // Add PIL PFM policies _setPILPolicyFrameworkManager(); @@ -42,8 +51,8 @@ contract BigBang_Integration_SingleNftCollection is BaseIntegration { attribution: false, commercialUse: true, commercialAttribution: true, - commercializerChecker: address(0), - commercializerCheckerData: "", + commercializerChecker: address(mockTokenGatedHook), + commercializerCheckerData: abi.encode(address(mockGatedNft)), commercialRevShare: derivCheapFlexibleRevShare, derivativesAllowed: true, derivativesAttribution: true, @@ -148,6 +157,10 @@ contract BigBang_Integration_SingleNftCollection is BaseIntegration { vm.startPrank(u.carl); mockNFT.mintId(u.carl, 6); + // Carl needs to hold an NFT from mockGatedNFT collection to mint license pil_com_deriv_cheap_flexible + // (verified by the mockTokenGatedHook commercializer checker) + mockGatedNft.mint(u.carl); + mockToken.approve(address(royaltyPolicyLAP), 100 ether); uint256[] memory carl_license_from_root_alice = new uint256[](1); @@ -180,7 +193,11 @@ contract BigBang_Integration_SingleNftCollection is BaseIntegration { // Carl activates one of the two licenses on his NFT 7 IPAccount, linking as child to Bob's NFT 3 IPAccount { vm.startPrank(u.carl); - mockNFT.mintId(u.carl, 7); + mockNFT.mintId(u.carl, 7); // NFT for Carl's IPAccount7 + + // Carl is minting license on non-commercial policy, so no commercializer checker is involved. + // Thus, no need to mint anything (although Carl already has mockGatedNft from above) + uint256[] memory carl_license_from_root_bob = new uint256[](1); carl_license_from_root_bob[0] = licensingModule.mintLicense( policyIds["pil_noncom_deriv_reciprocal_derivative"], @@ -190,7 +207,7 @@ contract BigBang_Integration_SingleNftCollection is BaseIntegration { emptyRoyaltyPolicyLAPInitParams ); - IRoyaltyPolicyLAP.InitParams memory params = IRoyaltyPolicyLAP.InitParams({ + IRoyaltyPolicyLAP.InitParams memory royaltyContextRegister = IRoyaltyPolicyLAP.InitParams({ targetAncestors: new address[](1), targetRoyaltyAmount: new uint32[](1), parentAncestors1: new address[](0), @@ -198,11 +215,28 @@ contract BigBang_Integration_SingleNftCollection is BaseIntegration { parentAncestorsRoyalties1: new uint32[](0), parentAncestorsRoyalties2: new uint32[](0) }); - params.targetAncestors[0] = ipAcct[3]; - params.targetRoyaltyAmount[0] = 0; - - ipAcct[7] = registerIpAccount(mockNFT, 7, u.carl); - linkIpToParents(carl_license_from_root_bob, ipAcct[7], u.carl, abi.encode(params)); + royaltyContextRegister.targetAncestors[0] = ipAcct[3]; + royaltyContextRegister.targetRoyaltyAmount[0] = 0; + + // TODO: events check + ipAssetRegistry.register( + carl_license_from_root_bob, + abi.encode(royaltyContextRegister), + block.chainid, + address(mockNFT), + 7, + address(ipResolver), + true, + abi.encode( + IP.MetadataV1({ + name: "IP NAME", + hash: bytes32("hash"), + registrationDate: uint64(block.timestamp), + registrant: u.carl, // caller + uri: "external URL" + }) + ) + ); } // Alice mints 2 license for policy "pil_com_deriv_cheap_flexible" on Bob's NFT 3 IPAccount @@ -216,6 +250,10 @@ contract BigBang_Integration_SingleNftCollection is BaseIntegration { mockToken.approve(address(royaltyPolicyLAP), mintAmount * 100 ether); + // Alice needs to hold an NFT from mockGatedNFT collection to mint license on pil_com_deriv_cheap_flexible + // (verified by the mockTokenGatedHook commercializer checker) + mockGatedNft.mint(u.alice); + uint256[] memory alice_license_from_root_bob = new uint256[](1); alice_license_from_root_bob[0] = licensingModule.mintLicense( policyIds["pil_com_deriv_cheap_flexible"], @@ -285,7 +323,7 @@ contract BigBang_Integration_SingleNftCollection is BaseIntegration { }); uint256[] memory carl_licenses = new uint256[](2); - // Commercial license + // Commercial license (Carl already has mockGatedNft from above, so he passes commercializer checker check) carl_licenses[0] = licensingModule.mintLicense( policyIds["pil_com_deriv_cheap_flexible"], // ipAcct[1] has this policy attached ipAcct[1], diff --git a/test/foundry/utils/BaseTest.t.sol b/test/foundry/utils/BaseTest.t.sol index 1eb233d9..614426d6 100644 --- a/test/foundry/utils/BaseTest.t.sol +++ b/test/foundry/utils/BaseTest.t.sol @@ -108,6 +108,13 @@ contract BaseTest is Test, DeployHelper, LicensingHelper { // NOTE: accessController is IAccessController, which doesn't expose `initialize` function. AccessController(address(accessController)).initialize(address(ipAccountRegistry), getModuleRegistry()); + accessController.setGlobalPermission( + address(ipAssetRegistry), + address(licensingModule), + bytes4(licensingModule.linkIpToParents.selector), + AccessPermission.ALLOW + ); + // If REAL Registration Module and Licensing Module are deployed, set global permissions if (deployConditions.module.registrationModule && deployConditions.module.licensingModule) { accessController.setGlobalPermission(