diff --git a/contracts/core/stablecoin/AccessController.sol b/contracts/core/stablecoin/AccessController.sol index 153d9d6..cd0bc0d 100644 --- a/contracts/core/stablecoin/AccessController.sol +++ b/contracts/core/stablecoin/AccessController.sol @@ -2,19 +2,23 @@ pragma solidity ^0.8.28; // Importing the necessary libraries -import "@openzeppelin/contracts/access/Ownable.sol"; // Importing the Ownable contract from OpenZeppelin +import "@openzeppelin/contracts/access/AccessControl.sol"; // AccessControl contract from OpenZeppelin /** * @title AccessController * @dev This contract manages access control for the stablecoin system. */ -contract AccessController is Ownable { +contract AccessController is AccessControl { // State variables of the contract bool public mintPaused; // Flag to pause minting (0 = not paused(active), 1 = paused) bool public burnPaused; // Flag to pause burning (0 = not paused(active), 1 = paused) // Flag to indicate if the contract is in emergency mode (0 = not in emergency mode, 1 = in emergency mode) bool public emergencyMode; + // Roles for access control + bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE"); // Role for pausing + bytes32 public constant ADMIN_ROLE = keccak256("ADMIN_ROLE"); // Role for admin + // Events @@ -40,10 +44,17 @@ contract AccessController is Ownable { event EmergencyShutdown(uint256 timestamp); // constructor to initialize the access controller - constructor() Ownable(msg.sender) { + constructor() { mintPaused = false; // Initialize minting as not paused burnPaused = false; // Initialize burning as not paused emergencyMode = false; // Initialize emergency mode as not active + + // Grant the deployer the default admin role + _grantRole(DEFAULT_ADMIN_ROLE, msg.sender); + // Grant the deployer the pauser role + _grantRole(PAUSER_ROLE, msg.sender); + // Grant the deployer the admin role + _grantRole(ADMIN_ROLE, msg.sender); } /** @@ -66,39 +77,56 @@ contract AccessController is Ownable { /** * @notice Checks if pausing is allowed - * @dev This function returns true if the caller is the owner + * @dev This function returns true if the caller has the pauser role * @param caller The address of the caller * @return bool True if pausing is allowed, false otherwise */ function canPause(address caller) external view returns (bool) { - return caller == owner(); // Check if the caller is the owner + // Check if the caller has the pauser or admin role + return hasRole(PAUSER_ROLE, caller) || hasRole(ADMIN_ROLE, caller); } /** * @notice Checks if unpausing is allowed - * @dev This function returns true if the caller is the owner and not in emergency mode + * @dev This function returns true if the caller has the pauser role and not in emergency mode * @param caller The address of the caller * @return bool True if unpausing is allowed, false otherwise */ function canUnpause(address caller) external view returns (bool) { - return caller == owner() && !emergencyMode; // Check if the caller is the owner and not in emergency mode + // Check if the caller has the pauser role or admin role and the contract is not in emergency mode + // This prevents the pauser from unpausing the contract during emergency mode + return (hasRole(PAUSER_ROLE, caller) || hasRole(ADMIN_ROLE, caller)) && !emergencyMode; } /** * @notice Checks if emergency shutdown is allowed - * @dev This function returns true if the caller is the owner + * @dev This function returns true if the caller has the ADMIN_ROLE * @param caller The address of the caller * @return bool True if emergency shutdown is allowed, false otherwise */ function canEmergencyShutdown(address caller) external view returns (bool) { - return caller == owner(); // Check if the caller is the owner + return hasRole(ADMIN_ROLE, caller); // Check if the caller has the ADMIN_ROLE + } + + /** + * @notice Checks if setting parameters is allowed + * @dev This function returns true if the caller has the ADMIN_ROLE + * @param caller The address of the caller + * @return bool True if setting parameters is allowed, false otherwise + */ + function canSetParams(address caller) external view returns (bool) { + return hasRole(ADMIN_ROLE, caller); // Check if the caller has the ADMIN_ROLE } /** * @dev Pause or start minting * @param _statusMinting true to pause minting, false to start minting */ - function setMintPaused(bool _statusMinting) external onlyOwner { + function setMintPaused(bool _statusMinting) external { + // Check if the caller has the pauser role or admin role + require(hasRole(PAUSER_ROLE, msg.sender) || hasRole(ADMIN_ROLE, msg.sender), + "Caller must have pauser or admin role"); + // Check if the new minting status is different from the current minting status // This prevents unnecessary updates which could lead to gas wastage require(_statusMinting != mintPaused, "New minting status must be different from current minting status"); @@ -110,7 +138,11 @@ contract AccessController is Ownable { * @dev Pause or start burning * @param _statusBurning true to pause burning, false to start burning */ - function setBurnPaused(bool _statusBurning) external onlyOwner { + function setBurnPaused(bool _statusBurning) external { + // Check if the caller has the pauser role or admin role + require(hasRole(PAUSER_ROLE, msg.sender) || hasRole(ADMIN_ROLE, msg.sender), + "Caller must have pauser or admin role"); + // Check if the new burning status is different from the current burning status // This prevents unnecessary updates which could lead to gas wastage require(_statusBurning != burnPaused, "New burning status must be different from current burning status"); @@ -122,7 +154,7 @@ contract AccessController is Ownable { * @dev Set the emergency mode status * @param _statusEmergency true to set emergency mode, false to unset emergency mode */ - function setEmergencyMode(bool _statusEmergency) external onlyOwner { + function setEmergencyMode(bool _statusEmergency) external onlyRole(ADMIN_ROLE) { // Check if the new emergency status is different from the current emergency status // This prevents unnecessary updates which could lead to gas wastage require(_statusEmergency != emergencyMode, diff --git a/contracts/core/stablecoin/FeeManager.sol b/contracts/core/stablecoin/FeeManager.sol index 96fc366..d0a6e80 100644 --- a/contracts/core/stablecoin/FeeManager.sol +++ b/contracts/core/stablecoin/FeeManager.sol @@ -1,17 +1,21 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.28; -// Importing the necessary libraries -import "@openzeppelin/contracts/access/Ownable.sol"; // Importing the Ownable contract from OpenZeppelin +// Import the AccessController interface to manage access control +import "../../interfaces/IAccessController.sol"; /** * @title FeeManager * @dev This contract manages the fees for a stablecoin system. */ -contract FeeManager is Ownable { +contract FeeManager { // State variables of the contract uint256 public stabilityFee; // Fee charged on minting (in basis points) uint256 public redemptionFee; // Fee charged on redemption (in basis points) + address public stablecoinCore; // Address of the stablecoin core contract + + // Access control interface + IAccessController public accessController; // Access controller instance // Constants uint256 private constant BASIS_POINTS = 10000; // Basis points constant for calculations @@ -31,11 +35,47 @@ contract FeeManager is Ownable { event RedemptionFeeUpdated(uint256 newRedemptionFee); // Constructor to initialize the FeeManager contract - constructor() Ownable(msg.sender) { + constructor(address _accessController) { stabilityFee = 50; // Initial stability fee set to 0.5% redemptionFee = 30; // Initial redemption fee set to 0.3% + + // Set the access controller instance + accessController = IAccessController(_accessController); + } + + /** + * @dev sets the address of the stablecoin core contract + * This function is only used once during deployment to avoid circular dependencies + * @param _stablecoinCore The address of the stablecoin core contract + */ + function setStablecoinCore(address _stablecoinCore) external { + // Only allow this to be set once + require(stablecoinCore == address(0), "StablecoinCore already set"); + // Check if the caller is authorized to set the stablecoin core address + // This is a security measure to ensure that only authorized addresses can set the core address + require(accessController.canSetParams(msg.sender), "Not authorized"); + // Check if the new stablecoin core address is valid + require(_stablecoinCore != address(0), "Invalid address"); + stablecoinCore = _stablecoinCore; } + /** + * @dev Updates the address of the access controller + * @param _newAccessController The address of the new access controller + */ + function setAccessController(address _newAccessController) external { + // Only StablecoinCore OR admin (before StablecoinCore is set) can update + require( + msg.sender == stablecoinCore || + (stablecoinCore == address(0) && accessController.canSetParams(msg.sender)), + "Not authorized" + ); + // Check if the new access controller address is valid + require(_newAccessController != address(0), "Invalid access controller address"); + accessController = IAccessController(_newAccessController); + } + + /** * @dev Calculate the mint fee (Stability Fee) based on the amount * @param amount The amount to be minted @@ -62,7 +102,9 @@ contract FeeManager is Ownable { * @dev Update the stability fee * @param _newStabilityFee New stability fee (in basis points) */ - function updateStabilityFee(uint256 _newStabilityFee) external onlyOwner { + function updateStabilityFee(uint256 _newStabilityFee) external { + // Check if the caller is authorized to update the stability fee + require(accessController.canSetParams(msg.sender), "Not authorized to set parameters"); // Check if the new stability fee is valid (>0) and is at most 10% (1000 basis points) require(_newStabilityFee > 0 && _newStabilityFee <= 1000, "Stability fee must be between 0 and 10%"); // Check if the new stability fee is different from the current stability fee @@ -77,7 +119,9 @@ contract FeeManager is Ownable { * @dev Update the redemption fee * @param _newRedemptionFee New redemption fee (in basis points) */ - function updateRedemptionFee(uint256 _newRedemptionFee) external onlyOwner { + function updateRedemptionFee(uint256 _newRedemptionFee) external { + // Check if the caller is authorized to update the redemption fee + require(accessController.canSetParams(msg.sender), "Not authorized to set parameters"); // Check if the new redemption fee is valid (>0) and is at most 5% (500 basis points) require(_newRedemptionFee > 0 && _newRedemptionFee <= 500, "Redemption fee must be between 0 and 5%"); // Check if the new redemption fee is different from the current redemption fee diff --git a/contracts/core/stablecoin/PegMechanism.sol b/contracts/core/stablecoin/PegMechanism.sol index f81a70d..c7842bf 100644 --- a/contracts/core/stablecoin/PegMechanism.sol +++ b/contracts/core/stablecoin/PegMechanism.sol @@ -1,8 +1,8 @@ //SPDX-License-Identifier: MIT pragma solidity ^0.8.28; -// Importing the necessary libraries -import "@openzeppelin/contracts/access/Ownable.sol"; // Importing the Ownable contract from OpenZeppelin +// Import the AccessController interface to manage access control +import "../../interfaces/IAccessController.sol"; // Import all the interfaces required for the stablecoin import "../../interfaces/IOracleAggregator.sol"; // Importing the Oracle Aggregator interface @@ -12,7 +12,7 @@ import "../../interfaces/IStabilityReserve.sol"; // Importing the Stability Rese * @title PegMechanism * @dev This contract manages the peg mechanism for the stablecoin. */ -contract PegMechanism is Ownable { +contract PegMechanism { // State variables for the PegMechanism contract uint256 public pegTarget; // The target peg value for the stablecoin (e.g., 1e18 for $1) uint256 public collateralRatio; // Dynamic collateral ratio (in basis points: 10000 = 100%) @@ -22,6 +22,7 @@ contract PegMechanism is Ownable { uint256 public lowerPegThreshold; // Lower threshold for peg adjustment uint256 public emergencyPegThreshold; // Emergency threshold for peg uint256 public lastAdjustmentTime; // Last time the peg was adjusted + address public stablecoinCore; // Address of the stablecoin core contract // Constants uint256 private constant BASIS_POINTS = 10000; // Basis points constant for calculations @@ -29,6 +30,9 @@ contract PegMechanism is Ownable { // Cooldown period for peg adjustments (to prevent market turbulence) uint256 public constant ADJUSTMENT_COOLDOWN = 6 hours; + // Access control interface + IAccessController public accessController; // Access controller instance + // Events /** @@ -82,7 +86,7 @@ contract PegMechanism is Ownable { IStabilityReserve public stabilityReserve; // Stability Reserve interface // Constructor to initialize the PegMechanism contract - constructor(uint256 _initialPegTarget) Ownable(msg.sender){ + constructor(uint256 _initialPegTarget, address _accessController) { pegTarget = _initialPegTarget; // Set the initial peg target collateralRatio = 10000; // Set the initial collateral ratio to 100% minCollateralRatio = 8000; // Set the minimum collateral ratio to 80% @@ -90,13 +94,47 @@ contract PegMechanism is Ownable { upperPegThreshold = 10500; // Set the upper peg threshold to 105% lowerPegThreshold = 9500; // Set the lower peg threshold to 95% emergencyPegThreshold = 9000; // Set the emergency peg threshold to 90% + accessController = IAccessController(_accessController); // Set the access controller instance + } + + /** + * @dev sets the address of the stablecoin core contract + * This function is only used once during deployment to avoid circular dependencies + * @param _stablecoinCore The address of the stablecoin core contract + */ + function setStablecoinCore(address _stablecoinCore) external { + // Only allow this to be set once + require(stablecoinCore == address(0), "StablecoinCore already set"); + // Check if the caller is authorized to set the stablecoin core address + // This is a security measure to ensure that only authorized addresses can set the core address + require(accessController.canSetParams(msg.sender), "Not authorized"); + // Check if the new stablecoin core address is valid + require(_stablecoinCore != address(0), "Invalid address"); + stablecoinCore = _stablecoinCore; + } + + /** + * @dev Updates the address of the access controller + * @param _newAccessController The address of the new access controller + */ + function setAccessController(address _newAccessController) external { + // Only StablecoinCore OR admin (before StablecoinCore is set) can update + require( + msg.sender == stablecoinCore || + (stablecoinCore == address(0) && accessController.canSetParams(msg.sender)), + "Not authorized" + ); + // Check if the new access controller address is valid + require(_newAccessController != address(0), "Invalid access controller address"); + accessController = IAccessController(_newAccessController); } /** * @dev Set the oracle aggregator contract * @param _oracleAggregator Address of the oracle aggregator contract */ - function setOracleAggregator(address _oracleAggregator) external onlyOwner { + function setOracleAggregator(address _oracleAggregator) external { + require(accessController.canSetParams(msg.sender), "Not authorized to set parameters"); require(_oracleAggregator != address(0), "Invalid oracle aggregator address"); // Check if the address is valid oracleAggregator = IOracleAggregator(_oracleAggregator); // Set the oracle aggregator contract emit OracleAggregatorSet(_oracleAggregator); // Emit the event @@ -106,7 +144,8 @@ contract PegMechanism is Ownable { * @dev Set the stability reserve contract * @param _stabilityReserve Address of the stability reserve contract */ - function setStabilityReserve(address _stabilityReserve) external onlyOwner { + function setStabilityReserve(address _stabilityReserve) external { + require(accessController.canSetParams(msg.sender), "Not authorized to set parameters"); require(_stabilityReserve != address(0), "Invalid stability reserve address"); // Check if the address is valid stabilityReserve = IStabilityReserve(_stabilityReserve); // Set the stability reserve contract emit StabilityReserveSet(_stabilityReserve); // Emit the event @@ -124,7 +163,8 @@ contract PegMechanism is Ownable { * @dev Update the peg target value * @param _newPegTarget New peg target value */ - function updatePegTarget(uint256 _newPegTarget) external onlyOwner { + function updatePegTarget(uint256 _newPegTarget) external { + require(accessController.canSetParams(msg.sender), "Not authorized to set parameters"); // Check if the new peg target is valid require(_newPegTarget > 0, "Invalid peg target value - must be greater than 0"); // Check if the new peg target is different from the current peg target @@ -138,7 +178,8 @@ contract PegMechanism is Ownable { * @dev Update the collateral ratio * @param _newCollateralRatio New collateral ratio (in basis points) */ - function updateCollateralRatio(uint256 _newCollateralRatio) external onlyOwner { + function updateCollateralRatio(uint256 _newCollateralRatio) external { + require(accessController.canSetParams(msg.sender), "Not authorized to set parameters"); // Check if the new collateral ratio is valid (>0) and is at least 50% (5000 basis points) require(_newCollateralRatio > 5000, "Collateral ratio must be greater than 50%"); // Check if the new collateral ratio is different from the current collateral ratio @@ -157,7 +198,8 @@ contract PegMechanism is Ownable { * @param _emergencyPegThreshold New emergency peg threshold */ function updatePegThresholds(uint256 _upperPegThreshold, uint256 _lowerPegThreshold, uint256 _emergencyPegThreshold) - external onlyOwner { + external { + require(accessController.canSetParams(msg.sender), "Not authorized to set parameters"); // Check if the new peg thresholds are valid (>0) require(_upperPegThreshold > 0 && _lowerPegThreshold > 0 && _emergencyPegThreshold > 0, "Peg thresholds must be greater than 0"); @@ -178,7 +220,7 @@ contract PegMechanism is Ownable { */ function adjustPeg() external returns (bool) { require( - msg.sender == address(stabilityReserve) || msg.sender == owner(), + msg.sender == address(stabilityReserve) || accessController.canSetParams(msg.sender), "Unauthorized caller" ); require(block.timestamp >= lastAdjustmentTime + ADJUSTMENT_COOLDOWN, "Cooldown period not met"); diff --git a/contracts/core/stablecoin/StablecoinCore.sol b/contracts/core/stablecoin/StablecoinCore.sol index 17bb424..36dfd03 100644 --- a/contracts/core/stablecoin/StablecoinCore.sol +++ b/contracts/core/stablecoin/StablecoinCore.sol @@ -2,12 +2,10 @@ pragma solidity ^0.8.28; // Importing the necessary libraries -import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; // Importing the ERC20 standard from OpenZeppelin -import "@openzeppelin/contracts/access/Ownable.sol"; // Importing the Ownable contract from OpenZeppelin // Importing the ReentrancyGuard contract from OpenZeppelin import "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; -// Importing the Pausable contract from OpenZeppelin -import "@openzeppelin/contracts/utils/Pausable.sol"; // Importing the Pausable contract from OpenZeppelin +// Importing the ERC20Pausable contract from OpenZeppelin +import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Pausable.sol"; // Import all the interfaces required for the stablecoin import "../../interfaces/ICollateralPool.sol"; // Importing the Collateral Pool interface @@ -18,7 +16,11 @@ import "../../interfaces/IPegMechanism.sol"; // Importing the Peg Mechanism inte import "../../interfaces/IVolumeController.sol"; // Importing the Volume Controller interface import "../../interfaces/IAccessController.sol"; // Importing the Access Controller interface -contract StablecoinCore is ERC20, Ownable, ReentrancyGuard, Pausable { +contract StablecoinCore is ERC20Pausable, ReentrancyGuard { + // State variables + address public pendingAccessController; // Address of the pending access controller + uint256 public accessControllerChangeTime; // Time remaining for access controller change + // Module interfaces IFeeManager public feeManager; // Fee Manager interface IPegMechanism public pegMechanism; // Peg Mechanism interface @@ -92,9 +94,16 @@ contract StablecoinCore is ERC20, Ownable, ReentrancyGuard, Pausable { */ event VolumeControllerSet(address indexed volumeController); + /** + * @notice Emitted when an access controller change is proposed + * @param proposedController The address of the proposed controller + * @param effectiveTime The timestamp when the change can be confirmed + */ + event AccessControllerChangeProposed(address indexed proposedController, uint256 effectiveTime); + /** * @notice Emitted when the access controller address is set - * @dev This is fired during the setAccessController function + * @dev This is fired during the confirmAccessControllerChange function * @param accessController The address of the new access controller */ event AccessControllerSet(address indexed accessController); @@ -110,7 +119,7 @@ contract StablecoinCore is ERC20, Ownable, ReentrancyGuard, Pausable { address _pegMechanism, address _volumeController, address _accessController - ) ERC20(name_, symbol_) Ownable(msg.sender){ + ) ERC20(name_, symbol_) { // Check whether the provided addresses are valid require(_collateralPool != address(0), "Invalid collateral pool address"); require(_oracleAggregator != address(0), "Invalid oracle aggregator address"); @@ -147,7 +156,8 @@ contract StablecoinCore is ERC20, Ownable, ReentrancyGuard, Pausable { * @dev Updates the address of the fee manager * @param _newFeeManager The address of the new fee manager */ - function setFeeManager(address _newFeeManager) external onlyOwner { + function setFeeManager(address _newFeeManager) external { + require(accessController.canSetParams(msg.sender), "Not authorized to set parameters"); require(_newFeeManager != address(0), "Invalid fee manager address"); feeManager = IFeeManager(_newFeeManager); emit FeeManagerSet(_newFeeManager); @@ -157,7 +167,8 @@ contract StablecoinCore is ERC20, Ownable, ReentrancyGuard, Pausable { * @dev Updates the address of the peg mechanism * @param _newPegMechanism The address of the new peg mechanism */ - function setPegMechanism(address _newPegMechanism) external onlyOwner { + function setPegMechanism(address _newPegMechanism) external { + require(accessController.canSetParams(msg.sender), "Not authorized to set parameters"); require(_newPegMechanism != address(0), "Invalid peg mechanism address"); pegMechanism = IPegMechanism(_newPegMechanism); emit PegMechanismSet(_newPegMechanism); @@ -167,20 +178,47 @@ contract StablecoinCore is ERC20, Ownable, ReentrancyGuard, Pausable { * @dev Updates the address of the volume controller * @param _newVolumeController The address of the new volume controller */ - function setVolumeController(address _newVolumeController) external onlyOwner { + function setVolumeController(address _newVolumeController) external { + require(accessController.canSetParams(msg.sender), "Not authorized to set parameters"); require(_newVolumeController != address(0), "Invalid volume controller address"); volumeController = IVolumeController(_newVolumeController); emit VolumeControllerSet(_newVolumeController); } /** - * @dev Updates the address of the access controller - * @param _newAccessController The address of the new access controller + * @notice Proposes a new access controller + * @dev Initiates a timelock before the new controller can be activated + * @param _newAccessController The address of the proposed access controller */ - function setAccessController(address _newAccessController) external onlyOwner { + function proposeAccessControllerChange(address _newAccessController) external { + require(accessController.canSetParams(msg.sender), "Not authorized to set parameters"); + // Check if the new access controller is valid require(_newAccessController != address(0), "Invalid access controller address"); - accessController = IAccessController(_newAccessController); - emit AccessControllerSet(_newAccessController); + + pendingAccessController = _newAccessController; // Set the pending access controller + // Set the time for the access controller change + accessControllerChangeTime = block.timestamp + 2 days; + emit AccessControllerChangeProposed(_newAccessController, accessControllerChangeTime); + } + + /** + * @notice Confirms a previously proposed access controller change after timelock + * @dev Can only be executed after the timelock period has passed + */ + function confirmAccessControllerChange() external { + require(pendingAccessController != address(0), "No pending controller"); + require(block.timestamp >= accessControllerChangeTime, "Timelock not expired"); + + // Update the access controller + accessController = IAccessController(pendingAccessController); + + // Update the access controller in all modules + feeManager.setAccessController(pendingAccessController); + pegMechanism.setAccessController(pendingAccessController); + volumeController.setAccessController(pendingAccessController); + + emit AccessControllerSet(pendingAccessController); + pendingAccessController = address(0); } // Functions to update the external contract addresses (useful when external contracts need to be updated) @@ -189,7 +227,8 @@ contract StablecoinCore is ERC20, Ownable, ReentrancyGuard, Pausable { * @dev Updates the address of the collateral pool * @param _newCollateralPool The address of the new collateral pool */ - function setCollateralPool(address _newCollateralPool) external onlyOwner { + function setCollateralPool(address _newCollateralPool) external { + require(accessController.canSetParams(msg.sender), "Not authorized to set parameters"); require(_newCollateralPool != address(0), "Invalid collateral pool address"); collateralPool = ICollateralPool(_newCollateralPool); emit CollateralPoolSet(_newCollateralPool); @@ -199,7 +238,8 @@ contract StablecoinCore is ERC20, Ownable, ReentrancyGuard, Pausable { * @dev Updates the address of the oracle aggregator * @param _newOracleAggregator The address of the new oracle aggregator */ - function setOracleAggregator(address _newOracleAggregator) external onlyOwner { + function setOracleAggregator(address _newOracleAggregator) external { + require(accessController.canSetParams(msg.sender), "Not authorized to set parameters"); require(_newOracleAggregator != address(0), "Invalid oracle aggregator address"); oracleAggregator = IOracleAggregator(_newOracleAggregator); emit OracleAggregatorSet(_newOracleAggregator); @@ -212,7 +252,8 @@ contract StablecoinCore is ERC20, Ownable, ReentrancyGuard, Pausable { * @dev Updates the address of the stability reserve * @param _newStabilityReserve The address of the new stability reserve */ - function setStabilityReserve(address _newStabilityReserve) external onlyOwner { + function setStabilityReserve(address _newStabilityReserve) external { + require(accessController.canSetParams(msg.sender), "Not authorized to set parameters"); require(_newStabilityReserve != address(0), "Invalid stability reserve address"); stabilityReserve = IStabilityReserve(_newStabilityReserve); emit StabilityReserveSet(_newStabilityReserve); diff --git a/contracts/core/stablecoin/VolumeController.sol b/contracts/core/stablecoin/VolumeController.sol index 5bda781..8362114 100644 --- a/contracts/core/stablecoin/VolumeController.sol +++ b/contracts/core/stablecoin/VolumeController.sol @@ -1,14 +1,14 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.28; -// Importing the necessary libraries -import "@openzeppelin/contracts/access/Ownable.sol"; // Importing the Ownable contract from OpenZeppelin +// Import the AccessController interface to manage access control +import "../../interfaces/IAccessController.sol"; /** * @title VolumeController * @dev This contract manages the volume control for the stablecoin system. Helps prevent flash loan attacks */ - contract VolumeController is Ownable { + contract VolumeController { // State variables of the contract uint256 public mintVolumeLimit; // Maximum mint volume limit uint256 public burnVolumeLimit; // Maximum burn volume limit @@ -17,6 +17,7 @@ import "@openzeppelin/contracts/access/Ownable.sol"; // Importing the Ownable co uint256 public mintVolumeInPeriod; // Total mint volume in the current period uint256 public burnVolumeInPeriod; // Total burn volume in the current period uint256 public accountVolumeLimitPercentage = 2000; // 20% of the total mint/burn volume limit + address public stablecoinCore; // Address of the stablecoin core contract // Mapping for user volume limits mapping(address => uint256) public userMintVolume; // User mint volume tracking mapping(address => uint256) public userBurnVolume; // User burn volume tracking @@ -28,6 +29,9 @@ import "@openzeppelin/contracts/access/Ownable.sol"; // Importing the Ownable co // Cooldown period for minting and burning volume resets uint256 public constant VOLUME_RESET_COOLDOWN = 1 hours; + // Access control interface + IAccessController public accessController; // Access controller instance + /** * @notice Emitted when the mint/burn volume limits are updated * @dev This is fired during the updateVolumeLimits function @@ -37,9 +41,42 @@ import "@openzeppelin/contracts/access/Ownable.sol"; // Importing the Ownable co event VolumeLimitsUpdated(uint256 mintVolumeLimit, uint256 burnVolumeLimit); // Constructor to initialize the volume controller - constructor(uint256 _mintVolumeLimit, uint256 _burnVolumeLimit) Ownable(msg.sender) { + constructor(uint256 _mintVolumeLimit, uint256 _burnVolumeLimit, address _accessController) { mintVolumeLimit = _mintVolumeLimit; // Initialize mint volume limit burnVolumeLimit = _burnVolumeLimit; // Initialize burn volume limit + accessController = IAccessController(_accessController); // Set the access controller instance + } + + /** + * @dev sets the address of the stablecoin core contract + * This function is only used once during deployment to avoid circular dependencies + * @param _stablecoinCore The address of the stablecoin core contract + */ + function setStablecoinCore(address _stablecoinCore) external { + // Only allow this to be set once + require(stablecoinCore == address(0), "StablecoinCore already set"); + // Check if the caller is authorized to set the stablecoin core address + // This is a security measure to ensure that only authorized addresses can set the core address + require(accessController.canSetParams(msg.sender), "Not authorized"); + // Check if the new stablecoin core address is valid + require(_stablecoinCore != address(0), "Invalid address"); + stablecoinCore = _stablecoinCore; + } + + /** + * @dev Updates the address of the access controller + * @param _newAccessController The address of the new access controller + */ + function setAccessController(address _newAccessController) external { + // Only StablecoinCore OR admin (before StablecoinCore is set) can update + require( + msg.sender == stablecoinCore || + (stablecoinCore == address(0) && accessController.canSetParams(msg.sender)), + "Not authorized" + ); + // Check if the new access controller address is valid + require(_newAccessController != address(0), "Invalid access controller address"); + accessController = IAccessController(_newAccessController); } /** @@ -113,7 +150,9 @@ import "@openzeppelin/contracts/access/Ownable.sol"; // Importing the Ownable co * @param _mintVolumeLimit The new mint volume limit * @param _burnVolumeLimit The new burn volume limit */ - function updateVolumeLimits(uint256 _mintVolumeLimit, uint256 _burnVolumeLimit) external onlyOwner { + function updateVolumeLimits(uint256 _mintVolumeLimit, uint256 _burnVolumeLimit) external { + // Check if the caller is authorized to update the volume limits + require(accessController.canSetParams(msg.sender), "Not authorized to set parameters"); // Check if the new mint and burn volume limits are greater than 0 require(_mintVolumeLimit > 0 && _burnVolumeLimit > 0, "Volume limits must be greater than 0"); // Ensure the new limits are different from the current limits diff --git a/contracts/interfaces/IAccessController.sol b/contracts/interfaces/IAccessController.sol index b6d341d..a55d389 100644 --- a/contracts/interfaces/IAccessController.sol +++ b/contracts/interfaces/IAccessController.sol @@ -41,6 +41,12 @@ interface IAccessController { */ function canEmergencyShutdown(address caller) external view returns (bool); + /** + * @dev This function returns true if the caller has the ADMIN_ROLE + * @param caller The address of the caller + * @return bool True if setting parameters is allowed, false otherwise + */ + function canSetParams(address caller) external view returns (bool); /** * @dev Pause or start minting diff --git a/contracts/interfaces/IFeeManager.sol b/contracts/interfaces/IFeeManager.sol index d011b04..65de119 100644 --- a/contracts/interfaces/IFeeManager.sol +++ b/contracts/interfaces/IFeeManager.sol @@ -19,6 +19,13 @@ interface IFeeManager { */ function redemptionFee() external view returns (uint256); + + /** + * @dev Updates the address of the access controller + * @param _newAccessController The address of the new access controller + */ + function setAccessController(address _newAccessController) external; + /** * @dev Calculate the mint fee (Stability fee) for a given amount. * @param amount The amount to mint. diff --git a/contracts/interfaces/IPegMechanism.sol b/contracts/interfaces/IPegMechanism.sol index 3b8f841..db4fa6c 100644 --- a/contracts/interfaces/IPegMechanism.sol +++ b/contracts/interfaces/IPegMechanism.sol @@ -20,6 +20,12 @@ interface IPegMechanism { */ function collateralRatio() external view returns (uint256); + /** + * @dev Updates the address of the access controller + * @param _newAccessController The address of the new access controller + */ + function setAccessController(address _newAccessController) external; + /** * @dev Calculate the mint amount based on the collateral value. * @param collateralValue The value of the collateral. diff --git a/contracts/interfaces/IVolumeController.sol b/contracts/interfaces/IVolumeController.sol index 6bb5ee8..5b4eb7f 100644 --- a/contracts/interfaces/IVolumeController.sol +++ b/contracts/interfaces/IVolumeController.sol @@ -20,6 +20,12 @@ interface IVolumeController { */ function burnVolumeLimit() external view returns (uint256); + /** + * @dev Updates the address of the access controller + * @param _newAccessController The address of the new access controller + */ + function setAccessController(address _newAccessController) external; + /** * @dev This function checks and updates the mint volume for the system and the user * @param user The address of the user whose mint volume is being checked