solana_central/meteora/
calculate_base_fee_numerator.rs

1use crate::CentralContext;
2use crate::types::meteora_dammv2_pool::MeteoraDammV2Pool;
3use std::cmp;
4use std::sync::Arc;
5use std::time::{SystemTime, UNIX_EPOCH};
6
7impl MeteoraDammV2Pool {
8  /// Calculate the base fee numerator for DAMMv2 pools with fee scheduling
9  ///
10  /// Supports multiple fee modes:
11  /// - Linear decay: Fee decreases linearly over periods
12  /// - Exponential decay: Fee decreases exponentially over periods
13  /// - Rate limiter: Not yet implemented, returns cliff fee
14  ///
15  /// The calculation is based on the activation point and elapsed time/slots since activation.
16  pub fn calculate_base_fee_numerator(&self, central_context: &Arc<CentralContext>) -> u64 {
17    // Decode overloaded fields based on base_fee_mode
18    match self.base_fee_mode {
19      0 | 1 => {
20        // Fee Scheduler modes (Linear or Exponential)
21        let number_of_period = self.first_factor;
22        let period_frequency = u64::from_le_bytes(self.second_factor);
23        let reduction_factor = self.third_factor;
24
25        // If period frequency is 0, return cliff fee
26        if period_frequency == 0 {
27          return self.cliff_fee_numerator;
28        }
29
30        // Determine current point based on activation type
31        let current_point = match self.activation_type {
32          // Slot-based timing
33          0 => *central_context.current_slot.read().unwrap(),
34          // Timestamp-based timing (in seconds)
35          1 => SystemTime::now()
36            .duration_since(UNIX_EPOCH)
37            .unwrap()
38            .as_secs(),
39          // Default to slot-based
40          _ => *central_context.current_slot.read().unwrap(),
41        };
42
43        // Check if we've reached the activation point
44        if current_point < self.activation_point {
45          return self.cliff_fee_numerator;
46        }
47
48        let periods_passed = cmp::min(
49          number_of_period as u64,
50          (current_point - self.activation_point) / period_frequency,
51        );
52
53        match self.base_fee_mode {
54          0 => {
55            // Linear: cliff_fee_numerator - (period * reduction_factor)
56            let reduction = periods_passed * reduction_factor;
57            if reduction >= self.cliff_fee_numerator {
58              0
59            } else {
60              self.cliff_fee_numerator - reduction
61            }
62          }
63          1 => {
64            // Exponential: cliff_fee_numerator * (1 - reduction_factor/BASIS_POINT_MAX)^period
65            let reduction_rate = reduction_factor as f64 / 10000f64;
66            let decay_factor = (1.0 - reduction_rate).powi(periods_passed as i32);
67            (self.cliff_fee_numerator as f64 * decay_factor) as u64
68          }
69          _ => unreachable!(),
70        }
71      }
72      2 => {
73        // Rate Limiter mode - not implemented yet
74        // Would need to decode: fee_increment_bps, max_limiter_duration, max_fee_bps, reference_amount
75        // For now, return cliff fee
76        self.cliff_fee_numerator
77      }
78      // Default to cliff fee for unknown modes
79      _ => self.cliff_fee_numerator,
80    }
81  }
82}