forked from storyprotocol/protocol-core
-
Notifications
You must be signed in to change notification settings - Fork 111
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Introduce IPA Grouping * add GroupingModule and update IPAssetRegistry
- Loading branch information
1 parent
47d8fd1
commit 0c3949b
Showing
16 changed files
with
1,464 additions
and
19 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,138 @@ | ||
// SPDX-License-Identifier: BUSL-1.1 | ||
pragma solidity 0.8.23; | ||
import { Base64 } from "@openzeppelin/contracts/utils/Base64.sol"; | ||
import { Strings } from "@openzeppelin/contracts/utils/Strings.sol"; | ||
import { IERC165 } from "@openzeppelin/contracts/utils/introspection/ERC165.sol"; | ||
import { ERC721Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol"; | ||
import { IERC721Metadata } from "@openzeppelin/contracts/token/ERC721/extensions/IERC721Metadata.sol"; | ||
import { UUPSUpgradeable } from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; | ||
// solhint-disable-next-line max-line-length | ||
import { AccessManagedUpgradeable } from "@openzeppelin/contracts-upgradeable/access/manager/AccessManagedUpgradeable.sol"; | ||
|
||
import { IIPAssetRegistry } from "./interfaces/registries/IIPAssetRegistry.sol"; | ||
import { IGroupNFT } from "./interfaces/IGroupNFT.sol"; | ||
import { Errors } from "./lib/Errors.sol"; | ||
|
||
/// @title GroupNFT | ||
contract GroupNFT is IGroupNFT, ERC721Upgradeable, AccessManagedUpgradeable, UUPSUpgradeable { | ||
using Strings for *; | ||
|
||
/// @custom:oz-upgrades-unsafe-allow state-variable-immutable | ||
IIPAssetRegistry public immutable IP_ASSET_REGISTRY; | ||
|
||
/// @notice Emitted for metadata updates, per EIP-4906 | ||
event BatchMetadataUpdate(uint256 _fromTokenId, uint256 _toTokenId); | ||
|
||
/// @dev Storage structure for the GroupNFT | ||
/// @custom:storage-location erc7201:story-protocol.GroupNFT | ||
struct GroupNFTStorage { | ||
string imageUrl; | ||
uint256 totalSupply; | ||
} | ||
|
||
// keccak256(abi.encode(uint256(keccak256("story-protocol.GroupNFT")) - 1)) & ~bytes32(uint256(0xff)); | ||
bytes32 private constant GroupNFTStorageLocation = | ||
0x1f63c78b3808749cafddcb77c269221c148dbaa356630c2195a6ec03d7fedb00; | ||
|
||
modifier onlyIPAssetRegistry() { | ||
if (msg.sender != address(IP_ASSET_REGISTRY)) { | ||
revert Errors.GroupNFT__CallerNotIPAssetRegistry(msg.sender); | ||
} | ||
_; | ||
} | ||
|
||
/// @custom:oz-upgrades-unsafe-allow constructor | ||
constructor(address iPAssetRegistry) { | ||
IP_ASSET_REGISTRY = IIPAssetRegistry(iPAssetRegistry); | ||
_disableInitializers(); | ||
} | ||
|
||
/// @dev Initializes the GroupNFT contract | ||
function initialize(address accessManager, string memory imageUrl) public initializer { | ||
if (accessManager == address(0)) { | ||
revert Errors.GroupNFT__ZeroAccessManager(); | ||
} | ||
__ERC721_init("Programmable IP Group IP NFT", "GroupNFT"); | ||
__AccessManaged_init(accessManager); | ||
__UUPSUpgradeable_init(); | ||
_getGroupNFTStorage().imageUrl = imageUrl; | ||
} | ||
|
||
/// @dev Sets the Licensing Image URL. | ||
/// @dev Enforced to be only callable by the protocol admin | ||
/// @param url The URL of the Licensing Image | ||
function setLicensingImageUrl(string calldata url) external restricted { | ||
GroupNFTStorage storage $ = _getGroupNFTStorage(); | ||
$.imageUrl = url; | ||
emit BatchMetadataUpdate(0, $.totalSupply); | ||
} | ||
|
||
/// @notice Mints a Group NFT. | ||
/// @param minter The address of the minter. | ||
/// @param receiver The address of the receiver of the minted Group NFT. | ||
/// @return groupNftId The ID of the minted Group NFT. | ||
function mintGroupNft(address minter, address receiver) external onlyIPAssetRegistry returns (uint256 groupNftId) { | ||
GroupNFTStorage storage $ = _getGroupNFTStorage(); | ||
groupNftId = $.totalSupply++; | ||
_mint(receiver, groupNftId); | ||
emit GroupNFTMinted(minter, receiver, groupNftId); | ||
} | ||
|
||
/// @notice Returns the total number of minted group IPA NFT since beginning, | ||
/// @return The total number of minted group IPA NFT. | ||
function totalSupply() external view returns (uint256) { | ||
return _getGroupNFTStorage().totalSupply; | ||
} | ||
|
||
/// @notice ERC721 OpenSea metadata JSON representation of Group IPA NFT | ||
function tokenURI( | ||
uint256 id | ||
) public view virtual override(ERC721Upgradeable, IERC721Metadata) returns (string memory) { | ||
GroupNFTStorage storage $ = _getGroupNFTStorage(); | ||
|
||
/* solhint-disable */ | ||
// Follows the OpenSea standard for JSON metadata | ||
|
||
// base json, open the attributes array | ||
string memory json = string( | ||
abi.encodePacked( | ||
"{", | ||
'"name": "Story Protocol IP Assets Group #', | ||
id.toString(), | ||
'",', | ||
'"description": IPAsset Group",', | ||
'"external_url": "https://protocol.storyprotocol.xyz/ipa/', | ||
id.toString(), | ||
'",', | ||
'"image": "', | ||
$.imageUrl, | ||
'"' | ||
) | ||
); | ||
|
||
// close the attributes array and the json metadata object | ||
json = string(abi.encodePacked(json, "}")); | ||
|
||
/* solhint-enable */ | ||
|
||
return string(abi.encodePacked("data:application/json;base64,", Base64.encode(bytes(json)))); | ||
} | ||
|
||
/// @notice IERC165 interface support. | ||
function supportsInterface( | ||
bytes4 interfaceId | ||
) public view virtual override(ERC721Upgradeable, IERC165) returns (bool) { | ||
return interfaceId == type(IGroupNFT).interfaceId || super.supportsInterface(interfaceId); | ||
} | ||
|
||
/// @dev Returns the storage struct of GroupNFT. | ||
function _getGroupNFTStorage() private pure returns (GroupNFTStorage storage $) { | ||
assembly { | ||
$.slot := GroupNFTStorageLocation | ||
} | ||
} | ||
|
||
/// @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
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,22 @@ | ||
// SPDX-License-Identifier: BUSL-1.1 | ||
pragma solidity 0.8.23; | ||
|
||
import { IERC721Metadata } from "@openzeppelin/contracts/token/ERC721/extensions/IERC721Metadata.sol"; | ||
|
||
/// @title IGroupNFT | ||
/// @notice Interface for the IP Group (ERC721) NFT collection that manages Group NFTs representing IP Group. | ||
/// Each Group NFT may represent a IP Group. | ||
/// Group NFTs are ERC721 NFTs that can be minted, transferred, but cannot be burned. | ||
interface IGroupNFT is IERC721Metadata { | ||
/// @notice Emitted when a IP Group NFT minted. | ||
/// @param minter The address of the minter of the IP Group NFT | ||
/// @param receiver The address of the receiver of the Group NFT. | ||
/// @param tokenId The ID of the minted IP Group NFT. | ||
event GroupNFTMinted(address indexed minter, address indexed receiver, uint256 indexed tokenId); | ||
|
||
/// @notice Mints a Group NFT. | ||
/// @param minter The address of the minter. | ||
/// @param receiver The address of the receiver of the minted Group NFT. | ||
/// @return groupNftId The ID of the minted Group NFT. | ||
function mintGroupNft(address minter, address receiver) external returns (uint256 groupNftId); | ||
} |
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
42 changes: 42 additions & 0 deletions
42
contracts/interfaces/modules/grouping/IGroupRewardPool.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,42 @@ | ||
// SPDX-License-Identifier: BUSL-1.1 | ||
pragma solidity 0.8.23; | ||
|
||
/// @title IGroupingPolicy | ||
/// @notice Interface for grouping policies | ||
interface IGroupRewardPool { | ||
/// @notice Distributes rewards to the given IP accounts in pool | ||
/// @param groupId The group ID | ||
/// @param token The reward tokens | ||
/// @param ipIds The IP IDs | ||
function distributeRewards( | ||
address groupId, | ||
address token, | ||
address[] calldata ipIds | ||
) external returns (uint256[] memory rewards); | ||
|
||
/// @notice Collects royalty revenue to the group pool through royalty module | ||
/// @param groupId The group ID | ||
/// @param token The reward token | ||
function collectRoyalties(address groupId, address token) external; | ||
|
||
/// @notice Adds an IP to the group pool | ||
/// @param groupId The group ID | ||
/// @param ipId The IP ID | ||
function addIp(address groupId, address ipId) external; | ||
|
||
/// @notice Removes an IP from the group pool | ||
/// @param groupId The group ID | ||
/// @param ipId The IP ID | ||
function removeIp(address groupId, address ipId) external; | ||
|
||
/// @notice Returns the available reward for each IP in the group | ||
/// @param groupId The group ID | ||
/// @param token The reward token | ||
/// @param ipIds The IP IDs | ||
/// @return The rewards for each IP | ||
function getAvailableReward( | ||
address groupId, | ||
address token, | ||
address[] calldata ipIds | ||
) external view returns (uint256[] 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
// SPDX-License-Identifier: BUSL-1.1 | ||
pragma solidity 0.8.23; | ||
|
||
import { IModule } from "../base/IModule.sol"; | ||
|
||
/// @title IGroupingModule | ||
/// @notice This interface defines the entry point for users to manage group in the Story Protocol. | ||
/// It defines the workflow of grouping actions and coordinates among all grouping components. | ||
/// The Grouping Module is responsible for adding ip to group, removing ip from group and claiming reward. | ||
interface IGroupingModule is IModule { | ||
/// @notice Emitted when a group is registered. | ||
/// @param groupId The address of the group. | ||
/// @param groupPool The address of the group pool. | ||
event IPGroupRegistered(address indexed groupId, address indexed groupPool); | ||
|
||
/// @notice Emitted when added ip to group. | ||
/// @param groupId The address of the group. | ||
/// @param ipIds The IP ID. | ||
event AddedIpToGroup(address indexed groupId, address[] ipIds); | ||
|
||
/// @notice Emitted when removed ip from group. | ||
/// @param groupId The address of the group. | ||
/// @param ipIds The IP ID. | ||
event RemovedIpFromGroup(address indexed groupId, address[] ipIds); | ||
|
||
/// @notice Emitted when claimed reward. | ||
/// @param groupId The address of the group. | ||
/// @param token The address of the token. | ||
/// @param ipId The IP ID. | ||
/// @param amount The amount of reward. | ||
event ClaimedReward(address indexed groupId, address indexed token, address[] ipId, uint256[] amount); | ||
|
||
/// @notice Registers a Group IPA. | ||
/// @param groupPool The address of the group pool. | ||
/// @return groupId The address of the newly registered Group IPA. | ||
function registerGroup(address groupPool) external returns (address groupId); | ||
|
||
/// @notice Whitelists a group reward pool. | ||
/// @param rewardPool The address of the group reward pool. | ||
function whitelistGroupRewardPool(address rewardPool) external; | ||
|
||
/// @notice Adds IP to group. | ||
/// the function must be called by the Group IP owner or an authorized operator. | ||
/// @param groupIpId The address of the group IP. | ||
/// @param ipIds The IP IDs. | ||
function addIp(address groupIpId, address[] calldata ipIds) external; | ||
|
||
/// @notice Removes IP from group. | ||
/// the function must be called by the Group IP owner or an authorized operator. | ||
/// @param groupIpId The address of the group IP. | ||
/// @param ipIds The IP IDs. | ||
function removeIp(address groupIpId, address[] calldata ipIds) external; | ||
|
||
/// @notice Claims reward. | ||
/// @param groupId The address of the group. | ||
/// @param token The address of the token. | ||
/// @param ipIds The IP IDs. | ||
function claimReward(address groupId, address token, address[] calldata ipIds) external; | ||
|
||
/// @notice Returns the available reward for each IP in the group. | ||
/// @param groupId The address of the group. | ||
/// @param token The address of the token. | ||
/// @param ipIds The IP IDs. | ||
/// @return The rewards for each IP. | ||
function getClaimableReward( | ||
address groupId, | ||
address token, | ||
address[] calldata ipIds | ||
) external view returns (uint256[] memory); | ||
} |
Oops, something went wrong.