forked from storyprotocol/protocol-periphery-v1
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Introduce Workflows and Group IPA Features (storyprotocol#43)
* chore: update changelog * chore: version bump and package update * chore: remove sepolia references * chore: add missing comments for SPGNFTLib * feat(spg): add licensing helper library * feat(spg): add metadata helper library * feat(spg): add permissions helper library * chore: update ISPG comments * refactor: add base workflow and refactor spg to use helper lib * feat: add grouping workflows * test: refactor & add grouping tests * feat(script): update scripts to support grouping * chore: lint, naming, comments & update package.json * fix(baseWorkflow): mark as abstract
- Loading branch information
1 parent
48a9cf9
commit bae5bee
Showing
28 changed files
with
1,637 additions
and
557 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,8 +1,22 @@ | ||
# CHANGELOG | ||
|
||
## v1.1.0 | ||
- Migrate periphery contracts from protocol core repo (#1) | ||
- Revamped SPG with NFT collection and mint token logic. (#5, #6) | ||
- Added support for batch transactions via `multicall` (#38) | ||
- Added functionality for registering IP with metadata and supporting metadata for SPG NFT. (#8, #20, #37) | ||
- Addressed ownership transfer issues in deployment script. (#18, #39) | ||
- Fixed issues with derivative registration, including minting fees for commercial licenses, license token flow, and making register and attach PIL terms idempotent. (#23, #25, #30) | ||
- Added SPG & SPG NFT upgrade scripts (#10) | ||
- Added IP Graph, Solady's ERC6551 integration, and core protocol package bumps. (#30) | ||
- Enhance CI/CD, repo, and misc.(#2, #3, #11, #32) | ||
|
||
**Full Changelog**: [v1.1.0](https://github.com/storyprotocol/protocol-periphery-v1/commits/v1.1.0) | ||
|
||
## v1.0.0-beta-rc1 | ||
|
||
This is the first release of the Story Protocol Gateway | ||
|
||
- Adds the SPG, a convenient wrapper around the core contracts for registration | ||
- Includes NFT minting management tooling for registering and minting in one-shot | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
// SPDX-License-Identifier: BUSL-1.1 | ||
pragma solidity ^0.8.23; | ||
|
||
import { IAccessController } from "@storyprotocol/core/interfaces/access/IAccessController.sol"; | ||
import { IIPAssetRegistry } from "@storyprotocol/core/interfaces/registries/IIPAssetRegistry.sol"; | ||
import { ILicenseRegistry } from "@storyprotocol/core/interfaces/registries/ILicenseRegistry.sol"; | ||
import { ILicensingModule } from "@storyprotocol/core/interfaces/modules/licensing/ILicensingModule.sol"; | ||
import { ICoreMetadataModule } from "@storyprotocol/core/interfaces/modules/metadata/ICoreMetadataModule.sol"; | ||
import { IPILicenseTemplate } from "@storyprotocol/core/interfaces/modules/licensing/IPILicenseTemplate.sol"; | ||
|
||
import { ISPGNFT } from "./interfaces/ISPGNFT.sol"; | ||
import { SPGNFTLib } from "./lib/SPGNFTLib.sol"; | ||
import { Errors } from "./lib/Errors.sol"; | ||
|
||
/// @title Base Workflow | ||
/// @notice The base contract for all Story Protocol Periphery workflows. | ||
abstract contract BaseWorkflow { | ||
/// @notice The address of the Access Controller. | ||
IAccessController public immutable ACCESS_CONTROLLER; | ||
|
||
/// @notice The address of the Core Metadata Module. | ||
ICoreMetadataModule public immutable CORE_METADATA_MODULE; | ||
|
||
/// @notice The address of the IP Asset Registry. | ||
IIPAssetRegistry public immutable IP_ASSET_REGISTRY; | ||
|
||
/// @notice The address of the Licensing Module. | ||
ILicensingModule public immutable LICENSING_MODULE; | ||
|
||
/// @notice The address of the License Registry. | ||
ILicenseRegistry public immutable LICENSE_REGISTRY; | ||
|
||
/// @notice The address of the PIL License Template. | ||
IPILicenseTemplate public immutable PIL_TEMPLATE; | ||
|
||
constructor( | ||
address accessController, | ||
address coreMetadataModule, | ||
address ipAssetRegistry, | ||
address licensingModule, | ||
address licenseRegistry, | ||
address pilTemplate | ||
) { | ||
// assumes 0 addresses are checked in the child contract | ||
ACCESS_CONTROLLER = IAccessController(accessController); | ||
CORE_METADATA_MODULE = ICoreMetadataModule(coreMetadataModule); | ||
IP_ASSET_REGISTRY = IIPAssetRegistry(ipAssetRegistry); | ||
LICENSING_MODULE = ILicensingModule(licensingModule); | ||
LICENSE_REGISTRY = ILicenseRegistry(licenseRegistry); | ||
PIL_TEMPLATE = IPILicenseTemplate(pilTemplate); | ||
} | ||
|
||
/// @notice Check that the caller has the minter role for the provided SPG NFT. | ||
/// @param spgNftContract The address of the SPG NFT. | ||
modifier onlyCallerWithMinterRole(address spgNftContract) { | ||
if (!ISPGNFT(spgNftContract).hasRole(SPGNFTLib.MINTER_ROLE, msg.sender)) | ||
revert Errors.SPG__CallerNotMinterRole(); | ||
_; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,248 @@ | ||
// SPDX-License-Identifier: BUSL-1.1 | ||
pragma solidity ^0.8.23; | ||
|
||
import { ERC165Checker } from "@openzeppelin/contracts/utils/introspection/ERC165Checker.sol"; | ||
import { UUPSUpgradeable } from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; | ||
import { MulticallUpgradeable } from "@openzeppelin/contracts-upgradeable/utils/MulticallUpgradeable.sol"; | ||
// solhint-disable-next-line max-line-length | ||
import { AccessManagedUpgradeable } from "@openzeppelin/contracts-upgradeable/access/manager/AccessManagedUpgradeable.sol"; | ||
|
||
import { IGroupingModule } from "@storyprotocol/core/interfaces/modules/grouping/IGroupingModule.sol"; | ||
import { ILicensingModule } from "@storyprotocol/core/interfaces/modules/licensing/ILicensingModule.sol"; | ||
import { ICoreMetadataModule } from "@storyprotocol/core/interfaces/modules/metadata/ICoreMetadataModule.sol"; | ||
|
||
import { GroupNFT } from "@storyprotocol/core/GroupNFT.sol"; | ||
import { PILTerms } from "@storyprotocol/core/interfaces/modules/licensing/IPILicenseTemplate.sol"; | ||
|
||
import { Errors } from "./lib/Errors.sol"; | ||
import { BaseWorkflow } from "./BaseWorkflow.sol"; | ||
import { ISPGNFT } from "./interfaces/ISPGNFT.sol"; | ||
import { MetadataHelper } from "./lib/MetadataHelper.sol"; | ||
import { LicensingHelper } from "./lib/LicensingHelper.sol"; | ||
import { PermissionHelper } from "./lib/PermissionHelper.sol"; | ||
import { IGroupingWorkflows } from "./interfaces/IGroupingWorkflows.sol"; | ||
import { IStoryProtocolGateway as ISPG } from "./interfaces/IStoryProtocolGateway.sol"; | ||
|
||
/// @title Grouping Workflows | ||
/// @notice This contract provides key workflows for engaging with Group IPA features in | ||
/// Story’s Proof of Creativity protocol. | ||
contract GroupingWorkflows is | ||
IGroupingWorkflows, | ||
BaseWorkflow, | ||
MulticallUpgradeable, | ||
AccessManagedUpgradeable, | ||
UUPSUpgradeable | ||
{ | ||
using ERC165Checker for address; | ||
|
||
/// @dev Storage structure for the Grouping Workflow. | ||
/// @param nftContractBeacon The address of the NFT contract beacon. | ||
/// @custom:storage-location erc7201:story-protocol-periphery.GroupingWorkflows | ||
struct GroupingWorkflowsStorage { | ||
address nftContractBeacon; | ||
} | ||
|
||
// solhint-disable-next-line max-line-length | ||
// keccak256(abi.encode(uint256(keccak256("story-protocol-periphery.GroupingWorkflows")) - 1)) & ~bytes32(uint256(0xff)); | ||
bytes32 private constant GroupingWorkflowsStorageLocation = | ||
0xa8ddbb5f662015e2b3d6b4c61921979ad3d3d1d19e338b1c4ba6a196b10c6400; | ||
|
||
/// @notice The address of the Grouping Module. | ||
IGroupingModule public immutable GROUPING_MODULE; | ||
|
||
/// @notice The address of the Group NFT contract. | ||
GroupNFT public immutable GROUP_NFT; | ||
|
||
constructor( | ||
address accessController, | ||
address coreMetadataModule, | ||
address groupingModule, | ||
address groupNft, | ||
address ipAssetRegistry, | ||
address licensingModule, | ||
address licenseRegistry, | ||
address pilTemplate | ||
) | ||
BaseWorkflow( | ||
accessController, | ||
coreMetadataModule, | ||
ipAssetRegistry, | ||
licensingModule, | ||
licenseRegistry, | ||
pilTemplate | ||
) | ||
{ | ||
if ( | ||
accessController == address(0) || | ||
coreMetadataModule == address(0) || | ||
groupingModule == address(0) || | ||
groupNft == address(0) || | ||
ipAssetRegistry == address(0) || | ||
licensingModule == address(0) || | ||
licenseRegistry == address(0) || | ||
pilTemplate == address(0) | ||
) revert Errors.GroupingWorkflows__ZeroAddressParam(); | ||
|
||
GROUPING_MODULE = IGroupingModule(groupingModule); | ||
GROUP_NFT = GroupNFT(groupNft); | ||
|
||
_disableInitializers(); | ||
} | ||
|
||
/// @dev Initializes the contract. | ||
/// @param accessManager The address of the protocol access manager. | ||
function initialize(address accessManager) external initializer { | ||
if (accessManager == address(0)) revert Errors.GroupingWorkflows__ZeroAddressParam(); | ||
__AccessManaged_init(accessManager); | ||
__UUPSUpgradeable_init(); | ||
} | ||
|
||
/// @dev Sets the NFT contract beacon address. | ||
/// @param newNftContractBeacon The address of the new NFT contract beacon. | ||
function setNftContractBeacon(address newNftContractBeacon) external restricted { | ||
if (newNftContractBeacon == address(0)) revert Errors.GroupingWorkflows__ZeroAddressParam(); | ||
GroupingWorkflowsStorage storage $ = _getGroupingWorkflowsStorage(); | ||
$.nftContractBeacon = newNftContractBeacon; | ||
} | ||
|
||
/// @notice Mint an NFT from a SPGNFT collection, register it with metadata as an IP, attach | ||
/// Programmable IP License Terms to the registered IP, and add it to a group IP. | ||
/// @dev Caller must have the minter role for the provided SPG NFT. | ||
/// @param spgNftContract The address of the SPGNFT collection. | ||
/// @param groupId The ID of the group IP to add the newly registered IP. | ||
/// @param recipient The address of the recipient of the minted NFT. | ||
/// @param licenseTermsId The ID of the registered PIL terms that will be attached to the newly registered IP. | ||
/// @param ipMetadata OPTIONAL. The desired metadata for the newly minted NFT and registered IP. | ||
/// @param sigAddToGroup Signature data for addIp to the group IP via the Grouping Module. | ||
/// @return ipId The ID of the newly registered IP. | ||
/// @return tokenId The ID of the newly minted NFT. | ||
function mintAndRegisterIpAndAttachPILTermsAndAddToGroup( | ||
address spgNftContract, | ||
address groupId, | ||
address recipient, | ||
uint256 licenseTermsId, | ||
ISPG.IPMetadata calldata ipMetadata, | ||
ISPG.SignatureData calldata sigAddToGroup | ||
) external onlyCallerWithMinterRole(spgNftContract) returns (address ipId, uint256 tokenId) { | ||
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); | ||
|
||
LICENSING_MODULE.attachLicenseTerms(ipId, address(PIL_TEMPLATE), licenseTermsId); | ||
|
||
PermissionHelper.setPermissionForModule( | ||
groupId, | ||
address(GROUPING_MODULE), | ||
address(ACCESS_CONTROLLER), | ||
IGroupingModule.addIp.selector, | ||
sigAddToGroup | ||
); | ||
|
||
address[] memory ipIds = new address[](1); | ||
ipIds[0] = ipId; | ||
GROUPING_MODULE.addIp(groupId, ipIds); | ||
|
||
ISPGNFT(spgNftContract).safeTransferFrom(address(this), recipient, tokenId, ""); | ||
} | ||
|
||
/// @notice Register an NFT as IP with metadata, attach Programmable IP License Terms to the registered IP, | ||
/// and add it to a group IP. | ||
/// @param nftContract The address of the NFT collection. | ||
/// @param tokenId The ID of the NFT. | ||
/// @param groupId The ID of the group IP to add the newly registered IP. | ||
/// @param licenseTermsId The ID of the registered PIL terms that will be attached to the newly registered IP. | ||
/// @param ipMetadata OPTIONAL. The desired metadata for the newly registered IP. | ||
/// @param sigMetadataAndAttach Signature data for setAll (metadata) and attachLicenseTerms to the IP | ||
/// via the Core Metadata Module and Licensing Module. | ||
/// @param sigAddToGroup Signature data for addIp to the group IP via the Grouping Module. | ||
/// @return ipId The ID of the newly registered IP. | ||
function registerIpAndAttachPILTermsAndAddToGroup( | ||
address nftContract, | ||
uint256 tokenId, | ||
address groupId, | ||
uint256 licenseTermsId, | ||
ISPG.IPMetadata calldata ipMetadata, | ||
ISPG.SignatureData calldata sigMetadataAndAttach, | ||
ISPG.SignatureData calldata sigAddToGroup | ||
) external returns (address ipId) { | ||
ipId = IP_ASSET_REGISTRY.register(block.chainid, nftContract, tokenId); | ||
|
||
address[] memory modules = new address[](2); | ||
bytes4[] memory selectors = new bytes4[](2); | ||
modules[0] = address(CORE_METADATA_MODULE); | ||
modules[1] = address(LICENSING_MODULE); | ||
selectors[0] = ICoreMetadataModule.setAll.selector; | ||
selectors[1] = ILicensingModule.attachLicenseTerms.selector; | ||
|
||
PermissionHelper.setBatchPermissionForModules( | ||
ipId, | ||
address(ACCESS_CONTROLLER), | ||
modules, | ||
selectors, | ||
sigMetadataAndAttach | ||
); | ||
|
||
MetadataHelper.setMetadata(ipId, address(CORE_METADATA_MODULE), ipMetadata); | ||
|
||
LICENSING_MODULE.attachLicenseTerms(ipId, address(PIL_TEMPLATE), licenseTermsId); | ||
|
||
PermissionHelper.setPermissionForModule( | ||
groupId, | ||
address(GROUPING_MODULE), | ||
address(ACCESS_CONTROLLER), | ||
IGroupingModule.addIp.selector, | ||
sigAddToGroup | ||
); | ||
|
||
address[] memory ipIds = new address[](1); | ||
ipIds[0] = ipId; | ||
GROUPING_MODULE.addIp(groupId, ipIds); | ||
} | ||
|
||
/// @notice Register a group IP with a group reward pool, register Programmable IP License Terms, | ||
/// attach it to the group IP, and add individual IPs to the group IP. | ||
/// @dev ipIds must have the same PIL terms as the group IP. | ||
/// @param groupPool The address of the group reward pool. | ||
/// @param ipIds The IDs of the IPs to add to the newly registered group IP. | ||
/// @param groupIpTerms The PIL terms to be registered and attached to the newly registered group IP. | ||
/// @return groupId The ID of the newly registered group IP. | ||
/// @return groupLicenseTermsId The ID of the newly registered PIL terms. | ||
function registerGroupAndAttachPILTermsAndAddIps( | ||
address groupPool, | ||
address[] calldata ipIds, | ||
PILTerms calldata groupIpTerms | ||
) external returns (address groupId, uint256 groupLicenseTermsId) { | ||
groupId = GROUPING_MODULE.registerGroup(groupPool); | ||
|
||
groupLicenseTermsId = LicensingHelper.registerPILTermsAndAttach( | ||
groupId, | ||
address(PIL_TEMPLATE), | ||
address(LICENSING_MODULE), | ||
address(LICENSE_REGISTRY), | ||
groupIpTerms | ||
); | ||
|
||
GROUPING_MODULE.addIp(groupId, ipIds); | ||
|
||
GROUP_NFT.safeTransferFrom(address(this), msg.sender, GROUP_NFT.totalSupply() - 1); | ||
} | ||
|
||
// | ||
// Upgrade | ||
// | ||
|
||
/// @dev Returns the storage struct of GroupingWorkflows. | ||
function _getGroupingWorkflowsStorage() private pure returns (GroupingWorkflowsStorage storage $) { | ||
assembly { | ||
$.slot := GroupingWorkflowsStorageLocation | ||
} | ||
} | ||
|
||
/// @dev Hook to authorize the upgrade according to UUPSUpgradeable | ||
/// @param newImplementation The address of the new implementation | ||
function _authorizeUpgrade(address newImplementation) internal override restricted {} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.