1use rust_decimal::{prelude::FromPrimitive, Decimal};
6#[cfg(feature = "serde")]
7use serde::{Deserialize, Serialize};
8use uuid::Uuid;
9
10use crate::{SimulationError, Token, UnlockEvent};
11
12#[derive(Debug, Default, PartialEq)]
15#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
16pub struct TokenBuilder {
17 pub name: Option<String>,
20
21 pub symbol: Option<String>,
24
25 pub total_supply: Option<i64>,
28
29 pub current_supply: Option<f64>,
32
33 pub initial_supply_percentage: Option<f64>,
36
37 pub inflation_rate: Option<f64>,
40
41 pub burn_rate: Option<f64>,
44
45 pub initial_price: Option<f64>,
48
49 pub airdrop_percentage: Option<f64>,
52
53 pub unlock_schedule: Option<Vec<UnlockEvent>>,
56}
57
58impl TokenBuilder {
59 pub fn new() -> Self {
65 TokenBuilder::default()
66 }
67
68 pub fn name(mut self, name: String) -> Self {
78 self.name = Some(name);
79 self
80 }
81
82 pub fn symbol(mut self, symbol: String) -> Self {
92 self.symbol = Some(symbol);
93 self
94 }
95
96 pub fn total_supply(mut self, total_supply: i64) -> Self {
106 self.total_supply = Some(total_supply);
107 self
108 }
109
110 pub fn current_supply(mut self, current_supply: f64) -> Self {
120 self.current_supply = Some(current_supply);
121 self
122 }
123
124 pub fn initial_supply_percentage(mut self, initial_supply_percentage: f64) -> Self {
134 self.initial_supply_percentage = Some(initial_supply_percentage);
135 self
136 }
137
138 pub fn inflation_rate(mut self, inflation_rate: f64) -> Self {
148 self.inflation_rate = Some(inflation_rate);
149 self
150 }
151
152 pub fn burn_rate(mut self, burn_rate: f64) -> Self {
162 self.burn_rate = Some(burn_rate);
163 self
164 }
165
166 pub fn initial_price(mut self, initial_price: f64) -> Self {
177 self.initial_price = Some(initial_price);
178 self
179 }
180
181 pub fn airdrop_percentage(mut self, airdrop_percentage: f64) -> Self {
191 self.airdrop_percentage = Some(airdrop_percentage);
192 self
193 }
194
195 pub fn unlock_schedule(mut self, unlock_schedule: Vec<UnlockEvent>) -> Self {
205 self.unlock_schedule = Some(unlock_schedule);
206 self
207 }
208
209 pub fn build(self) -> Result<Token, SimulationError> {
215 Ok(Token {
216 id: Uuid::new_v4(),
217 name: self.name.ok_or(SimulationError::MissingName)?,
218 symbol: self.symbol.unwrap_or_else(|| "TKN".to_string()),
219 total_supply: match self.total_supply {
220 Some(supply) => Decimal::from_i64(supply).ok_or(SimulationError::InvalidDecimal)?,
221 None => Decimal::new(1_000_000, 0),
222 },
223 current_supply: match self.current_supply {
224 Some(supply) => Decimal::from_f64(supply).ok_or(SimulationError::InvalidDecimal)?,
225 None => Decimal::default(),
226 },
227 initial_supply_percentage: match self.initial_supply_percentage {
228 Some(percentage) => {
229 Decimal::from_f64(percentage).ok_or(SimulationError::InvalidDecimal)?
230 }
231 None => Decimal::new(100, 0),
232 },
233 inflation_rate: match self.inflation_rate {
234 Some(rate) => Some(Decimal::from_f64(rate).ok_or(SimulationError::InvalidDecimal)?),
235 None => None,
236 },
237 burn_rate: match self.burn_rate {
238 Some(rate) => Some(Decimal::from_f64(rate).ok_or(SimulationError::InvalidDecimal)?),
239 None => None,
240 },
241 initial_price: match self.initial_price {
242 Some(price) => Decimal::from_f64(price).ok_or(SimulationError::InvalidDecimal)?,
243 None => Decimal::new(1, 0),
244 },
245 airdrop_percentage: match self.airdrop_percentage {
246 Some(percentage) => {
247 Some(Decimal::from_f64(percentage).ok_or(SimulationError::InvalidDecimal)?)
248 }
249 None => None,
250 },
251 unlock_schedule: self.unlock_schedule,
252 })
253 }
254}
255
256#[cfg(test)]
257mod tests {
258 use chrono::Utc;
259
260 use super::*;
261
262 #[test]
263 fn test_token_builder_with_defaults() {
264 let token = TokenBuilder::new()
265 .name("Test Token".to_string())
266 .build()
267 .unwrap();
268
269 assert_eq!(token.name, "Test Token");
270 assert_eq!(token.symbol, "TKN");
271 assert_eq!(token.total_supply, Decimal::new(1_000_000, 0));
272 assert_eq!(token.current_supply, Decimal::default());
273 assert_eq!(token.initial_supply_percentage, Decimal::new(100, 0));
274 assert_eq!(token.inflation_rate, None);
275 assert_eq!(token.burn_rate, None);
276 assert_eq!(token.initial_price, Decimal::new(1, 0));
277 assert_eq!(token.airdrop_percentage, None);
278 assert!(token.unlock_schedule.is_none());
279 }
280
281 #[test]
282 fn test_token_builder() {
283 let unlock_event = UnlockEvent {
284 date: Utc::now(),
285 amount: Decimal::new(100_000, 0),
286 };
287
288 let token = TokenBuilder::new()
289 .name("Test Token".to_string())
290 .symbol("TT".to_string())
291 .total_supply(1_000_000)
292 .current_supply(100_000.0)
293 .initial_supply_percentage(50.0)
294 .inflation_rate(5.0)
295 .burn_rate(1.0)
296 .initial_price(2.0)
297 .airdrop_percentage(10.0)
298 .unlock_schedule(vec![unlock_event])
299 .build()
300 .unwrap();
301
302 assert_eq!(token.name, "Test Token");
303 assert_eq!(token.symbol, "TT");
304 assert_eq!(token.total_supply, Decimal::new(1_000_000, 0));
305 assert_eq!(token.current_supply, Decimal::new(100_000, 0));
306 assert_eq!(token.initial_supply_percentage, Decimal::new(50, 0));
307 assert_eq!(token.inflation_rate, Some(Decimal::new(5, 0)));
308 assert_eq!(token.burn_rate, Some(Decimal::new(1, 0)));
309 assert_eq!(token.initial_price, Decimal::new(2, 0));
310 assert_eq!(token.airdrop_percentage, Some(Decimal::new(10, 0)));
311 assert_eq!(token.unlock_schedule.unwrap().len(), 1);
312 }
313
314 #[test]
315 fn test_token_builder_missing_name() {
316 let token = TokenBuilder::new().build();
317
318 assert_eq!(token, Err(SimulationError::MissingName));
319 }
320}