Skip to content

Commit

Permalink
feat: support multi license attachment in RT distribution
Browse files Browse the repository at this point in the history
  • Loading branch information
sebsadface committed Dec 13, 2024
1 parent b2a284e commit caad209
Show file tree
Hide file tree
Showing 4 changed files with 59 additions and 37 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,18 @@ interface IRoyaltyTokenDistributionWorkflows {
/// @param spgNftContract The address of the SPG NFT contract.
/// @param recipient The address to receive the NFT.
/// @param ipMetadata The metadata for the IP.
/// @param terms The PIL terms to attach to the IP (must be a commercial license).
/// @param terms The PIL terms to attach to the IP (the license terms at index 0 must be a commercial license).
/// @param royaltyShares Authors of the IP and their shares of the royalty tokens, see {WorkflowStructs.RoyaltyShare}.
/// @return ipId The ID of the registered IP.
/// @return tokenId The ID of the minted NFT.
/// @return licenseTermsId The ID of the attached PIL terms.
/// @return licenseTermsIds The IDs of the attached PIL terms.
function mintAndRegisterIpAndAttachPILTermsAndDistributeRoyaltyTokens(
address spgNftContract,
address recipient,
WorkflowStructs.IPMetadata calldata ipMetadata,
PILTerms calldata terms,
PILTerms[] calldata terms,
WorkflowStructs.RoyaltyShare[] calldata royaltyShares
) external returns (address ipId, uint256 tokenId, uint256 licenseTermsId);
) external returns (address ipId, uint256 tokenId, uint256[] memory licenseTermsIds);

/// @notice Mint an NFT and register the IP, make a derivative, and distribute royalty tokens.
/// @dev In order to successfully distribute royalty tokens, the license terms attached to the IP must be
Expand All @@ -51,20 +51,20 @@ interface IRoyaltyTokenDistributionWorkflows {
/// @param nftContract The address of the NFT contract.
/// @param tokenId The ID of the NFT.
/// @param ipMetadata The metadata for the IP.
/// @param terms The PIL terms to attach to the IP (must be a commercial license).
/// @param terms The PIL terms to attach to the IP (the license terms at index 0 must be a commercial license).
/// @param sigMetadata The signature data for the IP metadata.
/// @param sigAttach The signature data for attaching the PIL terms.
/// @return ipId The ID of the registered IP.
/// @return licenseTermsId The ID of the attached PIL terms.
/// @return licenseTermsIds The IDs of the attached PIL terms.
/// @return ipRoyaltyVault The address of the deployed royalty vault.
function registerIpAndAttachPILTermsAndDeployRoyaltyVault(
address nftContract,
uint256 tokenId,
WorkflowStructs.IPMetadata calldata ipMetadata,
PILTerms calldata terms,
PILTerms[] calldata terms,
WorkflowStructs.SignatureData calldata sigMetadata,
WorkflowStructs.SignatureData calldata sigAttach
) external returns (address ipId, uint256 licenseTermsId, address ipRoyaltyVault);
) external returns (address ipId, uint256[] memory licenseTermsIds, address ipRoyaltyVault);

