// SPDX-License-Identifier: MIT pragma solidity ^0.8.19; import "@openzeppelin/contracts/access/Ownable.sol"; import "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; import "../../../reserve/IReserveSystem.sol"; import "./IStablecoinPegManager.sol"; /** * @title StablecoinPegManager * @notice Maintains USD peg for USDT/USDC, ETH peg for WETH, and monitors deviations * @dev Monitors peg status and triggers rebalancing if deviation exceeds thresholds */ contract StablecoinPegManager is IStablecoinPegManager, Ownable, ReentrancyGuard { IReserveSystem public immutable reserveSystem; // Peg thresholds (basis points: 10000 = 100%) uint256 public usdPegThresholdBps = 50; // ±0.5% for USD stablecoins uint256 public ethPegThresholdBps = 10; // ±0.1% for ETH/WETH uint256 public constant MAX_PEG_THRESHOLD_BPS = 500; // 5% max // Target prices (in 18 decimals) uint256 public constant USD_TARGET_PRICE = 1e18; // $1.00 uint256 public constant ETH_TARGET_PRICE = 1e18; // 1:1 with ETH // Supported assets mapping(address => bool) public isUSDStablecoin; // USDT, USDC, DAI mapping(address => bool) public isWETH; // WETH address[] public supportedAssets; struct AssetPeg { address asset; uint256 targetPrice; uint256 thresholdBps; bool isActive; } mapping(address => AssetPeg) public assetPegs; event PegChecked( address indexed asset, uint256 currentPrice, uint256 targetPrice, int256 deviationBps, bool isMaintained ); event RebalancingTriggered( address indexed asset, int256 deviationBps, uint256 requiredAdjustment ); event AssetRegistered(address indexed asset, uint256 targetPrice, uint256 thresholdBps); event PegThresholdUpdated(address indexed asset, uint256 oldThreshold, uint256 newThreshold); error ZeroAddress(); error AssetNotRegistered(); error InvalidThreshold(); error InvalidTargetPrice(); /** * @notice Constructor * @param _reserveSystem ReserveSystem contract address */ constructor(address _reserveSystem) Ownable(msg.sender) { if (_reserveSystem == address(0)) revert ZeroAddress(); reserveSystem = IReserveSystem(_reserveSystem); } /** * @notice Register a USD stablecoin * @param asset Asset address (USDT, USDC, DAI) */ function registerUSDStablecoin(address asset) external onlyOwner { if (asset == address(0)) revert ZeroAddress(); isUSDStablecoin[asset] = true; assetPegs[asset] = AssetPeg({ asset: asset, targetPrice: USD_TARGET_PRICE, thresholdBps: usdPegThresholdBps, isActive: true }); // Add to supported assets if not already present bool alreadyAdded = false; for (uint256 i = 0; i < supportedAssets.length; i++) { if (supportedAssets[i] == asset) { alreadyAdded = true; break; } } if (!alreadyAdded) { supportedAssets.push(asset); } emit AssetRegistered(asset, USD_TARGET_PRICE, usdPegThresholdBps); } /** * @notice Register WETH * @param weth WETH token address */ function registerWETH(address weth) external onlyOwner { if (weth == address(0)) revert ZeroAddress(); isWETH[weth] = true; assetPegs[weth] = AssetPeg({ asset: weth, targetPrice: ETH_TARGET_PRICE, thresholdBps: ethPegThresholdBps, isActive: true }); // Add to supported assets if not already present bool alreadyAdded = false; for (uint256 i = 0; i < supportedAssets.length; i++) { if (supportedAssets[i] == weth) { alreadyAdded = true; break; } } if (!alreadyAdded) { supportedAssets.push(weth); } emit AssetRegistered(weth, ETH_TARGET_PRICE, ethPegThresholdBps); } /** * @notice Check USD peg for a stablecoin * @param stablecoin Stablecoin address * @return isMaintained Whether peg is maintained * @return deviationBps Deviation in basis points */ function checkUSDpeg(address stablecoin) external view override returns (bool isMaintained, int256 deviationBps) { if (!isUSDStablecoin[stablecoin]) revert AssetNotRegistered(); AssetPeg memory peg = assetPegs[stablecoin]; (uint256 currentPrice, ) = reserveSystem.getPrice(stablecoin); deviationBps = calculateDeviation(stablecoin, currentPrice, peg.targetPrice); isMaintained = _abs(deviationBps) <= peg.thresholdBps; return (isMaintained, deviationBps); } /** * @notice Check ETH peg for WETH * @param weth WETH address * @return isMaintained Whether peg is maintained * @return deviationBps Deviation in basis points */ function checkETHpeg(address weth) external view override returns (bool isMaintained, int256 deviationBps) { if (!isWETH[weth]) revert AssetNotRegistered(); AssetPeg memory peg = assetPegs[weth]; (uint256 currentPrice, ) = reserveSystem.getPrice(weth); deviationBps = calculateDeviation(weth, currentPrice, peg.targetPrice); isMaintained = _abs(deviationBps) <= peg.thresholdBps; return (isMaintained, deviationBps); } /** * @notice Calculate deviation from target price * @param asset Asset address * @param currentPrice Current price * @param targetPrice Target price * @return deviationBps Deviation in basis points (can be negative) */ function calculateDeviation( address asset, uint256 currentPrice, uint256 targetPrice ) public pure override returns (int256 deviationBps) { if (targetPrice == 0) revert InvalidTargetPrice(); // Calculate deviation: ((currentPrice - targetPrice) / targetPrice) * 10000 if (currentPrice >= targetPrice) { uint256 diff = currentPrice - targetPrice; deviationBps = int256((diff * 10000) / targetPrice); } else { uint256 diff = targetPrice - currentPrice; deviationBps = -int256((diff * 10000) / targetPrice); } return deviationBps; } /** * @notice Get peg status for an asset * @param asset Asset address * @return currentPrice Current price * @return targetPrice Target price * @return deviationBps Deviation in basis points * @return isMaintained Whether peg is maintained */ function getPegStatus( address asset ) external view override returns (uint256 currentPrice, uint256 targetPrice, int256 deviationBps, bool isMaintained) { AssetPeg memory peg = assetPegs[asset]; if (peg.asset == address(0)) revert AssetNotRegistered(); (currentPrice, ) = reserveSystem.getPrice(asset); targetPrice = peg.targetPrice; deviationBps = calculateDeviation(asset, currentPrice, targetPrice); isMaintained = _abs(deviationBps) <= peg.thresholdBps; // Note: Cannot emit in view function, events should be emitted by caller if needed return (currentPrice, targetPrice, deviationBps, isMaintained); } /** * @notice Get all supported assets * @return Array of supported asset addresses */ function getSupportedAssets() external view override returns (address[] memory) { return supportedAssets; } /** * @notice Set USD peg threshold * @param newThreshold New threshold in basis points */ function setUSDPegThreshold(uint256 newThreshold) external onlyOwner { if (newThreshold > MAX_PEG_THRESHOLD_BPS) revert InvalidThreshold(); uint256 oldThreshold = usdPegThresholdBps; usdPegThresholdBps = newThreshold; // Update all USD stablecoin thresholds for (uint256 i = 0; i < supportedAssets.length; i++) { if (isUSDStablecoin[supportedAssets[i]]) { uint256 oldAssetThreshold = assetPegs[supportedAssets[i]].thresholdBps; assetPegs[supportedAssets[i]].thresholdBps = newThreshold; emit PegThresholdUpdated(supportedAssets[i], oldAssetThreshold, newThreshold); } } emit PegThresholdUpdated(address(0), oldThreshold, newThreshold); } /** * @notice Set ETH peg threshold * @param newThreshold New threshold in basis points */ function setETHPegThreshold(uint256 newThreshold) external onlyOwner { if (newThreshold > MAX_PEG_THRESHOLD_BPS) revert InvalidThreshold(); uint256 oldThreshold = ethPegThresholdBps; ethPegThresholdBps = newThreshold; // Update all WETH thresholds for (uint256 i = 0; i < supportedAssets.length; i++) { if (isWETH[supportedAssets[i]]) { uint256 oldAssetThreshold = assetPegs[supportedAssets[i]].thresholdBps; assetPegs[supportedAssets[i]].thresholdBps = newThreshold; emit PegThresholdUpdated(supportedAssets[i], oldAssetThreshold, newThreshold); } } emit PegThresholdUpdated(address(0), oldThreshold, newThreshold); } /** * @notice Trigger rebalancing if deviation exceeds threshold * @param asset Asset address */ function triggerRebalancing(address asset) external onlyOwner nonReentrant { AssetPeg memory peg = assetPegs[asset]; if (peg.asset == address(0)) revert AssetNotRegistered(); (uint256 currentPrice, ) = reserveSystem.getPrice(asset); int256 deviationBps = calculateDeviation(asset, currentPrice, peg.targetPrice); if (_abs(deviationBps) > peg.thresholdBps) { // Calculate required adjustment uint256 adjustment = currentPrice > peg.targetPrice ? currentPrice - peg.targetPrice : peg.targetPrice - currentPrice; emit RebalancingTriggered(asset, deviationBps, adjustment); } } // ============ Internal Functions ============ /** * @notice Get absolute value of int256 * @param value Input value * @return Absolute value */ function _abs(int256 value) internal pure returns (uint256) { return value < 0 ? uint256(-value) : uint256(value); } }