use crate::error::CoreError;
use rust_decimal::Decimal;
use serde::{Deserialize, Serialize};
use std::collections::VecDeque;
use std::time::{Duration, SystemTime};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum BuybackStrategy {
Scheduled,
PriceTriggered,
TreasuryBased,
MetricTargeted,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BuybackConfig {
pub strategy: BuybackStrategy,
pub min_interval: Duration,
pub max_amount_per_buyback: Decimal,
pub max_slippage: Decimal,
pub burn_tokens: bool,
}
impl Default for BuybackConfig {
fn default() -> Self {
Self {
strategy: BuybackStrategy::Scheduled,
min_interval: Duration::from_secs(86400), max_amount_per_buyback: Decimal::new(10, 0), max_slippage: Decimal::new(5, 0), burn_tokens: true,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ScheduledBuybackConfig {
pub amount_per_interval: Decimal,
pub interval: Duration,
}
impl Default for ScheduledBuybackConfig {
fn default() -> Self {
Self {
amount_per_interval: Decimal::new(5, 0), interval: Duration::from_secs(604800), }
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PriceTriggeredBuybackConfig {
pub trigger_price: Decimal,
pub buyback_amount: Decimal,
pub cooldown: Duration,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TreasuryBuybackConfig {
pub treasury_allocation_pct: Decimal,
pub min_treasury_balance: Decimal,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BuybackExecution {
pub execution_id: String,
pub timestamp: SystemTime,
pub token_id: String,
pub btc_spent: Decimal,
pub tokens_bought: Decimal,
pub average_price: Decimal,
pub burned: bool,
pub strategy: BuybackStrategy,
}
pub struct AutomatedBuybackManager {
base_config: BuybackConfig,
scheduled_config: Option<ScheduledBuybackConfig>,
price_triggered_config: Option<PriceTriggeredBuybackConfig>,
treasury_config: Option<TreasuryBuybackConfig>,
execution_history: VecDeque<BuybackExecution>,
last_buyback_time: Option<SystemTime>,
execution_counter: u64,
}
impl AutomatedBuybackManager {
pub fn new(base_config: BuybackConfig) -> Self {
Self {
base_config,
scheduled_config: None,
price_triggered_config: None,
treasury_config: None,
execution_history: VecDeque::new(),
last_buyback_time: None,
execution_counter: 0,
}
}
pub fn with_defaults() -> Self {
Self::new(BuybackConfig::default())
}
pub fn with_scheduled_buyback(mut self, config: ScheduledBuybackConfig) -> Self {
self.scheduled_config = Some(config);
self
}
pub fn with_price_triggered_buyback(mut self, config: PriceTriggeredBuybackConfig) -> Self {
self.price_triggered_config = Some(config);
self
}
pub fn with_treasury_buyback(mut self, config: TreasuryBuybackConfig) -> Self {
self.treasury_config = Some(config);
self
}
pub fn should_execute_buyback(
&self,
current_price: Decimal,
treasury_balance: Decimal,
) -> Option<BuybackRecommendation> {
if let Some(last_time) = self.last_buyback_time {
let elapsed = SystemTime::now()
.duration_since(last_time)
.unwrap_or(Duration::ZERO);
if elapsed < self.base_config.min_interval {
return None;
}
}
if let Some(config) = &self.scheduled_config {
if self.is_scheduled_buyback_due(config) {
return Some(BuybackRecommendation {
strategy: BuybackStrategy::Scheduled,
recommended_amount: config.amount_per_interval,
reason: "Scheduled buyback interval elapsed".to_string(),
});
}
}
if let Some(config) = &self.price_triggered_config {
if current_price < config.trigger_price {
let cooldown_ok = if let Some(last_time) = self.last_buyback_time {
let elapsed = SystemTime::now()
.duration_since(last_time)
.unwrap_or(Duration::ZERO);
elapsed >= config.cooldown
} else {
true
};
if cooldown_ok {
return Some(BuybackRecommendation {
strategy: BuybackStrategy::PriceTriggered,
recommended_amount: config.buyback_amount,
reason: format!(
"Price {} below trigger {}",
current_price, config.trigger_price
),
});
}
}
}
if let Some(config) = &self.treasury_config {
if treasury_balance >= config.min_treasury_balance {
let buyback_amount =
treasury_balance * config.treasury_allocation_pct / Decimal::new(100, 0);
if buyback_amount > Decimal::ZERO {
return Some(BuybackRecommendation {
strategy: BuybackStrategy::TreasuryBased,
recommended_amount: buyback_amount
.min(self.base_config.max_amount_per_buyback),
reason: format!("Treasury balance {} exceeds minimum", treasury_balance),
});
}
}
}
None
}
fn is_scheduled_buyback_due(&self, config: &ScheduledBuybackConfig) -> bool {
match self.last_buyback_time {
None => true, Some(last_time) => {
let elapsed = SystemTime::now()
.duration_since(last_time)
.unwrap_or(Duration::ZERO);
elapsed >= config.interval
}
}
}
pub fn execute_buyback(
&mut self,
token_id: String,
btc_amount: Decimal,
token_amount: Decimal,
strategy: BuybackStrategy,
) -> Result<BuybackExecution, CoreError> {
if btc_amount > self.base_config.max_amount_per_buyback {
return Err(CoreError::Validation(format!(
"Buyback amount {} exceeds maximum {}",
btc_amount, self.base_config.max_amount_per_buyback
)));
}
if btc_amount <= Decimal::ZERO || token_amount <= Decimal::ZERO {
return Err(CoreError::Validation(
"Buyback amounts must be positive".to_string(),
));
}
let average_price = btc_amount / token_amount;
self.execution_counter += 1;
let execution = BuybackExecution {
execution_id: format!("buyback_{}", self.execution_counter),
timestamp: SystemTime::now(),
token_id,
btc_spent: btc_amount,
tokens_bought: token_amount,
average_price,
burned: self.base_config.burn_tokens,
strategy,
};
self.execution_history.push_back(execution.clone());
self.last_buyback_time = Some(SystemTime::now());
if self.execution_history.len() > 100 {
self.execution_history.pop_front();
}
Ok(execution)
}
pub fn get_statistics(&self) -> BuybackStatistics {
let total_btc_spent: Decimal = self.execution_history.iter().map(|e| e.btc_spent).sum();
let total_tokens_bought: Decimal =
self.execution_history.iter().map(|e| e.tokens_bought).sum();
let total_tokens_burned: Decimal = self
.execution_history
.iter()
.filter(|e| e.burned)
.map(|e| e.tokens_bought)
.sum();
let average_price = if total_tokens_bought > Decimal::ZERO {
total_btc_spent / total_tokens_bought
} else {
Decimal::ZERO
};
BuybackStatistics {
total_executions: self.execution_history.len(),
total_btc_spent,
total_tokens_bought,
total_tokens_burned,
average_price,
}
}
pub fn get_history(&self, limit: usize) -> Vec<&BuybackExecution> {
self.execution_history.iter().rev().take(limit).collect()
}
pub fn time_until_next_buyback(&self) -> Option<Duration> {
self.scheduled_config.as_ref().and_then(|config| {
self.last_buyback_time.map(|last_time| {
let elapsed = SystemTime::now()
.duration_since(last_time)
.unwrap_or(Duration::ZERO);
config.interval.saturating_sub(elapsed)
})
})
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BuybackRecommendation {
pub strategy: BuybackStrategy,
pub recommended_amount: Decimal,
pub reason: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BuybackStatistics {
pub total_executions: usize,
pub total_btc_spent: Decimal,
pub total_tokens_bought: Decimal,
pub total_tokens_burned: Decimal,
pub average_price: Decimal,
}
pub struct TreasuryManager {
total_balance: Decimal,
allocated_for_buyback: Decimal,
min_reserve: Decimal,
}
impl TreasuryManager {
pub fn new(total_balance: Decimal, min_reserve: Decimal) -> Self {
Self {
total_balance,
allocated_for_buyback: Decimal::ZERO,
min_reserve,
}
}
pub fn allocate_for_buyback(&mut self, amount: Decimal) -> Result<(), CoreError> {
let available = self.total_balance - self.min_reserve - self.allocated_for_buyback;
if amount > available {
return Err(CoreError::InsufficientBalance {
required: amount,
available,
});
}
self.allocated_for_buyback += amount;
Ok(())
}
pub fn execute_buyback(&mut self, btc_spent: Decimal) -> Result<(), CoreError> {
if btc_spent > self.allocated_for_buyback {
return Err(CoreError::InsufficientBalance {
required: btc_spent,
available: self.allocated_for_buyback,
});
}
self.total_balance -= btc_spent;
self.allocated_for_buyback -= btc_spent;
Ok(())
}
pub fn available_for_buyback(&self) -> Decimal {
(self.total_balance - self.min_reserve - self.allocated_for_buyback).max(Decimal::ZERO)
}
pub fn total_balance(&self) -> Decimal {
self.total_balance
}
pub fn add_to_treasury(&mut self, amount: Decimal) {
self.total_balance += amount;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_scheduled_buyback() {
let config = BuybackConfig {
strategy: BuybackStrategy::Scheduled,
..Default::default()
};
let manager =
AutomatedBuybackManager::new(config).with_scheduled_buyback(ScheduledBuybackConfig {
amount_per_interval: Decimal::new(5, 0),
interval: Duration::from_secs(60),
});
let recommendation = manager.should_execute_buyback(Decimal::ONE, Decimal::new(100, 0));
assert!(recommendation.is_some());
if let Some(rec) = recommendation {
assert_eq!(rec.strategy, BuybackStrategy::Scheduled);
assert_eq!(rec.recommended_amount, Decimal::new(5, 0));
}
}
#[test]
fn test_price_triggered_buyback() {
let config = BuybackConfig {
strategy: BuybackStrategy::PriceTriggered,
..Default::default()
};
let manager = AutomatedBuybackManager::new(config).with_price_triggered_buyback(
PriceTriggeredBuybackConfig {
trigger_price: Decimal::new(10, 0),
buyback_amount: Decimal::new(3, 0),
cooldown: Duration::from_secs(3600),
},
);
let recommendation1 =
manager.should_execute_buyback(Decimal::new(15, 0), Decimal::new(100, 0));
assert!(recommendation1.is_none());
let recommendation2 =
manager.should_execute_buyback(Decimal::new(8, 0), Decimal::new(100, 0));
assert!(recommendation2.is_some());
}
#[test]
fn test_execute_buyback() {
let mut manager = AutomatedBuybackManager::with_defaults();
let result = manager.execute_buyback(
"token1".to_string(),
Decimal::new(5, 0),
Decimal::new(100, 0),
BuybackStrategy::Scheduled,
);
assert!(result.is_ok());
let execution = result.unwrap();
assert_eq!(execution.btc_spent, Decimal::new(5, 0));
assert_eq!(execution.tokens_bought, Decimal::new(100, 0));
assert_eq!(execution.average_price, Decimal::new(5, 2)); }
#[test]
fn test_buyback_statistics() {
let mut manager = AutomatedBuybackManager::with_defaults();
manager
.execute_buyback(
"token1".to_string(),
Decimal::new(5, 0),
Decimal::new(100, 0),
BuybackStrategy::Scheduled,
)
.unwrap();
manager
.execute_buyback(
"token1".to_string(),
Decimal::new(3, 0),
Decimal::new(60, 0),
BuybackStrategy::Scheduled,
)
.unwrap();
let stats = manager.get_statistics();
assert_eq!(stats.total_executions, 2);
assert_eq!(stats.total_btc_spent, Decimal::new(8, 0));
assert_eq!(stats.total_tokens_bought, Decimal::new(160, 0));
}
#[test]
fn test_treasury_manager() {
let mut treasury = TreasuryManager::new(
Decimal::new(100, 0), Decimal::new(20, 0), );
assert!(treasury.allocate_for_buyback(Decimal::new(50, 0)).is_ok());
assert_eq!(treasury.available_for_buyback(), Decimal::new(30, 0));
assert!(treasury.execute_buyback(Decimal::new(25, 0)).is_ok());
assert_eq!(treasury.total_balance(), Decimal::new(75, 0));
assert_eq!(treasury.allocated_for_buyback, Decimal::new(25, 0));
}
#[test]
fn test_treasury_insufficient_balance() {
let mut treasury = TreasuryManager::new(Decimal::new(100, 0), Decimal::new(20, 0));
let result = treasury.allocate_for_buyback(Decimal::new(90, 0));
assert!(result.is_err());
}
#[test]
fn test_max_buyback_amount_validation() {
let mut manager = AutomatedBuybackManager::with_defaults();
let result = manager.execute_buyback(
"token1".to_string(),
Decimal::new(20, 0), Decimal::new(100, 0),
BuybackStrategy::Scheduled,
);
assert!(result.is_err());
}
}