130 lines
4.0 KiB
Solidity
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;
|
|
}
|
|
}
|
|
|