Created
June 10, 2025 21:52
-
-
Save kmjones1979/302c41af374bdedac53c67090b547d7f to your computer and use it in GitHub Desktop.
Updated version
This file contains hidden or 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
// SPDX-License-Identifier: MIT | |
pragma solidity ^0.8.27; | |
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; | |
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; | |
import "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol"; | |
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; | |
import "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol"; | |
import "./MentorNFT.sol"; | |
error ZeroAddressAdmin(); | |
error InvalidParams(); | |
error DeploymentLimitExceeded(); | |
error ZeroSupportDuration(); | |
/** | |
* @title MentorMenteeNFTFactory (Upgradeable) | |
* @notice Factory contract for deploying MentorMenteeNFT instances. | |
* Allows authorized deployers to create their own NFT collections for token-gating. | |
* Uses UUPS proxy pattern for upgradeability. | |
* Deployment of new NFTs can be paused by an admin. | |
*/ | |
contract MentorNFTFactoryImpl is | |
Initializable, | |
OwnableUpgradeable, | |
AccessControlUpgradeable, | |
UUPSUpgradeable, | |
PausableUpgradeable | |
{ | |
bytes32 public constant ADMIN_ROLE = keccak256("ADMIN_ROLE"); | |
bytes32 public constant UPGRADER_ROLE = keccak256("UPGRADER_ROLE"); | |
bytes32 public constant DEPLOYER_ROLE = keccak256("DEPLOYER_ROLE"); | |
bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE"); | |
address[] public deployedNFTs; | |
mapping(address => address[]) public mentorToNFTs; | |
address public feeAddress; | |
uint256 public feeBasisPoints; // Fee in basis points (0-2500 = 0-25%) | |
// Track total deployments for efficient counting without array length | |
uint256 public totalDeployments; | |
/** | |
* @notice Emitted when a new MentorMenteeNFT contract is deployed. | |
* @param mentor The address of the mentor who initiated the deployment. | |
* @param nftContract The address of the newly deployed NFT contract. | |
* @param name The name of the deployed NFT collection. | |
* @param symbol The symbol of the deployed NFT collection. | |
* @param maxSupply The max supply of the deployed NFT collection. | |
* @param mintPrice The mint price (in wei) of the deployed NFT collection. | |
* @param weeklyMeetings Does this tier grant weekly meeting access? | |
* @param minsPerWeek Minutes per week commitment for this mentorship tier. | |
* @param supportDurationWeeks How many weeks the mentorship support lasts. | |
* @param feeAddress The address of the fee recipient. | |
* @param feeBasisPoints The fee basis points for the deployed NFT collection. | |
*/ | |
event NFTDeployed( | |
address indexed mentor, | |
address indexed nftContract, | |
string name, | |
string symbol, | |
uint256 maxSupply, | |
uint256 mintPrice, | |
bool weeklyMeetings, | |
uint256 minsPerWeek, | |
uint256 supportDurationWeeks, | |
address feeAddress, | |
uint256 feeBasisPoints | |
); | |
/** | |
* @notice Emitted when the fee address is updated. | |
* @param oldFeeAddress The previous fee address. | |
* @param newFeeAddress The new fee address. | |
* @param updatedBy The address that made the update. | |
*/ | |
event FeeAddressUpdated(address indexed oldFeeAddress, address indexed newFeeAddress, address indexed updatedBy); | |
/** | |
* @notice Emitted when the fee basis points are updated. | |
* @param oldFeeBasisPoints The previous fee basis points. | |
* @param newFeeBasisPoints The new fee basis points. | |
* @param updatedBy The address that made the update. | |
*/ | |
event FeeBasisPointsUpdated(uint256 oldFeeBasisPoints, uint256 newFeeBasisPoints, address indexed updatedBy); | |
/** | |
* @notice Emitted when deployer role is granted. | |
* @param account The account granted the role. | |
* @param admin The admin who granted the role. | |
*/ | |
event DeployerRoleGranted(address indexed account, address indexed admin); | |
/** | |
* @notice Emitted when deployer role is revoked. | |
* @param account The account whose role was revoked. | |
* @param admin The admin who revoked the role. | |
*/ | |
event DeployerRoleRevoked(address indexed account, address indexed admin); | |
/** | |
* @notice Initialize the contract. | |
* @param _admin The address of the admin. | |
*/ | |
function initialize(address _admin) public initializer { | |
__Ownable_init(); | |
__AccessControl_init(); | |
__UUPSUpgradeable_init(); | |
__Pausable_init(); | |
if (_admin == address(0)) { | |
revert ZeroAddressAdmin(); | |
} | |
feeAddress = _admin; | |
feeBasisPoints = 1500; // 15% in basis points | |
totalDeployments = 0; | |
_grantRole(DEFAULT_ADMIN_ROLE, _admin); | |
_grantRole(ADMIN_ROLE, _admin); | |
_grantRole(UPGRADER_ROLE, _admin); | |
_grantRole(DEPLOYER_ROLE, _admin); | |
_grantRole(PAUSER_ROLE, _admin); | |
_setRoleAdmin(PAUSER_ROLE, ADMIN_ROLE); | |
_setRoleAdmin(DEPLOYER_ROLE, ADMIN_ROLE); | |
} | |
/** | |
* @notice Deploys a new MentorMenteeNFT contract. | |
* @dev The caller (msg.sender) MUST have the DEPLOYER_ROLE. | |
* The caller is designated as the mentor and initial owner of the new NFT contract. | |
* @param _name Name for the new NFT collection. | |
* @param _symbol Symbol for the new NFT collection. | |
* @param _maxSupply Maximum supply for the new NFT collection. | |
* @param _mintPrice Mint price (in wei) for the new NFT collection. | |
* @param _minsPerWeek Mentor defined minutes per week for this tier. | |
* @param _weeklyMeetings Does this tier grant weekly meeting access (e.g., Calendly)? | |
* @param _supportDurationWeeks How many weeks does the mentorship support last? | |
* @return nftAddress The address of the newly deployed MentorMenteeNFT contract. | |
*/ | |
function deployNFT( | |
string memory _name, | |
string memory _symbol, | |
uint256 _maxSupply, | |
uint256 _mintPrice, | |
uint256 _minsPerWeek, | |
bool _weeklyMeetings, | |
uint256 _supportDurationWeeks | |
) public onlyRole(DEPLOYER_ROLE) whenNotPaused returns (address nftAddress) { | |
// Enhanced validation | |
if (bytes(_name).length == 0 || bytes(_symbol).length == 0) revert InvalidParams(); | |
if (_maxSupply == 0 || _maxSupply > 10000) revert InvalidParams(); | |
if (_supportDurationWeeks == 0 || _supportDurationWeeks > 20) revert ZeroSupportDuration(); | |
if (_minsPerWeek == 0) revert InvalidParams(); | |
if (totalDeployments >= 1000) revert DeploymentLimitExceeded(); | |
if (mentorToNFTs[msg.sender].length >= 10) revert DeploymentLimitExceeded(); | |
address mentor = msg.sender; | |
MentorNFT newNFT = new MentorNFT( | |
_name, | |
_symbol, | |
_maxSupply, | |
_mintPrice, | |
mentor, | |
mentor, | |
_weeklyMeetings, | |
_minsPerWeek, | |
_supportDurationWeeks, | |
feeAddress, | |
feeBasisPoints | |
); | |
nftAddress = address(newNFT); | |
deployedNFTs.push(nftAddress); | |
mentorToNFTs[mentor].push(nftAddress); | |
// Use efficient counter instead of array.length | |
unchecked { | |
totalDeployments++; | |
} | |
emit NFTDeployed( | |
mentor, | |
nftAddress, | |
_name, | |
_symbol, | |
_maxSupply, | |
_mintPrice, | |
_weeklyMeetings, | |
_minsPerWeek, | |
_supportDurationWeeks, | |
feeAddress, | |
feeBasisPoints | |
); | |
return nftAddress; | |
} | |
/** | |
* @notice Get total number of deployed NFTs (gas efficient). | |
* @return count The total number of deployed NFTs. | |
*/ | |
function getTotalDeployments() public view returns (uint256 count) { | |
return totalDeployments; | |
} | |
/** | |
* @notice Get deployed NFTs with pagination to avoid gas issues. | |
* @param offset Starting index. | |
* @param limit Maximum number of results to return. | |
* @return addresses Array of NFT contract addresses. | |
* @return hasMore Whether there are more results beyond this page. | |
*/ | |
function getDeployedNFTsPaginated( | |
uint256 offset, | |
uint256 limit | |
) public view returns (address[] memory addresses, bool hasMore) { | |
uint256 totalLength = deployedNFTs.length; | |
if (offset >= totalLength) { | |
return (new address[](0), false); | |
} | |
uint256 end = offset + limit; | |
if (end > totalLength) { | |
end = totalLength; | |
} | |
uint256 resultLength = end - offset; | |
addresses = new address[](resultLength); | |
for (uint256 i = 0; i < resultLength; i++) { | |
addresses[i] = deployedNFTs[offset + i]; | |
} | |
hasMore = end < totalLength; | |
} | |
/** | |
* @notice Get all NFT contracts deployed by this factory. | |
* @dev WARNING: This can run out of gas for large arrays. Use pagination instead. | |
* @return addresses An array of deployed NFT contract addresses. | |
*/ | |
function getAllDeployedNFTs() public view returns (address[] memory addresses) { | |
return deployedNFTs; | |
} | |
/** | |
* @notice Get all NFT contracts deployed by a specific mentor. | |
* @param _mentor The address of the mentor. | |
* @return addresses An array of NFT contract addresses deployed by the mentor. | |
*/ | |
function getNFTsByMentor(address _mentor) public view returns (address[] memory addresses) { | |
return mentorToNFTs[_mentor]; | |
} | |
/** | |
* @notice Get number of NFTs deployed by a specific mentor. | |
* @param _mentor The address of the mentor. | |
* @return count The number of NFTs deployed by the mentor. | |
*/ | |
function getMentorDeploymentCount(address _mentor) public view returns (uint256 count) { | |
return mentorToNFTs[_mentor].length; | |
} | |
/** | |
* @notice Set the fee address. | |
* @param _feeAddress The address of the fee recipient. | |
*/ | |
function setFeeAddress(address _feeAddress) public onlyRole(ADMIN_ROLE) { | |
if (_feeAddress == address(0)) revert ZeroAddressAdmin(); | |
address oldFeeAddress = feeAddress; | |
feeAddress = _feeAddress; | |
emit FeeAddressUpdated(oldFeeAddress, _feeAddress, msg.sender); | |
} | |
/** | |
* @notice Set the fee basis points. | |
* @param _feeBasisPoints The fee basis points for the deployed NFT collection. | |
*/ | |
function setFeeBasisPoints(uint256 _feeBasisPoints) public onlyRole(ADMIN_ROLE) { | |
if (_feeBasisPoints > 2500) revert InvalidParams(); // Max 25% | |
uint256 oldFeeBasisPoints = feeBasisPoints; | |
feeBasisPoints = _feeBasisPoints; | |
emit FeeBasisPointsUpdated(oldFeeBasisPoints, _feeBasisPoints, msg.sender); | |
} | |
/** | |
* @notice Authorize the upgrade of the contract. | |
* @param newImplementation The address of the new implementation. | |
*/ | |
function _authorizeUpgrade(address newImplementation) internal override onlyRole(UPGRADER_ROLE) {} | |
/** | |
* @notice Grant the DEPLOYER_ROLE to an account. | |
* @param _account The address of the account to grant the DEPLOYER_ROLE to. | |
*/ | |
function grantDeployerRole(address _account) public onlyRole(ADMIN_ROLE) { | |
if (_account == address(0)) revert ZeroAddressAdmin(); | |
grantRole(DEPLOYER_ROLE, _account); | |
emit DeployerRoleGranted(_account, msg.sender); | |
} | |
/** | |
* @notice Revoke the DEPLOYER_ROLE from an account. | |
* @param _account The address of the account to revoke the DEPLOYER_ROLE from. | |
*/ | |
function revokeDeployerRole(address _account) public onlyRole(ADMIN_ROLE) { | |
revokeRole(DEPLOYER_ROLE, _account); | |
emit DeployerRoleRevoked(_account, msg.sender); | |
} | |
/** | |
* @notice Pause the contract. | |
* @dev Callable only by accounts with PAUSER_ROLE. | |
* Prevents actions modified by `whenNotPaused`. | |
*/ | |
function pause() public virtual onlyRole(PAUSER_ROLE) { | |
_pause(); | |
} | |
/** | |
* @notice Unpauses the contract. | |
* @dev Callable only by accounts with PAUSER_ROLE. | |
* Resumes actions modified by `whenNotPaused`. | |
*/ | |
function unpause() public virtual onlyRole(PAUSER_ROLE) { | |
_unpause(); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment