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