Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(story-nft): extract OrgNFT logic to BaseOrgStoryNFT #109

Merged
merged 3 commits into from
Nov 2, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions contracts/interfaces/story-nft/IOrgStoryNFT.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.26;

import { IStoryNFT } from "./IStoryNFT.sol";

/// @title Organization Story NFT Interface
/// @notice Interface for StoryNFTs with Organization NFT integration.
interface IOrgStoryNFT is IStoryNFT {
/// @notice Initializes the OrgStoryNFT.
/// @param orgTokenId_ The token ID of the organization NFT.
/// @param orgIpId_ The ID of the organization IP.
/// @param initParams The initialization parameters for StoryNFT {see {StoryNftInitParams}}.
function initialize(uint256 orgTokenId_, address orgIpId_, StoryNftInitParams calldata initParams) external;
}
6 changes: 0 additions & 6 deletions contracts/interfaces/story-nft/IStoryNFT.sol
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,6 @@ interface IStoryNFT is IERC721, IERC7572 {
////////////////////////////////////////////////////////////////////////////
// Functions //
////////////////////////////////////////////////////////////////////////////
/// @notice Initializes the StoryNFT.
/// @param orgTokenId_ The token ID of the organization NFT.
/// @param orgIpId_ The ID of the organization IP.
/// @param initParams The initialization parameters for StoryNFT {see {StoryNftInitParams}}.
function initialize(uint256 orgTokenId_, address orgIpId_, StoryNftInitParams calldata initParams) external;

/// @notice Sets the contractURI of the collection (follows OpenSea contract-level metadata standard).
function setContractURI(string memory contractURI) external;

Expand Down
4 changes: 2 additions & 2 deletions contracts/interfaces/story-nft/IStoryNFTFactory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@ interface IStoryNFTFactory {
error StoryNFTFactory__SignatureAlreadyUsed(bytes signature);

/// @notice BaseStoryNFT is not supported by the StoryNFTFactory.
/// @param tokenContract The address of the token contract that does not implement IStoryNFT.
error StoryNFTFactory__UnsupportedIStoryNFT(address tokenContract);
/// @param tokenContract The address of the token contract that does not implement IOrgStoryNFT.
error StoryNFTFactory__UnsupportedIOrgStoryNFT(address tokenContract);

/// @notice Zero address provided as a param to StoryNFTFactory functions.
error StoryNFTFactory__ZeroAddressParam();
Expand Down
86 changes: 86 additions & 0 deletions contracts/story-nft/BaseOrgStoryNFT.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.26;

import { IERC165 } from "@openzeppelin/contracts/interfaces/IERC165.sol";

import { IOrgStoryNFT } from "../interfaces/story-nft/IOrgStoryNFT.sol";
import { BaseStoryNFT } from "./BaseStoryNFT.sol";

/// @title Base Story NFT with OrgNFT integration
/// @notice Base Story NFT which integrates with the OrgNFT and StoryNFTFactory.
abstract contract BaseOrgStoryNFT is IOrgStoryNFT, BaseStoryNFT {
/// @notice Organization NFT address (see {OrgNFT}).
address public immutable ORG_NFT;

/// @dev Storage structure for the BaseOrgStoryNFT
/// @param orgTokenId Associated Organization NFT token ID.
/// @param orgIpId Associated Organization IP ID.
/// @custom:storage-location erc7201:story-protocol-periphery.BaseOrgStoryNFT
struct BaseOrgStoryNFTStorage {
uint256 orgTokenId;
address orgIpId;
}

// keccak256(abi.encode(uint256(keccak256("story-protocol-periphery.BaseOrgStoryNFT")) - 1)) & ~bytes32(uint256(0xff));
bytes32 private constant BaseOrgStoryNFTStorageLocation =
0x52eea8b3c549d1bd8b986d98314c387ab153ca0f32b6949d51f32dbd11b07900;

constructor(
address ipAssetRegistry,
address licensingModule,
address orgNft
) BaseStoryNFT(ipAssetRegistry, licensingModule) {
if (orgNft == address(0)) revert StoryNFT__ZeroAddressParam();
ORG_NFT = orgNft;
_disableInitializers();
}

/// @dev External initializer function, to be overridden by the inheriting contracts.
/// @param orgTokenId_ The token ID of the organization NFT.
/// @param orgIpId_ The ID of the organization IP.
/// @param initParams The initialization parameters for StoryNFT {see {IStoryNFT-StoryNftInitParams}}.
function initialize(
uint256 orgTokenId_,
address orgIpId_,
StoryNftInitParams calldata initParams
) external virtual initializer {}

/// @dev Initialize the BaseOrgStoryNFT
/// @param orgTokenId_ The token ID of the organization NFT.
/// @param orgIpId_ The ID of the organization IP.
/// @param initParams The initialization parameters for StoryNFT {see {IStoryNFT-StoryNftInitParams}}.
function __BaseOrgStoryNFT_init(
uint256 orgTokenId_,
address orgIpId_,
StoryNftInitParams calldata initParams
) internal onlyInitializing {
if (orgIpId_ == address(0)) revert StoryNFT__ZeroAddressParam();
__BaseStoryNFT_init(initParams);

BaseOrgStoryNFTStorage storage $ = _getBaseOrgStoryNFTStorage();
$.orgTokenId = orgTokenId_;
$.orgIpId = orgIpId_;
}

/// @notice Returns the token ID of the associated Organization NFT.
function orgTokenId() public view returns (uint256) {
return _getBaseOrgStoryNFTStorage().orgTokenId;
}

/// @notice Returns the ID of the associated Organization IP.
function orgIpId() public view returns (address) {
return _getBaseOrgStoryNFTStorage().orgIpId;
}

/// @notice IERC165 interface support.
function supportsInterface(bytes4 interfaceId) public view virtual override(BaseStoryNFT, IERC165) returns (bool) {
return interfaceId == type(IOrgStoryNFT).interfaceId || super.supportsInterface(interfaceId);
}

/// @dev Returns the storage struct of BaseOrgStoryNFT.
function _getBaseOrgStoryNFTStorage() private pure returns (BaseOrgStoryNFTStorage storage $) {
assembly {
$.slot := BaseOrgStoryNFTStorageLocation
}
}
}
108 changes: 43 additions & 65 deletions contracts/story-nft/BaseStoryNFT.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,9 @@
pragma solidity 0.8.26;

import { IERC165 } from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
import { ERC721 } from "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import { ERC721URIStorage } from "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
/* solhint-disable-next-line max-line-length */
import { ERC721URIStorageUpgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC721/extensions/ERC721URIStorageUpgradeable.sol";
import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import { IIPAssetRegistry } from "@story-protocol/protocol-core/contracts/interfaces/registries/IIPAssetRegistry.sol";
/*solhint-disable-next-line max-line-length*/
import { ILicensingModule } from "@story-protocol/protocol-core/contracts/interfaces/modules/licensing/ILicensingModule.sol";
Expand All @@ -17,72 +16,54 @@ import { IStoryNFT } from "../interfaces/story-nft/IStoryNFT.sol";
/// To create a new custom StoryNFT, inherit from this contract and override the required functions.
/// Note: the new StoryNFT must be whitelisted in `StoryNFTFactory` by the Story governance in order
/// to use the Story NFT Factory features.
abstract contract BaseStoryNFT is IStoryNFT, ERC721URIStorage, Ownable, Initializable {
abstract contract BaseStoryNFT is IStoryNFT, ERC721URIStorageUpgradeable, OwnableUpgradeable {
/// @notice Story Proof-of-Creativity IP Asset Registry address.
IIPAssetRegistry public immutable IP_ASSET_REGISTRY;

/// @notice Story Proof-of-Creativity Licensing Module address.
ILicensingModule public immutable LICENSING_MODULE;

/// @notice Organization NFT address (see {OrgNFT}).
address public immutable ORG_NFT;

/// @notice Associated Organization NFT token ID.
uint256 public orgTokenId;

/// @notice Associated Organization IP ID.
address public orgIpId;

/// @dev Name of the collection.
string private _name;

/// @dev Symbol of the collection.
string private _symbol;

/// @dev Contract URI of the collection (follows OpenSea contract-level metadata standard).
string private _contractURI;

/// @dev Base URI of the collection (see {ERC721URIStorage-tokenURI} for how it is used).
string private _baseURI_;
/// @dev Storage structure for the BaseStoryNFT
/// @param contractURI The contract URI of the collection.
/// @param baseURI The base URI of the collection.
/// @param totalSupply The total supply of the collection.
/// @custom:storage-location erc7201:story-protocol-periphery.BaseStoryNFT
struct BaseStoryNFTStorage {
string contractURI;
string baseURI;
uint256 totalSupply;
}

/// @dev Current total supply of the collection.
uint256 private _totalSupply;
// keccak256(abi.encode(uint256(keccak256("story-protocol-periphery.BaseStoryNFT")) - 1)) & ~bytes32(uint256(0xff));
bytes32 private constant BaseStoryNFTStorageLocation =
0x81ed94d7560ff7bef5060a232718049e514c358c346e3254b876807a753c0e00;

constructor(address ipAssetRegistry, address licensingModule, address orgNft) ERC721("", "") Ownable(msg.sender) {
if (ipAssetRegistry == address(0) || licensingModule == address(0) || orgNft == address(0))
revert StoryNFT__ZeroAddressParam();
constructor(address ipAssetRegistry, address licensingModule) {
if (ipAssetRegistry == address(0) || licensingModule == address(0)) revert StoryNFT__ZeroAddressParam();
IP_ASSET_REGISTRY = IIPAssetRegistry(ipAssetRegistry);
LICENSING_MODULE = ILicensingModule(licensingModule);
ORG_NFT = orgNft;

_disableInitializers();
}

/// @notice Initializes the StoryNFT
/// @param orgTokenId_ The token ID of the organization NFT.
/// @param orgIpId_ The ID of the organization IP.
/// @param initParams The initialization parameters for StoryNFT {see {IStoryNFT-StoryNftInitParams}}.
function initialize(
uint256 orgTokenId_,
address orgIpId_,
StoryNftInitParams calldata initParams
) public virtual initializer {
if (initParams.owner == address(0) || orgIpId_ == address(0)) revert StoryNFT__ZeroAddressParam();

orgTokenId = orgTokenId_;
orgIpId = orgIpId_;

_name = initParams.name;
_symbol = initParams.symbol;
_contractURI = initParams.contractURI;
_baseURI_ = initParams.baseURI;

_transferOwnership(initParams.owner);
function __BaseStoryNFT_init(StoryNftInitParams calldata initParams) internal onlyInitializing {
__Ownable_init(initParams.owner);
__ERC721URIStorage_init();
__ERC721_init(initParams.name, initParams.symbol);

BaseStoryNFTStorage storage $ = _getBaseStoryNFTStorage();
$.contractURI = initParams.contractURI;
$.baseURI = initParams.baseURI;

_customize(initParams.customInitData);
}

/// @notice Sets the contractURI of the collection (follows OpenSea contract-level metadata standard).
/// @param contractURI_ The new contractURI of the collection.
function setContractURI(string memory contractURI_) external onlyOwner {
_contractURI = contractURI_;
_getBaseStoryNFTStorage().contractURI = contractURI_;

emit ContractURIUpdated();
}
Expand All @@ -104,7 +85,7 @@ abstract contract BaseStoryNFT is IStoryNFT, ERC721URIStorage, Ownable, Initiali
address recipient,
string memory tokenURI_
) internal virtual returns (uint256 tokenId, address ipId) {
tokenId = _totalSupply++;
tokenId = _getBaseStoryNFTStorage().totalSupply++;
_safeMint(recipient, tokenId);
_setTokenURI(tokenId, tokenURI_);
ipId = IP_ASSET_REGISTRY.register(block.chainid, address(this), tokenId);
Expand Down Expand Up @@ -138,28 +119,18 @@ abstract contract BaseStoryNFT is IStoryNFT, ERC721URIStorage, Ownable, Initiali
/// @notice IERC165 interface support.
function supportsInterface(
bytes4 interfaceId
) public view virtual override(ERC721URIStorage, IERC165) returns (bool) {
) public view virtual override(ERC721URIStorageUpgradeable, IERC165) returns (bool) {
return interfaceId == type(IStoryNFT).interfaceId || super.supportsInterface(interfaceId);
}

/// @notice Returns the name of the collection.
function name() public view override returns (string memory) {
return _name;
}

/// @notice Returns the symbol of the collection.
function symbol() public view override returns (string memory) {
return _symbol;
}

/// @notice Returns the current total supply of the collection.
function totalSupply() public view returns (uint256) {
return _totalSupply;
return _getBaseStoryNFTStorage().totalSupply;
}

/// @notice Returns the contract URI of the collection (follows OpenSea contract-level metadata standard).
function contractURI() external view virtual returns (string memory) {
return _contractURI;
return _getBaseStoryNFTStorage().contractURI;
}

/// @notice Initializes the StoryNFT with custom data, required to be overridden by the inheriting contracts.
Expand All @@ -169,6 +140,13 @@ abstract contract BaseStoryNFT is IStoryNFT, ERC721URIStorage, Ownable, Initiali

/// @notice Returns the base URI of the collection (see {ERC721URIStorage-tokenURI} for how it is used).
function _baseURI() internal view virtual override returns (string memory) {
return _baseURI_;
return _getBaseStoryNFTStorage().baseURI;
}

/// @dev Returns the storage struct of BaseStoryNFT.
function _getBaseStoryNFTStorage() private pure returns (BaseStoryNFTStorage storage $) {
assembly {
$.slot := BaseStoryNFTStorageLocation
}
}
}
Loading
Loading