// SPDX-License-Identifier: MIT pragma solidity ^0.8.24; import "@openzeppelin/contracts/access/Ownable.sol"; import "../interfaces/IPolicyEngine.sol"; import "../interfaces/IConfigRegistry.sol"; import "../interfaces/IVault.sol"; /** * @title GovernanceGuard * @notice Enforces invariants and policy checks before execution * @dev Acts as the final gatekeeper for all system actions */ contract GovernanceGuard is Ownable { IPolicyEngine public policyEngine; IConfigRegistry public configRegistry; IVault public vault; // Strategy throttling struct ThrottleConfig { uint256 dailyCap; uint256 monthlyCap; uint256 dailyCount; uint256 monthlyCount; uint256 lastDailyReset; uint256 lastMonthlyReset; } mapping(bytes32 => ThrottleConfig) private strategyThrottles; event InvariantCheckFailed(string reason); event PolicyCheckFailed(string reason); event ThrottleExceeded(string strategy, string period); modifier onlyVault() { require(msg.sender == address(vault), "Only vault"); _; } constructor( address _policyEngine, address _configRegistry, address _vault, address initialOwner ) Ownable(initialOwner) { require(_policyEngine != address(0), "Invalid policy engine"); require(_configRegistry != address(0), "Invalid config registry"); require(_vault != address(0), "Invalid vault"); policyEngine = IPolicyEngine(_policyEngine); configRegistry = IConfigRegistry(_configRegistry); vault = IVault(_vault); } /** * @notice Verify invariants before action * @param actionType Action type identifier * @param actionData Action-specific data * @return success True if all checks pass */ function verifyInvariants( bytes32 actionType, bytes memory actionData ) external view returns (bool success) { // Policy check (bool policyAllowed, string memory policyReason) = policyEngine.evaluateAll(actionType, actionData); if (!policyAllowed) { return false; // Would emit event in actual execution } // Position invariant check (for amortization actions) if (actionType == keccak256("AMORTIZATION")) { // Decode and verify position improvement // This would decode the expected position changes and verify // For now, return true - actual implementation would check } return true; } /** * @notice Check and enforce invariants (with revert) * @param actionType Action type * @param actionData Action data */ function enforceInvariants(bytes32 actionType, bytes memory actionData) external { // Policy check (bool policyAllowed, string memory policyReason) = policyEngine.evaluateAll(actionType, actionData); if (!policyAllowed) { emit PolicyCheckFailed(policyReason); revert(string(abi.encodePacked("Policy check failed: ", policyReason))); } // Throttle check if (!checkThrottle(actionType)) { emit ThrottleExceeded(_bytes32ToString(actionType), "daily or monthly"); revert("Strategy throttle exceeded"); } // Record throttle usage recordThrottleUsage(actionType); } /** * @notice Verify position improved (invariant check) * @param collateralBefore Previous collateral value * @param debtBefore Previous debt value * @param healthFactorBefore Previous health factor */ function verifyPositionImproved( uint256 collateralBefore, uint256 debtBefore, uint256 healthFactorBefore ) external view returns (bool) { return vault.verifyPositionImproved(collateralBefore, debtBefore, healthFactorBefore); } /** * @notice Check throttle limits */ function checkThrottle(bytes32 strategy) public view returns (bool) { ThrottleConfig storage throttle = strategyThrottles[strategy]; // Reset if needed uint256 currentDailyCount = throttle.dailyCount; uint256 currentMonthlyCount = throttle.monthlyCount; if (block.timestamp - throttle.lastDailyReset >= 1 days) { currentDailyCount = 0; } if (block.timestamp - throttle.lastMonthlyReset >= 30 days) { currentMonthlyCount = 0; } // Check limits if (throttle.dailyCap > 0 && currentDailyCount >= throttle.dailyCap) { return false; } if (throttle.monthlyCap > 0 && currentMonthlyCount >= throttle.monthlyCap) { return false; } return true; } /** * @notice Record throttle usage */ function recordThrottleUsage(bytes32 strategy) internal { ThrottleConfig storage throttle = strategyThrottles[strategy]; // Reset daily if needed if (block.timestamp - throttle.lastDailyReset >= 1 days) { throttle.dailyCount = 0; throttle.lastDailyReset = block.timestamp; } // Reset monthly if needed if (block.timestamp - throttle.lastMonthlyReset >= 30 days) { throttle.monthlyCount = 0; throttle.lastMonthlyReset = block.timestamp; } throttle.dailyCount++; throttle.monthlyCount++; } /** * @notice Configure throttle for a strategy */ function setThrottle( bytes32 strategy, uint256 dailyCap, uint256 monthlyCap ) external onlyOwner { strategyThrottles[strategy] = ThrottleConfig({ dailyCap: dailyCap, monthlyCap: monthlyCap, dailyCount: 0, monthlyCount: 0, lastDailyReset: block.timestamp, lastMonthlyReset: block.timestamp }); } /** * @notice Update policy engine */ function setPolicyEngine(address newPolicyEngine) external onlyOwner { require(newPolicyEngine != address(0), "Invalid policy engine"); policyEngine = IPolicyEngine(newPolicyEngine); } /** * @notice Update config registry */ function setConfigRegistry(address newConfigRegistry) external onlyOwner { require(newConfigRegistry != address(0), "Invalid config registry"); configRegistry = IConfigRegistry(newConfigRegistry); } /** * @notice Helper to convert bytes32 to string */ function _bytes32ToString(bytes32 _bytes32) private pure returns (string memory) { uint8 i = 0; while (i < 32 && _bytes32[i] != 0) { i++; } bytes memory bytesArray = new bytes(i); for (i = 0; i < 32 && _bytes32[i] != 0; i++) { bytesArray[i] = _bytes32[i]; } return string(bytesArray); } }