Security Architecture
Loopify Finance implements comprehensive security measures across flash loan operations, yield optimization strategies, and user fund protection.
Core Security Principles
Focus on three strategic areas:
- Flash Loan Looping - Leveraged yield strategies
- Yield Optimization - Automated yield farming
- Pre-deposit System - Points-based early access with TGE conversion
Smart Contract Security
Access Control & Role Management
// Complete role-based access control with hierarchy
bytes32 public constant STRATEGIST_ROLE = keccak256("STRATEGIST_ROLE");
bytes32 public constant LIQUIDATOR_ROLE = keccak256("LIQUIDATOR_ROLE");
bytes32 public constant KEEPER_ROLE = keccak256("KEEPER_ROLE");
bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE");
bytes32 public constant RECOVERY_ROLE = keccak256("RECOVERY_ROLE");
bytes32 public constant GUARDIAN_ROLE = keccak256("GUARDIAN_ROLE");
uint256 public constant TIMELOCK_DURATION = 48 hours;
// Role hierarchy setup
function _setupRoles() internal {
_setRoleAdmin(STRATEGIST_ROLE, DEFAULT_ADMIN_ROLE);
_setRoleAdmin(LIQUIDATOR_ROLE, DEFAULT_ADMIN_ROLE);
_setRoleAdmin(KEEPER_ROLE, STRATEGIST_ROLE);
_setRoleAdmin(PAUSER_ROLE, GUARDIAN_ROLE);
_setRoleAdmin(RECOVERY_ROLE, GUARDIAN_ROLE);
}
// Role renunciation mechanism
function renounceRoleWithTimelock(bytes32 role) external {
require(hasRole(role, msg.sender), "Caller doesn't have role");
_scheduleRoleRenunciation(role, msg.sender, block.timestamp + TIMELOCK_DURATION);
}
Key Roles:
- Guardian: Highest security role for emergency actions
- Strategist: Strategy deployment and configuration
- Liquidator: Emergency position liquidation
- Keeper: Automated monitoring and rebalancing
- Pauser: Emergency pause functionality
- Recovery: Stuck position recovery operations
Flash Loan Security
// Enhanced flash loan security with gradual scaling
uint256 public constant MAX_FLASH_LOAN_AMOUNT = 10_000_000e18; // Start with $10M
uint256 public constant FLASH_LOAN_FEE_TOLERANCE = 10; // 0.1% max fee
uint256 public constant DEFAULT_USER_LIMIT = 1_000_000e18; // $1M default
mapping(address => uint256) public userFlashLoanLimit;
mapping(address => uint256) public userTrustScore;
uint256 public globalFlashLoanUsage;
function validateFlashLoanExecution(
address user,
uint256 amount,
uint256 fee
) internal nonReentrant returns (bool) {
require(amount <= getUserFlashLoanLimit(user), "Exceeds user limit");
require(globalFlashLoanUsage + amount <= MAX_FLASH_LOAN_AMOUNT, "Exceeds global limit");
require(fee <= (amount * FLASH_LOAN_FEE_TOLERANCE) / 10000, "Fee too high");
globalFlashLoanUsage += amount;
return true;
}
// Trust-based user limits
function getUserFlashLoanLimit(address user) public view returns (uint256) {
uint256 trustScore = userTrustScore[user];
if (trustScore >= 100) return MAX_FLASH_LOAN_AMOUNT;
if (trustScore >= 50) return MAX_FLASH_LOAN_AMOUNT / 2;
if (trustScore >= 10) return MAX_FLASH_LOAN_AMOUNT / 10;
return DEFAULT_USER_LIMIT;
}
Health Factor Monitoring
Continuous position health tracking with automated protection:
// FIXED: Correct health factor calculation with proper decimals
uint256 public constant SAFETY_BUFFER = 5e16; // 5% buffer
uint256 public constant MIN_HEALTH_FACTOR = 101e16; // 1.01 liquidation threshold
function calculateHealthFactor(
uint256 collateralValue,
uint256 debtValue,
uint256 liquidationThreshold
) public pure returns (uint256) {
require(liquidationThreshold <= 100, "Invalid threshold > 100%");
require(liquidationThreshold > 0, "Invalid threshold = 0");
if (debtValue == 0) return type(uint256).max;
// FIXED: Proper calculation with 18 decimal precision (divide by 100, not 10000)
return (collateralValue * liquidationThreshold * 1e18) / (debtValue * 100);
}
// Enhanced validation with safety buffer
function isPositionSafe(
uint256 collateralValue,
uint256 debtValue,
uint256 liquidationThreshold
) public pure returns (bool) {
uint256 healthFactor = calculateHealthFactor(collateralValue, debtValue, liquidationThreshold);
return healthFactor >= MIN_HEALTH_FACTOR + SAFETY_BUFFER;
}
Protection Thresholds (18 decimal precision):
- Safe: > 2.0e18 (200% health factor)
- Warning: 1.5e18 - 2.0e18 (150-200%)
- Critical: 1.05e18 - 1.5e18 (105-150%)
- Emergency: < 1.05e18 (auto-deleverage)
Operational Security
Circuit Breakers & Emergency Controls
// Emergency pause system
function emergencyShutdown(string calldata reason) external onlyRole(PAUSER_ROLE) {
_pause();
// Pause all strategy types
strategyPaused[keccak256("FLASH_LOAN_LOOPING")] = true;
strategyPaused[keccak256("YIELD_OPTIMIZATION")] = true;
strategyPaused[keccak256("PRE_DEPOSIT")] = true;
emit EmergencyShutdown(msg.sender, reason);
}
Oracle Security
Multi-oracle price validation with comprehensive median calculation:
uint256 public constant PRICE_DEVIATION_THRESHOLD = 300; // 3% max deviation
uint256 public constant STALE_PRICE_THRESHOLD = 3600; // 1 hour staleness limit
struct PriceData {
uint256 price;
uint256 timestamp;
address source;
}
function getSecurePrice(address asset) external view returns (uint256) {
PriceData[] memory prices = new PriceData[](3);
// Get prices from multiple sources
prices[0] = getChainlinkPrice(asset);
prices[1] = getUniswapTWAP(asset);
prices[2] = getPythPrice(asset);
// Validate price freshness
for (uint i = 0; i < prices.length; i++) {
require(
block.timestamp - prices[i].timestamp <= STALE_PRICE_THRESHOLD,
"Stale price detected"
);
require(prices[i].price > 0, "Invalid price");
}
// Calculate median price
uint256 medianPrice = calculateMedian(prices);
// Validate price deviation
for (uint i = 0; i < prices.length; i++) {
uint256 deviation = abs(prices[i].price - medianPrice) * 10000 / medianPrice;
require(deviation <= PRICE_DEVIATION_THRESHOLD, "Price deviation too high");
}
return medianPrice;
}
// Bubble sort for median calculation (3 elements only)
function calculateMedian(PriceData[] memory prices) internal pure returns (uint256) {
require(prices.length == 3, "Expected 3 price sources");
// Sort prices array
for (uint i = 0; i < 2; i++) {
for (uint j = 0; j < 2 - i; j++) {
if (prices[j].price > prices[j + 1].price) {
PriceData memory temp = prices[j];
prices[j] = prices[j + 1];
prices[j + 1] = temp;
}
}
}
// Return median (middle value)
return prices[1].price;
}
function abs(uint256 a - uint256 b) internal pure returns (uint256) {
return a >= b ? a - b : b - a;
}
Runtime Security Monitoring
Automated Monitoring
- 24/7 Health Factor Tracking: Continuous calculation across all positions
- Price Feed Integration: Multiple oracle sources for accurate valuations
- Gas-Optimized Checks: Efficient monitoring without excessive costs
- Multi-Position Management: Simultaneous tracking of leveraged strategies
Enhanced Recovery System
enum PositionStatus { ACTIVE, WARNING, STUCK, RECOVERING, CLOSED }
struct Position {
uint256 id;
PositionStatus status;
uint256 lastHealthCheck;
uint256 recoveryAttempts;
uint256 stuckTimestamp;
}
uint256 public constant MAX_RECOVERY_ATTEMPTS = 3;
mapping(address => mapping(uint256 => Position)) public positions;
function recoverStuckPosition(
address user,
uint256 positionId
) external onlyRole(RECOVERY_ROLE) nonReentrant {
Position storage position = positions[user][positionId];
require(position.status == PositionStatus.STUCK, "Position not stuck");
require(position.recoveryAttempts < MAX_RECOVERY_ATTEMPTS, "Max attempts exceeded");
position.status = PositionStatus.RECOVERING;
position.recoveryAttempts++;
// Implement comprehensive recovery logic
bool recoverySuccess = _executeRecovery(user, positionId);
if (recoverySuccess) {
position.status = PositionStatus.ACTIVE;
emit RecoverySuccess(user, positionId, position.recoveryAttempts);
} else {
position.status = PositionStatus.STUCK;
// Escalate to emergency if max attempts reached
if (position.recoveryAttempts >= MAX_RECOVERY_ATTEMPTS) {
_escalateToEmergency(user, positionId);
emit RecoveryEscalated(user, positionId);
} else {
emit RecoveryFailed(user, positionId, position.recoveryAttempts);
}
}
}
function _executeRecovery(
address user,
uint256 positionId
) internal returns (bool success) {
// Validate recovery preconditions
require(user != address(0), "Invalid user");
try this._attemptPositionRecovery(user, positionId) {
// Verify recovery success
return _validateRecoverySuccess(user, positionId);
} catch Error(string memory reason) {
emit RecoveryError(user, positionId, reason);
return false;
} catch {
emit RecoveryError(user, positionId, "Unknown recovery error");
return false;
}
}
// System health monitoring
struct SystemHealth {
uint256 totalValueLocked;
uint256 activePositions;
uint256 averageHealthFactor;
uint256 flashLoanUtilization;
uint256 lastOracleUpdate;
}
function monitorSystemHealth() external view returns (SystemHealth memory) {
return SystemHealth({
totalValueLocked: getTVL(),
activePositions: getActivePositionCount(),
averageHealthFactor: getAverageHealthFactor(),
flashLoanUtilization: globalFlashLoanUsage * 100 / MAX_FLASH_LOAN_AMOUNT,
lastOracleUpdate: lastOracleUpdateTime
});
}
Security Testing Framework
Automated Testing
Comprehensive test suite covering:
- Flash loan security validation
- Health factor calculations
- Liquidation protection mechanisms
- Emergency procedures
Contract Specifications
interface SecuritySpecs {
readonly maxLeverage: 5;
readonly maxLTV: 0.8;
readonly liquidationThreshold: 0.8;
readonly emergencyPause: true;
readonly timelockDuration: 48 * 3600; // 48 hours
readonly priceDeviationLimit: 0.05; // 5%
}
Contract Addresses:
- Testnet: Coming Soon
- Mainnet: In Development
Security Audit Status: In progress with Trail of Bits, Consensys Diligence Bug Bounty: $100,000 program launching with mainnet deployment Emergency Contact: security@loopify.finance