Skip to content

Commit

Permalink
feat(licensing): support custom license templates and decouple terms …
Browse files Browse the repository at this point in the history
…registration from attachment (#123)

* feat(licensing): support custom license template attachment

* refactor(test): update tests

* docs(licensing): update `LicenseAttachmentWorkflows` docs

* chore: linting & unused import
  • Loading branch information
sebsadface authored Nov 21, 2024
1 parent a6fbf2c commit 68f4a4a
Show file tree
Hide file tree
Showing 13 changed files with 301 additions and 339 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ function mintAndRegisterIpAndAttachPILTerms(
address nftContract,
address recipient,
IPMetadata calldata ipMetadata,
PILTerms calldata terms
PILTerms calldata terms,
bool allowDuplicates
) external onlyCallerWithMinterRole(nftContract) returns (address ipId, uint256 tokenId, uint256 licenseTermsId)
```

Expand Down
58 changes: 35 additions & 23 deletions contracts/interfaces/workflows/ILicenseAttachmentWorkflows.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,25 +8,13 @@ import { WorkflowStructs } from "../../lib/WorkflowStructs.sol";
/// @title License Attachment Workflows Interface
/// @notice Interface for IP license attachment workflows.
interface ILicenseAttachmentWorkflows {
/// @notice Register Programmable IP License Terms (if unregistered) and attach it to IP.
/// @param ipId The ID of the IP.
/// @param terms The PIL terms to be registered.
/// @param sigAttach Signature data for attachLicenseTerms to the IP via the Licensing Module.
/// @return licenseTermsId The ID of the newly registered PIL terms.
function registerPILTermsAndAttach(
address ipId,
PILTerms calldata terms,
WorkflowStructs.SignatureData calldata sigAttach
) external returns (uint256 licenseTermsId);

/// @notice Mint an NFT from a SPGNFT collection, register it with metadata as an IP,
/// register Programmable IPLicense
/// Terms (if unregistered), and attach it to the registered IP.
/// @notice Mint an NFT from a SPGNFT collection, register it as an IP, attach provided IP metadata,
/// register Programmable IPLicense Terms (if unregistered), and attach it to the newly registered IP.
/// @dev Requires caller to have the minter role or the SPG NFT to allow public minting.
/// @param spgNftContract The address of the SPGNFT collection.
/// @param recipient The address of the recipient of the minted NFT.
/// @param ipMetadata OPTIONAL. The desired metadata for the newly minted NFT and registered IP.
/// @param terms The PIL terms to be registered.
/// @param terms The PIL terms to be registered and attached to the newly registered IP.
/// @param allowDuplicates Set to true to allow minting an NFT with a duplicate metadata hash.
/// @return ipId The ID of the newly registered IP.
/// @return tokenId The ID of the newly minted NFT.
Expand All @@ -39,23 +27,47 @@ interface ILicenseAttachmentWorkflows {
bool allowDuplicates
) external returns (address ipId, uint256 tokenId, uint256 licenseTermsId);

/// @notice Register a given NFT as an IP and attach Programmable IP License Terms.
/// @dev Because IP Account is created in this function, we need to set the permission via signature to allow this
/// contract to attach PIL Terms to the newly created IP Account in the same function.
/// @notice Mint an NFT from a SPGNFT collection, register as an IP, attach provided IP metadata,
/// and attach the provided license terms to the newly registered IP.
/// @dev Requires caller to have the minter role or the SPG NFT to allow public minting.
/// @param spgNftContract The address of the SPGNFT collection.
/// @param recipient The address of the recipient of the newly minted NFT.
/// @param ipMetadata OPTIONAL. The desired metadata for the newly minted NFT and registered IP.
/// @param licenseTemplate The address of the license template used of the license terms to be attached.
/// @param licenseTermsId The ID of the license terms to attach. Must be a valid ID that exists
/// in the specified license template.
/// @param allowDuplicates Set to true to allow minting an NFT with a duplicate metadata hash.
/// @return ipId The ID of the newly registered IP.
/// @return tokenId The ID of the newly minted NFT.
function mintAndRegisterIpAndAttachLicenseTerms(
address spgNftContract,
address recipient,
WorkflowStructs.IPMetadata calldata ipMetadata,
address licenseTemplate,
uint256 licenseTermsId,
bool allowDuplicates
) external returns (address ipId, uint256 tokenId);

/// @notice Register a given NFT as an IP, attach provided IP metadata, and attach the provided license terms to the
/// newly registered IP.
/// @dev Since IP Account is created in this function, we need signatures to allow this contract to set metadata
/// and attach PIL Terms to the newly created IP Account on behalf of the owner.
/// @param nftContract The address of the NFT collection.
/// @param tokenId The ID of the NFT.
/// @param ipMetadata OPTIONAL. The desired metadata for the newly registered IP.
/// @param terms The PIL terms to be registered.
/// @param licenseTemplate The address of the license template used of the license terms to be attached.
/// @param licenseTermsId The ID of the license terms to attach. Must be a valid ID that exists
/// in the specified license template.
/// @param sigMetadata OPTIONAL. Signature data for setAll (metadata) for the IP via the Core Metadata Module.
/// @param sigAttach Signature data for attachLicenseTerms to the IP via the Licensing Module.
/// @return ipId The ID of the newly registered IP.
/// @return licenseTermsId The ID of the newly registered PIL terms.
function registerIpAndAttachPILTerms(
function registerIpAndAttachLicenseTerms(
address nftContract,
uint256 tokenId,
WorkflowStructs.IPMetadata calldata ipMetadata,
PILTerms calldata terms,
address licenseTemplate,
uint256 licenseTermsId,
WorkflowStructs.SignatureData calldata sigMetadata,
WorkflowStructs.SignatureData calldata sigAttach
) external returns (address ipId, uint256 licenseTermsId);
) external returns (address ipId);
}
6 changes: 3 additions & 3 deletions contracts/lib/LicensingHelper.sol
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ library LicensingHelper {
address ipId,
address pilTemplate,
address licensingModule,
PILTerms calldata terms
PILTerms memory terms
) internal returns (uint256 licenseTermsId) {
licenseTermsId = IPILicenseTemplate(pilTemplate).registerLicenseTerms(terms);
attachLicenseTerms(ipId, licensingModule, pilTemplate, licenseTermsId);
Expand Down Expand Up @@ -62,7 +62,7 @@ library LicensingHelper {
address pilTemplate,
address licensingModule,
PILTerms calldata terms,
WorkflowStructs.SignatureData calldata sigAttach
WorkflowStructs.SignatureData memory sigAttach
) internal returns (uint256 licenseTermsId) {
licenseTermsId = IPILicenseTemplate(pilTemplate).registerLicenseTerms(terms);
attachLicenseTermsWithSig(ipId, licensingModule, pilTemplate, licenseTermsId, sigAttach);
Expand All @@ -79,7 +79,7 @@ library LicensingHelper {
address licensingModule,
address licenseTemplate,
uint256 licenseTermsId,
WorkflowStructs.SignatureData calldata sigAttach
WorkflowStructs.SignatureData memory sigAttach
) internal {
try
IIPAccount(payable(ipId)).executeWithSig({
Expand Down
6 changes: 4 additions & 2 deletions contracts/workflows/DerivativeWorkflows.sol
Original file line number Diff line number Diff line change
Expand Up @@ -314,7 +314,8 @@ contract DerivativeWorkflows is
/// @param licenseTemplate The address of the license template.
/// @param parentIpIds The IDs of all the parent IPs.
/// @param licenseTermsIds The IDs of the license terms for each corresponding parent IP.
/// @param sigMintingFee OPTIONAL. Signature data for approving license minting fee for the IP via the currency token.
/// @param sigMintingFee OPTIONAL. Signature data for approving license minting fee for the IP
/// via the currency token.
function _collectMintFeesAndSetApproval(
address ipId,
address ipOwnerAddress,
Expand Down Expand Up @@ -344,7 +345,8 @@ contract DerivativeWorkflows is
IERC20(mintFeeCurrencyToken).transferFrom(payerAddress, address(this), totalMintFee);
IERC20(mintFeeCurrencyToken).forceApprove(address(ROYALTY_MODULE), totalMintFee);
} else {
// if owner is not this contract, we need to transfer the minting fee to IP account and use `executeWithSig` to approve royalty module
// if owner is not this contract, we need to transfer the minting fee to IP account and
// use `executeWithSig` to approve royalty module
IERC20(mintFeeCurrencyToken).transferFrom(payerAddress, address(ipId), totalMintFee);
IIPAccount(payable(ipId)).executeWithSig({
to: address(mintFeeCurrencyToken),
Expand Down
82 changes: 33 additions & 49 deletions contracts/workflows/LicenseAttachmentWorkflows.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@ import { AccessManagedUpgradeable } from "@openzeppelin/contracts-upgradeable/ac
import { ERC165Checker } from "@openzeppelin/contracts/utils/introspection/ERC165Checker.sol";
import { MulticallUpgradeable } from "@openzeppelin/contracts-upgradeable/utils/MulticallUpgradeable.sol";
import { UUPSUpgradeable } from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.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";
Expand Down Expand Up @@ -88,36 +86,7 @@ contract LicenseAttachmentWorkflows is
$.nftContractBeacon = newNftContractBeacon;
}

/// @notice Register Programmable IP License Terms (if unregistered) and attach it to IP.
/// @param ipId The ID of the IP.
/// @param terms The PIL terms to be registered.
/// @param sigAttach Signature data for attachLicenseTerms to the IP via the Licensing Module.
/// @return licenseTermsId The ID of the newly registered PIL terms.
function registerPILTermsAndAttach(
address ipId,
PILTerms calldata terms,
WorkflowStructs.SignatureData calldata sigAttach
) external returns (uint256 licenseTermsId) {
licenseTermsId = LicensingHelper.registerPILTermsAndAttachWithSig(
ipId,
address(PIL_TEMPLATE),
address(LICENSING_MODULE),
terms,
sigAttach
);
}

/// @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.
/// @dev Requires caller to have the minter role or the SPG NFT to allow public minting.
/// @param spgNftContract The address of the SPGNFT collection.
/// @param recipient The address of the recipient of the minted NFT.
/// @param ipMetadata OPTIONAL. The desired metadata for the newly minted NFT and registered IP.
/// @param terms The PIL terms to be registered.
/// @param allowDuplicates Set to true to allow minting an NFT with a duplicate metadata hash.
/// @return ipId The ID of the newly registered IP.
/// @return tokenId The ID of the newly minted NFT.
/// @return licenseTermsId The ID of the newly registered PIL terms.
/// TODO: add comments
function mintAndRegisterIpAndAttachPILTerms(
address spgNftContract,
address recipient,
Expand Down Expand Up @@ -146,33 +115,48 @@ contract LicenseAttachmentWorkflows is
ISPGNFT(spgNftContract).safeTransferFrom(address(this), recipient, tokenId, "");
}

/// @notice Register a given NFT as an IP and attach Programmable IP License Terms.
/// @dev Because IP Account is created in this function, we need to set the permission via signature to allow this
/// contract to attach PIL Terms to the newly created IP Account in the same function.
/// @param nftContract The address of the NFT collection.
/// @param tokenId The ID of the NFT.
/// @param ipMetadata OPTIONAL. The desired metadata for the newly registered IP.
/// @param terms The PIL terms to be registered.
/// @param sigMetadata OPTIONAL. Signature data for setAll (metadata) for the IP via the Core Metadata Module.
/// @param sigAttach Signature data for attachLicenseTerms to the IP via the Licensing Module.
/// @return ipId The ID of the newly registered IP.
/// @return licenseTermsId The ID of the newly registered PIL terms.
function registerIpAndAttachPILTerms(
// TODO: add comments
function mintAndRegisterIpAndAttachLicenseTerms(
address spgNftContract,
address recipient,
WorkflowStructs.IPMetadata calldata ipMetadata,
address licenseTemplate,
uint256 licenseTermsId,
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);

LicensingHelper.attachLicenseTerms(ipId, address(LICENSING_MODULE), licenseTemplate, licenseTermsId);

ISPGNFT(spgNftContract).safeTransferFrom(address(this), recipient, tokenId, "");
}

function registerIpAndAttachLicenseTerms(
address nftContract,
uint256 tokenId,
WorkflowStructs.IPMetadata calldata ipMetadata,
PILTerms calldata terms,
address licenseTemplate,
uint256 licenseTermsId,
WorkflowStructs.SignatureData calldata sigMetadata,
WorkflowStructs.SignatureData calldata sigAttach
) external returns (address ipId, uint256 licenseTermsId) {
) external returns (address ipId) {
ipId = IP_ASSET_REGISTRY.register(block.chainid, nftContract, tokenId);
MetadataHelper.setMetadataWithSig(ipId, address(CORE_METADATA_MODULE), ipMetadata, sigMetadata);

licenseTermsId = LicensingHelper.registerPILTermsAndAttachWithSig(
LicensingHelper.attachLicenseTermsWithSig(
ipId,
address(PIL_TEMPLATE),
address(LICENSING_MODULE),
terms,
licenseTemplate,
licenseTermsId,
sigAttach
);
}
Expand Down
1 change: 0 additions & 1 deletion contracts/workflows/RoyaltyWorkflows.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import { ERC165Checker } from "@openzeppelin/contracts/utils/introspection/ERC16
import { MulticallUpgradeable } from "@openzeppelin/contracts-upgradeable/utils/MulticallUpgradeable.sol";
import { UUPSUpgradeable } from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";

import { Errors as CoreErrors } from "@storyprotocol/core/lib/Errors.sol";
// solhint-disable-next-line max-line-length
import { IGraphAwareRoyaltyPolicy } from "@storyprotocol/core/interfaces/modules/royalty/policies/IGraphAwareRoyaltyPolicy.sol";
import { IIpRoyaltyVault } from "@storyprotocol/core/interfaces/modules/royalty/policies/IIpRoyaltyVault.sol";
Expand Down
10 changes: 5 additions & 5 deletions docs/WORKFLOWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,12 @@

### [License Attachment Workflows](../contracts/interfaces/workflows/ILicenseAttachmentWorkflows.sol)

- `registerPILTermsAndAttach`:
- Registers PIL terms → Attaches them to an IP
- `registerIpAndAttachPILTerms`:
- Registers an IP → Registers PIL terms → Attaches them to the IP
- `mintAndRegisterIpAndAttachPILTerms`:
- Mints a NFT → Registers it as an IP → Registers PIL terms → Attaches them to the IP
- `mintAndRegisterIpAndAttachLicenseTerms`:
- Mints a NFT → Registers it as an IP → Attaches license terms to the IP
- `registerIpAndAttachLicenseTerms`:
- Registers an IP → Attaches license terms to the IP

### [Derivative Workflows](../contracts/interfaces/workflows/IDerivativeWorkflows.sol)

Expand Down Expand Up @@ -50,4 +50,4 @@
- Transfers specified amounts of royalties from various royalty policies to the royalty vault of the ancestor IP -> Claims all the revenue in each currency token from the ancestor IP's royalty vault to the claimer.

- `claimAllRevenue`:
- Transfers all avaiable royalties from various royalty policies to the royalty vault of the ancestor IP -> Claims all the revenue in each currency token from the ancestor IP's royalty vault to the claimer.
- Transfers all available royalties from various royalty policies to the royalty vault of the ancestor IP -> Claims all the revenue in each currency token from the ancestor IP's royalty vault to the claimer.
Loading

0 comments on commit 68f4a4a

Please sign in to comment.