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

188 lines
5.8 KiB
Solidity

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