Initial commit

This commit is contained in:
Test User
2025-11-20 15:35:25 -08:00
commit bfbe3ee8b7
59 changed files with 7187 additions and 0 deletions

View File

@@ -0,0 +1,187 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import "../IPolicyModule.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
/**
* @title PolicyFlashVolume
* @notice Policy module that limits flash loan volume per time period
* @dev Prevents excessive flash loan usage
*/
contract PolicyFlashVolume is IPolicyModule, Ownable {
string public constant override name = "FlashVolume";
bool private _enabled = true;
// Time period for volume tracking (e.g., 1 day = 86400 seconds)
uint256 public periodDuration = 1 days;
// Volume limits per period
mapping(address => uint256) public assetVolumeLimit; // Per asset limit
uint256 public globalVolumeLimit = type(uint256).max; // Global limit
// Volume tracking
struct VolumePeriod {
uint256 volume;
uint256 startTime;
uint256 endTime;
}
mapping(address => mapping(uint256 => VolumePeriod)) private assetVolumes; // asset => periodId => VolumePeriod
mapping(uint256 => VolumePeriod) private globalVolumes; // periodId => VolumePeriod
event VolumeLimitUpdated(address indexed asset, uint256 oldLimit, uint256 newLimit);
event GlobalVolumeLimitUpdated(uint256 oldLimit, uint256 newLimit);
event PeriodDurationUpdated(uint256 oldDuration, uint256 newDuration);
modifier onlyEnabled() {
require(_enabled, "Policy disabled");
_;
}
constructor(address initialOwner) Ownable(initialOwner) {}
/**
* @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 (FLASH_LOAN, etc.)
* @param actionData Encoded action data: (asset, amount)
*/
function evaluate(
bytes32 actionType,
bytes memory actionData
) external view override onlyEnabled returns (PolicyDecision memory) {
if (actionType != keccak256("FLASH_LOAN")) {
return PolicyDecision({
allowed: true,
reason: ""
});
}
(address asset, uint256 amount) = abi.decode(actionData, (address, uint256));
// Get current period
uint256 periodId = getCurrentPeriodId();
// Check asset-specific limit
if (assetVolumeLimit[asset] > 0) {
VolumePeriod storage assetPeriod = assetVolumes[asset][periodId];
uint256 newVolume = assetPeriod.volume + amount;
if (newVolume > assetVolumeLimit[asset]) {
return PolicyDecision({
allowed: false,
reason: "Asset volume limit exceeded"
});
}
}
// Check global limit
if (globalVolumeLimit < type(uint256).max) {
VolumePeriod storage globalPeriod = globalVolumes[periodId];
uint256 newVolume = globalPeriod.volume + amount;
if (newVolume > globalVolumeLimit) {
return PolicyDecision({
allowed: false,
reason: "Global volume limit exceeded"
});
}
}
return PolicyDecision({
allowed: true,
reason: ""
});
}
/**
* @notice Record flash loan volume
*/
function recordVolume(address asset, uint256 amount) external {
uint256 periodId = getCurrentPeriodId();
// Update asset volume
VolumePeriod storage assetPeriod = assetVolumes[asset][periodId];
if (assetPeriod.startTime == 0) {
assetPeriod.startTime = block.timestamp;
assetPeriod.endTime = block.timestamp + periodDuration;
}
assetPeriod.volume += amount;
// Update global volume
VolumePeriod storage globalPeriod = globalVolumes[periodId];
if (globalPeriod.startTime == 0) {
globalPeriod.startTime = block.timestamp;
globalPeriod.endTime = block.timestamp + periodDuration;
}
globalPeriod.volume += amount;
}
/**
* @notice Set volume limit for an asset
*/
function setAssetVolumeLimit(address asset, uint256 limit) external onlyOwner {
require(asset != address(0), "Invalid asset");
uint256 oldLimit = assetVolumeLimit[asset];
assetVolumeLimit[asset] = limit;
emit VolumeLimitUpdated(asset, oldLimit, limit);
}
/**
* @notice Set global volume limit
*/
function setGlobalVolumeLimit(uint256 limit) external onlyOwner {
uint256 oldLimit = globalVolumeLimit;
globalVolumeLimit = limit;
emit GlobalVolumeLimitUpdated(oldLimit, limit);
}
/**
* @notice Set period duration
*/
function setPeriodDuration(uint256 duration) external onlyOwner {
require(duration > 0, "Invalid duration");
uint256 oldDuration = periodDuration;
periodDuration = duration;
emit PeriodDurationUpdated(oldDuration, duration);
}
/**
* @notice Get current period ID
*/
function getCurrentPeriodId() public view returns (uint256) {
return block.timestamp / periodDuration;
}
/**
* @notice Get current period volume for an asset
*/
function getAssetPeriodVolume(address asset) external view returns (uint256) {
uint256 periodId = getCurrentPeriodId();
return assetVolumes[asset][periodId].volume;
}
/**
* @notice Get current period global volume
*/
function getGlobalPeriodVolume() external view returns (uint256) {
uint256 periodId = getCurrentPeriodId();
return globalVolumes[periodId].volume;
}
}

