From 84db8d60b7bb1514606b2707ae55a30782741841 Mon Sep 17 00:00:00 2001 From: Seb Date: Thu, 19 Dec 2024 17:10:07 -0800 Subject: [PATCH] feat: add backward compatibility to `LicenseAttachmentWorkflows` (#150) --- .../workflows/ILicenseAttachmentWorkflows.sol | 64 +++ .../workflows/LicenseAttachmentWorkflows.sol | 191 +++++++ .../LicenseAttachmentWorkflows.t.sol | 512 ++++++++++++++++++ 3 files changed, 767 insertions(+) diff --git a/contracts/interfaces/workflows/ILicenseAttachmentWorkflows.sol b/contracts/interfaces/workflows/ILicenseAttachmentWorkflows.sol index c75874e..8b8ce3b 100644 --- a/contracts/interfaces/workflows/ILicenseAttachmentWorkflows.sol +++ b/contracts/interfaces/workflows/ILicenseAttachmentWorkflows.sol @@ -1,5 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.26; + +import { PILTerms } from "@storyprotocol/core/interfaces/modules/licensing/IPILicenseTemplate.sol"; import { WorkflowStructs } from "../../lib/WorkflowStructs.sol"; /// @title License Attachment Workflows Interface @@ -54,4 +56,66 @@ interface ILicenseAttachmentWorkflows { WorkflowStructs.LicenseTermsData[] calldata licenseTermsData, WorkflowStructs.SignatureData calldata sigMetadataAndAttachAndConfig ) external returns (address ipId, uint256[] memory licenseTermsIds); + + //////////////////////////////////////////////////////////////////////////// + // DEPRECATED // + //////////////////////////////////////////////////////////////////////////// + + /// @notice Register Programmable IP License Terms (if unregistered) and attach it to IP. + /// @notice THIS VERSION OF THE FUNCTION IS DEPRECATED, WILL BE REMOVED IN V1.4 + function registerPILTermsAndAttach( + address ipId, + PILTerms[] calldata terms, + WorkflowStructs.SignatureData calldata sigAttach + ) external returns (uint256[] memory licenseTermsIds); + + /// @notice Mint an NFT from a SPGNFT collection, register it with metadata as an IP, + /// register Programmable IPLicense + /// @notice THIS VERSION OF THE FUNCTION IS DEPRECATED, WILL BE REMOVED IN V1.4 + function mintAndRegisterIpAndAttachPILTerms( + address spgNftContract, + address recipient, + WorkflowStructs.IPMetadata calldata ipMetadata, + PILTerms[] calldata terms + ) external returns (address ipId, uint256 tokenId, uint256[] memory licenseTermsIds); + + /// @notice Register a given NFT as an IP and attach Programmable IP License Terms. + /// @notice THIS VERSION OF THE FUNCTION IS DEPRECATED, WILL BE REMOVED IN V1.4 + function registerIpAndAttachPILTerms( + address nftContract, + uint256 tokenId, + WorkflowStructs.IPMetadata calldata ipMetadata, + PILTerms[] calldata terms, + WorkflowStructs.SignatureData calldata sigMetadata, + WorkflowStructs.SignatureData calldata sigAttach + ) external returns (address ipId, uint256[] memory licenseTermsIds); + + /// @notice Register Programmable IP License Terms (if unregistered) and attach it to IP. + /// @notice THIS VERSION OF THE FUNCTION IS DEPRECATED, WILL BE REMOVED IN V1.4 + function registerPILTermsAndAttach( + address ipId, + PILTerms calldata terms, + WorkflowStructs.SignatureData calldata sigAttach + ) external returns (uint256 licenseTermsId); + + /// Mint an NFT from a SPGNFT collection, register it with metadata as an IP, + /// register Programmable IPLicense + /// @notice THIS VERSION OF THE FUNCTION IS DEPRECATED, WILL BE REMOVED IN V1.4 + function mintAndRegisterIpAndAttachPILTerms( + address spgNftContract, + address recipient, + WorkflowStructs.IPMetadata calldata ipMetadata, + PILTerms calldata terms + ) external returns (address ipId, uint256 tokenId, uint256 licenseTermsId); + + /// @notice Register a given NFT as an IP and attach Programmable IP License Terms. + /// @notice THIS VERSION OF THE FUNCTION IS DEPRECATED, WILL BE REMOVED IN V1.4 + function registerIpAndAttachPILTerms( + address nftContract, + uint256 tokenId, + WorkflowStructs.IPMetadata calldata ipMetadata, + PILTerms calldata terms, + WorkflowStructs.SignatureData calldata sigMetadata, + WorkflowStructs.SignatureData calldata sigAttach + ) external returns (address ipId, uint256 licenseTermsId); } diff --git a/contracts/workflows/LicenseAttachmentWorkflows.sol b/contracts/workflows/LicenseAttachmentWorkflows.sol index a8d8c18..0f8fecd 100644 --- a/contracts/workflows/LicenseAttachmentWorkflows.sol +++ b/contracts/workflows/LicenseAttachmentWorkflows.sol @@ -9,6 +9,7 @@ import { UUPSUpgradeable } from "@openzeppelin/contracts-upgradeable/proxy/utils import { ICoreMetadataModule } from "@storyprotocol/core/interfaces/modules/metadata/ICoreMetadataModule.sol"; import { ILicensingModule } from "@storyprotocol/core/interfaces/modules/licensing/ILicensingModule.sol"; +import { PILTerms } from "@storyprotocol/core/interfaces/modules/licensing/IPILicenseTemplate.sol"; import { BaseWorkflow } from "../BaseWorkflow.sol"; import { Errors } from "../lib/Errors.sol"; @@ -238,4 +239,194 @@ contract LicenseAttachmentWorkflows is /// @dev Hook to authorize the upgrade according to UUPSUpgradeable /// @param newImplementation The address of the new implementation function _authorizeUpgrade(address newImplementation) internal override restricted {} + + //////////////////////////////////////////////////////////////////////////// + // DEPRECATED // + //////////////////////////////////////////////////////////////////////////// + + /// @notice Register Programmable IP License Terms (if unregistered) and attach it to IP. + /// @notice THIS VERSION OF THE FUNCTION IS DEPRECATED, WILL BE REMOVED IN V1.4 + function registerPILTermsAndAttach( + address ipId, + PILTerms[] calldata terms, + WorkflowStructs.SignatureData calldata sigAttach + ) external returns (uint256[] memory licenseTermsIds) { + if (terms.length == 0) revert Errors.LicenseAttachmentWorkflows__NoLicenseTermsData(); + + PermissionHelper.setPermissionForModule( + ipId, + address(LICENSING_MODULE), + address(ACCESS_CONTROLLER), + ILicensingModule.attachLicenseTerms.selector, + sigAttach + ); + + licenseTermsIds = _registerMultiplePILTermsAndAttach(ipId, terms); + } + + /// @notice Mint an NFT from a SPGNFT collection, register it with metadata as an IP, + /// register Programmable IP License Terms (if unregistered), and attach it to the registered IP. + /// @notice THIS VERSION OF THE FUNCTION IS DEPRECATED, WILL BE REMOVED IN V1.4 + function mintAndRegisterIpAndAttachPILTerms( + address spgNftContract, + address recipient, + WorkflowStructs.IPMetadata calldata ipMetadata, + PILTerms[] calldata terms + ) + external + onlyMintAuthorized(spgNftContract) + returns (address ipId, uint256 tokenId, uint256[] memory licenseTermsIds) + { + if (terms.length == 0) revert Errors.LicenseAttachmentWorkflows__NoLicenseTermsData(); + + tokenId = ISPGNFT(spgNftContract).mintByPeriphery({ + to: address(this), + payer: msg.sender, + nftMetadataURI: ipMetadata.nftMetadataURI, + nftMetadataHash: "", + allowDuplicates: true + }); + ipId = IP_ASSET_REGISTRY.register(block.chainid, spgNftContract, tokenId); + MetadataHelper.setMetadata(ipId, address(CORE_METADATA_MODULE), ipMetadata); + + licenseTermsIds = _registerMultiplePILTermsAndAttach(ipId, terms); + + ISPGNFT(spgNftContract).safeTransferFrom(address(this), recipient, tokenId, ""); + } + + /// @notice Register a given NFT as an IP and attach Programmable IP License Terms. + /// @notice THIS VERSION OF THE FUNCTION IS DEPRECATED, WILL BE REMOVED IN V1.4 + function registerIpAndAttachPILTerms( + address nftContract, + uint256 tokenId, + WorkflowStructs.IPMetadata calldata ipMetadata, + PILTerms[] calldata terms, + WorkflowStructs.SignatureData calldata sigMetadata, + WorkflowStructs.SignatureData calldata sigAttach + ) external returns (address ipId, uint256[] memory licenseTermsIds) { + if (terms.length == 0) revert Errors.LicenseAttachmentWorkflows__NoLicenseTermsData(); + + ipId = IP_ASSET_REGISTRY.register(block.chainid, nftContract, tokenId); + MetadataHelper.setMetadataWithSig( + ipId, + address(CORE_METADATA_MODULE), + address(ACCESS_CONTROLLER), + ipMetadata, + sigMetadata + ); + + PermissionHelper.setPermissionForModule( + ipId, + address(LICENSING_MODULE), + address(ACCESS_CONTROLLER), + ILicensingModule.attachLicenseTerms.selector, + sigAttach + ); + + licenseTermsIds = _registerMultiplePILTermsAndAttach(ipId, terms); + } + + /// @notice Register Programmable IP License Terms (if unregistered) and attach it to IP. + /// @notice THIS VERSION OF THE FUNCTION IS DEPRECATED, WILL BE REMOVED IN V1.4 + function registerPILTermsAndAttach( + address ipId, + PILTerms calldata terms, + WorkflowStructs.SignatureData calldata sigAttach + ) external returns (uint256 licenseTermsId) { + PermissionHelper.setPermissionForModule( + ipId, + address(LICENSING_MODULE), + address(ACCESS_CONTROLLER), + ILicensingModule.attachLicenseTerms.selector, + sigAttach + ); + + licenseTermsId = LicensingHelper.registerPILTermsAndAttach( + ipId, + address(PIL_TEMPLATE), + address(LICENSING_MODULE), + terms + ); + } + + /// @notice Mint an NFT from a SPGNFT collection, register it with metadata as an IP, + /// register Programmable IP License Terms (if unregistered), and attach it to the registered IP. + /// @notice THIS VERSION OF THE FUNCTION IS DEPRECATED, WILL BE REMOVED IN V1.4 + function mintAndRegisterIpAndAttachPILTerms( + address spgNftContract, + address recipient, + WorkflowStructs.IPMetadata calldata ipMetadata, + PILTerms calldata terms + ) external onlyMintAuthorized(spgNftContract) returns (address ipId, uint256 tokenId, uint256 licenseTermsId) { + tokenId = ISPGNFT(spgNftContract).mintByPeriphery({ + to: address(this), + payer: msg.sender, + nftMetadataURI: ipMetadata.nftMetadataURI, + nftMetadataHash: "", + allowDuplicates: true + }); + ipId = IP_ASSET_REGISTRY.register(block.chainid, spgNftContract, tokenId); + MetadataHelper.setMetadata(ipId, address(CORE_METADATA_MODULE), ipMetadata); + + licenseTermsId = LicensingHelper.registerPILTermsAndAttach( + ipId, + address(PIL_TEMPLATE), + address(LICENSING_MODULE), + terms + ); + + ISPGNFT(spgNftContract).safeTransferFrom(address(this), recipient, tokenId, ""); + } + + /// @notice Register a given NFT as an IP and attach Programmable IP License Terms. + /// @notice THIS VERSION OF THE FUNCTION IS DEPRECATED, WILL BE REMOVED IN V1.4 + function registerIpAndAttachPILTerms( + address nftContract, + uint256 tokenId, + WorkflowStructs.IPMetadata calldata ipMetadata, + PILTerms calldata terms, + WorkflowStructs.SignatureData calldata sigMetadata, + WorkflowStructs.SignatureData calldata sigAttach + ) external returns (address ipId, uint256 licenseTermsId) { + ipId = IP_ASSET_REGISTRY.register(block.chainid, nftContract, tokenId); + MetadataHelper.setMetadataWithSig( + ipId, + address(CORE_METADATA_MODULE), + address(ACCESS_CONTROLLER), + ipMetadata, + sigMetadata + ); + + PermissionHelper.setPermissionForModule( + ipId, + address(LICENSING_MODULE), + address(ACCESS_CONTROLLER), + ILicensingModule.attachLicenseTerms.selector, + sigAttach + ); + + licenseTermsId = LicensingHelper.registerPILTermsAndAttach( + ipId, + address(PIL_TEMPLATE), + address(LICENSING_MODULE), + terms + ); + } + + /// @notice THIS VERSION OF THE FUNCTION IS DEPRECATED, WILL BE REMOVED IN V1.4 + function _registerMultiplePILTermsAndAttach( + address ipId, + PILTerms[] calldata terms + ) private returns (uint256[] memory licenseTermsIds) { + licenseTermsIds = new uint256[](terms.length); + uint256 length = terms.length; + for (uint256 i; i < length; i++) { + licenseTermsIds[i] = LicensingHelper.registerPILTermsAndAttach( + ipId, + address(PIL_TEMPLATE), + address(LICENSING_MODULE), + terms[i] + ); + } + } } diff --git a/test/workflows/LicenseAttachmentWorkflows.t.sol b/test/workflows/LicenseAttachmentWorkflows.t.sol index 155fc39..cc65f91 100644 --- a/test/workflows/LicenseAttachmentWorkflows.t.sol +++ b/test/workflows/LicenseAttachmentWorkflows.t.sol @@ -10,6 +10,7 @@ import { IIPAccount } from "@storyprotocol/core/interfaces/IIPAccount.sol"; import { ILicensingModule } from "@storyprotocol/core/interfaces/modules/licensing/ILicensingModule.sol"; import { Licensing } from "@storyprotocol/core/lib/Licensing.sol"; import { PILFlavors } from "@storyprotocol/core/lib/PILFlavors.sol"; +import { PILTerms } from "@storyprotocol/core/interfaces/modules/licensing/IPILicenseTemplate.sol"; // contracts import { Errors } from "../../contracts/lib/Errors.sol"; @@ -29,10 +30,12 @@ contract LicenseAttachmentWorkflowsTest is BaseTest { mapping(uint256 index => IPAsset) internal ipAsset; WorkflowStructs.LicenseTermsData[] internal commTermsData; + PILTerms[] private terms; function setUp() public override { super.setUp(); _setUpLicenseTermsData(); + _setUpTerms(); } modifier withIp(address owner) { @@ -443,4 +446,513 @@ contract LicenseAttachmentWorkflowsTest is BaseTest { }) ); } + + //////////////////////////////////////////////////////////////////////////// + // DEPRECATED // + //////////////////////////////////////////////////////////////////////////// + + function test_LicenseAttachmentWorkflows_registerPILTermsAndAttach_DEPR() public withCollection withIp(u.alice) { + address payable ipId = ipAsset[1].ipId; + uint256 deadline = block.timestamp + 1000; + + (bytes memory signature, , ) = _getSetPermissionSigForPeriphery({ + ipId: ipId, + to: address(licenseAttachmentWorkflows), + module: address(licensingModule), + selector: ILicensingModule.attachLicenseTerms.selector, + deadline: deadline, + state: IIPAccount(ipId).state(), + signerSk: sk.alice + }); + + uint256 ltAmt = pilTemplate.totalRegisteredLicenseTerms(); + + uint256[] memory licenseTermsIds = licenseAttachmentWorkflows.registerPILTermsAndAttach({ + ipId: ipId, + terms: terms, + sigAttach: WorkflowStructs.SignatureData({ signer: u.alice, deadline: deadline, signature: signature }) + }); + + assertEq(licenseTermsIds[0], ltAmt + 1); + assertEq(licenseTermsIds[1], ltAmt + 2); + assertEq(licenseTermsIds[2], ltAmt + 3); + assertEq(licenseTermsIds[3], ltAmt + 4); + assertEq(licenseTermsIds[4], ltAmt + 5); + } + + function test_LicenseAttachmentWorkflows_mintAndRegisterIpAndAttachPILTerms_DEPR() + public + withCollection + whenCallerHasMinterRole + withEnoughTokens(address(licenseAttachmentWorkflows)) + { + (address ipId1, uint256 tokenId1, uint256[] memory licenseTermsIds1) = licenseAttachmentWorkflows + .mintAndRegisterIpAndAttachPILTerms({ + spgNftContract: address(nftContract), + recipient: caller, + ipMetadata: ipMetadataEmpty, + terms: terms + }); + assertTrue(ipAssetRegistry.isRegistered(ipId1)); + assertEq(tokenId1, 1); + assertEq(licenseTermsIds1[0], 2); + assertEq(licenseTermsIds1[1], 3); + assertEq(licenseTermsIds1[2], 4); + assertEq(licenseTermsIds1[3], 5); + assertEq(licenseTermsIds1[4], 6); + assertEq(nftContract.tokenURI(tokenId1), string.concat(testBaseURI, tokenId1.toString())); + assertMetadata(ipId1, ipMetadataEmpty); + (address licenseTemplate, uint256 licenseTermsId) = licenseRegistry.getAttachedLicenseTerms(ipId1, 0); + assertEq(licenseTemplate, address(pilTemplate)); + assertEq(licenseTermsId, licenseTermsIds1[0]); + (licenseTemplate, licenseTermsId) = licenseRegistry.getAttachedLicenseTerms(ipId1, 1); + assertEq(licenseTemplate, address(pilTemplate)); + assertEq(licenseTermsId, licenseTermsIds1[1]); + (licenseTemplate, licenseTermsId) = licenseRegistry.getAttachedLicenseTerms(ipId1, 2); + assertEq(licenseTemplate, address(pilTemplate)); + assertEq(licenseTermsId, licenseTermsIds1[2]); + (licenseTemplate, licenseTermsId) = licenseRegistry.getAttachedLicenseTerms(ipId1, 3); + assertEq(licenseTemplate, address(pilTemplate)); + assertEq(licenseTermsId, licenseTermsIds1[3]); + (licenseTemplate, licenseTermsId) = licenseRegistry.getAttachedLicenseTerms(ipId1, 4); + assertEq(licenseTemplate, address(pilTemplate)); + assertEq(licenseTermsId, licenseTermsIds1[4]); + + (address ipId2, uint256 tokenId2, uint256[] memory licenseTermsIds2) = licenseAttachmentWorkflows + .mintAndRegisterIpAndAttachPILTerms({ + spgNftContract: address(nftContract), + recipient: caller, + ipMetadata: ipMetadataDefault, + terms: terms + }); + assertTrue(ipAssetRegistry.isRegistered(ipId2)); + assertEq(tokenId2, 2); + assertEq(licenseTermsIds1[0], licenseTermsIds2[0]); + assertEq(licenseTermsIds1[1], licenseTermsIds2[1]); + assertEq(licenseTermsIds1[2], licenseTermsIds2[2]); + assertEq(licenseTermsIds1[3], licenseTermsIds2[3]); + assertEq(licenseTermsIds1[4], licenseTermsIds2[4]); + assertEq(nftContract.tokenURI(tokenId2), string.concat(testBaseURI, ipMetadataDefault.nftMetadataURI)); + assertMetadata(ipId2, ipMetadataDefault); + } + + function test_LicenseAttachmentWorkflows_registerIpAndAttachPILTerms_DEPR() + public + withCollection + whenCallerHasMinterRole + withEnoughTokens(address(licenseAttachmentWorkflows)) + { + uint256 tokenId = nftContract.mint(address(caller), ipMetadataEmpty.nftMetadataURI, "", true); + address payable ipId = payable(ipAssetRegistry.ipId(block.chainid, address(nftContract), tokenId)); + + uint256 deadline = block.timestamp + 1000; + + (bytes memory sigMetadata, bytes32 expectedState, ) = _getSetPermissionSigForPeriphery({ + ipId: ipId, + to: address(licenseAttachmentWorkflows), + module: address(coreMetadataModule), + selector: ICoreMetadataModule.setAll.selector, + deadline: deadline, + state: bytes32(0), + signerSk: sk.alice + }); + + (bytes memory sigAttach, , ) = _getSetPermissionSigForPeriphery({ + ipId: ipId, + to: address(licenseAttachmentWorkflows), + module: address(licensingModule), + selector: ILicensingModule.attachLicenseTerms.selector, + deadline: deadline, + state: expectedState, + signerSk: sk.alice + }); + + licenseAttachmentWorkflows.registerIpAndAttachPILTerms({ + nftContract: address(nftContract), + tokenId: tokenId, + ipMetadata: ipMetadataDefault, + terms: terms, + sigMetadata: WorkflowStructs.SignatureData({ signer: u.alice, deadline: deadline, signature: sigMetadata }), + sigAttach: WorkflowStructs.SignatureData({ signer: u.alice, deadline: deadline, signature: sigAttach }) + }); + + assertTrue(ipAssetRegistry.isRegistered(ipId)); + assertMetadata(ipId, ipMetadataDefault); + (address licenseTemplate, uint256 licenseTermsId) = licenseRegistry.getAttachedLicenseTerms(ipId, 0); + assertEq(licenseTemplate, address(pilTemplate)); + assertEq(licenseTermsId, pilTemplate.getLicenseTermsId(terms[0])); + (licenseTemplate, licenseTermsId) = licenseRegistry.getAttachedLicenseTerms(ipId, 1); + assertEq(licenseTemplate, address(pilTemplate)); + assertEq(licenseTermsId, pilTemplate.getLicenseTermsId(terms[1])); + (licenseTemplate, licenseTermsId) = licenseRegistry.getAttachedLicenseTerms(ipId, 2); + assertEq(licenseTemplate, address(pilTemplate)); + assertEq(licenseTermsId, pilTemplate.getLicenseTermsId(terms[2])); + (licenseTemplate, licenseTermsId) = licenseRegistry.getAttachedLicenseTerms(ipId, 3); + assertEq(licenseTemplate, address(pilTemplate)); + assertEq(licenseTermsId, pilTemplate.getLicenseTermsId(terms[3])); + (licenseTemplate, licenseTermsId) = licenseRegistry.getAttachedLicenseTerms(ipId, 4); + assertEq(licenseTemplate, address(pilTemplate)); + assertEq(licenseTermsId, pilTemplate.getLicenseTermsId(terms[4])); + } + + function test_LicenseAttachmentWorkflows_registerPILTermsAndAttach_idempotency_DEPR() + public + withCollection + withIp(u.alice) + { + address payable ipId = ipAsset[1].ipId; + uint256 deadline = block.timestamp + 1000; + + (bytes memory signature1, , ) = _getSetPermissionSigForPeriphery({ + ipId: ipId, + to: address(licenseAttachmentWorkflows), + module: address(licensingModule), + selector: ILicensingModule.attachLicenseTerms.selector, + deadline: deadline, + state: IIPAccount(ipId).state(), + signerSk: sk.alice + }); + + uint256[] memory licenseTermsIds1 = licenseAttachmentWorkflows.registerPILTermsAndAttach({ + ipId: ipId, + terms: terms, + sigAttach: WorkflowStructs.SignatureData({ signer: u.alice, deadline: deadline, signature: signature1 }) + }); + + (bytes memory signature2, , ) = _getSetPermissionSigForPeriphery({ + ipId: ipId, + to: address(licenseAttachmentWorkflows), + module: address(licensingModule), + selector: ILicensingModule.attachLicenseTerms.selector, + deadline: deadline, + state: IIPAccount(ipId).state(), + signerSk: sk.alice + }); + + /// attach the same license terms to the IP again, but it shouldn't revert + uint256[] memory licenseTermsIds2 = licenseAttachmentWorkflows.registerPILTermsAndAttach({ + ipId: ipId, + terms: terms, + sigAttach: WorkflowStructs.SignatureData({ signer: u.alice, deadline: deadline, signature: signature2 }) + }); + + assertEq(licenseTermsIds1[0], licenseTermsIds2[0]); + assertEq(licenseTermsIds1[1], licenseTermsIds2[1]); + assertEq(licenseTermsIds1[2], licenseTermsIds2[2]); + assertEq(licenseTermsIds1[3], licenseTermsIds2[3]); + assertEq(licenseTermsIds1[4], licenseTermsIds2[4]); + } + + function test_revert_registerPILTermsAndAttach_DerivativesCannotAddLicenseTerms_DEPR() + public + withCollection + whenCallerHasMinterRole + withEnoughTokens(address(licenseAttachmentWorkflows)) + { + (address ipIdParent, , uint256[] memory licenseTermsIdsParent) = licenseAttachmentWorkflows + .mintAndRegisterIpAndAttachPILTerms({ + spgNftContract: address(nftContract), + recipient: caller, + ipMetadata: ipMetadataDefault, + terms: terms + }); + + address[] memory parentIpIds = new address[](1); + parentIpIds[0] = ipIdParent; + + uint256[] memory licenseTermsIds = new uint256[](1); + licenseTermsIds[0] = licenseTermsIdsParent[0]; + + (address ipIdChild, ) = derivativeWorkflows.mintAndRegisterIpAndMakeDerivative({ + spgNftContract: address(nftContract), + derivData: WorkflowStructs.MakeDerivativeDEPR({ + parentIpIds: parentIpIds, + licenseTemplate: address(pilTemplate), + licenseTermsIds: licenseTermsIds, + royaltyContext: "" + }), + ipMetadata: ipMetadataDefault, + recipient: caller + }); + + uint256 deadline = block.timestamp + 1000; + + (bytes memory signature, , ) = _getSetPermissionSigForPeriphery({ + ipId: ipIdChild, + to: address(licenseAttachmentWorkflows), + module: address(licensingModule), + selector: ILicensingModule.attachLicenseTerms.selector, + deadline: deadline, + state: IIPAccount(payable(ipIdChild)).state(), + signerSk: sk.alice + }); + + /// attach license terms to the child ip, should revert with the correct error + vm.expectRevert(CoreErrors.LicensingModule__DerivativesCannotAddLicenseTerms.selector); + licenseAttachmentWorkflows.registerPILTermsAndAttach({ + ipId: ipIdChild, + terms: terms, + sigAttach: WorkflowStructs.SignatureData({ signer: u.alice, deadline: deadline, signature: signature }) + }); + } + + function test_LicenseAttachmentWorkflows_registerPILTermsAndAttach_SingleTerms_DEPR() + public + withCollection + withIp(u.alice) + { + address payable ipId = ipAsset[1].ipId; + uint256 deadline = block.timestamp + 1000; + + (bytes memory signature, , ) = _getSetPermissionSigForPeriphery({ + ipId: ipId, + to: address(licenseAttachmentWorkflows), + module: address(licensingModule), + selector: ILicensingModule.attachLicenseTerms.selector, + deadline: deadline, + state: IIPAccount(ipId).state(), + signerSk: sk.alice + }); + + uint256 ltAmt = pilTemplate.totalRegisteredLicenseTerms(); + + uint256 licenseTermsId = licenseAttachmentWorkflows.registerPILTermsAndAttach({ + ipId: ipId, + terms: PILFlavors.commercialUse({ + mintingFee: 100, + currencyToken: address(mockToken), + royaltyPolicy: address(royaltyPolicyLAP) + }), + sigAttach: WorkflowStructs.SignatureData({ signer: u.alice, deadline: deadline, signature: signature }) + }); + + assertEq(licenseTermsId, ltAmt + 1); + } + + function test_LicenseAttachmentWorkflows_mintAndRegisterIpAndAttachPILTerms_SingleTerms_DEPR() + public + withCollection + whenCallerHasMinterRole + withEnoughTokens(address(licenseAttachmentWorkflows)) + { + (address ipId1, uint256 tokenId1, uint256 licenseTermsId1) = licenseAttachmentWorkflows + .mintAndRegisterIpAndAttachPILTerms({ + spgNftContract: address(nftContract), + recipient: caller, + ipMetadata: ipMetadataEmpty, + terms: PILFlavors.nonCommercialSocialRemixing() + }); + assertTrue(ipAssetRegistry.isRegistered(ipId1)); + assertEq(tokenId1, 1); + assertEq(licenseTermsId1, 1); + assertEq(nftContract.tokenURI(tokenId1), string.concat(testBaseURI, tokenId1.toString())); + assertMetadata(ipId1, ipMetadataEmpty); + (address licenseTemplate, uint256 licenseTermsId) = licenseRegistry.getAttachedLicenseTerms(ipId1, 0); + assertEq(licenseTemplate, address(pilTemplate)); + assertEq(licenseTermsId, licenseTermsId1); + + (address ipId2, uint256 tokenId2, uint256 licenseTermsId2) = licenseAttachmentWorkflows + .mintAndRegisterIpAndAttachPILTerms({ + spgNftContract: address(nftContract), + recipient: caller, + ipMetadata: ipMetadataDefault, + terms: PILFlavors.nonCommercialSocialRemixing() + }); + assertTrue(ipAssetRegistry.isRegistered(ipId2)); + assertEq(tokenId2, 2); + assertEq(licenseTermsId1, licenseTermsId2); + assertEq(nftContract.tokenURI(tokenId2), string.concat(testBaseURI, ipMetadataDefault.nftMetadataURI)); + assertMetadata(ipId2, ipMetadataDefault); + } + + function test_LicenseAttachmentWorkflows_registerIpAndAttachPILTerms_SingleTerms_DEPR() + public + withCollection + whenCallerHasMinterRole + withEnoughTokens(address(licenseAttachmentWorkflows)) + { + uint256 tokenId = nftContract.mint(address(caller), ipMetadataEmpty.nftMetadataURI, "", true); + address payable ipId = payable(ipAssetRegistry.ipId(block.chainid, address(nftContract), tokenId)); + + uint256 deadline = block.timestamp + 1000; + + (bytes memory sigMetadata, bytes32 expectedState, ) = _getSetPermissionSigForPeriphery({ + ipId: ipId, + to: address(licenseAttachmentWorkflows), + module: address(coreMetadataModule), + selector: ICoreMetadataModule.setAll.selector, + deadline: deadline, + state: bytes32(0), + signerSk: sk.alice + }); + + (bytes memory sigAttach, , ) = _getSetPermissionSigForPeriphery({ + ipId: ipId, + to: address(licenseAttachmentWorkflows), + module: address(licensingModule), + selector: ILicensingModule.attachLicenseTerms.selector, + deadline: deadline, + state: expectedState, + signerSk: sk.alice + }); + + licenseAttachmentWorkflows.registerIpAndAttachPILTerms({ + nftContract: address(nftContract), + tokenId: tokenId, + ipMetadata: ipMetadataDefault, + terms: PILFlavors.nonCommercialSocialRemixing(), + sigMetadata: WorkflowStructs.SignatureData({ signer: u.alice, deadline: deadline, signature: sigMetadata }), + sigAttach: WorkflowStructs.SignatureData({ signer: u.alice, deadline: deadline, signature: sigAttach }) + }); + } + + function test_LicenseAttachmentWorkflows_registerPILTermsAndAttach_idempotency_SingleTerms_DEPR() + public + withCollection + withIp(u.alice) + { + address payable ipId = ipAsset[1].ipId; + uint256 deadline = block.timestamp + 1000; + + (bytes memory signature1, , ) = _getSetPermissionSigForPeriphery({ + ipId: ipId, + to: address(licenseAttachmentWorkflows), + module: address(licensingModule), + selector: ILicensingModule.attachLicenseTerms.selector, + deadline: deadline, + state: IIPAccount(ipId).state(), + signerSk: sk.alice + }); + + uint256 licenseTermsId1 = licenseAttachmentWorkflows.registerPILTermsAndAttach({ + ipId: ipId, + terms: PILFlavors.commercialUse({ + mintingFee: 100, + currencyToken: address(mockToken), + royaltyPolicy: address(royaltyPolicyLAP) + }), + sigAttach: WorkflowStructs.SignatureData({ signer: u.alice, deadline: deadline, signature: signature1 }) + }); + + (bytes memory signature2, , ) = _getSetPermissionSigForPeriphery({ + ipId: ipId, + to: address(licenseAttachmentWorkflows), + module: address(licensingModule), + selector: ILicensingModule.attachLicenseTerms.selector, + deadline: deadline, + state: IIPAccount(ipId).state(), + signerSk: sk.alice + }); + + // attach the same license terms to the IP again, but it shouldn't revert + uint256 licenseTermsId2 = licenseAttachmentWorkflows.registerPILTermsAndAttach({ + ipId: ipId, + terms: PILFlavors.commercialUse({ + mintingFee: 100, + currencyToken: address(mockToken), + royaltyPolicy: address(royaltyPolicyLAP) + }), + sigAttach: WorkflowStructs.SignatureData({ signer: u.alice, deadline: deadline, signature: signature2 }) + }); + + assertEq(licenseTermsId1, licenseTermsId2); + } + + function test_revert_registerPILTermsAndAttach_DerivativesCannotAddLicenseTerms_SingleTerms_DEPR() + public + withCollection + whenCallerHasMinterRole + withEnoughTokens(address(licenseAttachmentWorkflows)) + { + (address ipIdParent, , uint256 licenseTermsIdParent) = licenseAttachmentWorkflows + .mintAndRegisterIpAndAttachPILTerms({ + spgNftContract: address(nftContract), + recipient: caller, + ipMetadata: ipMetadataDefault, + terms: PILFlavors.nonCommercialSocialRemixing() + }); + + address[] memory parentIpIds = new address[](1); + parentIpIds[0] = ipIdParent; + + uint256[] memory licenseTermsIds = new uint256[](1); + licenseTermsIds[0] = licenseTermsIdParent; + + (address ipIdChild, ) = derivativeWorkflows.mintAndRegisterIpAndMakeDerivative({ + spgNftContract: address(nftContract), + derivData: WorkflowStructs.MakeDerivativeDEPR({ + parentIpIds: parentIpIds, + licenseTemplate: address(pilTemplate), + licenseTermsIds: licenseTermsIds, + royaltyContext: "" + }), + ipMetadata: ipMetadataDefault, + recipient: caller + }); + + uint256 deadline = block.timestamp + 1000; + + (bytes memory signature, , ) = _getSetPermissionSigForPeriphery({ + ipId: ipIdChild, + to: address(licenseAttachmentWorkflows), + module: address(licensingModule), + selector: ILicensingModule.attachLicenseTerms.selector, + deadline: deadline, + state: IIPAccount(payable(ipIdChild)).state(), + signerSk: sk.alice + }); + + // attach a different license terms to the child ip, should revert with the correct error + vm.expectRevert(CoreErrors.LicensingModule__DerivativesCannotAddLicenseTerms.selector); + licenseAttachmentWorkflows.registerPILTermsAndAttach({ + ipId: ipIdChild, + terms: PILFlavors.commercialUse({ + mintingFee: 100, + currencyToken: address(mockToken), + royaltyPolicy: address(royaltyPolicyLAP) + }), + sigAttach: WorkflowStructs.SignatureData({ signer: u.alice, deadline: deadline, signature: signature }) + }); + } + + function _setUpTerms() private { + terms.push( + PILFlavors.commercialRemix({ + mintingFee: 0, + commercialRevShare: 5 * 10 ** 6, // 5% + royaltyPolicy: address(royaltyPolicyLAP), + currencyToken: address(mockToken) + }) + ); + terms.push( + PILFlavors.commercialUse({ + mintingFee: 0, + currencyToken: address(mockToken), + royaltyPolicy: address(royaltyPolicyLAP) + }) + ); + terms.push( + PILFlavors.commercialUse({ + mintingFee: 0, + currencyToken: address(mockToken), + royaltyPolicy: address(royaltyPolicyLRP) + }) + ); + terms.push( + PILFlavors.commercialRemix({ + mintingFee: 0, + commercialRevShare: 5 * 10 ** 6, // 5% + royaltyPolicy: address(royaltyPolicyLRP), + currencyToken: address(mockToken) + }) + ); + terms.push( + PILFlavors.commercialRemix({ + mintingFee: 100 * 10 ** mockToken.decimals(), + commercialRevShare: 10 * 10 ** 6, // 10% + royaltyPolicy: address(royaltyPolicyLRP), + currencyToken: address(mockToken) + }) + ); + } }