forked from storyprotocol/protocol-core
-
Notifications
You must be signed in to change notification settings - Fork 96
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Introducing Core Metadata Module and View Module (#15)
* Implement CoreMetadataModule * add error and module name * add functions to set and get all metadata * add metadataURI * add immutable flag * add events and more tests
- Loading branch information
1 parent
ee1030d
commit 5832c61
Showing
8 changed files
with
808 additions
and
0 deletions.
There are no files selected for viewing
54 changes: 54 additions & 0 deletions
54
contracts/interfaces/modules/metadata/ICoreMetadataModule.sol
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,54 @@ | ||
// SPDX-License-Identifier: BUSL-1.1 | ||
pragma solidity ^0.8.23; | ||
|
||
import { IModule } from "../../../../contracts/interfaces/modules/base/IModule.sol"; | ||
|
||
/// @title CoreMetadataModule | ||
/// @notice Manages the core metadata for IP assets within the Story Protocol. | ||
/// @dev This contract allows setting and updating core metadata attributes for IP assets. | ||
interface ICoreMetadataModule is IModule { | ||
/// @notice Emitted when the nftTokenURI for an IP asset is set. | ||
event NFTTokenURISet(address indexed ipId, string nftTokenURI, bytes32 nftMetadataHash); | ||
|
||
/// @notice Emitted when the metadataURI for an IP asset is set. | ||
event MetadataURISet(address indexed ipId, string metadataURI, bytes32 metadataHash); | ||
|
||
/// @notice Emitted when all metadata for an IP asset are frozen. | ||
event MetadataFrozen(address indexed ipId); | ||
|
||
/// @notice Update the nftTokenURI for an IP asset, | ||
/// by retrieve the latest TokenURI from IP NFT to which the IP Asset bound. | ||
/// @dev Will revert if IP asset's metadata is frozen. | ||
/// @param ipId The address of the IP asset. | ||
/// @param nftMetadataHash A bytes32 hash representing the metadata of the NFT. | ||
/// This metadata is associated with the IP Asset and is accessible via the NFT's TokenURI. | ||
/// Use bytes32(0) to indicate that the metadata is not available. | ||
function updateNftTokenURI(address ipId, bytes32 nftMetadataHash) external; | ||
|
||
/// @notice Sets the metadataURI for an IP asset. | ||
/// @dev Will revert if IP asset's metadata is frozen. | ||
/// @param ipId The address of the IP asset. | ||
/// @param metadataURI The metadataURI to set for the IP asset. | ||
/// @param metadataHash The hash of metadata at metadataURI. | ||
/// Use bytes32(0) to indicate that the metadata is not available. | ||
function setMetadataURI(address ipId, string memory metadataURI, bytes32 metadataHash) external; | ||
|
||
/// @notice Sets all core metadata for an IP asset. | ||
/// @dev Will revert if IP asset's metadata is frozen. | ||
/// @param ipId The address of the IP asset. | ||
/// @param metadataURI The metadataURI to set for the IP asset. | ||
/// @param metadataHash The hash of metadata at metadataURI. | ||
/// Use bytes32(0) to indicate that the metadata is not available. | ||
/// @param nftMetadataHash A bytes32 hash representing the metadata of the NFT. | ||
/// This metadata is associated with the IP Asset and is accessible via the NFT's TokenURI. | ||
/// Use bytes32(0) to indicate that the metadata is not available. | ||
function setAll(address ipId, string memory metadataURI, bytes32 metadataHash, bytes32 nftMetadataHash) external; | ||
|
||
/// @notice make all metadata of the IP Asset immutable. | ||
/// @param ipId The address of the IP asset. | ||
function freezeMetadata(address ipId) external; | ||
|
||
/// @notice Check if the metadata of the IP Asset is immutable. | ||
/// @param ipId The address of the IP asset. | ||
function isMetadataFrozen(address ipId) external view returns (bool); | ||
} |
64 changes: 64 additions & 0 deletions
64
contracts/interfaces/modules/metadata/ICoreMetadataViewModule.sol
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,64 @@ | ||
// SPDX-License-Identifier: BUSL-1.1 | ||
pragma solidity ^0.8.23; | ||
|
||
import { IViewModule } from "../base/IViewModule.sol"; | ||
|
||
/// @title CoreMetadataViewModule | ||
/// @notice This view module provides getter functions to access all core metadata | ||
/// and generate json string of all core metadata returned by tokenURI(). | ||
/// The view module consolidates core metadata for IPAssets from both IPAssetRegistry and CoreMetadataModule. | ||
/// @dev The "name" from CoreMetadataModule overrides the "name" from IPAssetRegistry if set. | ||
interface ICoreMetadataViewModule is IViewModule { | ||
/// @notice Core metadata struct for IPAssets. | ||
struct CoreMetadata { | ||
string nftTokenURI; | ||
bytes32 nftMetadataHash; | ||
string metadataURI; | ||
bytes32 metadataHash; | ||
uint256 registrationDate; | ||
address owner; | ||
} | ||
|
||
/// @notice Retrieves the metadataURI of the IPAsset from CoreMetadataModule. | ||
/// @param ipId The address of the IPAsset. | ||
/// @return The metadataURI of the IPAsset. | ||
function getMetadataURI(address ipId) external view returns (string memory); | ||
|
||
/// @notice Retrieves the metadata hash of the IPAsset from CoreMetadataModule. | ||
/// @param ipId The address of the IPAsset. | ||
/// @return The metadata hash of the IPAsset. | ||
function getMetadataHash(address ipId) external view returns (bytes32); | ||
|
||
/// @notice Retrieves the registration date of the IPAsset from IPAssetRegistry. | ||
/// @param ipId The address of the IPAsset. | ||
/// @return The registration date of the IPAsset. | ||
function getRegistrationDate(address ipId) external view returns (uint256); | ||
|
||
/// @notice Retrieves the TokenURI of the NFT to which IP Asset bound. | ||
/// @param ipId The address of the IPAsset. | ||
/// @return The NFT TokenURI of the IPAsset. | ||
function getNftTokenURI(address ipId) external view returns (string memory); | ||
|
||
/// @notice Retrieves the metadata hash of the NFT to which IP Asset bound. | ||
/// @param ipId The address of the IPAsset. | ||
/// @return The NFT metadata hash of the IPAsset. | ||
function getNftMetadataHash(address ipId) external view returns (bytes32); | ||
|
||
/// @notice Retrieves the owner of the IPAsset. | ||
/// @param ipId The address of the IPAsset. | ||
/// @return The address of the owner of the IPAsset. | ||
function getOwner(address ipId) external view returns (address); | ||
|
||
/// @notice Retrieves all core metadata of the IPAsset. | ||
/// @param ipId The address of the IPAsset. | ||
/// @return The CoreMetadata struct of the IPAsset. | ||
function getCoreMetadata(address ipId) external view returns (CoreMetadata memory); | ||
|
||
/// @notice Generates a JSON string formatted according to the standard NFT metadata schema for the IPAsset, | ||
//// including all relevant metadata fields. | ||
/// @dev This function consolidates metadata from both IPAssetRegistry | ||
/// and CoreMetadataModule, with "name" from CoreMetadataModule taking precedence. | ||
/// @param ipId The address of the IPAsset. | ||
/// @return A JSON string representing all metadata of the IPAsset. | ||
function getJsonString(address ipId) external view returns (string memory); | ||
} |
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
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,116 @@ | ||
// SPDX-License-Identifier: BUSL-1.1 | ||
pragma solidity ^0.8.23; | ||
|
||
import { IERC165 } from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; | ||
import { IERC721Metadata } from "@openzeppelin/contracts/token/ERC721/extensions/IERC721Metadata.sol"; | ||
import { IIPAccount } from "../../interfaces/IIPAccount.sol"; | ||
import { AccessControlled } from "../../access/AccessControlled.sol"; | ||
import { BaseModule } from "../BaseModule.sol"; | ||
import { Errors } from "../../lib/Errors.sol"; | ||
import { IPAccountStorageOps } from "../../lib/IPAccountStorageOps.sol"; | ||
import { CORE_METADATA_MODULE_KEY } from "../../lib/modules/Module.sol"; | ||
import { ICoreMetadataModule } from "../../interfaces/modules/metadata/ICoreMetadataModule.sol"; | ||
|
||
/// @title CoreMetadataModule | ||
/// @notice Manages the core metadata for IP assets within the Story Protocol. | ||
/// @dev This contract allows setting core metadata attributes for IP assets. | ||
/// It implements the ICoreMetadataModule interface. | ||
/// The metadata can be set and updated by the owner of the IP asset. | ||
/// The metadata can be frozen to prevent further changes. | ||
contract CoreMetadataModule is BaseModule, AccessControlled, ICoreMetadataModule { | ||
using IPAccountStorageOps for IIPAccount; | ||
|
||
string public override name = CORE_METADATA_MODULE_KEY; | ||
|
||
/// @notice Modifier to ensure that metadata can only be changed when mutable. | ||
modifier onlyMutable(address ipId) { | ||
if (IIPAccount(payable(ipId)).getBool("IMMUTABLE")) { | ||
revert Errors.CoreMetadataModule__MetadataAlreadyFrozen(); | ||
} | ||
_; | ||
} | ||
|
||
/// @notice Creates a new CoreMetadataModule instance. | ||
/// @param accessController The address of the AccessController contract. | ||
/// @param ipAccountRegistry The address of the IPAccountRegistry contract. | ||
constructor( | ||
address accessController, | ||
address ipAccountRegistry | ||
) AccessControlled(accessController, ipAccountRegistry) {} | ||
|
||
/// @notice Update the nftTokenURI for an IP asset, | ||
/// by retrieve the latest TokenURI from IP NFT to which the IP Asset bound. | ||
/// @dev Will revert if IP asset's metadata is frozen. | ||
/// @param ipId The address of the IP asset. | ||
/// @param nftMetadataHash A bytes32 hash representing the metadata of the NFT. | ||
/// This metadata is associated with the IP Asset and is accessible via the NFT's TokenURI. | ||
/// Use bytes32(0) to indicate that the metadata is not available. | ||
function updateNftTokenURI(address ipId, bytes32 nftMetadataHash) external verifyPermission(ipId) { | ||
_updateNftTokenURI(ipId, nftMetadataHash); | ||
} | ||
|
||
/// @notice Sets the metadataURI for an IP asset. | ||
/// @dev Will revert if IP asset's metadata is frozen. | ||
/// @param ipId The address of the IP asset. | ||
/// @param metadataURI The metadataURI to set for the IP asset. | ||
/// @param metadataHash The hash of metadata at metadataURI. | ||
/// Use bytes32(0) to indicate that the metadata is not available. | ||
function setMetadataURI( | ||
address ipId, | ||
string memory metadataURI, | ||
bytes32 metadataHash | ||
) external verifyPermission(ipId) { | ||
_setMetadataURI(ipId, metadataURI, metadataHash); | ||
} | ||
|
||
/// @notice Sets all core metadata for an IP asset. | ||
/// @dev Will revert if IP asset's metadata is frozen. | ||
/// @param ipId The address of the IP asset. | ||
/// @param metadataURI The metadataURI to set for the IP asset. | ||
/// @param metadataHash The hash of metadata at metadataURI. | ||
/// Use bytes32(0) to indicate that the metadata is not available. | ||
/// @param nftMetadataHash A bytes32 hash representing the metadata of the NFT. | ||
/// This metadata is associated with the IP Asset and is accessible via the NFT's TokenURI. | ||
/// Use bytes32(0) to indicate that the metadata is not available. | ||
function setAll( | ||
address ipId, | ||
string memory metadataURI, | ||
bytes32 metadataHash, | ||
bytes32 nftMetadataHash | ||
) external verifyPermission(ipId) { | ||
_updateNftTokenURI(ipId, nftMetadataHash); | ||
_setMetadataURI(ipId, metadataURI, metadataHash); | ||
} | ||
|
||
/// @notice make all metadata of the IP Asset immutable. | ||
/// @param ipId The address of the IP asset. | ||
function freezeMetadata(address ipId) external verifyPermission(ipId) { | ||
IIPAccount(payable(ipId)).setBool("IMMUTABLE", true); | ||
emit MetadataFrozen(ipId); | ||
} | ||
|
||
/// @notice Check if the metadata of the IP Asset is immutable. | ||
/// @param ipId The address of the IP asset. | ||
function isMetadataFrozen(address ipId) external view returns (bool) { | ||
return IIPAccount(payable(ipId)).getBool("IMMUTABLE"); | ||
} | ||
|
||
/// @dev Implements the IERC165 interface. | ||
function supportsInterface(bytes4 interfaceId) public view virtual override(BaseModule, IERC165) returns (bool) { | ||
return interfaceId == type(ICoreMetadataModule).interfaceId || super.supportsInterface(interfaceId); | ||
} | ||
|
||
function _updateNftTokenURI(address ipId, bytes32 nftMetadataHash) internal onlyMutable(ipId) { | ||
(, address tokenAddress, uint256 tokenId) = IIPAccount(payable(ipId)).token(); | ||
string memory nftTokenURI = IERC721Metadata(tokenAddress).tokenURI(tokenId); | ||
IIPAccount(payable(ipId)).setString("NFT_TOKEN_URI", nftTokenURI); | ||
IIPAccount(payable(ipId)).setBytes32("NFT_METADATA_HASH", nftMetadataHash); | ||
emit NFTTokenURISet(ipId, nftTokenURI, nftMetadataHash); | ||
} | ||
|
||
function _setMetadataURI(address ipId, string memory metadataURI, bytes32 metadataHash) internal onlyMutable(ipId) { | ||
IIPAccount(payable(ipId)).setString("METADATA_URI", metadataURI); | ||
IIPAccount(payable(ipId)).setBytes32("METADATA_HASH", metadataHash); | ||
emit MetadataURISet(ipId, metadataURI, metadataHash); | ||
} | ||
} |
Oops, something went wrong.