use rust_decimal::{prelude::FromPrimitive, Decimal};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use uuid::Uuid;
use crate::{SimulationError, Token, UnlockEvent};
#[derive(Debug, Default, PartialEq)]
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
pub struct TokenBuilder {
pub name: Option<String>,
pub symbol: Option<String>,
pub total_supply: Option<i64>,
pub current_supply: Option<f64>,
pub initial_supply_percentage: Option<f64>,
pub inflation_rate: Option<f64>,
pub burn_rate: Option<f64>,
pub initial_price: Option<f64>,
pub airdrop_percentage: Option<f64>,
pub unlock_schedule: Option<Vec<UnlockEvent>>,
}
impl TokenBuilder {
pub fn new() -> Self {
TokenBuilder::default()
}
pub fn name(mut self, name: String) -> Self {
self.name = Some(name);
self
}
pub fn symbol(mut self, symbol: String) -> Self {
self.symbol = Some(symbol);
self
}
pub fn total_supply(mut self, total_supply: i64) -> Self {
self.total_supply = Some(total_supply);
self
}
pub fn current_supply(mut self, current_supply: f64) -> Self {
self.current_supply = Some(current_supply);
self
}
pub fn initial_supply_percentage(mut self, initial_supply_percentage: f64) -> Self {
self.initial_supply_percentage = Some(initial_supply_percentage);
self
}
pub fn inflation_rate(mut self, inflation_rate: f64) -> Self {
self.inflation_rate = Some(inflation_rate);
self
}
pub fn burn_rate(mut self, burn_rate: f64) -> Self {
self.burn_rate = Some(burn_rate);
self
}
pub fn initial_price(mut self, initial_price: f64) -> Self {
self.initial_price = Some(initial_price);
self
}
pub fn airdrop_percentage(mut self, airdrop_percentage: f64) -> Self {
self.airdrop_percentage = Some(airdrop_percentage);
self
}
pub fn unlock_schedule(mut self, unlock_schedule: Vec<UnlockEvent>) -> Self {
self.unlock_schedule = Some(unlock_schedule);
self
}
pub fn build(self) -> Result<Token, SimulationError> {
Ok(Token {
id: Uuid::new_v4(),
name: self.name.ok_or(SimulationError::MissingName)?,
symbol: self.symbol.unwrap_or_else(|| "TKN".to_string()),
total_supply: match self.total_supply {
Some(supply) => Decimal::from_i64(supply).ok_or(SimulationError::InvalidDecimal)?,
None => Decimal::new(1_000_000, 0),
},
current_supply: match self.current_supply {
Some(supply) => Decimal::from_f64(supply).ok_or(SimulationError::InvalidDecimal)?,
None => Decimal::default(),
},
initial_supply_percentage: match self.initial_supply_percentage {
Some(percentage) => {
Decimal::from_f64(percentage).ok_or(SimulationError::InvalidDecimal)?
}
None => Decimal::new(100, 0),
},
inflation_rate: match self.inflation_rate {
Some(rate) => Some(Decimal::from_f64(rate).ok_or(SimulationError::InvalidDecimal)?),
None => None,
},
burn_rate: match self.burn_rate {
Some(rate) => Some(Decimal::from_f64(rate).ok_or(SimulationError::InvalidDecimal)?),
None => None,
},
initial_price: match self.initial_price {
Some(price) => Decimal::from_f64(price).ok_or(SimulationError::InvalidDecimal)?,
None => Decimal::new(1, 0),
},
airdrop_percentage: match self.airdrop_percentage {
Some(percentage) => {
Some(Decimal::from_f64(percentage).ok_or(SimulationError::InvalidDecimal)?)
}
None => None,
},
unlock_schedule: self.unlock_schedule,
})
}
}
#[cfg(test)]
mod tests {
use chrono::Utc;
use super::*;
#[test]
fn test_token_builder_with_defaults() {
let token = TokenBuilder::new()
.name("Test Token".to_string())
.build()
.unwrap();
assert_eq!(token.name, "Test Token");
assert_eq!(token.symbol, "TKN");
assert_eq!(token.total_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, Decimal::new(1, 0));
assert_eq!(token.airdrop_percentage, None);
assert!(token.unlock_schedule.is_none());
}
#[test]
fn test_token_builder() {
let unlock_event = UnlockEvent {
date: Utc::now(),
amount: Decimal::new(100_000, 0),
};
let token = TokenBuilder::new()
.name("Test Token".to_string())
.symbol("TT".to_string())
.total_supply(1_000_000)
.current_supply(100_000.0)
.initial_supply_percentage(50.0)
.inflation_rate(5.0)
.burn_rate(1.0)
.initial_price(2.0)
.airdrop_percentage(10.0)
.unlock_schedule(vec![unlock_event])
.build()
.unwrap();
assert_eq!(token.name, "Test Token");
assert_eq!(token.symbol, "TT");
assert_eq!(token.total_supply, Decimal::new(1_000_000, 0));
assert_eq!(token.current_supply, Decimal::new(100_000, 0));
assert_eq!(token.initial_supply_percentage, Decimal::new(50, 0));
assert_eq!(token.inflation_rate, Some(Decimal::new(5, 0)));
assert_eq!(token.burn_rate, Some(Decimal::new(1, 0)));
assert_eq!(token.initial_price, Decimal::new(2, 0));
assert_eq!(token.airdrop_percentage, Some(Decimal::new(10, 0)));
assert_eq!(token.unlock_schedule.unwrap().len(), 1);
}
#[test]
fn test_token_builder_missing_name() {
let token = TokenBuilder::new().build();
assert_eq!(token, Err(SimulationError::MissingName));
}
}