| |
| pragma solidity ^0.8.20; |
|
|
| import "@openzeppelin/contracts/access/Ownable.sol"; |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| contract RiskRegistry is Ownable { |
|
|
| |
|
|
| struct AssetRiskParams { |
| uint256 maxWeight; |
| uint256 minWeight; |
| uint256 depegThresholdBps; |
| uint256 smartContractRisk; |
| uint256 liquidityScore; |
| bool active; |
| } |
|
|
| struct GlobalRiskParams { |
| uint256 maxDrawdownBps; |
| uint256 rebalanceThresholdBps; |
| uint256 minRebalanceInterval; |
| uint256 maxGasPerRebalanceUsd; |
| uint256 maxSlippageBps; |
| uint256 circuitBreakerCooldown; |
| } |
|
|
| struct DepegEvent { |
| address asset; |
| uint256 timestamp; |
| uint256 deviationBps; |
| string description; |
| bool resolved; |
| } |
|
|
| |
|
|
| |
| mapping(address => AssetRiskParams) public assetRisk; |
| |
| |
| GlobalRiskParams public globalRisk; |
| |
| |
| DepegEvent[] public depegEvents; |
| |
| |
| mapping(address => bool) public riskManagers; |
| |
| |
| address[] public registeredAssets; |
|
|
| |
|
|
| 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() Ownable(msg.sender) { |
| |
| globalRisk = GlobalRiskParams({ |
| maxDrawdownBps: 1000, |
| rebalanceThresholdBps: 500, |
| minRebalanceInterval: 4 hours, |
| maxGasPerRebalanceUsd: 5e18, |
| maxSlippageBps: 50, |
| circuitBreakerCooldown: 24 hours |
| }); |
| } |
|
|
| |
|
|
| |
| |
| |
| 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); |
| } |
|
|
| |
| |
| |
| 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); |
| } |
|
|
| |
|
|
| |
| |
| |
| 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); |
| } |
|
|
| |
| |
| |
| function resolveDepeg(uint256 eventIndex) external onlyRiskManager { |
| require(eventIndex < depegEvents.length, "Invalid index"); |
| depegEvents[eventIndex].resolved = true; |
| emit DepegResolved(eventIndex); |
| } |
|
|
| |
|
|
| 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; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| 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, ""); |
| } |
|
|
| |
|
|
| 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); |
| } |
| } |
|
|