// SPDX-License-Identifier: MIT pragma solidity ^0.8.24; import "../IPolicyModule.sol"; import "@openzeppelin/contracts/access/Ownable.sol"; /** * @title PolicyLiquiditySpread * @notice Policy module that validates liquidity spreads * @dev Ensures sufficient liquidity depth for operations */ contract PolicyLiquiditySpread is IPolicyModule, Ownable { string public constant override name = "LiquiditySpread"; bool private _enabled = true; // Maximum acceptable spread (basis points, e.g., 50 = 0.5%) uint256 public maxSpreadBps = 50; // 0.5% uint256 private constant BPS_SCALE = 10000; // Minimum liquidity depth required (in USD, scaled by 1e8) mapping(address => uint256) public minLiquidityDepth; // Interface for checking liquidity interface ILiquidityChecker { function getLiquidityDepth(address asset) external view returns (uint256); function getSpread(address asset, uint256 amount) external view returns (uint256); } ILiquidityChecker public liquidityChecker; event MaxSpreadUpdated(uint256 oldSpread, uint256 newSpread); event MinLiquidityDepthUpdated(address indexed asset, uint256 oldDepth, uint256 newDepth); event LiquidityCheckerUpdated(address oldChecker, address newChecker); modifier onlyEnabled() { require(_enabled, "Policy disabled"); _; } constructor(address initialOwner, address _liquidityChecker) Ownable(initialOwner) { liquidityChecker = ILiquidityChecker(_liquidityChecker); } /** * @notice Check if module is enabled */ function isEnabled() external view override returns (bool) { return _enabled; } /** * @notice Enable or disable the module */ function setEnabled(bool enabled) external override onlyOwner { _enabled = enabled; } /** * @notice Evaluate policy for proposed action * @param actionType Action type (SWAP, FLASH_LOAN, etc.) * @param actionData Encoded action data: (asset, amount, spreadBps, liquidityDepth) */ function evaluate( bytes32 actionType, bytes memory actionData ) external view override onlyEnabled returns (PolicyDecision memory) { // For swaps and flash loans, check liquidity if (actionType != keccak256("SWAP") && actionType != keccak256("FLASH_LOAN")) { return PolicyDecision({ allowed: true, reason: "" }); } (address asset, uint256 amount, uint256 spreadBps, uint256 liquidityDepth) = abi.decode(actionData, (address, uint256, uint256, uint256)); // Check spread if (spreadBps > maxSpreadBps) { return PolicyDecision({ allowed: false, reason: "Spread too high" }); } // Check minimum liquidity depth uint256 requiredDepth = minLiquidityDepth[asset]; if (requiredDepth > 0 && liquidityDepth < requiredDepth) { return PolicyDecision({ allowed: false, reason: "Insufficient liquidity depth" }); } // Additional check: ensure liquidity depth is sufficient for the amount // Rule: liquidity should be at least 2x the operation amount if (liquidityDepth < amount * 2) { return PolicyDecision({ allowed: false, reason: "Liquidity depth insufficient for operation size" }); } return PolicyDecision({ allowed: true, reason: "" }); } /** * @notice Update maximum spread */ function setMaxSpread(uint256 newSpreadBps) external onlyOwner { require(newSpreadBps <= BPS_SCALE, "Invalid spread"); uint256 oldSpread = maxSpreadBps; maxSpreadBps = newSpreadBps; emit MaxSpreadUpdated(oldSpread, newSpreadBps); } /** * @notice Update minimum liquidity depth for an asset */ function setMinLiquidityDepth(address asset, uint256 depth) external onlyOwner { require(asset != address(0), "Invalid asset"); uint256 oldDepth = minLiquidityDepth[asset]; minLiquidityDepth[asset] = depth; emit MinLiquidityDepthUpdated(asset, oldDepth, depth); } /** * @notice Update liquidity checker contract */ function setLiquidityChecker(address newChecker) external onlyOwner { require(newChecker != address(0), "Invalid checker"); address oldChecker = address(liquidityChecker); liquidityChecker = ILiquidityChecker(newChecker); emit LiquidityCheckerUpdated(oldChecker, newChecker); } }