// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; import "@openzeppelin/contracts/access/Ownable.sol"; /** * @title RiskRegistry * @notice On-chain registry of risk parameters, depeg thresholds, * and circuit breaker configuration for the RWA Yield Router. * * @dev Serves as the source of truth for risk parameters that both * the off-chain RL agent and on-chain contracts reference. * Parameters can be updated via governance or authorized operators. */ contract RiskRegistry is Ownable { // ─────────────────── Types ────────────────────── struct AssetRiskParams { uint256 maxWeight; // Max allocation in bps (10000 = 100%) uint256 minWeight; // Min allocation in bps uint256 depegThresholdBps; // Depeg detection threshold in bps uint256 smartContractRisk; // Risk score 0-10000 uint256 liquidityScore; // Liquidity depth score 0-10000 bool active; } struct GlobalRiskParams { uint256 maxDrawdownBps; // Max portfolio drawdown trigger (bps) uint256 rebalanceThresholdBps; // Min drift to trigger rebalance (bps) uint256 minRebalanceInterval; // Minimum seconds between rebalances uint256 maxGasPerRebalanceUsd; // Max gas budget per rebalance (USD * 1e18) uint256 maxSlippageBps; // Max acceptable slippage (bps) uint256 circuitBreakerCooldown; // Seconds before circuit breaker resets } struct DepegEvent { address asset; uint256 timestamp; uint256 deviationBps; string description; bool resolved; } // ─────────────────── State ────────────────────── // Asset address → risk parameters mapping(address => AssetRiskParams) public assetRisk; // Global risk parameters GlobalRiskParams public globalRisk; // Depeg event history DepegEvent[] public depegEvents; // Authorized risk managers mapping(address => bool) public riskManagers; // Asset registry address[] public registeredAssets; // ─────────────────── Events ───────────────────── event AssetRiskUpdated(address indexed asset, uint256 maxWeight, uint256 smartContractRisk); event GlobalRiskUpdated(uint256 maxDrawdown, uint256 rebalanceThreshold); event DepegDetected(address indexed asset, uint256 deviationBps, string description); event DepegResolved(uint256 indexed eventIndex); event RiskManagerUpdated(address indexed manager, bool authorized); // ─────────────────── Constructor ───────────────── constructor() Ownable(msg.sender) { // Default global risk parameters globalRisk = GlobalRiskParams({ maxDrawdownBps: 1000, // 10% rebalanceThresholdBps: 500, // 5% minRebalanceInterval: 4 hours, maxGasPerRebalanceUsd: 5e18, // $5 maxSlippageBps: 50, // 0.5% circuitBreakerCooldown: 24 hours }); } // ─────────────────── Asset Risk Management ────── /** * @notice Register or update risk parameters for an asset. */ function setAssetRisk( address asset, uint256 maxWeight, uint256 minWeight, uint256 depegThresholdBps, uint256 smartContractRisk, uint256 liquidityScore ) external onlyRiskManager { require(maxWeight <= 9000, "Max weight too high"); require(minWeight >= 100, "Min weight too low"); require(smartContractRisk <= 10000, "Risk score out of range"); bool isNew = !assetRisk[asset].active; assetRisk[asset] = AssetRiskParams({ maxWeight: maxWeight, minWeight: minWeight, depegThresholdBps: depegThresholdBps, smartContractRisk: smartContractRisk, liquidityScore: liquidityScore, active: true }); if (isNew) { registeredAssets.push(asset); } emit AssetRiskUpdated(asset, maxWeight, smartContractRisk); } /** * @notice Update global risk parameters. */ function setGlobalRisk( uint256 maxDrawdownBps, uint256 rebalanceThresholdBps, uint256 minRebalanceInterval, uint256 maxGasPerRebalanceUsd, uint256 maxSlippageBps, uint256 circuitBreakerCooldown ) external onlyRiskManager { globalRisk = GlobalRiskParams({ maxDrawdownBps: maxDrawdownBps, rebalanceThresholdBps: rebalanceThresholdBps, minRebalanceInterval: minRebalanceInterval, maxGasPerRebalanceUsd: maxGasPerRebalanceUsd, maxSlippageBps: maxSlippageBps, circuitBreakerCooldown: circuitBreakerCooldown }); emit GlobalRiskUpdated(maxDrawdownBps, rebalanceThresholdBps); } // ─────────────────── Depeg Tracking ───────────── /** * @notice Record a depeg event. */ function reportDepeg( address asset, uint256 deviationBps, string calldata description ) external onlyRiskManager { depegEvents.push(DepegEvent({ asset: asset, timestamp: block.timestamp, deviationBps: deviationBps, description: description, resolved: false })); emit DepegDetected(asset, deviationBps, description); } /** * @notice Mark a depeg event as resolved. */ function resolveDepeg(uint256 eventIndex) external onlyRiskManager { require(eventIndex < depegEvents.length, "Invalid index"); depegEvents[eventIndex].resolved = true; emit DepegResolved(eventIndex); } // ─────────────────── View Functions ───────────── function getRegisteredAssetCount() external view returns (uint256) { return registeredAssets.length; } function getDepegEventCount() external view returns (uint256) { return depegEvents.length; } function getActiveDepegEvents() external view returns (DepegEvent[] memory) { uint256 activeCount = 0; for (uint256 i = 0; i < depegEvents.length; i++) { if (!depegEvents[i].resolved) activeCount++; } DepegEvent[] memory active = new DepegEvent[](activeCount); uint256 j = 0; for (uint256 i = 0; i < depegEvents.length; i++) { if (!depegEvents[i].resolved) { active[j++] = depegEvents[i]; } } return active; } /** * @notice Check if a proposed allocation respects all asset risk limits. * @param assets Asset addresses * @param weights Proposed weights in bps * @return valid Whether the allocation is valid * @return reason Failure reason if invalid */ function validateAllocation( address[] calldata assets, uint256[] calldata weights ) external view returns (bool valid, string memory reason) { require(assets.length == weights.length, "Length mismatch"); uint256 totalWeight = 0; for (uint256 i = 0; i < assets.length; i++) { AssetRiskParams memory params = assetRisk[assets[i]]; if (!params.active) { return (false, "Asset not registered"); } if (weights[i] > params.maxWeight) { return (false, "Weight exceeds asset max"); } if (weights[i] < params.minWeight) { return (false, "Weight below asset min"); } totalWeight += weights[i]; } if (totalWeight != 10000) { return (false, "Weights don't sum to 100%"); } return (true, ""); } // ─────────────────── Access Control ───────────── modifier onlyRiskManager() { require( msg.sender == owner() || riskManagers[msg.sender], "Not a risk manager" ); _; } function setRiskManager(address manager, bool authorized) external onlyOwner { riskManagers[manager] = authorized; emit RiskManagerUpdated(manager, authorized); } }