View File

@@ -0,0 +1,188 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import "../IPolicyModule.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
/**
* @title PolicyHFTrend
* @notice Policy module that monitors health factor trends
* @dev Prevents actions that would worsen health factor trajectory
*/
contract PolicyHFTrend is IPolicyModule, Ownable {
string public constant override name = "HealthFactorTrend";
bool private _enabled = true;
uint256 private constant HF_SCALE = 1e18;
uint256 private minHFImprovement = 0.01e18; // 1% minimum improvement
uint256 private minHFThreshold = 1.05e18; // 1.05 minimum HF
// Track HF history for trend analysis
struct HFHistory {
uint256[] values;
uint256[] timestamps;
uint256 maxHistoryLength;
}
mapping(address => HFHistory) private vaultHistory;
event HFThresholdUpdated(uint256 oldThreshold, uint256 newThreshold);
event MinHFImprovementUpdated(uint256 oldMin, uint256 newMin);
modifier onlyEnabled() {
require(_enabled, "Policy disabled");
_;
}
constructor(address initialOwner) Ownable(initialOwner) {}
/**
* @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 (AMORTIZATION, LEVERAGE, etc.)
* @param actionData Encoded action data: (vault, hfBefore, hfAfter, collateralChange, debtChange)
*/
function evaluate(
bytes32 actionType,
bytes memory actionData
) external view override onlyEnabled returns (PolicyDecision memory) {
// Decode action data
(
address vault,
uint256 hfBefore,
uint256 hfAfter,
int256 collateralChange,
int256 debtChange
) = abi.decode(actionData, (address, uint256, uint256, int256, int256));
// Check minimum HF threshold
if (hfAfter < minHFThreshold) {
return PolicyDecision({
allowed: false,
reason: "HF below minimum threshold"
});
}
// For amortization actions, require improvement
if (actionType == keccak256("AMORTIZATION")) {
if (hfAfter <= hfBefore) {
return PolicyDecision({
allowed: false,
reason: "HF must improve"
});
}
uint256 hfImprovement = hfAfter > hfBefore ? hfAfter - hfBefore : 0;
if (hfImprovement < minHFImprovement) {
return PolicyDecision({
allowed: false,
reason: "HF improvement too small"
});
}
}
// Check trend (require improving trajectory)
if (hfAfter < hfBefore) {
return PolicyDecision({
allowed: false,
reason: "HF trend declining"
});
}
// Check that collateral increases or debt decreases (amortization requirement)
if (actionType == keccak256("AMORTIZATION")) {
if (collateralChange <= 0 && debtChange >= 0) {
return PolicyDecision({
allowed: false,
reason: "Amortization requires collateral increase or debt decrease"
});
}
}
return PolicyDecision({
allowed: true,
reason: ""
});
}
/**
* @notice Update minimum HF threshold
*/
function setMinHFThreshold(uint256 newThreshold) external onlyOwner {
require(newThreshold >= 1e18, "HF must be >= 1.0");
uint256 oldThreshold = minHFThreshold;
minHFThreshold = newThreshold;
emit HFThresholdUpdated(oldThreshold, newThreshold);
}
/**
* @notice Update minimum HF improvement required
*/
function setMinHFImprovement(uint256 newMinImprovement) external onlyOwner {
require(newMinImprovement <= HF_SCALE, "Invalid improvement");
uint256 oldMin = minHFImprovement;
minHFImprovement = newMinImprovement;
emit MinHFImprovementUpdated(oldMin, newMinImprovement);
}
/**
* @notice Get minimum HF threshold
*/
function getMinHFThreshold() external view returns (uint256) {
return minHFThreshold;
}
/**
* @notice Record HF value for trend tracking
*/
function recordHF(address vault, uint256 hf) external {
HFHistory storage history = vaultHistory[vault];
if (history.maxHistoryLength == 0) {
history.maxHistoryLength = 10; // Default max history
}
history.values.push(hf);
history.timestamps.push(block.timestamp);
// Limit history length
if (history.values.length > history.maxHistoryLength) {
// Remove oldest entry (shift array)
for (uint256 i = 0; i < history.values.length - 1; i++) {
history.values[i] = history.values[i + 1];
history.timestamps[i] = history.timestamps[i + 1];
}
history.values.pop();
history.timestamps.pop();
}
}
/**
* @notice Get HF trend (slope)
* @return trend Positive = improving, negative = declining
*/
function getHFTrend(address vault) external view returns (int256 trend) {
HFHistory storage history = vaultHistory[vault];
if (history.values.length < 2) {
return 0;
}
uint256 latest = history.values[history.values.length - 1];
uint256 previous = history.values[history.values.length - 2];
return int256(latest) - int256(previous);
}
}