/// @notice Register an IP, make a derivative, and deploy a royalty vault.
/// @dev In order to successfully deploy a royalty vault, the license terms attached to the IP must be
Expand Down
4 changes: 2 additions & 2 deletions contracts/lib/LicensingHelper.sol
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ library LicensingHelper {
address pilTemplate,
address licensingModule,
address licenseRegistry,
PILTerms[] calldata terms
PILTerms[] memory terms
) internal returns (uint256[] memory licenseTermsIds) {
licenseTermsIds = new uint256[](terms.length);
for (uint256 i = 0; i < terms.length; i++) {
Expand All @@ -51,7 +51,7 @@ library LicensingHelper {
address pilTemplate,
address licensingModule,
address licenseRegistry,
PILTerms calldata terms
PILTerms memory terms
) internal returns (uint256 licenseTermsId) {
licenseTermsId = IPILicenseTemplate(pilTemplate).registerLicenseTerms(terms);
attachLicenseTerms(ipId, licensingModule, licenseRegistry, pilTemplate, licenseTermsId);
Expand Down
29 changes: 16 additions & 13 deletions contracts/workflows/RoyaltyTokenDistributionWorkflows.sol
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,6 @@ contract RoyaltyTokenDistributionWorkflows is
) revert Errors.RoyaltyTokenDistributionWorkflows__ZeroAddressParam();

ROYALTY_MODULE = IRoyaltyModule(royaltyModule);

_disableInitializers();
}

Expand All @@ -108,18 +107,22 @@ contract RoyaltyTokenDistributionWorkflows is
/// @param spgNftContract The address of the SPG NFT contract.
/// @param recipient The address to receive the NFT.
/// @param ipMetadata The metadata for the IP.
/// @param terms The PIL terms to attach to the IP (must be a commercial license).
/// @param terms The PIL terms to attach to the IP (the license terms at index 0must be a commercial license).
/// @param royaltyShares Authors of the IP and their shares of the royalty tokens, see {WorkflowStructs.RoyaltyShare}.
/// @return ipId The ID of the registered IP.
/// @return tokenId The ID of the minted NFT.
/// @return licenseTermsId The ID of the attached PIL terms.
/// @return licenseTermsIds The IDs of the attached PIL terms.
function mintAndRegisterIpAndAttachPILTermsAndDistributeRoyaltyTokens(
address spgNftContract,
address recipient,
WorkflowStructs.IPMetadata calldata ipMetadata,
PILTerms calldata terms,
PILTerms[] calldata terms,
WorkflowStructs.RoyaltyShare[] calldata royaltyShares
) external onlyMintAuthorized(spgNftContract) returns (address ipId, uint256 tokenId, uint256 licenseTermsId) {
)
external
onlyMintAuthorized(spgNftContract)
returns (address ipId, uint256 tokenId, uint256[] memory licenseTermsIds)
{
tokenId = ISPGNFT(spgNftContract).mintByPeriphery({
to: address(this),
payer: msg.sender,
Expand All @@ -128,7 +131,7 @@ contract RoyaltyTokenDistributionWorkflows is
ipId = IP_ASSET_REGISTRY.register(block.chainid, spgNftContract, tokenId);
MetadataHelper.setMetadata(ipId, address(CORE_METADATA_MODULE), ipMetadata);

licenseTermsId = LicensingHelper.registerPILTermsAndAttach(
licenseTermsIds = LicensingHelper.registerPILTermsAndAttach(
ipId,
address(PIL_TEMPLATE),
address(LICENSING_MODULE),
Expand All @@ -138,7 +141,7 @@ contract RoyaltyTokenDistributionWorkflows is

_distributeRoyaltyTokens(
ipId,
_deployRoyaltyVault(ipId, address(PIL_TEMPLATE), licenseTermsId),
_deployRoyaltyVault(ipId, address(PIL_TEMPLATE), licenseTermsIds[0]),
royaltyShares,
WorkflowStructs.SignatureData(address(0), 0, "") // no signature required.
);
Expand Down Expand Up @@ -205,20 +208,20 @@ contract RoyaltyTokenDistributionWorkflows is
/// @param nftContract The address of the NFT contract.
/// @param tokenId The ID of the NFT.
/// @param ipMetadata The metadata for the IP.
/// @param terms The PIL terms to attach to the IP (must be a commercial license).
/// @param terms The PIL terms to attach to the IP (the license terms at index 0 must be a commercial license).
/// @param sigMetadata The signature data for the IP metadata.
/// @param sigAttach The signature data for attaching the PIL terms.
/// @return ipId The ID of the registered IP.
/// @return licenseTermsId The ID of the attached PIL terms.
/// @return licenseTermsIds The IDs of the attached PIL terms.
/// @return ipRoyaltyVault The address of the deployed royalty vault.
function registerIpAndAttachPILTermsAndDeployRoyaltyVault(
address nftContract,
uint256 tokenId,
WorkflowStructs.IPMetadata calldata ipMetadata,
PILTerms calldata terms,
PILTerms[] calldata terms,
WorkflowStructs.SignatureData calldata sigMetadata,
WorkflowStructs.SignatureData calldata sigAttach
) external returns (address ipId, uint256 licenseTermsId, address ipRoyaltyVault) {
) external returns (address ipId, uint256[] memory licenseTermsIds, address ipRoyaltyVault) {
ipId = IP_ASSET_REGISTRY.register(block.chainid, nftContract, tokenId);
MetadataHelper.setMetadataWithSig(
ipId,
Expand All @@ -236,15 +239,15 @@ contract RoyaltyTokenDistributionWorkflows is
sigAttach
);

licenseTermsId = LicensingHelper.registerPILTermsAndAttach(
licenseTermsIds = LicensingHelper.registerPILTermsAndAttach(
ipId,
address(PIL_TEMPLATE),
address(LICENSING_MODULE),
address(LICENSE_REGISTRY),
terms
);

ipRoyaltyVault = _deployRoyaltyVault(ipId, address(PIL_TEMPLATE), licenseTermsId);
ipRoyaltyVault = _deployRoyaltyVault(ipId, address(PIL_TEMPLATE), licenseTermsIds[0]);
}

/// @notice Register an IP, make a derivative, and deploy a royalty vault.
Expand Down
47 changes: 33 additions & 14 deletions test/workflows/RoyaltyTokenDistributionWorkflows.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ contract RoyaltyTokenDistributionWorkflowsTest is BaseTest {
uint256 private nftMintingFee;
uint256 private licenseMintingFee;

PILTerms private commRemixTerms;
PILTerms[] private commRemixTerms;

WorkflowStructs.RoyaltyShare[] private royaltyShares;
WorkflowStructs.MakeDerivative private derivativeData;
Expand All @@ -45,7 +45,7 @@ contract RoyaltyTokenDistributionWorkflowsTest is BaseTest {
mockToken.approve(address(spgNftPublic), nftMintingFee);
mockToken.approve(address(royaltyTokenDistributionWorkflows), licenseMintingFee);

(address ipId, uint256 tokenId, uint256 licenseTermsId) = royaltyTokenDistributionWorkflows
(address ipId, uint256 tokenId, uint256[] memory licenseTermsIds) = royaltyTokenDistributionWorkflows
.mintAndRegisterIpAndAttachPILTermsAndDistributeRoyaltyTokens({
spgNftContract: address(spgNftPublic),
recipient: u.alice,
Expand All @@ -59,13 +59,16 @@ contract RoyaltyTokenDistributionWorkflowsTest is BaseTest {
assertEq(tokenId, 2);
assertEq(spgNftPublic.tokenURI(tokenId), string.concat(testBaseURI, ipMetadataDefault.nftMetadataURI));
assertMetadata(ipId, ipMetadataDefault);
assertEq(licenseTermsId, pilTemplate.getLicenseTermsId(commRemixTerms));
assertEq(licenseTermsIds[0], pilTemplate.getLicenseTermsId(commRemixTerms[0]));
(address licenseTemplateAttached, uint256 licenseTermsIdAttached) = licenseRegistry.getAttachedLicenseTerms(
ipId,
0
);
assertEq(licenseTemplateAttached, address(pilTemplate));
assertEq(licenseTermsIdAttached, pilTemplate.getLicenseTermsId(commRemixTerms));
assertEq(licenseTermsIdAttached, pilTemplate.getLicenseTermsId(commRemixTerms[0]));
(licenseTemplateAttached, licenseTermsIdAttached) = licenseRegistry.getAttachedLicenseTerms(ipId, 1);
assertEq(licenseTemplateAttached, address(pilTemplate));
assertEq(licenseTermsIdAttached, pilTemplate.getLicenseTermsId(commRemixTerms[1]));
_assertRoyaltyTokenDistribution(ipId);
}

Expand Down Expand Up @@ -156,7 +159,7 @@ contract RoyaltyTokenDistributionWorkflowsTest is BaseTest {
vm.startPrank(u.alice);
mockToken.mint(u.alice, licenseMintingFee);
mockToken.approve(address(royaltyTokenDistributionWorkflows), licenseMintingFee);
(address ipId, uint256 licenseTermsId, address ipRoyaltyVault) = royaltyTokenDistributionWorkflows
(address ipId, uint256[] memory licenseTermsIds, address ipRoyaltyVault) = royaltyTokenDistributionWorkflows
.registerIpAndAttachPILTermsAndDeployRoyaltyVault({
nftContract: address(mockNft),
tokenId: tokenId,
Expand Down Expand Up @@ -199,13 +202,16 @@ contract RoyaltyTokenDistributionWorkflowsTest is BaseTest {

assertTrue(ipAssetRegistry.isRegistered(ipId));
assertMetadata(ipId, ipMetadataDefault);
assertEq(licenseTermsId, pilTemplate.getLicenseTermsId(commRemixTerms));
assertEq(licenseTermsIds[0], pilTemplate.getLicenseTermsId(commRemixTerms[0]));
(address licenseTemplateAttached, uint256 licenseTermsIdAttached) = licenseRegistry.getAttachedLicenseTerms(
ipId,
0
);
assertEq(licenseTemplateAttached, address(pilTemplate));
assertEq(licenseTermsIdAttached, pilTemplate.getLicenseTermsId(commRemixTerms));
assertEq(licenseTermsIdAttached, pilTemplate.getLicenseTermsId(commRemixTerms[0]));
(licenseTemplateAttached, licenseTermsIdAttached) = licenseRegistry.getAttachedLicenseTerms(ipId, 1);
assertEq(licenseTemplateAttached, address(pilTemplate));
assertEq(licenseTermsIdAttached, pilTemplate.getLicenseTermsId(commRemixTerms[1]));
_assertRoyaltyTokenDistribution(ipId);
}

Expand Down Expand Up @@ -347,12 +353,14 @@ contract RoyaltyTokenDistributionWorkflowsTest is BaseTest {
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({
spgNftContract: address(spgNftPublic),
recipient: u.alice,
ipMetadata: ipMetadataDefault,
terms: PILFlavors.nonCommercialSocialRemixing(),
terms: terms,
royaltyShares: royaltyShares
});
vm.stopPrank();
Expand All @@ -364,12 +372,23 @@ contract RoyaltyTokenDistributionWorkflowsTest is BaseTest {

uint32 testCommRevShare = 5 * 10 ** 6; // 5%

commRemixTerms = PILFlavors.commercialRemix({
mintingFee: licenseMintingFee,
commercialRevShare: testCommRevShare,
royaltyPolicy: address(royaltyPolicyLAP),
currencyToken: address(mockToken)
});
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({
Expand Down

0 comments on commit caad209

Please sign in to comment.