From 8efd8a2d1a7e91d1a982da3735b9fa1cde96d4b4 Mon Sep 17 00:00:00 2001 From: Sebastian Liu Date: Sun, 15 Dec 2024 11:43:03 -0800 Subject: [PATCH] fix: inherit IERC165 & additional checks in tokenizer --- .../modules/tokenizer/IOwnableERC20.sol | 5 +--- .../modules/tokenizer/ITokenizerModule.sol | 10 +++++++ contracts/lib/Errors.sol | 10 +++++++ .../modules/tokenizer/TokenizerModule.sol | 30 +++++++++++++++++-- 4 files changed, 48 insertions(+), 7 deletions(-) diff --git a/contracts/interfaces/modules/tokenizer/IOwnableERC20.sol b/contracts/interfaces/modules/tokenizer/IOwnableERC20.sol index da86324..bf43a49 100644 --- a/contracts/interfaces/modules/tokenizer/IOwnableERC20.sol +++ b/contracts/interfaces/modules/tokenizer/IOwnableERC20.sol @@ -2,6 +2,7 @@ pragma solidity 0.8.26; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { IERC165 } from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; /// @title Ownable ERC20 Interface /// @notice Interface for the Ownable ERC20 token @@ -29,8 +30,4 @@ interface IOwnableERC20 is IERC20 { /// @notice Returns the upgradable beacon function upgradableBeacon() external view returns (address); - - /// @notice Returns whether the contract supports an interface - /// @param interfaceId The interface ID - function supportsInterface(bytes4 interfaceId) external view returns (bool); } diff --git a/contracts/interfaces/modules/tokenizer/ITokenizerModule.sol b/contracts/interfaces/modules/tokenizer/ITokenizerModule.sol index 078fa09..0227035 100644 --- a/contracts/interfaces/modules/tokenizer/ITokenizerModule.sol +++ b/contracts/interfaces/modules/tokenizer/ITokenizerModule.sol @@ -27,4 +27,14 @@ interface ITokenizerModule is IModule { /// @param initData The initialization data for the token /// @return token The address of the newly created token function tokenize(address ipId, address tokenTemplate, bytes calldata initData) external returns (address token); + + /// @notice Returns the fractionalized token for an IP + /// @param ipId The address of the IP + /// @return token The address of the token + function getFractionalizedToken(address ipId) external view returns (address token); + + /// @notice Checks if a token template is whitelisted + /// @param tokenTemplate The address of the token template + /// @return allowed The whitelisting status (true if whitelisted, false if not) + function isWhitelistedTokenTemplate(address tokenTemplate) external view returns (bool allowed); } diff --git a/contracts/lib/Errors.sol b/contracts/lib/Errors.sol index c0b5314..8d4e24d 100644 --- a/contracts/lib/Errors.sol +++ b/contracts/lib/Errors.sol @@ -104,17 +104,27 @@ library Errors { error TokenizerModule__ZeroTokenTemplate(); /// @notice Token template is not supported. + /// @param tokenTemplate The address of the token template that is not supported error TokenizerModule__UnsupportedERC20(address tokenTemplate); /// @notice IP is disputed. + /// @param ipId The address of the disputed IP error TokenizerModule__DisputedIpId(address ipId); /// @notice Token template is not whitelisted. + /// @param tokenTemplate The address of the token template error TokenizerModule__TokenTemplateNotWhitelisted(address tokenTemplate); /// @notice IP is not registered. + /// @param ipId The address of the IP error TokenizerModule__IpNotRegistered(address ipId); /// @notice IP is expired. + /// @param ipId The address of the expired IP error TokenizerModule__IpExpired(address ipId); + + /// @notice IP is already tokenized. + /// @param ipId The address of the already tokenized IP + /// @param token The address of the fractionalized token for the IP + error TokenizerModule__IpAlreadyTokenized(address ipId, address token); } diff --git a/contracts/modules/tokenizer/TokenizerModule.sol b/contracts/modules/tokenizer/TokenizerModule.sol index 90d48de..62cbe5e 100644 --- a/contracts/modules/tokenizer/TokenizerModule.sol +++ b/contracts/modules/tokenizer/TokenizerModule.sol @@ -2,9 +2,10 @@ pragma solidity 0.8.26; import { Strings } from "@openzeppelin/contracts/utils/Strings.sol"; -import { ERC165Checker } from "@openzeppelin/contracts/utils/introspection/ERC165Checker.sol"; import { BeaconProxy } from "@openzeppelin/contracts/proxy/beacon/BeaconProxy.sol"; +import { ERC165Checker } from "@openzeppelin/contracts/utils/introspection/ERC165Checker.sol"; import { UUPSUpgradeable } from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; +import { ReentrancyGuardUpgradeable } from "@openzeppelin/contracts-upgradeable/utils/ReentrancyGuardUpgradeable.sol"; import { BaseModule } from "@storyprotocol/core/modules/BaseModule.sol"; import { AccessControlled } from "@storyprotocol/core/access/AccessControlled.sol"; @@ -27,6 +28,7 @@ contract TokenizerModule is BaseModule, AccessControlled, ProtocolPausableUpgradeable, + ReentrancyGuardUpgradeable, UUPSUpgradeable { using Strings for *; @@ -35,9 +37,11 @@ contract TokenizerModule is /// @dev Storage structure for the TokenizerModule /// @param isWhitelistedTokenTemplate Mapping of token templates to their whitelisting status + /// @param fractionalizedTokens Mapping of IP IDs to their fractionalized tokens /// @custom:storage-location erc7201:story-protocol-periphery.TokenizerModule struct TokenizerModuleStorage { mapping(address => bool) isWhitelistedTokenTemplate; + mapping(address ipId => address token) fractionalizedTokens; } /// solhint-disable-next-line max-line-length @@ -76,7 +80,7 @@ contract TokenizerModule is emit TokenTemplateWhitelisted(tokenTemplate, allowed); } - /// @notice Tokenizes an IP + /// @notice Tokenizes (fractionalizes) an IP /// @param ipId The address of the IP /// @param tokenTemplate The address of the token template /// @param initData The initialization data for the token @@ -85,12 +89,14 @@ contract TokenizerModule is address ipId, address tokenTemplate, bytes calldata initData - ) external verifyPermission(ipId) returns (address token) { + ) external verifyPermission(ipId) nonReentrant returns (address token) { if (DISPUTE_MODULE.isIpTagged(ipId)) revert Errors.TokenizerModule__DisputedIpId(ipId); if (!IP_ASSET_REGISTRY.isRegistered(ipId)) revert Errors.TokenizerModule__IpNotRegistered(ipId); if (_isExpiredNow(ipId)) revert Errors.TokenizerModule__IpExpired(ipId); TokenizerModuleStorage storage $ = _getTokenizerModuleStorage(); + address existingToken = $.fractionalizedTokens[ipId]; + if (existingToken != address(0)) revert Errors.TokenizerModule__IpAlreadyTokenized(ipId, existingToken); if (!$.isWhitelistedTokenTemplate[tokenTemplate]) revert Errors.TokenizerModule__TokenTemplateNotWhitelisted(tokenTemplate); @@ -101,9 +107,27 @@ contract TokenizerModule is ) ); + $.fractionalizedTokens[ipId] = token; + emit IPTokenized(ipId, token); } + /// @notice Returns the fractionalized token for an IP + /// @param ipId The address of the IP + /// @return token The address of the token (0 address if IP has not been tokenized) + function getFractionalizedToken(address ipId) external view returns (address token) { + TokenizerModuleStorage storage $ = _getTokenizerModuleStorage(); + return $.fractionalizedTokens[ipId]; + } + + /// @notice Checks if a token template is whitelisted + /// @param tokenTemplate The address of the token template + /// @return allowed The whitelisting status (true if whitelisted, false if not) + function isWhitelistedTokenTemplate(address tokenTemplate) external view returns (bool allowed) { + TokenizerModuleStorage storage $ = _getTokenizerModuleStorage(); + return $.isWhitelistedTokenTemplate[tokenTemplate]; + } + /// @dev Check if an IP is expired now /// @param ipId The address of the IP function _isExpiredNow(address ipId) internal view returns (bool) {