use chrono::{DateTime, Utc};
use rust_decimal::Decimal;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use uuid::Uuid;
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
pub struct Token {
pub id: Uuid,
pub name: String,
pub symbol: String,
#[cfg_attr(
feature = "serde",
serde(with = "rust_decimal::serde::arbitrary_precision")
)]
pub total_supply: Decimal,
#[cfg_attr(feature = "serde", serde(with = "rust_decimal::serde::float"))]
pub current_supply: Decimal,
#[cfg_attr(feature = "serde", serde(with = "rust_decimal::serde::float"))]
pub initial_supply_percentage: Decimal,
#[cfg_attr(feature = "serde", serde(with = "rust_decimal::serde::float_option"))]
pub inflation_rate: Option<Decimal>,
#[cfg_attr(feature = "serde", serde(with = "rust_decimal::serde::float_option"))]
pub burn_rate: Option<Decimal>,
#[cfg_attr(feature = "serde", serde(with = "rust_decimal::serde::float"))]
pub initial_price: Decimal,
#[cfg_attr(feature = "serde", serde(with = "rust_decimal::serde::float_option"))]
pub airdrop_percentage: Option<Decimal>,
pub unlock_schedule: Option<Vec<UnlockEvent>>,
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
pub struct UnlockEvent {
pub date: DateTime<Utc>,
#[cfg_attr(feature = "serde", serde(with = "rust_decimal::serde::float"))]
pub amount: Decimal,
}
impl Token {
pub fn airdrop(&mut self, percentage: Decimal) -> Decimal {
#[cfg(feature = "log")]
log::debug!(
"Airdropping {}% of total supply for token {}",
percentage,
self.name
);
let airdrop_amount = (self.total_supply * percentage / Decimal::new(100, 0)).round();
let remaining_supply = self.total_supply - self.current_supply;
let final_airdrop_amount = if airdrop_amount > remaining_supply {
remaining_supply
} else {
airdrop_amount
};
self.current_supply += final_airdrop_amount;
final_airdrop_amount
}
pub fn add_unlock_event(&mut self, date: DateTime<Utc>, amount: Decimal) {
#[cfg(feature = "log")]
log::debug!(
"Adding unlock event for token {} on {} for {} tokens",
self.name,
date,
amount
);
let event = UnlockEvent { date, amount };
if let Some(schedule) = &mut self.unlock_schedule {
schedule.push(event);
} else {
self.unlock_schedule = Some(vec![event]);
}
}
pub fn process_unlocks(&mut self, current_date: DateTime<Utc>) {
if let Some(schedule) = &mut self.unlock_schedule {
#[cfg(feature = "log")]
log::debug!("Processing unlock events for token {}", self.name);
schedule.retain(|event| {
if event.date <= current_date {
self.current_supply += event.amount;
false
} else {
true
}
});
}
}
pub fn initial_supply(&self) -> Decimal {
(self.total_supply * self.initial_supply_percentage / Decimal::new(100, 0)).round()
}
}
#[cfg(test)]
mod tests {
use crate::TokenBuilder;
use super::*;
#[test]
fn test_token_airdrop() {
let mut token = TokenBuilder::new()
.name("Test Token".to_string())
.total_supply(1_000_000)
.build()
.unwrap();
let final_amount = Decimal::new(100000, 0);
let airdrop_amount = token.airdrop(Decimal::new(10, 0));
assert_eq!(airdrop_amount, final_amount);
assert_eq!(token.current_supply, final_amount);
let airdrop_amount = token.airdrop(Decimal::new(100, 0));
assert_eq!(airdrop_amount, Decimal::new(900000, 0));
assert_eq!(token.current_supply, Decimal::new(1_000_000, 0));
}
#[test]
fn test_add_unlock_event() {
let mut token = TokenBuilder::new()
.name("Test Token".to_string())
.total_supply(1_000_000)
.build()
.unwrap();
let date = Utc::now();
let amount = Decimal::new(100000, 0);
token.add_unlock_event(date, amount);
token.add_unlock_event(date, amount);
assert_eq!(token.unlock_schedule.unwrap().len(), 2);
}
#[test]
fn test_process_unlock() {
let mut token = TokenBuilder::new()
.name("Test Token".to_string())
.total_supply(1_000_000)
.build()
.unwrap();
let date = Utc::now();
let amount = Decimal::new(100000, 0);
token.add_unlock_event(date, amount);
let current_date = date + chrono::Duration::days(1);
token.process_unlocks(current_date);
assert_eq!(token.current_supply, amount);
assert!(token.unlock_schedule.unwrap().is_empty());
}
}