tokenomics_simulator/
token.rsuse chrono::{DateTime, Utc};
use rust_decimal::Decimal;
use serde::{Deserialize, Serialize};
use uuid::Uuid;
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct Token {
pub id: Uuid,
pub name: String,
pub symbol: Option<String>,
pub maximum_supply: Decimal,
pub current_supply: Decimal,
pub initial_supply_percentage: Decimal,
pub inflation_rate: Option<Decimal>,
pub burn_rate: Option<Decimal>,
pub initial_price: Option<Decimal>,
pub airdrop_percentage: Option<Decimal>,
pub unlock_schedule: Option<Vec<UnlockEvent>>,
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
pub struct UnlockEvent {
pub date: DateTime<Utc>,
pub amount: Decimal,
}
impl Default for Token {
fn default() -> Self {
Self {
id: Uuid::new_v4(),
name: "Token".to_string(),
symbol: Some("TKN".to_string()),
maximum_supply: Decimal::new(1_000_000, 0),
current_supply: Decimal::default(),
initial_supply_percentage: Decimal::new(100, 0),
inflation_rate: None,
burn_rate: None,
initial_price: Some(Decimal::new(1, 0)),
airdrop_percentage: None,
unlock_schedule: None,
}
}
}
impl Token {
pub fn airdrop(&mut self, percentage: Decimal) -> Decimal {
let airdrop_amount = (self.maximum_supply * percentage / Decimal::new(100, 0)).round();
let remaining_supply = self.maximum_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) {
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 {
schedule.retain(|event| {
if event.date <= current_date {
self.current_supply += event.amount;
false
} else {
true
}
});
}
}
pub fn initial_supply(&self) -> Decimal {
(self.maximum_supply * self.initial_supply_percentage / Decimal::new(100, 0)).round()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_token_default() {
let token = Token::default();
assert_eq!(token.name, "Token");
assert_eq!(token.symbol, Some("TKN".to_string()));
assert_eq!(token.maximum_supply, Decimal::new(1_000_000, 0));
assert_eq!(token.current_supply, Decimal::default());
assert_eq!(token.initial_supply_percentage, Decimal::new(100, 0));
assert_eq!(token.inflation_rate, None);
assert_eq!(token.burn_rate, None);
assert_eq!(token.initial_price, Some(Decimal::new(1, 0)));
assert_eq!(token.airdrop_percentage, None);
assert_eq!(token.unlock_schedule, None);
}
#[test]
fn test_token_airdrop() {
let mut token = Token::default();
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));
}
}