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

130 lines
4.0 KiB
Solidity

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import "@openzeppelin/contracts/access/Ownable.sol";
import "../interfaces/IPolicyEngine.sol";
import "../interfaces/IPolicyModule.sol";
/**
* @title PolicyEngine
* @notice Aggregates policy decisions from multiple modules
* @dev All registered modules must approve an action for it to be allowed
*/
contract PolicyEngine is IPolicyEngine, Ownable {
// Registered policy modules
address[] private policyModules;
mapping(address => bool) private isRegisteredModule;
modifier onlyRegistered(address module) {
require(isRegisteredModule[module], "Module not registered");
_;
}
constructor(address initialOwner) Ownable(initialOwner) {}
/**
* @notice Register a policy module
*/
function registerPolicyModule(address module) external override onlyOwner {
require(module != address(0), "Invalid module");
require(!isRegisteredModule[module], "Module already registered");
// Verify it implements IPolicyModule
try IPolicyModule(module).name() returns (string memory) {
// Module is valid
} catch {
revert("Invalid policy module");
}
policyModules.push(module);
isRegisteredModule[module] = true;
emit PolicyModuleRegistered(module, IPolicyModule(module).name());
}
/**
* @notice Unregister a policy module
*/
function unregisterPolicyModule(address module) external override onlyOwner onlyRegistered(module) {
// Remove from array
for (uint256 i = 0; i < policyModules.length; i++) {
if (policyModules[i] == module) {
policyModules[i] = policyModules[policyModules.length - 1];
policyModules.pop();
break;
}
}
delete isRegisteredModule[module];
emit PolicyModuleUnregistered(module);
}
/**
* @notice Evaluate all registered policy modules
* @return allowed True if ALL modules allow the action
* @return reason Reason from first denying module
*/
function evaluateAll(
bytes32 actionType,
bytes memory actionData
) external view override returns (bool allowed, string memory reason) {
// If no modules registered, allow by default
if (policyModules.length == 0) {
return (true, "");
}
// Check all modules
for (uint256 i = 0; i < policyModules.length; i++) {
address module = policyModules[i];
// Skip if module is disabled
try IPolicyModule(module).isEnabled() returns (bool enabled) {
if (!enabled) {
continue;
}
} catch {
continue; // Skip if check fails
}
// Get decision from module
IPolicyModule.PolicyDecision memory decision;
try IPolicyModule(module).evaluate(actionType, actionData) returns (IPolicyModule.PolicyDecision memory d) {
decision = d;
} catch {
// If evaluation fails, deny for safety
return (false, "Policy evaluation failed");
}
// If any module denies, return denial
if (!decision.allowed) {
return (false, decision.reason);
}
}
// All modules allowed
return (true, "");
}
/**
* @notice Get all registered policy modules
*/
function getPolicyModules() external view override returns (address[] memory) {
return policyModules;
}
/**
* @notice Check if a module is registered
*/
function isRegistered(address module) external view override returns (bool) {
return isRegisteredModule[module];
}
/**
* @notice Get number of registered modules
*/
function getModuleCount() external view returns (uint256) {
return policyModules.length;
}
}