From 47ec5521c21e7f766142a4fcb1906ec64d78b76d Mon Sep 17 00:00:00 2001 From: Sebastian Liu Date: Tue, 11 Feb 2025 14:50:32 -0800 Subject: [PATCH 1/3] fix(rt-distri): prevent disabling license terms when already attached to IP --- .../RoyaltyTokenDistributionWorkflows.sol | 73 +++++++++++-------- script/utils/DeployHelper.sol | 3 +- .../RoyaltyTokenDistributionWorkflows.t.sol | 60 +++++++++++++++ 3 files changed, 105 insertions(+), 31 deletions(-) diff --git a/contracts/workflows/RoyaltyTokenDistributionWorkflows.sol b/contracts/workflows/RoyaltyTokenDistributionWorkflows.sol index f7d3477..9004530 100644 --- a/contracts/workflows/RoyaltyTokenDistributionWorkflows.sol +++ b/contracts/workflows/RoyaltyTokenDistributionWorkflows.sol @@ -45,9 +45,9 @@ contract RoyaltyTokenDistributionWorkflows is /// @custom:oz-upgrades-unsafe-allow state-variable-immutable IRoyaltyModule public immutable ROYALTY_MODULE; - /// @notice The address of the Liquid Absolute Percentage (LAP) Royalty Policy. + /// @notice The address of the Liquid Relative Percentage (LRP) Royalty Policy. /// @custom:oz-upgrades-unsafe-allow state-variable-immutable - address public immutable ROYALTY_POLICY_LAP; + address public immutable ROYALTY_POLICY_LRP; /// @notice The address of the Wrapped IP (WIP) token contract. /// @custom:oz-upgrades-unsafe-allow state-variable-immutable @@ -62,7 +62,7 @@ contract RoyaltyTokenDistributionWorkflows is address licensingModule, address pilTemplate, address royaltyModule, - address royaltyPolicyLAP, + address royaltyPolicyLRP, address wip ) BaseWorkflow( @@ -82,12 +82,12 @@ contract RoyaltyTokenDistributionWorkflows is licensingModule == address(0) || pilTemplate == address(0) || royaltyModule == address(0) || - royaltyPolicyLAP == address(0) || + royaltyPolicyLRP == address(0) || wip == address(0) ) revert Errors.RoyaltyTokenDistributionWorkflows__ZeroAddressParam(); ROYALTY_MODULE = IRoyaltyModule(royaltyModule); - ROYALTY_POLICY_LAP = royaltyPolicyLAP; + ROYALTY_POLICY_LRP = royaltyPolicyLRP; WIP = wip; _disableInitializers(); @@ -324,17 +324,27 @@ contract RoyaltyTokenDistributionWorkflows is /// @return ipRoyaltyVault The address of the deployed royalty vault. function _deployRoyaltyVault(address ipId) internal returns (address ipRoyaltyVault) { if (ROYALTY_MODULE.ipRoyaltyVaults(ipId) == address(0)) { + uint256 licenseTermsId = PIL_TEMPLATE.registerLicenseTerms( + PILFlavors.commercialUse({ mintingFee: 0, currencyToken: WIP, royaltyPolicy: ROYALTY_POLICY_LRP }) + ); + + // check if the license is already attached to the IP + bool hasIpAttachedLicenseTerms = LICENSE_REGISTRY.hasIpAttachedLicenseTerms( + ipId, + address(PIL_TEMPLATE), + licenseTermsId + ); + + // if the license is not already attached to the IP, // attach a temporary commercial license to the IP for the royalty vault deployment - uint256 licenseTermsId = LicensingHelper.registerPILTermsAndAttach({ - ipId: ipId, - pilTemplate: address(PIL_TEMPLATE), - licensingModule: address(LICENSING_MODULE), - terms: PILFlavors.commercialUse({ - mintingFee: 0, - currencyToken: WIP, - royaltyPolicy: ROYALTY_POLICY_LAP - }) - }); + if (!hasIpAttachedLicenseTerms) { + LicensingHelper.attachLicenseTerms( + ipId, + address(LICENSING_MODULE), + address(PIL_TEMPLATE), + licenseTermsId + ); + } uint256[] memory licenseTermsIds = new uint256[](1); licenseTermsIds[0] = licenseTermsId; @@ -351,22 +361,25 @@ contract RoyaltyTokenDistributionWorkflows is maxRevenueShare: 0 }); + // if the license is not intended to be attached to the IP, // set the licensing configuration to disable the temporary license - LICENSING_MODULE.setLicensingConfig({ - ipId: ipId, - licenseTemplate: address(PIL_TEMPLATE), - licenseTermsId: licenseTermsId, - licensingConfig: Licensing.LicensingConfig({ - isSet: true, - mintingFee: 0, - licensingHook: address(0), - hookData: "", - commercialRevShare: 0, - disabled: true, - expectMinimumGroupRewardShare: 0, - expectGroupRewardPool: address(0) - }) - }); + if (!hasIpAttachedLicenseTerms) { + LICENSING_MODULE.setLicensingConfig({ + ipId: ipId, + licenseTemplate: address(PIL_TEMPLATE), + licenseTermsId: licenseTermsId, + licensingConfig: Licensing.LicensingConfig({ + isSet: true, + mintingFee: 0, + licensingHook: address(0), + hookData: "", + commercialRevShare: 0, + disabled: true, + expectMinimumGroupRewardShare: 0, + expectGroupRewardPool: address(0) + }) + }); + } } ipRoyaltyVault = ROYALTY_MODULE.ipRoyaltyVaults(ipId); diff --git a/script/utils/DeployHelper.sol b/script/utils/DeployHelper.sol index 5f3ca16..c53b550 100644 --- a/script/utils/DeployHelper.sol +++ b/script/utils/DeployHelper.sol @@ -434,7 +434,7 @@ contract DeployHelper is licensingModuleAddr, pilTemplateAddr, royaltyModuleAddr, - royaltyPolicyLAPAddr, + royaltyPolicyLRPAddr, wipAddr )); royaltyTokenDistributionWorkflows = RoyaltyTokenDistributionWorkflows( @@ -826,6 +826,7 @@ contract DeployHelper is abi.encodeCall(RoyaltyPolicyLRP.initialize, address(protocolAccessManager)) ) ); + royaltyPolicyLRPAddr = address(royaltyPolicyLRP); require( _getDeployedAddress(type(RoyaltyPolicyLRP).name) == address(royaltyPolicyLRP), "Deploy: Royalty Policy LRP Address Mismatch" diff --git a/test/workflows/RoyaltyTokenDistributionWorkflows.t.sol b/test/workflows/RoyaltyTokenDistributionWorkflows.t.sol index 07b475d..7af167a 100644 --- a/test/workflows/RoyaltyTokenDistributionWorkflows.t.sol +++ b/test/workflows/RoyaltyTokenDistributionWorkflows.t.sol @@ -279,6 +279,66 @@ contract RoyaltyTokenDistributionWorkflowsTest is BaseTest { vm.stopPrank(); } + function test_RoyaltyTokenDistributionWorkflows_deployRoyaltyVault_LicenseShouldNotBeDisabled() public { + uint256 tokenId = mockNft.mint(u.alice); + address expectedIpId = ipAssetRegistry.ipId(block.chainid, address(mockNft), tokenId); + uint256 deadline = block.timestamp + 1000; + + WorkflowStructs.LicenseTermsData[] memory licenseTermsData = new WorkflowStructs.LicenseTermsData[](1); + licenseTermsData[0] = WorkflowStructs.LicenseTermsData({ + terms: PILFlavors.commercialUse({ + mintingFee: 0, + currencyToken: wipAddr, + royaltyPolicy: royaltyPolicyLRPAddr + }), + licensingConfig: Licensing.LicensingConfig({ + isSet: false, + mintingFee: 0, + licensingHook: address(0), + hookData: "", + commercialRevShare: 0, + disabled: false, + expectMinimumGroupRewardShare: 0, + expectGroupRewardPool: address(0) + }) + }); + + (bytes memory signatureMetadataAndAttachAndConfig, , ) = _getSetBatchPermissionSigForPeriphery({ + ipId: expectedIpId, + permissionList: _getMetadataAndAttachTermsAndConfigPermissionList( + expectedIpId, + address(royaltyTokenDistributionWorkflows) + ), + deadline: deadline, + state: bytes32(0), + signerSk: sk.alice + }); + + // register IP, attach PIL terms, and deploy royalty vault + vm.startPrank(u.alice); + (address ipId, uint256[] memory licenseTermsIds, address ipRoyaltyVault) = royaltyTokenDistributionWorkflows + .registerIpAndAttachPILTermsAndDeployRoyaltyVault({ + nftContract: address(mockNft), + tokenId: tokenId, + ipMetadata: ipMetadataDefault, + licenseTermsData: licenseTermsData, + sigMetadataAndAttachAndConfig: WorkflowStructs.SignatureData({ + signer: u.alice, + deadline: deadline, + signature: signatureMetadataAndAttachAndConfig + }) + }); + vm.stopPrank(); + + // check that the license is not disabled + Licensing.LicensingConfig memory licensingConfig = licenseRegistry.getLicensingConfig( + ipId, + address(pilTemplate), + licenseTermsIds[0] + ); + assertEq(licensingConfig.disabled, false); + } + function _setUpTest() private { nftMintingFee = 1 * 10 ** mockToken.decimals(); licenseMintingFee = 1 * 10 ** mockToken.decimals(); From 29cb2b56cf680f4c28cc61224426eb9503a5d8a3 Mon Sep 17 00:00:00 2001 From: Sebastian Liu Date: Tue, 11 Feb 2025 16:13:04 -0800 Subject: [PATCH 2/3] fix: remove unused bc fns --- .../IRoyaltyTokenDistributionWorkflows.sol | 25 ---- .../RoyaltyTokenDistributionWorkflows.sol | 132 ------------------ .../RoyaltyTokenDistributionWorkflows.t.sol | 118 +--------------- 3 files changed, 2 insertions(+), 273 deletions(-) diff --git a/contracts/interfaces/workflows/IRoyaltyTokenDistributionWorkflows.sol b/contracts/interfaces/workflows/IRoyaltyTokenDistributionWorkflows.sol index 49c52e6..18ca1d3 100644 --- a/contracts/interfaces/workflows/IRoyaltyTokenDistributionWorkflows.sol +++ b/contracts/interfaces/workflows/IRoyaltyTokenDistributionWorkflows.sol @@ -89,29 +89,4 @@ interface IRoyaltyTokenDistributionWorkflows { WorkflowStructs.RoyaltyShare[] calldata royaltyShares, WorkflowStructs.SignatureData calldata sigApproveRoyaltyTokens ) external; - - //////////////////////////////////////////////////////////////////////////// - // DEPRECATED, WILL BE REMOVED IN V1.4 // - //////////////////////////////////////////////////////////////////////////// - - /// @notice Mint an NFT and register the IP, attach PIL terms, and distribute royalty tokens. - /// @dev THIS VERSION OF THE FUNCTION IS DEPRECATED, WILL BE REMOVED IN V1.4 - function mintAndRegisterIpAndAttachPILTermsAndDistributeRoyaltyTokens_deprecated( - address spgNftContract, - address recipient, - WorkflowStructs.IPMetadata calldata ipMetadata, - PILTerms[] calldata terms, - WorkflowStructs.RoyaltyShare[] calldata royaltyShares - ) external returns (address ipId, uint256 tokenId, uint256[] memory licenseTermsIds); - - /// @notice Register an IP, attach PIL terms, and deploy a royalty vault. - /// @dev THIS VERSION OF THE FUNCTION IS DEPRECATED, WILL BE REMOVED IN V1.4 - function registerIpAndAttachPILTermsAndDeployRoyaltyVault_deprecated( - 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, address ipRoyaltyVault); } diff --git a/contracts/workflows/RoyaltyTokenDistributionWorkflows.sol b/contracts/workflows/RoyaltyTokenDistributionWorkflows.sol index 9004530..1f0b2c8 100644 --- a/contracts/workflows/RoyaltyTokenDistributionWorkflows.sol +++ b/contracts/workflows/RoyaltyTokenDistributionWorkflows.sol @@ -458,136 +458,4 @@ contract RoyaltyTokenDistributionWorkflows 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, WILL BE REMOVED IN V1.4 // - //////////////////////////////////////////////////////////////////////////// - - /// @notice Mint an NFT and register the IP, attach PIL terms, and distribute royalty tokens. - /// @dev THIS VERSION OF THE FUNCTION IS DEPRECATED, WILL BE REMOVED IN V1.4 - function mintAndRegisterIpAndAttachPILTermsAndDistributeRoyaltyTokens_deprecated( - address spgNftContract, - address recipient, - WorkflowStructs.IPMetadata calldata ipMetadata, - PILTerms[] calldata terms, - WorkflowStructs.RoyaltyShare[] calldata royaltyShares - ) - external - onlyMintAuthorized(spgNftContract) - returns (address ipId, uint256 tokenId, uint256[] memory licenseTermsIds) - { - 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); - - _deployRoyaltyVaultDEPR(ipId, address(PIL_TEMPLATE), licenseTermsIds[0]); - _distributeRoyaltyTokens( - ipId, - royaltyShares, - WorkflowStructs.SignatureData(address(0), 0, "") // no signature required. - ); - - ISPGNFT(spgNftContract).safeTransferFrom(address(this), recipient, tokenId, ""); - } - - /// @notice Register an IP, attach PIL terms, and deploy a royalty vault. - /// @dev THIS VERSION OF THE FUNCTION IS DEPRECATED, WILL BE REMOVED IN V1.4 - function registerIpAndAttachPILTermsAndDeployRoyaltyVault_deprecated( - 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, address ipRoyaltyVault) { - if (msg.sender != sigMetadata.signer) - revert Errors.RoyaltyTokenDistributionWorkflows__CallerNotSigner(msg.sender, sigMetadata.signer); - if (msg.sender != sigAttach.signer) - revert Errors.RoyaltyTokenDistributionWorkflows__CallerNotSigner(msg.sender, sigAttach.signer); - - ipId = IP_ASSET_REGISTRY.register(block.chainid, nftContract, tokenId); - MetadataHelper.setMetadataWithSig( - ipId, - address(CORE_METADATA_MODULE), - address(ACCESS_CONTROLLER), - ipMetadata, - sigMetadata - ); - - PermissionHelper.setTransientPermissionForModule( - ipId, - address(LICENSING_MODULE), - address(ACCESS_CONTROLLER), - ILicensingModule.attachLicenseTerms.selector, - sigAttach - ); - - licenseTermsIds = _registerMultiplePILTermsAndAttach(ipId, terms); - - ipRoyaltyVault = _deployRoyaltyVaultDEPR(ipId, address(PIL_TEMPLATE), licenseTermsIds[0]); - } - - /// @dev Deploys a royalty vault for the IP. - /// @dev THIS FUNCTION IS DEPRECATED, WILL BE REMOVED IN V1.4 - function _deployRoyaltyVaultDEPR( - address ipId, - address licenseTemplate, - uint256 licenseTermsId - ) internal returns (address ipRoyaltyVault) { - // if no royalty vault, mint a license token to trigger the vault deployment - if (ROYALTY_MODULE.ipRoyaltyVaults(ipId) == address(0)) { - address[] memory parentIpIds = new address[](1); - uint256[] memory licenseTermsIds = new uint256[](1); - parentIpIds[0] = ipId; - licenseTermsIds[0] = licenseTermsId; - - LicensingHelper.collectMintFeesAndSetApproval({ - payerAddress: msg.sender, - royaltyModule: address(ROYALTY_MODULE), - licensingModule: address(LICENSING_MODULE), - licenseTemplate: licenseTemplate, - parentIpIds: parentIpIds, - licenseTermsIds: licenseTermsIds - }); - - LICENSING_MODULE.mintLicenseTokens({ - licensorIpId: ipId, - licenseTemplate: licenseTemplate, - licenseTermsId: licenseTermsId, - amount: 1, - receiver: msg.sender, - royaltyContext: "", - maxMintingFee: 0, - maxRevenueShare: 0 - }); - } - - ipRoyaltyVault = ROYALTY_MODULE.ipRoyaltyVaults(ipId); - if (ipRoyaltyVault == address(0)) revert Errors.RoyaltyTokenDistributionWorkflows__RoyaltyVaultNotDeployed(); - } - - /// @notice THIS 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/RoyaltyTokenDistributionWorkflows.t.sol b/test/workflows/RoyaltyTokenDistributionWorkflows.t.sol index 7af167a..b063436 100644 --- a/test/workflows/RoyaltyTokenDistributionWorkflows.t.sol +++ b/test/workflows/RoyaltyTokenDistributionWorkflows.t.sol @@ -29,15 +29,9 @@ contract RoyaltyTokenDistributionWorkflowsTest is BaseTest { WorkflowStructs.RoyaltyShare[] private royaltyShares; WorkflowStructs.MakeDerivative private derivativeData; - /// DEPRECATED, WILL BE REMOVED IN V1.4---------------------------------------------------------------------------- - PILTerms[] private commRemixTerms; - WorkflowStructs.MakeDerivativeDEPR private derivativeDataDEPR; - ///---------------------------------------------------------------------------------------------------------------- - function setUp() public override { super.setUp(); _setUpTest(); - _setUpDEPR(); } function test_RoyaltyTokenDistributionWorkflows_mintAndRegisterIpAndAttachPILTermsAndDistributeRoyaltyTokens() @@ -59,7 +53,7 @@ contract RoyaltyTokenDistributionWorkflowsTest is BaseTest { vm.stopPrank(); assertTrue(ipAssetRegistry.isRegistered(ipId)); - assertEq(tokenId, 3); + assertEq(tokenId, 2); assertEq(spgNftPublic.tokenURI(tokenId), string.concat(testBaseURI, ipMetadataDefault.nftMetadataURI)); assertMetadata(ipId, ipMetadataDefault); _assertAttachedLicenseTerms(ipId, licenseTermsIds); @@ -85,7 +79,7 @@ contract RoyaltyTokenDistributionWorkflowsTest is BaseTest { }); vm.stopPrank(); - assertEq(tokenId, 3); + assertEq(tokenId, 2); assertEq(spgNftPublic.tokenURI(tokenId), string.concat(testBaseURI, ipMetadataDefault.nftMetadataURI)); assertEq(ipAssetRegistry.ipId(block.chainid, address(spgNftPublic), tokenId), ipId); assertMetadata(ipId, ipMetadataDefault); @@ -546,112 +540,4 @@ contract RoyaltyTokenDistributionWorkflowsTest is BaseTest { (uint8 v, bytes32 r, bytes32 s) = vm.sign(signerSk, digest); signature = abi.encodePacked(r, s, v); } - - //////////////////////////////////////////////////////////////////////////// - // DEPRECATED, WILL BE REMOVED IN V1.4 // - //////////////////////////////////////////////////////////////////////////// - - function test_RoyaltyTokenDistributionWorkflows_mintAndRegisterIpAndAttachPILTermsAndDistributeRoyaltyTokens_DEPR() - public - { - vm.startPrank(u.alice); - mockToken.mint(u.alice, nftMintingFee + licenseMintingFee); - mockToken.approve(address(spgNftPublic), nftMintingFee); - mockToken.approve(address(royaltyTokenDistributionWorkflows), licenseMintingFee); - - (address ipId, uint256 tokenId, uint256[] memory licenseTermsIds) = royaltyTokenDistributionWorkflows - .mintAndRegisterIpAndAttachPILTermsAndDistributeRoyaltyTokens_deprecated({ - spgNftContract: address(spgNftPublic), - recipient: u.alice, - ipMetadata: ipMetadataDefault, - terms: commRemixTerms, - royaltyShares: royaltyShares - }); - vm.stopPrank(); - - assertTrue(ipAssetRegistry.isRegistered(ipId)); - assertEq(tokenId, 3); - assertEq(spgNftPublic.tokenURI(tokenId), string.concat(testBaseURI, ipMetadataDefault.nftMetadataURI)); - assertMetadata(ipId, ipMetadataDefault); - assertEq(licenseTermsIds[0], pilTemplate.getLicenseTermsId(commRemixTerms[0])); - (address licenseTemplateAttached, uint256 licenseTermsIdAttached) = licenseRegistry.getAttachedLicenseTerms( - ipId, - 0 - ); - assertEq(licenseTemplateAttached, address(pilTemplate)); - assertEq(licenseTermsIdAttached, pilTemplate.getLicenseTermsId(commRemixTerms[0])); - (licenseTemplateAttached, licenseTermsIdAttached) = licenseRegistry.getAttachedLicenseTerms(ipId, 1); - assertEq(licenseTemplateAttached, address(pilTemplate)); - assertEq(licenseTermsIdAttached, pilTemplate.getLicenseTermsId(commRemixTerms[1])); - _assertRoyaltyTokenDistribution(ipId); - } - - function test_RoyaltyTokenDistributionWorkflows_revert_RoyaltyVaultNotDeployed_DEPR() public { - vm.startPrank(u.alice); - mockToken.mint(u.alice, licenseMintingFee); - mockToken.approve(address(spgNftPublic), licenseMintingFee); - - PILTerms[] memory terms = new PILTerms[](1); - terms[0] = PILFlavors.nonCommercialSocialRemixing(); - vm.expectRevert(Errors.RoyaltyTokenDistributionWorkflows__RoyaltyVaultNotDeployed.selector); - royaltyTokenDistributionWorkflows.mintAndRegisterIpAndAttachPILTermsAndDistributeRoyaltyTokens_deprecated({ - spgNftContract: address(spgNftPublic), - recipient: u.alice, - ipMetadata: ipMetadataDefault, - terms: terms, - royaltyShares: royaltyShares - }); - vm.stopPrank(); - } - - function _setUpDEPR() private { - uint32 testCommRevShare = 5 * 10 ** 6; // 5% - - commRemixTerms.push( - PILFlavors.commercialRemix({ - mintingFee: licenseMintingFee, - commercialRevShare: testCommRevShare, - royaltyPolicy: address(royaltyPolicyLAP), - currencyToken: address(mockToken) - }) - ); - - commRemixTerms.push( - PILFlavors.commercialRemix({ - mintingFee: licenseMintingFee, - commercialRevShare: testCommRevShare, - royaltyPolicy: address(royaltyPolicyLRP), - currencyToken: address(mockToken) - }) - ); - - PILTerms[] memory commRemixTermsParent = new PILTerms[](1); - commRemixTermsParent[0] = PILFlavors.commercialRemix({ - mintingFee: licenseMintingFee, - commercialRevShare: testCommRevShare, - royaltyPolicy: address(royaltyPolicyLRP), - currencyToken: address(mockToken) - }); - - address[] memory ipIdParent = new address[](1); - uint256[] memory licenseTermsIdsParent; - vm.startPrank(u.alice); - mockToken.mint(u.alice, licenseMintingFee); - mockToken.approve(address(spgNftPublic), licenseMintingFee); - (ipIdParent[0], , licenseTermsIdsParent) = licenseAttachmentWorkflows - .mintAndRegisterIpAndAttachPILTerms_deprecated({ - spgNftContract: address(spgNftPublic), - recipient: u.alice, - ipMetadata: ipMetadataDefault, - terms: commRemixTermsParent - }); - vm.stopPrank(); - - derivativeDataDEPR = WorkflowStructs.MakeDerivativeDEPR({ - parentIpIds: ipIdParent, - licenseTemplate: address(pilTemplate), - licenseTermsIds: licenseTermsIdsParent, - royaltyContext: "" - }); - } } From 35fd82af2a919d1a4f1ee0372c9b055c40dc8390 Mon Sep 17 00:00:00 2001 From: Sebastian Liu Date: Wed, 12 Feb 2025 13:38:25 -0800 Subject: [PATCH 3/3] chore: clean up code --- .../IRoyaltyTokenDistributionWorkflows.sol | 25 +++ .../RoyaltyTokenDistributionWorkflows.sol | 185 +++++++++++++++--- .../RoyaltyTokenDistributionWorkflows.t.sol | 118 ++++++++++- 3 files changed, 297 insertions(+), 31 deletions(-) diff --git a/contracts/interfaces/workflows/IRoyaltyTokenDistributionWorkflows.sol b/contracts/interfaces/workflows/IRoyaltyTokenDistributionWorkflows.sol index 18ca1d3..49c52e6 100644 --- a/contracts/interfaces/workflows/IRoyaltyTokenDistributionWorkflows.sol +++ b/contracts/interfaces/workflows/IRoyaltyTokenDistributionWorkflows.sol @@ -89,4 +89,29 @@ interface IRoyaltyTokenDistributionWorkflows { WorkflowStructs.RoyaltyShare[] calldata royaltyShares, WorkflowStructs.SignatureData calldata sigApproveRoyaltyTokens ) external; + + //////////////////////////////////////////////////////////////////////////// + // DEPRECATED, WILL BE REMOVED IN V1.4 // + //////////////////////////////////////////////////////////////////////////// + + /// @notice Mint an NFT and register the IP, attach PIL terms, and distribute royalty tokens. + /// @dev THIS VERSION OF THE FUNCTION IS DEPRECATED, WILL BE REMOVED IN V1.4 + function mintAndRegisterIpAndAttachPILTermsAndDistributeRoyaltyTokens_deprecated( + address spgNftContract, + address recipient, + WorkflowStructs.IPMetadata calldata ipMetadata, + PILTerms[] calldata terms, + WorkflowStructs.RoyaltyShare[] calldata royaltyShares + ) external returns (address ipId, uint256 tokenId, uint256[] memory licenseTermsIds); + + /// @notice Register an IP, attach PIL terms, and deploy a royalty vault. + /// @dev THIS VERSION OF THE FUNCTION IS DEPRECATED, WILL BE REMOVED IN V1.4 + function registerIpAndAttachPILTermsAndDeployRoyaltyVault_deprecated( + 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, address ipRoyaltyVault); } diff --git a/contracts/workflows/RoyaltyTokenDistributionWorkflows.sol b/contracts/workflows/RoyaltyTokenDistributionWorkflows.sol index 1f0b2c8..a6c2a66 100644 --- a/contracts/workflows/RoyaltyTokenDistributionWorkflows.sol +++ b/contracts/workflows/RoyaltyTokenDistributionWorkflows.sol @@ -126,17 +126,7 @@ contract RoyaltyTokenDistributionWorkflows is { if (licenseTermsData.length == 0) revert Errors.RoyaltyTokenDistributionWorkflows__NoLicenseTermsData(); - tokenId = ISPGNFT(spgNftContract).mintByPeriphery({ - to: address(this), - payer: msg.sender, - nftMetadataURI: ipMetadata.nftMetadataURI, - nftMetadataHash: ipMetadata.nftMetadataHash, - allowDuplicates: allowDuplicates - }); - - ipId = IP_ASSET_REGISTRY.register(block.chainid, spgNftContract, tokenId); - MetadataHelper.setMetadata(ipId, address(CORE_METADATA_MODULE), ipMetadata); - + (tokenId, ipId) = _mintAndRegisterIp(spgNftContract, ipMetadata, allowDuplicates); licenseTermsIds = LicensingHelper.registerMultiplePILTermsAndAttachAndSetConfigs({ ipId: ipId, pilTemplate: address(PIL_TEMPLATE), @@ -171,18 +161,7 @@ contract RoyaltyTokenDistributionWorkflows is WorkflowStructs.RoyaltyShare[] calldata royaltyShares, bool allowDuplicates ) external onlyMintAuthorized(spgNftContract) returns (address ipId, uint256 tokenId) { - tokenId = ISPGNFT(spgNftContract).mintByPeriphery({ - to: address(this), - payer: msg.sender, - nftMetadataURI: ipMetadata.nftMetadataURI, - nftMetadataHash: ipMetadata.nftMetadataHash, - allowDuplicates: allowDuplicates - }); - - ipId = IP_ASSET_REGISTRY.register(block.chainid, spgNftContract, tokenId); - - MetadataHelper.setMetadata(ipId, address(CORE_METADATA_MODULE), ipMetadata); - + (tokenId, ipId) = _mintAndRegisterIp(spgNftContract, ipMetadata, allowDuplicates); LicensingHelper.collectMintFeesAndMakeDerivative({ childIpId: ipId, royaltyModule: address(ROYALTY_MODULE), @@ -346,9 +325,6 @@ contract RoyaltyTokenDistributionWorkflows is ); } - uint256[] memory licenseTermsIds = new uint256[](1); - licenseTermsIds[0] = licenseTermsId; - // mint a license token to trigger the royalty vault deployment LICENSING_MODULE.mintLicenseTokens({ licensorIpId: ipId, @@ -427,6 +403,28 @@ contract RoyaltyTokenDistributionWorkflows is } } + /// @dev Mints an NFT and registers the IP. + /// @param spgNftContract The address of the SPG NFT contract. + /// @param ipMetadata The metadata for the IP. + /// @param allowDuplicates Set to true to allow minting an NFT with a duplicate metadata hash. + /// @return tokenId The ID of the minted NFT. + /// @return ipId The ID of the registered IP. + function _mintAndRegisterIp( + address spgNftContract, + WorkflowStructs.IPMetadata calldata ipMetadata, + bool allowDuplicates + ) internal returns (uint256 tokenId, address ipId) { + tokenId = ISPGNFT(spgNftContract).mintByPeriphery({ + to: address(this), + payer: msg.sender, + nftMetadataURI: ipMetadata.nftMetadataURI, + nftMetadataHash: ipMetadata.nftMetadataHash, + allowDuplicates: allowDuplicates + }); + ipId = IP_ASSET_REGISTRY.register(block.chainid, spgNftContract, tokenId); + MetadataHelper.setMetadata(ipId, address(CORE_METADATA_MODULE), ipMetadata); + } + /// @dev Validates the royalty shares. /// @param ipId The ID of the IP. /// @param ipRoyaltyVault The address of the royalty vault. @@ -440,15 +438,12 @@ contract RoyaltyTokenDistributionWorkflows is for (uint256 i; i < royaltyShares.length; i++) { totalPercentages += royaltyShares[i].percentage; } - uint32 ipRoyaltyVaultBalance = uint32(IERC20(ipRoyaltyVault).balanceOf(ipId)); if (totalPercentages > ipRoyaltyVaultBalance) revert Errors.RoyaltyTokenDistributionWorkflows__TotalSharesExceedsIPAccountBalance( totalPercentages, ipRoyaltyVaultBalance ); - - return totalPercentages; } // @@ -458,4 +453,136 @@ contract RoyaltyTokenDistributionWorkflows 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, WILL BE REMOVED IN V1.4 // + //////////////////////////////////////////////////////////////////////////// + + /// @notice Mint an NFT and register the IP, attach PIL terms, and distribute royalty tokens. + /// @dev THIS VERSION OF THE FUNCTION IS DEPRECATED, WILL BE REMOVED IN V1.4 + function mintAndRegisterIpAndAttachPILTermsAndDistributeRoyaltyTokens_deprecated( + address spgNftContract, + address recipient, + WorkflowStructs.IPMetadata calldata ipMetadata, + PILTerms[] calldata terms, + WorkflowStructs.RoyaltyShare[] calldata royaltyShares + ) + external + onlyMintAuthorized(spgNftContract) + returns (address ipId, uint256 tokenId, uint256[] memory licenseTermsIds) + { + 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); + + _deployRoyaltyVaultDEPR(ipId, address(PIL_TEMPLATE), licenseTermsIds[0]); + _distributeRoyaltyTokens( + ipId, + royaltyShares, + WorkflowStructs.SignatureData(address(0), 0, "") // no signature required. + ); + + ISPGNFT(spgNftContract).safeTransferFrom(address(this), recipient, tokenId, ""); + } + + /// @notice Register an IP, attach PIL terms, and deploy a royalty vault. + /// @dev THIS VERSION OF THE FUNCTION IS DEPRECATED, WILL BE REMOVED IN V1.4 + function registerIpAndAttachPILTermsAndDeployRoyaltyVault_deprecated( + 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, address ipRoyaltyVault) { + if (msg.sender != sigMetadata.signer) + revert Errors.RoyaltyTokenDistributionWorkflows__CallerNotSigner(msg.sender, sigMetadata.signer); + if (msg.sender != sigAttach.signer) + revert Errors.RoyaltyTokenDistributionWorkflows__CallerNotSigner(msg.sender, sigAttach.signer); + + ipId = IP_ASSET_REGISTRY.register(block.chainid, nftContract, tokenId); + MetadataHelper.setMetadataWithSig( + ipId, + address(CORE_METADATA_MODULE), + address(ACCESS_CONTROLLER), + ipMetadata, + sigMetadata + ); + + PermissionHelper.setTransientPermissionForModule( + ipId, + address(LICENSING_MODULE), + address(ACCESS_CONTROLLER), + ILicensingModule.attachLicenseTerms.selector, + sigAttach + ); + + licenseTermsIds = _registerMultiplePILTermsAndAttach(ipId, terms); + + ipRoyaltyVault = _deployRoyaltyVaultDEPR(ipId, address(PIL_TEMPLATE), licenseTermsIds[0]); + } + + /// @dev Deploys a royalty vault for the IP. + /// @dev THIS FUNCTION IS DEPRECATED, WILL BE REMOVED IN V1.4 + function _deployRoyaltyVaultDEPR( + address ipId, + address licenseTemplate, + uint256 licenseTermsId + ) internal returns (address ipRoyaltyVault) { + // if no royalty vault, mint a license token to trigger the vault deployment + if (ROYALTY_MODULE.ipRoyaltyVaults(ipId) == address(0)) { + address[] memory parentIpIds = new address[](1); + uint256[] memory licenseTermsIds = new uint256[](1); + parentIpIds[0] = ipId; + licenseTermsIds[0] = licenseTermsId; + + LicensingHelper.collectMintFeesAndSetApproval({ + payerAddress: msg.sender, + royaltyModule: address(ROYALTY_MODULE), + licensingModule: address(LICENSING_MODULE), + licenseTemplate: licenseTemplate, + parentIpIds: parentIpIds, + licenseTermsIds: licenseTermsIds + }); + + LICENSING_MODULE.mintLicenseTokens({ + licensorIpId: ipId, + licenseTemplate: licenseTemplate, + licenseTermsId: licenseTermsId, + amount: 1, + receiver: msg.sender, + royaltyContext: "", + maxMintingFee: 0, + maxRevenueShare: 0 + }); + } + + ipRoyaltyVault = ROYALTY_MODULE.ipRoyaltyVaults(ipId); + if (ipRoyaltyVault == address(0)) revert Errors.RoyaltyTokenDistributionWorkflows__RoyaltyVaultNotDeployed(); + } + + /// @notice THIS 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/RoyaltyTokenDistributionWorkflows.t.sol b/test/workflows/RoyaltyTokenDistributionWorkflows.t.sol index b063436..7af167a 100644 --- a/test/workflows/RoyaltyTokenDistributionWorkflows.t.sol +++ b/test/workflows/RoyaltyTokenDistributionWorkflows.t.sol @@ -29,9 +29,15 @@ contract RoyaltyTokenDistributionWorkflowsTest is BaseTest { WorkflowStructs.RoyaltyShare[] private royaltyShares; WorkflowStructs.MakeDerivative private derivativeData; + /// DEPRECATED, WILL BE REMOVED IN V1.4---------------------------------------------------------------------------- + PILTerms[] private commRemixTerms; + WorkflowStructs.MakeDerivativeDEPR private derivativeDataDEPR; + ///---------------------------------------------------------------------------------------------------------------- + function setUp() public override { super.setUp(); _setUpTest(); + _setUpDEPR(); } function test_RoyaltyTokenDistributionWorkflows_mintAndRegisterIpAndAttachPILTermsAndDistributeRoyaltyTokens() @@ -53,7 +59,7 @@ contract RoyaltyTokenDistributionWorkflowsTest is BaseTest { vm.stopPrank(); assertTrue(ipAssetRegistry.isRegistered(ipId)); - assertEq(tokenId, 2); + assertEq(tokenId, 3); assertEq(spgNftPublic.tokenURI(tokenId), string.concat(testBaseURI, ipMetadataDefault.nftMetadataURI)); assertMetadata(ipId, ipMetadataDefault); _assertAttachedLicenseTerms(ipId, licenseTermsIds); @@ -79,7 +85,7 @@ contract RoyaltyTokenDistributionWorkflowsTest is BaseTest { }); vm.stopPrank(); - assertEq(tokenId, 2); + assertEq(tokenId, 3); assertEq(spgNftPublic.tokenURI(tokenId), string.concat(testBaseURI, ipMetadataDefault.nftMetadataURI)); assertEq(ipAssetRegistry.ipId(block.chainid, address(spgNftPublic), tokenId), ipId); assertMetadata(ipId, ipMetadataDefault); @@ -540,4 +546,112 @@ contract RoyaltyTokenDistributionWorkflowsTest is BaseTest { (uint8 v, bytes32 r, bytes32 s) = vm.sign(signerSk, digest); signature = abi.encodePacked(r, s, v); } + + //////////////////////////////////////////////////////////////////////////// + // DEPRECATED, WILL BE REMOVED IN V1.4 // + //////////////////////////////////////////////////////////////////////////// + + function test_RoyaltyTokenDistributionWorkflows_mintAndRegisterIpAndAttachPILTermsAndDistributeRoyaltyTokens_DEPR() + public + { + vm.startPrank(u.alice); + mockToken.mint(u.alice, nftMintingFee + licenseMintingFee); + mockToken.approve(address(spgNftPublic), nftMintingFee); + mockToken.approve(address(royaltyTokenDistributionWorkflows), licenseMintingFee); + + (address ipId, uint256 tokenId, uint256[] memory licenseTermsIds) = royaltyTokenDistributionWorkflows + .mintAndRegisterIpAndAttachPILTermsAndDistributeRoyaltyTokens_deprecated({ + spgNftContract: address(spgNftPublic), + recipient: u.alice, + ipMetadata: ipMetadataDefault, + terms: commRemixTerms, + royaltyShares: royaltyShares + }); + vm.stopPrank(); + + assertTrue(ipAssetRegistry.isRegistered(ipId)); + assertEq(tokenId, 3); + assertEq(spgNftPublic.tokenURI(tokenId), string.concat(testBaseURI, ipMetadataDefault.nftMetadataURI)); + assertMetadata(ipId, ipMetadataDefault); + assertEq(licenseTermsIds[0], pilTemplate.getLicenseTermsId(commRemixTerms[0])); + (address licenseTemplateAttached, uint256 licenseTermsIdAttached) = licenseRegistry.getAttachedLicenseTerms( + ipId, + 0 + ); + assertEq(licenseTemplateAttached, address(pilTemplate)); + assertEq(licenseTermsIdAttached, pilTemplate.getLicenseTermsId(commRemixTerms[0])); + (licenseTemplateAttached, licenseTermsIdAttached) = licenseRegistry.getAttachedLicenseTerms(ipId, 1); + assertEq(licenseTemplateAttached, address(pilTemplate)); + assertEq(licenseTermsIdAttached, pilTemplate.getLicenseTermsId(commRemixTerms[1])); + _assertRoyaltyTokenDistribution(ipId); + } + + function test_RoyaltyTokenDistributionWorkflows_revert_RoyaltyVaultNotDeployed_DEPR() public { + vm.startPrank(u.alice); + mockToken.mint(u.alice, licenseMintingFee); + mockToken.approve(address(spgNftPublic), licenseMintingFee); + + PILTerms[] memory terms = new PILTerms[](1); + terms[0] = PILFlavors.nonCommercialSocialRemixing(); + vm.expectRevert(Errors.RoyaltyTokenDistributionWorkflows__RoyaltyVaultNotDeployed.selector); + royaltyTokenDistributionWorkflows.mintAndRegisterIpAndAttachPILTermsAndDistributeRoyaltyTokens_deprecated({ + spgNftContract: address(spgNftPublic), + recipient: u.alice, + ipMetadata: ipMetadataDefault, + terms: terms, + royaltyShares: royaltyShares + }); + vm.stopPrank(); + } + + function _setUpDEPR() private { + uint32 testCommRevShare = 5 * 10 ** 6; // 5% + + commRemixTerms.push( + PILFlavors.commercialRemix({ + mintingFee: licenseMintingFee, + commercialRevShare: testCommRevShare, + royaltyPolicy: address(royaltyPolicyLAP), + currencyToken: address(mockToken) + }) + ); + + commRemixTerms.push( + PILFlavors.commercialRemix({ + mintingFee: licenseMintingFee, + commercialRevShare: testCommRevShare, + royaltyPolicy: address(royaltyPolicyLRP), + currencyToken: address(mockToken) + }) + ); + + PILTerms[] memory commRemixTermsParent = new PILTerms[](1); + commRemixTermsParent[0] = PILFlavors.commercialRemix({ + mintingFee: licenseMintingFee, + commercialRevShare: testCommRevShare, + royaltyPolicy: address(royaltyPolicyLRP), + currencyToken: address(mockToken) + }); + + address[] memory ipIdParent = new address[](1); + uint256[] memory licenseTermsIdsParent; + vm.startPrank(u.alice); + mockToken.mint(u.alice, licenseMintingFee); + mockToken.approve(address(spgNftPublic), licenseMintingFee); + (ipIdParent[0], , licenseTermsIdsParent) = licenseAttachmentWorkflows + .mintAndRegisterIpAndAttachPILTerms_deprecated({ + spgNftContract: address(spgNftPublic), + recipient: u.alice, + ipMetadata: ipMetadataDefault, + terms: commRemixTermsParent + }); + vm.stopPrank(); + + derivativeDataDEPR = WorkflowStructs.MakeDerivativeDEPR({ + parentIpIds: ipIdParent, + licenseTemplate: address(pilTemplate), + licenseTermsIds: licenseTermsIdsParent, + royaltyContext: "" + }); + } }