Skip to main content

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:

  1. Flash Loan Looping - Leveraged yield strategies
  2. Yield Optimization - Automated yield farming
  3. 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