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

142 lines
4.6 KiB
Solidity

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