tokenomics_simulator/
engine.rs

1//! # Engine module
2//!
3//! The engine module contains the core logic for the tokenomics simulation.
4//!
5//! This module provides the simulation struct and related types to simulate the tokenomics of a token.
6//! The simulation contains the input parameters, token, and reports for the simulation.
7
8use 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/// Simulation.
21#[derive(Debug)]
22#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
23pub struct Simulation {
24    /// ID of the simulation.
25    pub id: Uuid,
26
27    /// Name of the simulation.
28    /// This is used to identify the simulation.
29    pub name: String,
30
31    /// Token used in the simulation.
32    /// This token is used to simulate the tokenomics.
33    pub token: Token,
34
35    /// Description of the simulation.
36    /// This is used to provide additional information about the simulation.
37    pub description: Option<String>,
38
39    /// Status of the simulation.
40    /// Default is `SimulationStatus::Pending`.
41    pub status: SimulationStatus,
42
43    /// Input parameters for the simulation.
44    /// These parameters are used to configure the simulation.
45    pub options: SimulationOptions,
46
47    /// Report of the results for each interval of the simulation.
48    /// This is used to track the progress of the simulation.
49    pub interval_reports: Vec<SimulationReport>,
50
51    /// Report of the total results of the simulation.
52    /// This is used to provide a summary of the simulation.
53    pub report: SimulationReport,
54
55    /// Date and time the simulation was created.
56    pub created_at: DateTime<Utc>,
57
58    /// Date and time the simulation was last updated.
59    pub updated_at: DateTime<Utc>,
60}
61
62/// Status of a simulation.
63#[derive(Debug, PartialEq, Eq)]
64#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
65pub enum SimulationStatus {
66    /// Simulation has not started.
67    Pending,
68
69    /// Simulation is currently running.
70    Running,
71
72    /// Simulation has completed.
73    Completed,
74}
75
76/// Interval type for the simulation.
77/// This is used to determine the duration of each interval in the simulation.
78#[derive(Debug, Clone, PartialEq, Eq)]
79#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
80pub enum SimulationInterval {
81    /// Hourly interval.
82    Hourly,
83
84    /// Daily interval.
85    Daily,
86
87    /// Weekly interval.
88    Weekly,
89
90    /// Monthly interval.
91    Monthly,
92}
93
94impl Simulation {
95    /// Create a new simulation with the given token and options.
96    ///
97    /// # Returns
98    ///
99    /// New simulation builder.
100    pub fn builder() -> SimulationBuilder {
101        SimulationBuilder::new()
102    }
103
104    /// Create a new simulation options builder to configure the simulation.
105    ///
106    /// # Returns
107    ///
108    /// New simulation options builder.
109    pub fn options_builder() -> SimulationOptionsBuilder {
110        SimulationOptionsBuilder::new()
111    }
112
113    /// Create a new token builder to configure the token used in the simulation.
114    ///
115    /// # Returns
116    ///
117    /// New token builder.
118    pub fn token_builder() -> TokenBuilder {
119        TokenBuilder::new()
120    }
121
122    /// Update the status of the simulation.   
123    ///
124    /// # Arguments
125    ///
126    /// * `status` - The new status of the simulation.
127    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    /// Simulate user adoption based on the current number of users and the adoption rate.
136    /// If the adoption rate is not set, the current number of users is returned.
137    ///
138    /// # Arguments
139    ///
140    /// * `current_users` - The current number of users.
141    ///
142    /// # Returns
143    ///
144    /// The new number of users after adoption.
145    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    /// Calculate the valuation of the token based on the number of users and the initial price.
167    /// The valuation model is used to determine how the valuation is calculated.
168    /// If the valuation model is not set, the default valuation is returned.
169    ///
170    /// # Arguments
171    ///
172    /// * `token` - The token used in the simulation.
173    /// * `users` - The current number of users.
174    ///
175    /// # Returns
176    ///
177    /// The calculated token valuation.
178    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    /// Run the simulation.
215    /// This will simulate the tokenomics based on the input parameters.
216    /// The simulation will run for the specified duration and generate reports for each interval.
217    /// The final report will be generated at the end of the simulation.
218    ///
219    /// # Returns
220    ///
221    /// Result of the simulation.
222    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        // Distribute airdrop amount among users, if available
252        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            // Process unlock events up to the current time
281            let current_date = Utc::now() + chrono::Duration::hours(time as i64);
282            self.token.process_unlocks(current_date);
283
284            // Simulate user adoption
285            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    /// Simulate trades for a given interval.
314    /// This will simulate trades for each user in the list and generate a report for the interval.
315    ///
316    /// # Arguments
317    ///
318    /// * `users` - A list of users.
319    /// * `interval` - Duration of the interval.
320    ///
321    /// # Returns
322    ///
323    /// A report of the simulation results for the interval.
324    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                // Skip users with zero balance
339                if user.balance.is_zero() {
340                    continue;
341                }
342
343                if rng.random_bool(0.5) {
344                    // Simulate a successful trade and randomize the fraction between 1% and 10% of the user's balance
345                    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                    // Ensure the range is valid
353                    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    /// Generate the interval report for the simulation.
394    ///
395    /// # Arguments
396    ///
397    /// * `users` - A list of users.
398    /// * `report` - The simulation report for the interval.
399    /// * `interval` - Duration of the interval.
400    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    /// Calculate the final report for the simulation.
437    /// This will generate a summary of the simulation results based on the interval reports.
438    ///
439    /// # Arguments
440    ///
441    /// * `users` - A list of users.
442    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    /// Get the interval for the simulation.
495    ///
496    /// # Returns
497    ///
498    /// The duration of the simulation interval.
499    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}