From b2a284e0e8bd32507150f09aa7e0b1ba080ebda3 Mon Sep 17 00:00:00 2001 From: Seb Date: Fri, 22 Nov 2024 14:57:24 -0800 Subject: [PATCH] fix(licensing): restore single license attachment for backward compatibility (#127) --- .../workflows/ILicenseAttachmentWorkflows.sol | 33 +++ .../workflows/LicenseAttachmentWorkflows.sol | 92 ++++++++ .../LicenseAttachmentWorkflows.t.sol | 222 ++++++++++++++++++ 3 files changed, 347 insertions(+) diff --git a/contracts/interfaces/workflows/ILicenseAttachmentWorkflows.sol b/contracts/interfaces/workflows/ILicenseAttachmentWorkflows.sol index ad04e1f..f296792 100644 --- a/contracts/interfaces/workflows/ILicenseAttachmentWorkflows.sol +++ b/contracts/interfaces/workflows/ILicenseAttachmentWorkflows.sol @@ -56,4 +56,37 @@ interface ILicenseAttachmentWorkflows { WorkflowStructs.SignatureData calldata sigMetadata, WorkflowStructs.SignatureData calldata sigAttach ) external returns (address ipId, uint256[] memory licenseTermsIds); + + //////////////////////////////////////////////////////////////////////////// + // DEPRECATED // + //////////////////////////////////////////////////////////////////////////// + + /// @notice Register Programmable IP License Terms (if unregistered) and attach it to IP. + /// @notice [DEPRECATED] + 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 [DEPRECATED] + 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 [DEPRECATED] + 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 9a8d3f7..c8c0c79 100644 --- a/contracts/workflows/LicenseAttachmentWorkflows.sol +++ b/contracts/workflows/LicenseAttachmentWorkflows.sol @@ -200,6 +200,98 @@ contract LicenseAttachmentWorkflows is ); } + //////////////////////////////////////////////////////////////////////////// + // DEPRECATED // + //////////////////////////////////////////////////////////////////////////// + + /// @notice Register Programmable IP License Terms (if unregistered) and attach it to IP. + /// @notice [DEPRECATED] + 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), + address(LICENSE_REGISTRY), + 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 [DEPRECATED] + 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 + }); + 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), + address(LICENSE_REGISTRY), + terms + ); + + ISPGNFT(spgNftContract).safeTransferFrom(address(this), recipient, tokenId, ""); + } + + /// @notice Register a given NFT as an IP and attach Programmable IP License Terms. + /// @dev [DEPRECATED] + 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), + address(LICENSE_REGISTRY), + terms + ); + } + // // Upgrade // diff --git a/test/workflows/LicenseAttachmentWorkflows.t.sol b/test/workflows/LicenseAttachmentWorkflows.t.sol index de3529a..5926a33 100644 --- a/test/workflows/LicenseAttachmentWorkflows.t.sol +++ b/test/workflows/LicenseAttachmentWorkflows.t.sol @@ -330,4 +330,226 @@ contract LicenseAttachmentWorkflowsTest is BaseTest { sigAttach: WorkflowStructs.SignatureData({ signer: u.alice, deadline: deadline, signature: signature }) }); } + + //////////////////////////////////////////////////////////////////////////// + // DEPRECATED // + //////////////////////////////////////////////////////////////////////////// + function test_LicenseAttachmentWorkflows_registerPILTermsAndAttach_deprecated() + 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_deprecated() + 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_deprecated() + public + withCollection + whenCallerHasMinterRole + withEnoughTokens(address(licenseAttachmentWorkflows)) + { + uint256 tokenId = nftContract.mint(address(caller), ipMetadataEmpty.nftMetadataURI); + 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_deprecated() + 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_deprecated() + 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.MakeDerivative({ + 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 }) + }); + } }