// SPDX-License-Identifier: MIT // PermissionedManager contract that controls minting based on available minting credits // and allows contract updates with a delay after an update proposal. pragma solidity ^0.8.20; import {IJiritsuERC20PermissionedMintingManagerV1} from "./interfaces/IJiritsuERC20PermissionedMintingManagerV1.sol"; import {IJiritsuERC20PermissionedMintingManagerV2} from "./interfaces/IJiritsuERC20PermissionedMintingManagerV2.sol"; import {IPermissionedMintingManager} from "../interfaces/IPermissionedMintingManager.sol"; import {AccessControl} from "@openzeppelin/contracts/access/AccessControl.sol"; import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; /** * @title PermissionedManager * @notice This contract controls minting permissions based on minting credits and handles contract upgrades * with a proposal system that includes a delay before an upgrade is permitted. */ contract JiritsuERC20PermissionedMintingManagerV2 is IPermissionedMintingManager, AccessControl, IJiritsuERC20PermissionedMintingManagerV2, ReentrancyGuard { uint256 public mintingCredits; // Available minting credits for permissioned minting uint256 public updateProposalTimestamp; // Timestamp when an update was proposed address public pendingAddress; // Proposed new permissioned manager address address public finalAddress; // new permissioned manager address // slither-disable-next-line naming-convention uint256 public immutable WAITING_PERIOD; // Waiting period for contract updates address public managedContract; /* The contract managed by this PermissionedManager. This value is important for security so that only the managed contract can communicate with this manager contract. */ // Role definitions bytes32 public constant CREDIT_REPORTER_ROLE = keccak256("CREDIT_REPORTER_ROLE"); bytes32 public constant SET_MANAGED_CONTRACT_ROLE = keccak256("SET_MANAGED_CONTRACT_ROLE"); // Events /** * @dev Emitted when the countdown for setting a new permissioned minting manager begins. * @param newContractAddress The address of the new permissioned minting manager contract. */ event UpgradeReviewPeriodStart(address indexed newContractAddress); /** * @dev Emitted when new minting credits are added. * @param amount The amount of minting credits added. */ event MintingCreditAdded(uint256 amount); /** * @dev Emitted when new minting credits are subtracted. * @param amount The amount of minting credits added. */ event MintingCreditSubtracted(uint256 amount); /** * @dev Emitted when a new contract is set to be managed by this PermissionedManager. * @param contractAddress The address of the contract now being managed. */ event NewManagedContract(address indexed contractAddress); // Custom Errors error NoZeroAddress(); // Thrown when a zero address is provided error OnlySetOnce(); // Thrown when attempting to set managedContract more than once error OnlyManagedContract(); // Thrown when a function restricted to the managed contract is called by another address error NotEnoughCredits(); // Thrown when the managed ERC20 contract calls checkMintingPermission() without the required credit error NotSupported(); // Thrown when an unsupported feature is called error OnlyNewManagerContract(); // Thrown when migrateStateToContract is called by a non-finalized address // Modifiers modifier onlyManagedContract() { if (msg.sender != managedContract) { revert OnlyManagedContract(); } _; } /** * @dev Constructor that sets the deployer as both the admin and a credit reporter. * @param waitingPeriod The delay period (in seconds) before an update can be finalized. */ constructor(uint256 waitingPeriod) { _grantRole(DEFAULT_ADMIN_ROLE, msg.sender); // Set deployer as the admin _grantRole(CREDIT_REPORTER_ROLE, msg.sender); // Set deployer as the credit reporter _grantRole(SET_MANAGED_CONTRACT_ROLE, msg.sender); // Set deployer as the contract setter WAITING_PERIOD = waitingPeriod; // Set the custom waiting period } /** * @notice Sets the contract to be managed by this PermissionedManager. * @dev Can only be set once. Reverts if `contractAddress` is the zero address or if it has already been set. * @param contractAddress The address of the contract to be managed. */ function setManagedContract( address contractAddress ) external onlyRole(SET_MANAGED_CONTRACT_ROLE) { if (managedContract != address(0)) revert OnlySetOnce(); if (contractAddress == address(0)) revert NoZeroAddress(); managedContract = contractAddress; emit NewManagedContract(contractAddress); } /** * @notice Adds minting credits to the system. * @dev Can only be called by an account with the `CREDIT_REPORTER_ROLE`. * @param amount The amount of minting credits to add. */ function addMintingCredits( uint256 amount ) external onlyRole(CREDIT_REPORTER_ROLE) { mintingCredits += amount; emit MintingCreditAdded(amount); } /** * @notice Allows all transfers by default. */ /* solhint-disable no-empty-blocks */ function checkTransferPermission( address /*from*/, address /*to*/, uint256 /*amount*/ ) external onlyManagedContract { // This is a stub implementation and does not perform any checks. } /** * @notice Checks if minting is allowed based on available minting credits. * @param amount The amount of tokens to mint. */ function checkMintingPermission( address /*account*/, uint256 amount, address /*sender*/ ) external onlyManagedContract { if (mintingCredits < amount) { revert NotEnoughCredits(); } mintingCredits -= amount; emit MintingCreditSubtracted(amount); } /** * @notice Initiates a cross-chain token transfer (stub implementation). */ function transferToChain( uint16 /*chainId*/, address /*account*/, uint256 /*amount*/ ) external pure { revert NotSupported(); } /** * @notice Handles token redemption (stub implementation). * @param account The account whose tokens are being redeemed. * @param amount The amount of tokens to redeem. */ // solhint-disable-next-line no-empty-blocks function triggerRedemption(address account, uint256 amount) external { // No redemption bridge messaging in this version. } /** * @notice Checks if the contract upgrade is allowed. * @dev The upgrade is allowed only after the proposal has been made and a certain time has passed. * Reverts if the caller is not the managed contract. * @param newContractAddress The address of the proposed new contract. * @return bool True if the upgrade is allowed, false otherwise. */ function isUpdateAllowed( address newContractAddress, address /*sender*/ ) external onlyManagedContract returns (bool) { if (newContractAddress == address(0)) revert NoZeroAddress(); if (newContractAddress != pendingAddress) { pendingAddress = newContractAddress; updateProposalTimestamp = block.timestamp; emit UpgradeReviewPeriodStart(newContractAddress); return false; } // Use the custom waiting period for the delay // slither-disable-next-line timestamp if (block.timestamp >= updateProposalTimestamp + WAITING_PERIOD) { finalAddress = pendingAddress; return true; } return false; } /** * @notice Migrates the state from an old contract (stub implementation). * @param oldContract The address of the old contract to migrate state from. */ function migrateStateFromContract( address oldContract ) external nonReentrant { if (msg.sender != managedContract) revert OnlyManagedContract(); // Fetch the minting credits from the old contract // slither-disable-next-line reentrancy-benign uint256 migratedCredits = IJiritsuERC20PermissionedMintingManagerV1( oldContract ).migrateStateToContract(); if (migratedCredits > 0) { mintingCredits += migratedCredits; emit MintingCreditAdded(migratedCredits); } } /** * @notice Transfers all minting credits from this contract to the new manager contract. * @dev Can only be called by the new manager contract (`finalAddress`). Resets the minting credits to zero * after transferring them and emits an event showing the amount subtracted. * @return uint The amount of minting credits that were transferred to the new manager contract. */ function migrateStateToContract() external returns (uint256) { // Ensure that only the managed contract can call this function if (msg.sender != finalAddress) revert OnlyNewManagerContract(); // Store the current amount of minting credits uint256 previousMintingCredits = mintingCredits; // Reset minting credits to zero mintingCredits = 0; // Emit an event showing that all minting credits were subtracted emit MintingCreditSubtracted(previousMintingCredits); // Return the amount of minting credits that were transferred return previousMintingCredits; } }