use std::str::FromStr;
use std::sync::Arc;
use crate::errors::AppError;
use crate::services::SettingsService;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum FeePolicy {
#[default]
CompanyPaysAll,
UserPaysSwap,
UserPaysPrivacy,
UserPaysAll,
}
impl FromStr for FeePolicy {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"company_pays_all" => Ok(Self::CompanyPaysAll),
"user_pays_swap" => Ok(Self::UserPaysSwap),
"user_pays_privacy" => Ok(Self::UserPaysPrivacy),
"user_pays_all" => Ok(Self::UserPaysAll),
_ => Err(format!(
"Unrecognized fee policy '{}'. Valid: company_pays_all, user_pays_swap, user_pays_privacy, user_pays_all",
s
)),
}
}
}
#[derive(Debug, Clone)]
pub struct FeeConfig {
pub policy: FeePolicy,
pub privacy_fixed_lamports: u64,
pub privacy_percent_bps: u32,
pub swap_fixed_lamports: u64,
pub swap_percent_bps: u32,
pub company_fixed_lamports: u64,
pub company_percent_bps: u32,
}
impl Default for FeeConfig {
fn default() -> Self {
Self {
policy: FeePolicy::default(),
privacy_fixed_lamports: 6_000_000,
privacy_percent_bps: 35,
swap_fixed_lamports: 1_000_000,
swap_percent_bps: 10,
company_fixed_lamports: 0,
company_percent_bps: 0,
}
}
}
#[derive(Debug, Clone)]
pub struct CalculatedFees {
pub privacy_fee_lamports: i64,
pub swap_fee_lamports: i64,
pub company_fee_lamports: i64,
pub total_lamports: i64,
}
pub struct DepositFeeService {
settings_service: Arc<SettingsService>,
}
impl DepositFeeService {
pub fn new(settings_service: Arc<SettingsService>) -> Self {
Self { settings_service }
}
pub async fn get_config(&self) -> Result<FeeConfig, AppError> {
let policy_str = self
.settings_service
.get("deposit_fee_policy")
.await?
.unwrap_or_else(|| "company_pays_all".to_string());
let privacy_fixed = self
.settings_service
.get_u64("privacy_fee_fixed_lamports")
.await?
.unwrap_or(6_000_000);
let privacy_bps = self
.settings_service
.get_u32("privacy_fee_percent_bps")
.await?
.unwrap_or(35);
let swap_fixed = self
.settings_service
.get_u64("swap_fee_fixed_lamports")
.await?
.unwrap_or(1_000_000);
let swap_bps = self
.settings_service
.get_u32("swap_fee_percent_bps")
.await?
.unwrap_or(10);
let company_fixed = self
.settings_service
.get_u64("company_fee_fixed_lamports")
.await?
.unwrap_or(0);
let company_bps = self
.settings_service
.get_u32("company_fee_percent_bps")
.await?
.unwrap_or(0);
Ok(FeeConfig {
policy: policy_str
.parse::<FeePolicy>()
.map_err(|e| AppError::Config(e))?,
privacy_fixed_lamports: privacy_fixed,
privacy_percent_bps: privacy_bps,
swap_fixed_lamports: swap_fixed,
swap_percent_bps: swap_bps,
company_fixed_lamports: company_fixed,
company_percent_bps: company_bps,
})
}
pub fn calculate_fees(
&self,
amount_lamports: u64,
has_swap: bool,
has_privacy: bool,
config: &FeeConfig,
) -> CalculatedFees {
let mut privacy_fee: i64 = 0;
let mut swap_fee: i64 = 0;
if has_privacy {
let percent_fee =
(amount_lamports as u128 * config.privacy_percent_bps as u128 / 10_000) as i64;
privacy_fee = config.privacy_fixed_lamports as i64 + percent_fee;
}
if has_swap {
let percent_fee =
(amount_lamports as u128 * config.swap_percent_bps as u128 / 10_000) as i64;
swap_fee = config.swap_fixed_lamports as i64 + percent_fee;
}
let company_percent_fee =
(amount_lamports as u128 * config.company_percent_bps as u128 / 10_000) as i64;
let company_fee = config.company_fixed_lamports as i64 + company_percent_fee;
CalculatedFees {
privacy_fee_lamports: privacy_fee,
swap_fee_lamports: swap_fee,
company_fee_lamports: company_fee,
total_lamports: privacy_fee + swap_fee + company_fee,
}
}
pub fn user_deduction(&self, fees: &CalculatedFees, policy: FeePolicy) -> i64 {
let base = fees.company_fee_lamports;
match policy {
FeePolicy::CompanyPaysAll => base,
FeePolicy::UserPaysSwap => base + fees.swap_fee_lamports,
FeePolicy::UserPaysPrivacy => base + fees.privacy_fee_lamports,
FeePolicy::UserPaysAll => fees.total_lamports,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_fee_policy_from_str() {
assert_eq!(
"company_pays_all".parse::<FeePolicy>().unwrap(),
FeePolicy::CompanyPaysAll
);
assert_eq!(
"user_pays_swap".parse::<FeePolicy>().unwrap(),
FeePolicy::UserPaysSwap
);
assert_eq!(
"user_pays_privacy".parse::<FeePolicy>().unwrap(),
FeePolicy::UserPaysPrivacy
);
assert_eq!(
"user_pays_all".parse::<FeePolicy>().unwrap(),
FeePolicy::UserPaysAll
);
assert!(
"unknown".parse::<FeePolicy>().is_err(),
"Unknown fee policy should return error"
);
}
#[test]
fn test_calculate_fees_privacy_only() {
let config = FeeConfig::default();
let service = DepositFeeService {
settings_service: Arc::new(SettingsService::new(Arc::new(
crate::repositories::InMemorySystemSettingsRepository::new(),
))),
};
let fees = service.calculate_fees(1_000_000_000, false, true, &config);
assert_eq!(fees.privacy_fee_lamports, 9_500_000);
assert_eq!(fees.swap_fee_lamports, 0);
assert_eq!(fees.company_fee_lamports, 0);
assert_eq!(fees.total_lamports, 9_500_000);
}
#[test]
fn test_calculate_fees_swap_only() {
let config = FeeConfig::default();
let service = DepositFeeService {
settings_service: Arc::new(SettingsService::new(Arc::new(
crate::repositories::InMemorySystemSettingsRepository::new(),
))),
};
let fees = service.calculate_fees(1_000_000_000, true, false, &config);
assert_eq!(fees.swap_fee_lamports, 2_000_000);
assert_eq!(fees.privacy_fee_lamports, 0);
assert_eq!(fees.company_fee_lamports, 0);
assert_eq!(fees.total_lamports, 2_000_000);
}
#[test]
fn test_calculate_fees_both() {
let config = FeeConfig::default();
let service = DepositFeeService {
settings_service: Arc::new(SettingsService::new(Arc::new(
crate::repositories::InMemorySystemSettingsRepository::new(),
))),
};
let fees = service.calculate_fees(1_000_000_000, true, true, &config);
assert_eq!(fees.privacy_fee_lamports, 9_500_000);
assert_eq!(fees.swap_fee_lamports, 2_000_000);
assert_eq!(fees.company_fee_lamports, 0);
assert_eq!(fees.total_lamports, 11_500_000);
}
#[test]
fn test_calculate_fees_with_company_fee() {
let config = FeeConfig {
company_fixed_lamports: 1_000_000, company_percent_bps: 5, ..FeeConfig::default()
};
let service = DepositFeeService {
settings_service: Arc::new(SettingsService::new(Arc::new(
crate::repositories::InMemorySystemSettingsRepository::new(),
))),
};
let fees = service.calculate_fees(1_000_000_000, true, true, &config);
assert_eq!(fees.privacy_fee_lamports, 9_500_000);
assert_eq!(fees.swap_fee_lamports, 2_000_000);
assert_eq!(fees.company_fee_lamports, 1_500_000);
assert_eq!(fees.total_lamports, 13_000_000);
}
#[test]
fn test_user_deduction_policies() {
let fees = CalculatedFees {
privacy_fee_lamports: 9_500_000,
swap_fee_lamports: 2_000_000,
company_fee_lamports: 0,
total_lamports: 11_500_000,
};
let service = DepositFeeService {
settings_service: Arc::new(SettingsService::new(Arc::new(
crate::repositories::InMemorySystemSettingsRepository::new(),
))),
};
assert_eq!(service.user_deduction(&fees, FeePolicy::CompanyPaysAll), 0);
assert_eq!(
service.user_deduction(&fees, FeePolicy::UserPaysSwap),
2_000_000
);
assert_eq!(
service.user_deduction(&fees, FeePolicy::UserPaysPrivacy),
9_500_000
);
assert_eq!(
service.user_deduction(&fees, FeePolicy::UserPaysAll),
11_500_000
);
}
#[test]
fn test_user_deduction_with_company_fee() {
let fees = CalculatedFees {
privacy_fee_lamports: 9_500_000,
swap_fee_lamports: 2_000_000,
company_fee_lamports: 1_500_000, total_lamports: 13_000_000,
};
let service = DepositFeeService {
settings_service: Arc::new(SettingsService::new(Arc::new(
crate::repositories::InMemorySystemSettingsRepository::new(),
))),
};
assert_eq!(
service.user_deduction(&fees, FeePolicy::CompanyPaysAll),
1_500_000
); assert_eq!(
service.user_deduction(&fees, FeePolicy::UserPaysSwap),
3_500_000
); assert_eq!(
service.user_deduction(&fees, FeePolicy::UserPaysPrivacy),
11_000_000
); assert_eq!(
service.user_deduction(&fees, FeePolicy::UserPaysAll),
13_000_000
); }
}