use crate::error::CoreError;
use rust_decimal::Decimal;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::time::{Duration, SystemTime};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RebateProgram {
pub program_id: String,
pub rebate_type: RebateType,
pub eligibility_criteria: Vec<EligibilityCriterion>,
pub rebate_percentage: Decimal,
pub max_rebate_per_user: Option<Decimal>,
pub program_budget: Decimal,
pub remaining_budget: Decimal,
pub start_time: SystemTime,
pub end_time: Option<SystemTime>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum RebateType {
MakerRebate,
VolumeRebate,
LiquidityIncentive,
OnboardingRebate,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum EligibilityCriterion {
MinVolume {
threshold: Decimal,
},
MinLiquidity {
threshold: Decimal,
},
MakerOnly,
MinAccountAge {
duration: Duration,
},
TokenPairs {
pairs: Vec<(String, String)>,
},
}
pub struct RebateManager {
programs: HashMap<String, RebateProgram>,
user_rebates: HashMap<String, Vec<UserRebate>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UserRebate {
pub user_id: String,
pub program_id: String,
pub rebate_amount: Decimal,
pub original_fee: Decimal,
pub timestamp: SystemTime,
pub trade_id: String,
}
impl RebateManager {
pub fn new() -> Self {
Self {
programs: HashMap::new(),
user_rebates: HashMap::new(),
}
}
pub fn add_program(&mut self, program: RebateProgram) -> Result<(), CoreError> {
if self.programs.contains_key(&program.program_id) {
return Err(CoreError::AlreadyExists(format!(
"Rebate program {} already exists",
program.program_id
)));
}
self.programs.insert(program.program_id.clone(), program);
Ok(())
}
pub fn calculate_rebate(
&self,
_user_id: &str,
fee_amount: Decimal,
is_maker: bool,
trading_volume: Decimal,
liquidity_provided: Decimal,
account_age: Duration,
) -> Decimal {
let mut total_rebate = Decimal::ZERO;
for program in self.programs.values() {
if let Some(end_time) = program.end_time {
if SystemTime::now() > end_time {
continue;
}
}
if !self.check_eligibility(
program,
is_maker,
trading_volume,
liquidity_provided,
account_age,
) {
continue;
}
if program.remaining_budget <= Decimal::ZERO {
continue;
}
let rebate = fee_amount * program.rebate_percentage / Decimal::new(100, 0);
let limited_rebate = if let Some(max_rebate) = program.max_rebate_per_user {
rebate.min(max_rebate)
} else {
rebate
};
let final_rebate = limited_rebate.min(program.remaining_budget);
total_rebate += final_rebate;
}
total_rebate.min(fee_amount) }
fn check_eligibility(
&self,
program: &RebateProgram,
is_maker: bool,
trading_volume: Decimal,
liquidity_provided: Decimal,
account_age: Duration,
) -> bool {
for criterion in &program.eligibility_criteria {
match criterion {
EligibilityCriterion::MinVolume { threshold } => {
if trading_volume < *threshold {
return false;
}
}
EligibilityCriterion::MinLiquidity { threshold } => {
if liquidity_provided < *threshold {
return false;
}
}
EligibilityCriterion::MakerOnly => {
if !is_maker {
return false;
}
}
EligibilityCriterion::MinAccountAge { duration } => {
if account_age < *duration {
return false;
}
}
EligibilityCriterion::TokenPairs { .. } => {
continue;
}
}
}
true
}
pub fn record_rebate(
&mut self,
user_id: String,
program_id: String,
rebate_amount: Decimal,
original_fee: Decimal,
trade_id: String,
) -> Result<(), CoreError> {
if let Some(program) = self.programs.get_mut(&program_id) {
if program.remaining_budget < rebate_amount {
return Err(CoreError::InsufficientBalance {
required: rebate_amount,
available: program.remaining_budget,
});
}
program.remaining_budget -= rebate_amount;
}
let rebate = UserRebate {
user_id: user_id.clone(),
program_id,
rebate_amount,
original_fee,
timestamp: SystemTime::now(),
trade_id,
};
self.user_rebates.entry(user_id).or_default().push(rebate);
Ok(())
}
pub fn get_user_total_rebates(&self, user_id: &str) -> Decimal {
self.user_rebates
.get(user_id)
.map(|rebates| rebates.iter().map(|r| r.rebate_amount).sum())
.unwrap_or(Decimal::ZERO)
}
}
impl Default for RebateManager {
fn default() -> Self {
Self::new()
}
}
pub struct FeeOptimizer {
fee_history: Vec<FeePerformance>,
current_fees: FeeConfiguration,
strategy: OptimizationStrategy,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FeePerformance {
pub timestamp: SystemTime,
pub fee_rate: Decimal,
pub trading_volume: Decimal,
pub revenue: Decimal,
pub user_count: usize,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FeeConfiguration {
pub base_fee: Decimal,
pub maker_fee: Decimal,
pub taker_fee: Decimal,
pub min_fee: Decimal,
pub max_fee: Decimal,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum OptimizationStrategy {
MaximizeRevenue,
MaximizeVolume,
Balanced,
Competitive,
}
impl FeeOptimizer {
pub fn new(initial_config: FeeConfiguration, strategy: OptimizationStrategy) -> Self {
Self {
fee_history: Vec::new(),
current_fees: initial_config,
strategy,
}
}
pub fn record_performance(&mut self, performance: FeePerformance) {
self.fee_history.push(performance);
if self.fee_history.len() > 1000 {
self.fee_history.remove(0);
}
}
pub fn optimize_fees(&mut self) -> FeeConfiguration {
if self.fee_history.len() < 10 {
return self.current_fees.clone();
}
match self.strategy {
OptimizationStrategy::MaximizeRevenue => self.optimize_for_revenue(),
OptimizationStrategy::MaximizeVolume => self.optimize_for_volume(),
OptimizationStrategy::Balanced => self.optimize_balanced(),
OptimizationStrategy::Competitive => self.current_fees.clone(), }
}
fn optimize_for_revenue(&self) -> FeeConfiguration {
let best_performance = self.fee_history.iter().max_by_key(|p| p.revenue).unwrap();
let mut config = self.current_fees.clone();
config.base_fee = best_performance.fee_rate;
config.taker_fee = best_performance.fee_rate;
config.maker_fee = best_performance.fee_rate * Decimal::new(80, 2);
config
}
fn optimize_for_volume(&self) -> FeeConfiguration {
let best_performance = self
.fee_history
.iter()
.max_by_key(|p| p.trading_volume)
.unwrap();
let mut config = self.current_fees.clone();
config.base_fee = best_performance.fee_rate;
config.taker_fee = best_performance.fee_rate;
config.maker_fee = best_performance.fee_rate * Decimal::new(70, 2);
config
}
fn optimize_balanced(&self) -> FeeConfiguration {
let mut best_ratio = Decimal::ZERO;
let mut best_fee_rate = self.current_fees.base_fee;
for performance in &self.fee_history {
if performance.trading_volume > Decimal::ZERO {
let ratio = performance.revenue / performance.trading_volume;
if ratio > best_ratio {
best_ratio = ratio;
best_fee_rate = performance.fee_rate;
}
}
}
let mut config = self.current_fees.clone();
config.base_fee = best_fee_rate;
config.taker_fee = best_fee_rate;
config.maker_fee = best_fee_rate * Decimal::new(75, 2);
config
}
pub fn suggest_adjustment(
&self,
current_volume: Decimal,
target_volume: Decimal,
) -> FeeAdjustment {
if current_volume < target_volume * Decimal::new(80, 2) / Decimal::new(100, 0) {
FeeAdjustment {
direction: AdjustmentDirection::Decrease,
magnitude: Decimal::new(5, 0), reason: "Trading volume below target".to_string(),
}
} else if current_volume > target_volume * Decimal::new(120, 2) / Decimal::new(100, 0) {
FeeAdjustment {
direction: AdjustmentDirection::Increase,
magnitude: Decimal::new(3, 0), reason: "Trading volume above target".to_string(),
}
} else {
FeeAdjustment {
direction: AdjustmentDirection::NoChange,
magnitude: Decimal::ZERO,
reason: "Trading volume within target range".to_string(),
}
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FeeAdjustment {
pub direction: AdjustmentDirection,
pub magnitude: Decimal,
pub reason: String,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum AdjustmentDirection {
Increase,
Decrease,
NoChange,
}
pub struct GasOptimizer {
gas_price_history: Vec<GasPrice>,
#[allow(dead_code)]
pending_transactions: Vec<PendingTransaction>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GasPrice {
pub timestamp: SystemTime,
pub price: Decimal,
pub block_number: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PendingTransaction {
pub tx_id: String,
pub priority: TransactionPriority,
pub gas_limit: u64,
pub created_at: SystemTime,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, PartialOrd, Ord)]
pub enum TransactionPriority {
Low = 1,
Medium = 2,
High = 3,
Critical = 4,
}
impl GasOptimizer {
pub fn new() -> Self {
Self {
gas_price_history: Vec::new(),
pending_transactions: Vec::new(),
}
}
pub fn record_gas_price(&mut self, price: Decimal, block_number: u64) {
self.gas_price_history.push(GasPrice {
timestamp: SystemTime::now(),
price,
block_number,
});
if self.gas_price_history.len() > 1000 {
self.gas_price_history.remove(0);
}
}
pub fn predict_gas_price(&self, priority: TransactionPriority) -> Decimal {
if self.gas_price_history.is_empty() {
return Decimal::new(50, 0); }
let recent_prices: Vec<Decimal> = self
.gas_price_history
.iter()
.rev()
.take(20)
.map(|g| g.price)
.collect();
let avg_price: Decimal =
recent_prices.iter().sum::<Decimal>() / Decimal::from(recent_prices.len());
match priority {
TransactionPriority::Low => avg_price * Decimal::new(90, 2), TransactionPriority::Medium => avg_price, TransactionPriority::High => avg_price * Decimal::new(115, 2), TransactionPriority::Critical => avg_price * Decimal::new(150, 2), }
}
pub fn batch_transactions(
&self,
transactions: Vec<PendingTransaction>,
) -> Vec<Vec<PendingTransaction>> {
let mut batches: Vec<Vec<PendingTransaction>> = Vec::new();
let mut current_batch: Vec<PendingTransaction> = Vec::new();
let mut current_gas: u64 = 0;
const MAX_GAS_PER_BATCH: u64 = 10_000_000;
for tx in transactions {
if current_gas + tx.gas_limit > MAX_GAS_PER_BATCH && !current_batch.is_empty() {
batches.push(current_batch);
current_batch = Vec::new();
current_gas = 0;
}
current_gas += tx.gas_limit;
current_batch.push(tx);
}
if !current_batch.is_empty() {
batches.push(current_batch);
}
batches
}
pub fn prioritize_transactions(
&self,
mut transactions: Vec<PendingTransaction>,
) -> Vec<PendingTransaction> {
transactions.sort_by(|a, b| match b.priority.cmp(&a.priority) {
std::cmp::Ordering::Equal => a.created_at.cmp(&b.created_at),
other => other,
});
transactions
}
}
impl Default for GasOptimizer {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_rebate_calculation() {
let mut manager = RebateManager::new();
let program = RebateProgram {
program_id: "maker_rebate_1".to_string(),
rebate_type: RebateType::MakerRebate,
eligibility_criteria: vec![
EligibilityCriterion::MakerOnly,
EligibilityCriterion::MinVolume {
threshold: Decimal::new(10, 0),
},
],
rebate_percentage: Decimal::new(25, 0), max_rebate_per_user: Some(Decimal::new(1, 0)),
program_budget: Decimal::new(100, 0),
remaining_budget: Decimal::new(100, 0),
start_time: SystemTime::now(),
end_time: None,
};
manager.add_program(program).unwrap();
let rebate = manager.calculate_rebate(
"user1",
Decimal::new(4, 0), true, Decimal::new(20, 0), Decimal::ZERO,
Duration::from_secs(3600),
);
assert_eq!(rebate, Decimal::new(1, 0)); }
#[test]
fn test_fee_optimizer_revenue() {
let config = FeeConfiguration {
base_fee: Decimal::new(25, 2), maker_fee: Decimal::new(20, 2),
taker_fee: Decimal::new(30, 2),
min_fee: Decimal::new(10, 2),
max_fee: Decimal::new(100, 2),
};
let mut optimizer = FeeOptimizer::new(config, OptimizationStrategy::MaximizeRevenue);
optimizer.record_performance(FeePerformance {
timestamp: SystemTime::now(),
fee_rate: Decimal::new(25, 2),
trading_volume: Decimal::new(1000, 0),
revenue: Decimal::new(25, 1), user_count: 100,
});
optimizer.record_performance(FeePerformance {
timestamp: SystemTime::now(),
fee_rate: Decimal::new(30, 2),
trading_volume: Decimal::new(800, 0),
revenue: Decimal::new(24, 1), user_count: 80,
});
let optimized = optimizer.optimize_fees();
assert_eq!(optimized.base_fee, Decimal::new(25, 2));
}
#[test]
fn test_gas_price_prediction() {
let mut optimizer = GasOptimizer::new();
optimizer.record_gas_price(Decimal::new(50, 0), 1000);
optimizer.record_gas_price(Decimal::new(60, 0), 1001);
optimizer.record_gas_price(Decimal::new(55, 0), 1002);
let low_priority = optimizer.predict_gas_price(TransactionPriority::Low);
let high_priority = optimizer.predict_gas_price(TransactionPriority::High);
assert!(low_priority < high_priority);
}
#[test]
fn test_transaction_batching() {
let optimizer = GasOptimizer::new();
let transactions = vec![
PendingTransaction {
tx_id: "tx1".to_string(),
priority: TransactionPriority::Medium,
gas_limit: 8_000_000,
created_at: SystemTime::now(),
},
PendingTransaction {
tx_id: "tx2".to_string(),
priority: TransactionPriority::Medium,
gas_limit: 3_000_000,
created_at: SystemTime::now(),
},
PendingTransaction {
tx_id: "tx3".to_string(),
priority: TransactionPriority::Low,
gas_limit: 2_000_000,
created_at: SystemTime::now(),
},
];
let batches = optimizer.batch_transactions(transactions);
assert_eq!(batches.len(), 2); }
#[test]
fn test_transaction_prioritization() {
let optimizer = GasOptimizer::new();
let transactions = vec![
PendingTransaction {
tx_id: "tx1".to_string(),
priority: TransactionPriority::Low,
gas_limit: 1_000_000,
created_at: SystemTime::now(),
},
PendingTransaction {
tx_id: "tx2".to_string(),
priority: TransactionPriority::Critical,
gas_limit: 1_000_000,
created_at: SystemTime::now(),
},
PendingTransaction {
tx_id: "tx3".to_string(),
priority: TransactionPriority::Medium,
gas_limit: 1_000_000,
created_at: SystemTime::now(),
},
];
let prioritized = optimizer.prioritize_transactions(transactions);
assert_eq!(prioritized[0].tx_id, "tx2"); assert_eq!(prioritized[2].tx_id, "tx1"); }
}