1use chrono::{DateTime, Utc};
9use rand::Rng;
10use rust_decimal::{prelude::*, Decimal, MathematicalOps};
11#[cfg(feature = "serde")]
12use serde::{Deserialize, Serialize};
13use uuid::Uuid;
14
15use crate::{
16 SimulationBuilder, SimulationError, SimulationOptions, SimulationOptionsBuilder,
17 SimulationReport, Token, TokenBuilder, User, ValuationModel,
18};
19
20#[derive(Debug)]
22#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
23pub struct Simulation {
24 pub id: Uuid,
26
27 pub name: String,
30
31 pub token: Token,
34
35 pub description: Option<String>,
38
39 pub status: SimulationStatus,
42
43 pub options: SimulationOptions,
46
47 pub interval_reports: Vec<SimulationReport>,
50
51 pub report: SimulationReport,
54
55 pub created_at: DateTime<Utc>,
57
58 pub updated_at: DateTime<Utc>,
60}
61
62#[derive(Debug, PartialEq, Eq)]
64#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
65pub enum SimulationStatus {
66 Pending,
68
69 Running,
71
72 Completed,
74}
75
76#[derive(Debug, Clone, PartialEq, Eq)]
79#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
80pub enum SimulationInterval {
81 Hourly,
83
84 Daily,
86
87 Weekly,
89
90 Monthly,
92}
93
94impl Simulation {
95 pub fn builder() -> SimulationBuilder {
101 SimulationBuilder::new()
102 }
103
104 pub fn options_builder() -> SimulationOptionsBuilder {
110 SimulationOptionsBuilder::new()
111 }
112
113 pub fn token_builder() -> TokenBuilder {
119 TokenBuilder::new()
120 }
121
122 pub fn update_status(&mut self, status: SimulationStatus) {
128 #[cfg(feature = "log")]
129 log::debug!("Updating simulation status: {:?}", status);
130
131 self.status = status;
132 self.updated_at = Utc::now();
133 }
134
135 pub fn simulate_adoption(&self, current_users: u64) -> Result<u64, SimulationError> {
146 match self.options.adoption_rate {
147 Some(rate) => {
148 #[cfg(feature = "log")]
149 log::debug!("Simulating user adoption for simulation: {}", self.name);
150
151 let new_users = (current_users as f64
152 * rate.to_f64().ok_or(SimulationError::InvalidDecimal)?)
153 .round() as u64;
154
155 let total = current_users + new_users;
156
157 #[cfg(feature = "log")]
158 log::debug!("User adoption simulated: {}", total);
159
160 Ok(total)
161 }
162 None => Ok(current_users),
163 }
164 }
165
166 pub fn calculate_valuation(&self, token: &Token, users: u64) -> Decimal {
179 match self.options.valuation_model {
180 Some(ValuationModel::Linear) => {
181 #[cfg(feature = "log")]
182 log::debug!("Calculating linear valuation for simulation: {}", self.name);
183
184 let valuation = Decimal::from(users) * token.initial_price;
185
186 #[cfg(feature = "log")]
187 log::debug!("Linear valuation calculated: {}", valuation);
188
189 valuation
190 }
191 Some(ValuationModel::Exponential(factor)) => {
192 #[cfg(feature = "log")]
193 log::debug!("Calculating exponential valuation with factor: {}", factor);
194
195 let exponent = match Decimal::from_f64(factor) {
196 Some(factor) => Decimal::from(users) / factor,
197 None => Decimal::from(users),
198 };
199
200 let valuation = match exponent.checked_exp() {
201 Some(exp) => token.initial_price * exp,
202 None => token.initial_price,
203 };
204
205 #[cfg(feature = "log")]
206 log::debug!("Exponential valuation calculated: {}", valuation);
207
208 valuation
209 }
210 _ => Decimal::default(),
211 }
212 }
213
214 pub fn run(&mut self) -> Result<(), SimulationError> {
223 #[cfg(feature = "log")]
224 log::debug!("Running simulation: {}", self.name);
225
226 self.update_status(SimulationStatus::Running);
227
228 let decimal_precision = self.options.decimal_precision;
229
230 #[cfg(feature = "log")]
231 log::debug!(
232 "Generating initial user distribution for simulation: {}",
233 self.name
234 );
235
236 let airdrop_amount = match self.token.airdrop_percentage {
237 Some(percentage) => self.token.airdrop(percentage),
238 None => Decimal::default(),
239 };
240
241 let mut users = User::generate(
242 self.options.total_users,
243 self.token.initial_supply(),
244 self.token.initial_price,
245 decimal_precision,
246 );
247
248 #[cfg(feature = "log")]
249 log::debug!("Initial user distribution generated");
250
251 if !airdrop_amount.is_zero() {
253 #[cfg(feature = "log")]
254 log::debug!("Distributing airdrop amount: {}", airdrop_amount);
255
256 let airdrop_per_user = airdrop_amount / Decimal::new(users.len() as i64, 0);
257
258 #[cfg(feature = "log")]
259 log::debug!("Airdrop amount per user: {}", airdrop_per_user);
260
261 for user in &mut users {
262 user.balance += airdrop_per_user.round_dp(decimal_precision);
263 }
264
265 #[cfg(feature = "log")]
266 log::debug!("Airdrop amount distributed");
267 }
268
269 self.interval_reports = vec![];
270
271 let interval = self.get_interval();
272
273 #[cfg(feature = "log")]
274 log::debug!("Simulation interval: {}", interval);
275
276 for time in (0..self.options.duration * interval).step_by(interval as usize) {
277 #[cfg(feature = "log")]
278 log::debug!("Processing interval: {}", time);
279
280 let current_date = Utc::now() + chrono::Duration::hours(time as i64);
282 self.token.process_unlocks(current_date);
283
284 let current_users = self.simulate_adoption(users.len() as u64)?;
286 users = User::generate(
287 current_users,
288 self.token.initial_supply(),
289 self.token.initial_price,
290 decimal_precision,
291 );
292
293 let valuation = self.calculate_valuation(&self.token, current_users);
294 let mut report = self.process_interval(&mut users, interval)?;
295 report.token_price = valuation;
296 report.interval = current_date.timestamp_millis();
297
298 self.interval_reports.push(report);
299
300 #[cfg(feature = "log")]
301 log::debug!("Interval processed: {}", time);
302 }
303
304 self.generate_final_report(users);
305 self.update_status(SimulationStatus::Completed);
306
307 #[cfg(feature = "log")]
308 log::debug!("Simulation completed: {}", self.name);
309
310 Ok(())
311 }
312
313 pub fn process_interval(
325 &self,
326 users: &mut [User],
327 interval: u64,
328 ) -> Result<SimulationReport, SimulationError> {
329 let mut rng = rand::rng();
330
331 let decimal_precision = self.options.decimal_precision;
332 let mut total_burned = Decimal::default();
333 let mut total_new_tokens = Decimal::default();
334 let mut report = SimulationReport::default();
335
336 for _ in 0..interval {
337 for user in users.iter_mut() {
338 if user.balance.is_zero() {
340 continue;
341 }
342
343 if rng.random_bool(0.5) {
344 let trade_fraction = rng.random_range(0.01..0.1);
346 let max_trade_amount = user
347 .balance
348 .to_f64()
349 .ok_or(SimulationError::InvalidDecimal)?
350 * trade_fraction;
351
352 if max_trade_amount > 0.0 {
354 let trade_amount =
355 Decimal::from_f64(rng.random_range(0.0..max_trade_amount))
356 .ok_or(SimulationError::InvalidDecimal)?
357 .round_dp(decimal_precision);
358
359 user.balance -= trade_amount;
360 report.profit_loss += trade_amount;
361 report.successful_trades += 1;
362
363 if let Some(burn_rate) = self.token.burn_rate {
364 let burned = trade_amount * burn_rate;
365 user.balance -= burned;
366 total_burned += burned;
367 }
368
369 if let Some(inflation_rate) = self.token.inflation_rate {
370 let new_tokens = trade_amount * inflation_rate;
371 user.balance += new_tokens;
372 total_new_tokens += new_tokens;
373 }
374
375 if let Some(fee) = self.options.transaction_fee_percentage {
376 let fee = trade_amount * (fee / Decimal::new(100, 0));
377 user.balance -= fee.round_dp(decimal_precision);
378 }
379 } else {
380 report.failed_trades += 1;
381 }
382 } else {
383 report.failed_trades += 1;
384 }
385 }
386 }
387
388 self.generate_interval_report(users, &mut report, interval);
389
390 Ok(report)
391 }
392
393 pub fn generate_interval_report(
401 &self,
402 users: &[User],
403 report: &mut SimulationReport,
404 interval: u64,
405 ) {
406 #[cfg(feature = "log")]
407 log::debug!("Generating interval report for simulation: {}", self.name);
408
409 let decimal_precision = self.options.decimal_precision;
410
411 report.trades = report.successful_trades + report.failed_trades;
412 report.liquidity = report.calculate_liquidity(
413 Decimal::new(report.trades as i64, 0),
414 Decimal::new(interval as i64, 0),
415 decimal_precision,
416 );
417 report.adoption_rate = report.calculate_adoption_rate(users, decimal_precision);
418 report.burn_rate = report.calculate_burn_rate(
419 report.total_burned,
420 Decimal::new(users.len() as i64, 0),
421 decimal_precision,
422 );
423 report.user_retention = report.calculate_user_retention(users, decimal_precision);
424 report.market_volatility = self.options.market_volatility;
425 report.network_activity = report.trades / interval;
426 report.inflation_rate = report.calculate_inflation_rate(
427 report.total_new_tokens,
428 Decimal::new(users.len() as i64, 0),
429 decimal_precision,
430 );
431
432 #[cfg(feature = "log")]
433 log::debug!("Interval report generated for simulation: {}", self.name);
434 }
435
436 pub fn generate_final_report(&mut self, users: Vec<User>) {
443 #[cfg(feature = "log")]
444 log::debug!("Generating final report for simulation: {}", self.name);
445
446 let mut report = SimulationReport {
447 market_volatility: self.options.market_volatility,
448 ..Default::default()
449 };
450
451 let mut total_burned = Decimal::default();
452 let mut total_new_tokens = Decimal::default();
453 let mut total_token_price = Decimal::default();
454 let decimal_precision = self.options.decimal_precision;
455 let total_users = Decimal::new(self.options.total_users as i64, 0);
456
457 #[cfg(feature = "log")]
458 log::debug!("Total interval reports: {}", self.interval_reports.len());
459
460 for result in self.interval_reports.iter() {
461 report.profit_loss += result.profit_loss;
462 report.trades += result.trades;
463 report.successful_trades += result.successful_trades;
464 report.failed_trades += result.failed_trades;
465
466 total_burned += result.burn_rate * total_users;
467 total_new_tokens += result.inflation_rate * total_users;
468 report.liquidity += result.liquidity;
469 report.adoption_rate += result.adoption_rate;
470 report.user_retention += result.user_retention;
471 total_token_price += result.token_price;
472 }
473
474 let total_trades = Decimal::new(report.trades as i64, 0);
475 let total_intervals = Decimal::new(self.interval_reports.len() as i64, 0);
476
477 report.liquidity = (report.liquidity / total_intervals).round_dp(decimal_precision);
478 report.adoption_rate = (report.adoption_rate / total_intervals).round_dp(decimal_precision);
479 report.user_retention =
480 (report.user_retention / total_intervals).round_dp(decimal_precision);
481 report.burn_rate =
482 report.calculate_burn_rate(total_burned, total_trades, self.options.decimal_precision);
483 report.inflation_rate = (total_new_tokens / total_trades).round_dp(decimal_precision);
484 report.network_activity = report.trades / self.options.duration;
485 report.token_price = (total_token_price / total_intervals).round_dp(decimal_precision);
486 report.users = Some(users);
487
488 self.report = report;
489
490 #[cfg(feature = "log")]
491 log::debug!("Final report generated for simulation: {}", self.name);
492 }
493
494 pub fn get_interval(&self) -> u64 {
500 match self.options.interval_type {
501 SimulationInterval::Hourly => 1,
502 SimulationInterval::Daily => 24,
503 SimulationInterval::Weekly => 24 * 7,
504 SimulationInterval::Monthly => 24 * 30,
505 }
506 }
507}
508
509#[cfg(test)]
510mod tests {
511 use super::*;
512
513 fn setup() -> Simulation {
514 let token = Simulation::token_builder()
515 .name("Test Token".to_string())
516 .total_supply(1_000_000)
517 .build()
518 .unwrap();
519
520 Simulation {
521 id: Uuid::new_v4(),
522 name: "Test Simulation".to_string(),
523 token,
524 description: None,
525 status: SimulationStatus::Running,
526 options: SimulationOptions {
527 duration: 30,
528 total_users: 100,
529 decimal_precision: 4,
530 market_volatility: Decimal::new(5, 1),
531 transaction_fee_percentage: None,
532 interval_type: SimulationInterval::Daily,
533 adoption_rate: None,
534 valuation_model: Some(ValuationModel::Exponential(0.1)),
535 },
536 interval_reports: vec![],
537 report: SimulationReport::default(),
538 created_at: Utc::now(),
539 updated_at: Utc::now(),
540 }
541 }
542
543 #[test]
544 fn test_builder() {
545 let builder = Simulation::builder();
546 assert_eq!(builder, SimulationBuilder::new());
547 }
548
549 #[test]
550 fn test_options_builder() {
551 let builder = Simulation::options_builder();
552 assert_eq!(builder, SimulationOptionsBuilder::new());
553 }
554
555 #[test]
556 fn test_token_builder() {
557 let builder = Simulation::token_builder();
558 assert_eq!(builder, TokenBuilder::new());
559 }
560
561 #[test]
562 fn test_get_interval() {
563 let daily_simulation = setup();
564 assert_eq!(daily_simulation.get_interval(), 24);
565
566 let mut hourly_simulation = setup();
567 hourly_simulation.options.interval_type = SimulationInterval::Hourly;
568 assert_eq!(hourly_simulation.get_interval(), 1);
569
570 let mut weekly_simulation = setup();
571 weekly_simulation.options.interval_type = SimulationInterval::Weekly;
572 assert_eq!(weekly_simulation.get_interval(), 24 * 7);
573
574 let mut monthly_simulation = setup();
575 monthly_simulation.options.interval_type = SimulationInterval::Monthly;
576 assert_eq!(monthly_simulation.get_interval(), 24 * 30);
577 }
578
579 #[test]
580 fn test_update_status() {
581 let mut simulation = setup();
582 simulation.update_status(SimulationStatus::Completed);
583
584 assert_eq!(simulation.status, SimulationStatus::Completed);
585 }
586
587 #[test]
588 fn test_run_with_valid_exponential_factor() {
589 let mut simulation = setup();
590 simulation.options.valuation_model = Some(ValuationModel::Exponential(1.0));
591
592 simulation.run().unwrap();
593
594 assert_eq!(simulation.status, SimulationStatus::Completed);
595 assert_eq!(simulation.interval_reports.len(), 30);
596 }
597
598 #[test]
599 fn test_run_with_airdrop() {
600 let mut simulation = setup();
601 simulation.token.airdrop_percentage = Some(Decimal::new(10, 0));
602
603 simulation.run().unwrap();
604
605 assert_eq!(simulation.status, SimulationStatus::Completed);
606 assert_eq!(simulation.interval_reports.len(), 30);
607 assert_eq!(simulation.report.users.unwrap().len(), 100);
608 }
609
610 #[test]
611 fn test_calculate_valuation_linear() {
612 let mut simulation = setup();
613 simulation.token.initial_price = Decimal::new(1, 2);
614 simulation.options.valuation_model = Some(ValuationModel::Linear);
615
616 let token = &simulation.token;
617 let users = 99;
618 let valuation = simulation.calculate_valuation(token, users);
619
620 assert_eq!(valuation, Decimal::new(99, 2));
621 }
622
623 #[test]
624 fn test_calculate_valuation_exponential() {
625 let mut simulation = setup();
626 simulation.options.valuation_model = Some(ValuationModel::Exponential(2.0));
627
628 let token = &simulation.token;
629 let users = 100;
630 let valuation = simulation.calculate_valuation(token, users);
631
632 assert_eq!(valuation, Decimal::new(1, 0));
633 }
634
635 #[test]
636 fn test_calculate_valuation_exponential_overflow() {
637 let mut simulation = setup();
638 simulation.options.valuation_model = Some(ValuationModel::Exponential(0.1));
639
640 let token = &simulation.token;
641 let users = 1_000_000;
642 let valuation = simulation.calculate_valuation(token, users);
643
644 assert_eq!(valuation, Decimal::new(1, 0));
645 }
646
647 #[test]
648 fn test_calculate_valuation_default() {
649 let mut simulation = setup();
650 simulation.options.valuation_model = None;
651
652 let token = &simulation.token;
653 let users = 1_000_000;
654 let valuation = simulation.calculate_valuation(token, users);
655
656 assert_eq!(valuation, Decimal::default());
657 }
658
659 #[test]
660 fn test_simulate_adoption_with_rate() {
661 let simulation = setup();
662 let current_users = 100;
663
664 let new_users = simulation.simulate_adoption(current_users).unwrap();
665 assert_eq!(new_users, 100);
666
667 let simulation = setup();
668 let current_users = 100;
669
670 let new_users = simulation.simulate_adoption(current_users).unwrap();
671 assert_eq!(new_users, 100);
672 }
673
674 #[test]
675 fn test_simulate_adoption_without_rate() {
676 let simulation = setup();
677 let current_users = 100;
678
679 let new_users = simulation.simulate_adoption(current_users).unwrap();
680 assert_eq!(new_users, 100);
681 }
682}