View File

@@ -0,0 +1,141 @@
// 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);
}
}

View File

@@ -0,0 +1,200 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import "../IPolicyModule.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "../../interfaces/IFlashLoanRouter.sol";
/**
* @title PolicyProviderConcentration
* @notice Policy module that prevents over-concentration in single providers
* @dev Ensures diversification across flash loan providers
*/
contract PolicyProviderConcentration is IPolicyModule, Ownable {
string public constant override name = "ProviderConcentration";
bool private _enabled = true;
// Maximum percentage of total flash loans from a single provider (basis points)
uint256 public maxProviderConcentrationBps = 5000; // 50%
uint256 private constant BPS_SCALE = 10000;
// Time window for concentration tracking
uint256 public trackingWindow = 7 days;
// Provider usage tracking
struct ProviderUsage {
uint256 totalVolume;
uint256 lastResetTime;
mapping(IFlashLoanRouter.FlashLoanProvider => uint256) providerVolumes;
}
mapping(address => ProviderUsage) private vaultProviderUsage; // vault => ProviderUsage
ProviderUsage private globalProviderUsage;
event MaxConcentrationUpdated(uint256 oldMax, uint256 newMax);
event TrackingWindowUpdated(uint256 oldWindow, uint256 newWindow);
modifier onlyEnabled() {
require(_enabled, "Policy disabled");
_;
}
constructor(address initialOwner) Ownable(initialOwner) {}
/**
* @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 (FLASH_LOAN, etc.)
* @param actionData Encoded action data: (vault, asset, amount, provider)
*/
function evaluate(
bytes32 actionType,
bytes memory actionData
) external view override onlyEnabled returns (PolicyDecision memory) {
if (actionType != keccak256("FLASH_LOAN")) {
return PolicyDecision({
allowed: true,
reason: ""
});
}
(
address vault,
address asset,
uint256 amount,
IFlashLoanRouter.FlashLoanProvider provider
) = abi.decode(actionData, (address, address, uint256, IFlashLoanRouter.FlashLoanProvider));
// Reset usage if window expired
ProviderUsage storage vaultUsage = vaultProviderUsage[vault];
if (block.timestamp - vaultUsage.lastResetTime > trackingWindow) {
// Would reset in actual implementation, but for evaluation assume fresh window
vaultUsage = globalProviderUsage; // Use global as proxy for "reset" state
}
// Calculate new provider volume
uint256 newProviderVolume = vaultUsage.providerVolumes[provider] + amount;
uint256 newTotalVolume = vaultUsage.totalVolume + amount;
if (newTotalVolume > 0) {
uint256 newConcentration = (newProviderVolume * BPS_SCALE) / newTotalVolume;
if (newConcentration > maxProviderConcentrationBps) {
return PolicyDecision({
allowed: false,
reason: "Provider concentration limit exceeded"
});
}
}
return PolicyDecision({
allowed: true,
reason: ""
});
}
/**
* @notice Record flash loan usage
*/
function recordUsage(
address vault,
address asset,
uint256 amount,
IFlashLoanRouter.FlashLoanProvider provider
) external {
// Reset if window expired
ProviderUsage storage vaultUsage = vaultProviderUsage[vault];
if (block.timestamp - vaultUsage.lastResetTime > trackingWindow) {
_resetUsage(vault);
vaultUsage = vaultProviderUsage[vault];
}
// Update usage
vaultUsage.providerVolumes[provider] += amount;
vaultUsage.totalVolume += amount;
// Update global usage
ProviderUsage storage global = globalProviderUsage;
if (block.timestamp - global.lastResetTime > trackingWindow) {
_resetGlobalUsage();
global = globalProviderUsage;
}
global.providerVolumes[provider] += amount;
global.totalVolume += amount;
}
/**
* @notice Reset usage for a vault
*/
function _resetUsage(address vault) internal {
ProviderUsage storage usage = vaultProviderUsage[vault];
usage.totalVolume = 0;
usage.lastResetTime = block.timestamp;
// Reset all provider volumes
for (uint256 i = 0; i <= uint256(IFlashLoanRouter.FlashLoanProvider.DAI_FLASH); i++) {
usage.providerVolumes[IFlashLoanRouter.FlashLoanProvider(i)] = 0;
}
}
/**
* @notice Reset global usage
*/
function _resetGlobalUsage() internal {
globalProviderUsage.totalVolume = 0;
globalProviderUsage.lastResetTime = block.timestamp;
for (uint256 i = 0; i <= uint256(IFlashLoanRouter.FlashLoanProvider.DAI_FLASH); i++) {
globalProviderUsage.providerVolumes[IFlashLoanRouter.FlashLoanProvider(i)] = 0;
}
}
/**
* @notice Update maximum provider concentration
*/
function setMaxConcentration(uint256 newMaxBps) external onlyOwner {
require(newMaxBps <= BPS_SCALE, "Invalid concentration");
uint256 oldMax = maxProviderConcentrationBps;
maxProviderConcentrationBps = newMaxBps;
emit MaxConcentrationUpdated(oldMax, newMaxBps);
}
/**
* @notice Update tracking window
*/
function setTrackingWindow(uint256 newWindow) external onlyOwner {
require(newWindow > 0, "Invalid window");
uint256 oldWindow = trackingWindow;
trackingWindow = newWindow;
emit TrackingWindowUpdated(oldWindow, newWindow);
}
/**
* @notice Get provider concentration for a vault
*/
function getProviderConcentration(
address vault,
IFlashLoanRouter.FlashLoanProvider provider
) external view returns (uint256 concentrationBps) {
ProviderUsage storage usage = vaultProviderUsage[vault];
if (usage.totalVolume == 0) {
return 0;
}
return (usage.providerVolumes[provider] * BPS_SCALE) / usage.totalVolume;
}
}