- Created .gitignore to exclude sensitive files and directories. - Added API documentation in API_DOCUMENTATION.md. - Included deployment instructions in DEPLOYMENT.md. - Established project structure documentation in PROJECT_STRUCTURE.md. - Updated README.md with project status and team information. - Added recommendations and status tracking documents. - Introduced testing guidelines in TESTING.md. - Set up CI workflow in .github/workflows/ci.yml. - Created Dockerfile for backend and frontend setups. - Added various service and utility files for backend functionality. - Implemented frontend components and pages for user interface. - Included mobile app structure and services. - Established scripts for deployment across multiple chains.
219 lines
7.5 KiB
Solidity
219 lines
7.5 KiB
Solidity
// SPDX-License-Identifier: MIT
|
|
pragma solidity ^0.8.24;
|
|
|
|
import {ISecurityFacet} from "../../interfaces/ISecurityFacet.sol";
|
|
import {LibDiamond} from "../../libraries/LibDiamond.sol";
|
|
import {LibAccessControl} from "../../libraries/LibAccessControl.sol";
|
|
import {ILiquidityFacet} from "../../interfaces/ILiquidityFacet.sol";
|
|
|
|
contract SecurityFacet is ISecurityFacet {
|
|
struct SecurityStorage {
|
|
bool paused;
|
|
PauseReason pauseReason;
|
|
address pausedBy;
|
|
uint256 pauseTime;
|
|
uint256 maxPauseDuration; // Maximum pause duration in seconds (0 = unlimited)
|
|
mapping(uint256 => CircuitBreaker) circuitBreakers;
|
|
mapping(string => uint256) lastAuditTime;
|
|
mapping(uint256 => uint256) poolPriceHistory; // poolId => last price
|
|
mapping(uint256 => uint256) maxPriceDeviation; // poolId => max deviation in basis points
|
|
}
|
|
|
|
bytes32 private constant SECURITY_STORAGE_POSITION = keccak256("asle.security.storage");
|
|
|
|
function securityStorage() internal pure returns (SecurityStorage storage ss) {
|
|
bytes32 position = SECURITY_STORAGE_POSITION;
|
|
assembly {
|
|
ss.slot := position
|
|
}
|
|
}
|
|
|
|
modifier whenNotPaused() {
|
|
require(!securityStorage().paused, "SecurityFacet: System is paused");
|
|
_;
|
|
}
|
|
|
|
modifier onlyAuthorized() {
|
|
require(
|
|
LibAccessControl.hasRole(LibAccessControl.SECURITY_ADMIN_ROLE, msg.sender) ||
|
|
LibAccessControl.hasRole(LibAccessControl.DEFAULT_ADMIN_ROLE, msg.sender),
|
|
"SecurityFacet: Not authorized"
|
|
);
|
|
_;
|
|
}
|
|
|
|
function pauseSystem(PauseReason reason) external override onlyAuthorized {
|
|
SecurityStorage storage ss = securityStorage();
|
|
require(!ss.paused, "SecurityFacet: Already paused");
|
|
|
|
ss.paused = true;
|
|
ss.pauseReason = reason;
|
|
ss.pausedBy = msg.sender;
|
|
ss.pauseTime = block.timestamp;
|
|
|
|
emit SystemPaused(reason, msg.sender);
|
|
}
|
|
|
|
function pauseSystemWithDuration(PauseReason reason, uint256 duration) external onlyAuthorized {
|
|
SecurityStorage storage ss = securityStorage();
|
|
require(!ss.paused, "SecurityFacet: Already paused");
|
|
|
|
ss.paused = true;
|
|
ss.pauseReason = reason;
|
|
ss.pausedBy = msg.sender;
|
|
ss.pauseTime = block.timestamp;
|
|
ss.maxPauseDuration = duration;
|
|
|
|
emit SystemPaused(reason, msg.sender);
|
|
}
|
|
|
|
function unpauseSystem() external override onlyAuthorized {
|
|
SecurityStorage storage ss = securityStorage();
|
|
require(ss.paused, "SecurityFacet: Not paused");
|
|
|
|
// Check if pause has expired (if max duration is set)
|
|
if (ss.maxPauseDuration > 0) {
|
|
require(block.timestamp >= ss.pauseTime + ss.maxPauseDuration, "SecurityFacet: Pause duration not expired");
|
|
}
|
|
|
|
ss.paused = false;
|
|
address unpauser = msg.sender;
|
|
ss.maxPauseDuration = 0;
|
|
|
|
emit SystemUnpaused(unpauser);
|
|
}
|
|
|
|
function isPaused() external view override returns (bool) {
|
|
return securityStorage().paused;
|
|
}
|
|
|
|
function setCircuitBreaker(
|
|
uint256 poolId,
|
|
uint256 threshold,
|
|
uint256 timeWindow
|
|
) external override onlyAuthorized {
|
|
SecurityStorage storage ss = securityStorage();
|
|
ss.circuitBreakers[poolId] = CircuitBreaker({
|
|
threshold: threshold,
|
|
timeWindow: timeWindow,
|
|
currentValue: 0,
|
|
windowStart: block.timestamp,
|
|
triggered: false
|
|
});
|
|
}
|
|
|
|
function checkCircuitBreaker(uint256 poolId, uint256 value) external override returns (bool) {
|
|
SecurityStorage storage ss = securityStorage();
|
|
CircuitBreaker storage cb = ss.circuitBreakers[poolId];
|
|
|
|
if (cb.triggered) {
|
|
return false; // Circuit breaker already triggered
|
|
}
|
|
|
|
// Reset window if expired
|
|
if (block.timestamp > cb.windowStart + cb.timeWindow) {
|
|
cb.windowStart = block.timestamp;
|
|
cb.currentValue = 0;
|
|
}
|
|
|
|
cb.currentValue += value;
|
|
|
|
if (cb.currentValue > cb.threshold) {
|
|
cb.triggered = true;
|
|
emit CircuitBreakerTriggered(poolId, cb.currentValue);
|
|
|
|
// Automatically pause if circuit breaker triggers
|
|
if (!ss.paused) {
|
|
ss.paused = true;
|
|
ss.pauseReason = PauseReason.CircuitBreaker;
|
|
ss.pausedBy = address(this);
|
|
ss.pauseTime = block.timestamp;
|
|
emit SystemPaused(PauseReason.CircuitBreaker, address(this));
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
function resetCircuitBreaker(uint256 poolId) external onlyAuthorized {
|
|
SecurityStorage storage ss = securityStorage();
|
|
CircuitBreaker storage cb = ss.circuitBreakers[poolId];
|
|
require(cb.triggered, "SecurityFacet: Circuit breaker not triggered");
|
|
|
|
cb.triggered = false;
|
|
cb.currentValue = 0;
|
|
cb.windowStart = block.timestamp;
|
|
}
|
|
|
|
function triggerCircuitBreaker(uint256 poolId) external override {
|
|
SecurityStorage storage ss = securityStorage();
|
|
CircuitBreaker storage cb = ss.circuitBreakers[poolId];
|
|
|
|
require(!cb.triggered, "SecurityFacet: Already triggered");
|
|
|
|
cb.triggered = true;
|
|
emit CircuitBreakerTriggered(poolId, cb.currentValue);
|
|
|
|
// Optionally pause the system
|
|
// Note: This would need to be called externally or through Diamond
|
|
// pauseSystem(PauseReason.CircuitBreaker);
|
|
}
|
|
|
|
function recordSecurityAudit(string calldata auditType, bool passed) external override onlyAuthorized {
|
|
SecurityStorage storage ss = securityStorage();
|
|
ss.lastAuditTime[auditType] = block.timestamp;
|
|
|
|
emit SecurityAudit(block.timestamp, auditType, passed);
|
|
|
|
if (!passed && !ss.paused) {
|
|
ss.paused = true;
|
|
ss.pauseReason = PauseReason.ComplianceViolation;
|
|
ss.pausedBy = msg.sender;
|
|
ss.pauseTime = block.timestamp;
|
|
emit SystemPaused(PauseReason.ComplianceViolation, msg.sender);
|
|
}
|
|
}
|
|
|
|
function checkPriceDeviation(uint256 poolId, uint256 currentPrice) external returns (bool) {
|
|
SecurityStorage storage ss = securityStorage();
|
|
uint256 lastPrice = ss.poolPriceHistory[poolId];
|
|
uint256 maxDeviation = ss.maxPriceDeviation[poolId];
|
|
|
|
if (lastPrice == 0) {
|
|
ss.poolPriceHistory[poolId] = currentPrice;
|
|
return true;
|
|
}
|
|
|
|
if (maxDeviation == 0) {
|
|
maxDeviation = 1000; // Default 10% deviation
|
|
}
|
|
|
|
uint256 deviation;
|
|
if (currentPrice > lastPrice) {
|
|
deviation = ((currentPrice - lastPrice) * 10000) / lastPrice;
|
|
} else {
|
|
deviation = ((lastPrice - currentPrice) * 10000) / lastPrice;
|
|
}
|
|
|
|
if (deviation > maxDeviation) {
|
|
// Trigger circuit breaker or pause
|
|
CircuitBreaker storage cb = ss.circuitBreakers[poolId];
|
|
if (!cb.triggered) {
|
|
cb.triggered = true;
|
|
emit CircuitBreakerTriggered(poolId, deviation);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
ss.poolPriceHistory[poolId] = currentPrice;
|
|
return true;
|
|
}
|
|
|
|
function setMaxPriceDeviation(uint256 poolId, uint256 maxDeviation) external onlyAuthorized {
|
|
securityStorage().maxPriceDeviation[poolId] = maxDeviation;
|
|
}
|
|
}
|
|
|