use chrono::{DateTime, Utc};
use rust_decimal::Decimal;
use rust_decimal_macros::dec;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use uuid::Uuid;
use crate::error::{CoreError, Result};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FlashLoanConfig {
pub fee_percentage: Decimal,
pub max_loan_amount: Decimal,
pub min_loan_amount: Decimal,
pub enabled: bool,
}
impl Default for FlashLoanConfig {
fn default() -> Self {
Self {
fee_percentage: dec!(0.09), max_loan_amount: dec!(1000000.0), min_loan_amount: dec!(1.0),
enabled: true,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FlashLoanRequest {
pub loan_id: Uuid,
pub borrower_id: Uuid,
pub token_id: Uuid,
pub amount: Decimal,
pub fee: Decimal,
pub repay_amount: Decimal,
pub timestamp: DateTime<Utc>,
pub status: FlashLoanStatus,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum FlashLoanStatus {
Initiated,
Executed,
Repaid,
Failed,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FlashLoanResult {
pub loan_id: Uuid,
pub success: bool,
pub amount_borrowed: Decimal,
pub fee_paid: Decimal,
pub profit: Option<Decimal>,
pub error: Option<String>,
}
pub trait FlashLoanExecutor {
fn execute(
&mut self,
token_id: Uuid,
amount: Decimal,
fee: Decimal,
params: &[u8],
) -> Result<bool>;
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FlashLoanPool {
pub pool_id: Uuid,
pub token_id: Uuid,
pub available_liquidity: Decimal,
pub total_borrowed: Decimal,
pub total_fees_collected: Decimal,
pub successful_loans: u64,
pub failed_loans: u64,
pub config: FlashLoanConfig,
active_loans: HashMap<Uuid, FlashLoanRequest>,
}
impl FlashLoanPool {
pub fn new(token_id: Uuid, initial_liquidity: Decimal, config: FlashLoanConfig) -> Self {
Self {
pool_id: Uuid::new_v4(),
token_id,
available_liquidity: initial_liquidity,
total_borrowed: Decimal::ZERO,
total_fees_collected: Decimal::ZERO,
successful_loans: 0,
failed_loans: 0,
config,
active_loans: HashMap::new(),
}
}
pub fn add_liquidity(&mut self, amount: Decimal) -> Result<()> {
if amount <= Decimal::ZERO {
return Err(CoreError::Validation("Amount must be positive".to_string()));
}
self.available_liquidity += amount;
Ok(())
}
pub fn remove_liquidity(&mut self, amount: Decimal) -> Result<()> {
if amount <= Decimal::ZERO {
return Err(CoreError::Validation("Amount must be positive".to_string()));
}
if amount > self.available_liquidity {
return Err(CoreError::InsufficientLiquidity(
"Insufficient liquidity in pool".to_string(),
));
}
self.available_liquidity -= amount;
Ok(())
}
pub fn calculate_fee(&self, amount: Decimal) -> Decimal {
amount * self.config.fee_percentage / dec!(100.0)
}
pub fn initiate_loan(
&mut self,
borrower_id: Uuid,
amount: Decimal,
) -> Result<FlashLoanRequest> {
if !self.config.enabled {
return Err(CoreError::Validation(
"Flash loans are disabled".to_string(),
));
}
if amount < self.config.min_loan_amount {
return Err(CoreError::Validation(format!(
"Amount below minimum: {}",
self.config.min_loan_amount
)));
}
if amount > self.config.max_loan_amount {
return Err(CoreError::Validation(format!(
"Amount exceeds maximum: {}",
self.config.max_loan_amount
)));
}
if amount > self.available_liquidity {
return Err(CoreError::InsufficientLiquidity(
"Insufficient liquidity for flash loan".to_string(),
));
}
if self.active_loans.contains_key(&borrower_id) {
return Err(CoreError::Validation(
"Reentrancy detected: borrower has active loan".to_string(),
));
}
let fee = self.calculate_fee(amount);
let repay_amount = amount + fee;
let loan = FlashLoanRequest {
loan_id: Uuid::new_v4(),
borrower_id,
token_id: self.token_id,
amount,
fee,
repay_amount,
timestamp: Utc::now(),
status: FlashLoanStatus::Initiated,
};
self.active_loans.insert(borrower_id, loan.clone());
self.available_liquidity -= amount;
Ok(loan)
}
pub fn complete_loan(
&mut self,
loan_id: Uuid,
borrower_id: Uuid,
repaid_amount: Decimal,
) -> Result<FlashLoanResult> {
let loan = self
.active_loans
.get(&borrower_id)
.ok_or_else(|| CoreError::NotFound("No active loan found".to_string()))?
.clone();
if loan.loan_id != loan_id {
return Err(CoreError::Validation("Loan ID mismatch".to_string()));
}
if repaid_amount < loan.repay_amount {
self.active_loans.remove(&borrower_id);
self.available_liquidity += loan.amount; self.failed_loans += 1;
return Ok(FlashLoanResult {
loan_id,
success: false,
amount_borrowed: loan.amount,
fee_paid: Decimal::ZERO,
profit: None,
error: Some("Insufficient repayment".to_string()),
});
}
self.active_loans.remove(&borrower_id);
self.available_liquidity += loan.repay_amount;
self.total_borrowed += loan.amount;
self.total_fees_collected += loan.fee;
self.successful_loans += 1;
Ok(FlashLoanResult {
loan_id,
success: true,
amount_borrowed: loan.amount,
fee_paid: loan.fee,
profit: Some(repaid_amount - loan.repay_amount),
error: None,
})
}
pub fn cancel_loan(&mut self, borrower_id: Uuid) -> Result<()> {
if let Some(loan) = self.active_loans.remove(&borrower_id) {
self.available_liquidity += loan.amount;
self.failed_loans += 1;
Ok(())
} else {
Err(CoreError::NotFound("No active loan found".to_string()))
}
}
pub fn get_stats(&self) -> FlashLoanPoolStats {
let total_loans = self.successful_loans + self.failed_loans;
let success_rate = if total_loans > 0 {
(self.successful_loans as f64 / total_loans as f64) * 100.0
} else {
0.0
};
FlashLoanPoolStats {
pool_id: self.pool_id,
token_id: self.token_id,
available_liquidity: self.available_liquidity,
total_borrowed: self.total_borrowed,
total_fees_collected: self.total_fees_collected,
successful_loans: self.successful_loans,
failed_loans: self.failed_loans,
success_rate,
active_loan_count: self.active_loans.len() as u64,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FlashLoanPoolStats {
pub pool_id: Uuid,
pub token_id: Uuid,
pub available_liquidity: Decimal,
pub total_borrowed: Decimal,
pub total_fees_collected: Decimal,
pub successful_loans: u64,
pub failed_loans: u64,
pub success_rate: f64,
pub active_loan_count: u64,
}
#[derive(Debug)]
pub struct FlashLoanManager {
pools: HashMap<Uuid, FlashLoanPool>,
}
impl FlashLoanManager {
pub fn new() -> Self {
Self {
pools: HashMap::new(),
}
}
pub fn create_pool(
&mut self,
token_id: Uuid,
initial_liquidity: Decimal,
config: FlashLoanConfig,
) -> Result<Uuid> {
if self.pools.contains_key(&token_id) {
return Err(CoreError::Validation(
"Pool already exists for this token".to_string(),
));
}
let pool = FlashLoanPool::new(token_id, initial_liquidity, config);
let pool_id = pool.pool_id;
self.pools.insert(token_id, pool);
Ok(pool_id)
}
pub fn get_pool(&self, token_id: Uuid) -> Result<&FlashLoanPool> {
self.pools
.get(&token_id)
.ok_or_else(|| CoreError::NotFound("Pool not found".to_string()))
}
pub fn get_pool_mut(&mut self, token_id: Uuid) -> Result<&mut FlashLoanPool> {
self.pools
.get_mut(&token_id)
.ok_or_else(|| CoreError::NotFound("Pool not found".to_string()))
}
#[allow(clippy::too_many_arguments)]
pub fn execute_flash_loan(
&mut self,
token_id: Uuid,
borrower_id: Uuid,
amount: Decimal,
executor: &mut dyn FlashLoanExecutor,
params: &[u8],
) -> Result<FlashLoanResult> {
let pool = self.get_pool_mut(token_id)?;
let loan = pool.initiate_loan(borrower_id, amount)?;
let execution_result = executor.execute(token_id, amount, loan.fee, params);
match execution_result {
Ok(true) => {
let pool = self.get_pool_mut(token_id)?;
pool.complete_loan(loan.loan_id, borrower_id, loan.repay_amount)
}
Ok(false) | Err(_) => {
let pool = self.get_pool_mut(token_id)?;
pool.cancel_loan(borrower_id)?;
Ok(FlashLoanResult {
loan_id: loan.loan_id,
success: false,
amount_borrowed: loan.amount,
fee_paid: Decimal::ZERO,
profit: None,
error: Some("Execution failed".to_string()),
})
}
}
}
pub fn get_all_stats(&self) -> Vec<FlashLoanPoolStats> {
self.pools.values().map(|pool| pool.get_stats()).collect()
}
}
impl Default for FlashLoanManager {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
struct MockExecutor {
should_succeed: bool,
}
impl FlashLoanExecutor for MockExecutor {
fn execute(
&mut self,
_token_id: Uuid,
_amount: Decimal,
_fee: Decimal,
_params: &[u8],
) -> Result<bool> {
Ok(self.should_succeed)
}
}
#[test]
fn test_flash_loan_pool_creation() {
let token_id = Uuid::new_v4();
let config = FlashLoanConfig::default();
let pool = FlashLoanPool::new(token_id, dec!(10000.0), config);
assert_eq!(pool.token_id, token_id);
assert_eq!(pool.available_liquidity, dec!(10000.0));
assert_eq!(pool.successful_loans, 0);
}
#[test]
fn test_add_liquidity() {
let token_id = Uuid::new_v4();
let config = FlashLoanConfig::default();
let mut pool = FlashLoanPool::new(token_id, dec!(10000.0), config);
pool.add_liquidity(dec!(5000.0)).unwrap();
assert_eq!(pool.available_liquidity, dec!(15000.0));
}
#[test]
fn test_calculate_fee() {
let token_id = Uuid::new_v4();
let config = FlashLoanConfig::default();
let pool = FlashLoanPool::new(token_id, dec!(10000.0), config);
let fee = pool.calculate_fee(dec!(1000.0));
assert_eq!(fee, dec!(0.9)); }
#[test]
fn test_successful_flash_loan() {
let token_id = Uuid::new_v4();
let borrower_id = Uuid::new_v4();
let config = FlashLoanConfig::default();
let mut pool = FlashLoanPool::new(token_id, dec!(10000.0), config);
let loan = pool.initiate_loan(borrower_id, dec!(1000.0)).unwrap();
assert_eq!(loan.amount, dec!(1000.0));
assert_eq!(loan.fee, dec!(0.9));
assert_eq!(pool.available_liquidity, dec!(9000.0));
let result = pool
.complete_loan(loan.loan_id, borrower_id, loan.repay_amount)
.unwrap();
assert!(result.success);
assert_eq!(result.fee_paid, dec!(0.9));
assert_eq!(pool.available_liquidity, dec!(10000.9));
assert_eq!(pool.successful_loans, 1);
}
#[test]
fn test_failed_flash_loan_insufficient_repayment() {
let token_id = Uuid::new_v4();
let borrower_id = Uuid::new_v4();
let config = FlashLoanConfig::default();
let mut pool = FlashLoanPool::new(token_id, dec!(10000.0), config);
let loan = pool.initiate_loan(borrower_id, dec!(1000.0)).unwrap();
let result = pool
.complete_loan(loan.loan_id, borrower_id, dec!(1000.0))
.unwrap();
assert!(!result.success);
assert_eq!(pool.failed_loans, 1);
assert_eq!(pool.available_liquidity, dec!(10000.0)); }
#[test]
fn test_reentrancy_protection() {
let token_id = Uuid::new_v4();
let borrower_id = Uuid::new_v4();
let config = FlashLoanConfig::default();
let mut pool = FlashLoanPool::new(token_id, dec!(10000.0), config);
pool.initiate_loan(borrower_id, dec!(1000.0)).unwrap();
let result = pool.initiate_loan(borrower_id, dec!(500.0));
assert!(result.is_err());
}
#[test]
fn test_flash_loan_manager() {
let token_id = Uuid::new_v4();
let borrower_id = Uuid::new_v4();
let mut manager = FlashLoanManager::new();
manager
.create_pool(token_id, dec!(10000.0), FlashLoanConfig::default())
.unwrap();
let mut executor = MockExecutor {
should_succeed: true,
};
let result = manager
.execute_flash_loan(token_id, borrower_id, dec!(1000.0), &mut executor, &[])
.unwrap();
assert!(result.success);
assert_eq!(result.amount_borrowed, dec!(1000.0));
assert_eq!(result.fee_paid, dec!(0.9));
}
#[test]
fn test_flash_loan_execution_failure() {
let token_id = Uuid::new_v4();
let borrower_id = Uuid::new_v4();
let mut manager = FlashLoanManager::new();
manager
.create_pool(token_id, dec!(10000.0), FlashLoanConfig::default())
.unwrap();
let mut executor = MockExecutor {
should_succeed: false,
};
let result = manager
.execute_flash_loan(token_id, borrower_id, dec!(1000.0), &mut executor, &[])
.unwrap();
assert!(!result.success);
assert_eq!(result.fee_paid, Decimal::ZERO);
let pool = manager.get_pool(token_id).unwrap();
assert_eq!(pool.available_liquidity, dec!(10000.0));
}
#[test]
fn test_pool_stats() {
let token_id = Uuid::new_v4();
let borrower_id = Uuid::new_v4();
let config = FlashLoanConfig::default();
let mut pool = FlashLoanPool::new(token_id, dec!(10000.0), config);
let loan = pool.initiate_loan(borrower_id, dec!(1000.0)).unwrap();
pool.complete_loan(loan.loan_id, borrower_id, loan.repay_amount)
.unwrap();
let stats = pool.get_stats();
assert_eq!(stats.successful_loans, 1);
assert_eq!(stats.total_borrowed, dec!(1000.0));
assert_eq!(stats.total_fees_collected, dec!(0.9));
assert_eq!(stats.success_rate, 100.0);
}
}