Files
no_five/contracts/governance/GovernanceGuard.sol
2025-11-20 15:35:25 -08:00

218 lines
6.8 KiB
Solidity

// 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);
}
}