mantle-rwa-yield-router / contracts /RiskRegistry.sol
muthuk1's picture
Add contracts/RiskRegistry.sol
a5903d8 verified
Raw
History Blame
8.87 kB
// 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);
}
}