tokenomics_simulator/
user.rs

1//! # User module
2//!
3//! This module provides functionality to create and manage users in the tokenomics simulator.
4//! Users are entities that interact with the tokenomics system by buying, selling, and holding tokens.
5
6use rand::Rng;
7use rust_decimal::{prelude::*, Decimal};
8#[cfg(feature = "serde")]
9use serde::{Deserialize, Serialize};
10use uuid::Uuid;
11
12/// User.
13#[derive(Debug)]
14#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
15pub struct User {
16    /// ID for the user.
17    pub id: Uuid,
18
19    /// Balance of the user.
20    pub balance: Decimal,
21
22    /// Market behaviour of the user.
23    pub behaviour: UserBehaviour,
24}
25
26/// Market behaviour of the user.
27#[derive(Debug)]
28#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
29pub enum UserBehaviour {
30    /// Speculator: Users who buy and sell tokens frequently to make a profit.
31    Speculator,
32
33    /// Holder: Users who buy tokens and hold them for a long time.
34    Holder,
35
36    /// Trader: Users who trade tokens frequently but do not hold them for long.
37    Trader,
38}
39
40impl User {
41    /// Create a new user.
42    ///
43    /// # Arguments
44    ///
45    /// * `id` - ID for the user.
46    /// * `balance` - Balance of the user.
47    ///
48    /// # Returns
49    ///
50    /// New user.
51    pub fn new(id: Uuid, balance: Decimal) -> Self {
52        User {
53            id,
54            balance,
55            behaviour: UserBehaviour::Trader,
56        }
57    }
58
59    /// Generate a list of users with random balances.
60    ///
61    /// # Arguments
62    ///
63    /// * `total_users` - Total number of users to generate.
64    /// * `supply` - Initial supply of the token.
65    /// * `price` - Initial price of the token.
66    /// * `decimals` - Number of decimal places for the token.
67    ///
68    /// # Returns
69    ///
70    /// List of users with random balances.
71    pub fn generate(total_users: u64, supply: Decimal, price: Decimal, decimals: u32) -> Vec<User> {
72        #[cfg(feature = "log")]
73        log::debug!(
74            "Generating {} users with initial supply of {} and price of {}",
75            total_users,
76            supply,
77            price
78        );
79
80        let mut rng = rand::rng();
81        let mut users = vec![];
82
83        let mut total_balance = Decimal::default();
84        for _ in 0..total_users {
85            let balance = Decimal::from_f64(
86                rng.random_range(
87                    0.0..(supply / Decimal::new(total_users as i64, 0))
88                        .to_f64()
89                        .unwrap(),
90                ),
91            )
92            .unwrap()
93            .round_dp(decimals);
94            total_balance += balance;
95
96            users.push(User {
97                id: Uuid::new_v4(),
98                balance,
99                behaviour: UserBehaviour::Trader,
100            });
101        }
102
103        // Normalize balances to ensure the total does not exceed initial supply
104        let normalization_factor = supply / total_balance;
105        for user in &mut users {
106            user.balance *= normalization_factor;
107            user.balance = user.balance.round_dp(decimals);
108        }
109
110        // Adjust balances based on the initial price
111        for user in &mut users {
112            user.balance *= price;
113            user.balance = user.balance.round_dp(decimals);
114        }
115
116        // Distribute any remaining balance to ensure total balance matches initial supply
117        let mut remaining_balance = supply - users.iter().map(|u| u.balance).sum::<Decimal>();
118        for user in &mut users {
119            if remaining_balance.is_zero() {
120                break;
121            }
122
123            let add_balance = Decimal::min(remaining_balance, Decimal::new(1, 4));
124            user.balance += add_balance;
125            remaining_balance -= add_balance;
126        }
127
128        users
129    }
130}
131
132#[cfg(test)]
133mod tests {
134    use super::*;
135
136    #[test]
137    fn test_user_new() {
138        let id = Uuid::new_v4();
139        let balance = Decimal::new(100, 0);
140
141        let user = User::new(id, balance);
142
143        assert_eq!(user.id, id);
144        assert_eq!(user.balance, balance);
145    }
146
147    #[test]
148    fn test_user_generate() {
149        let total_users = 10;
150        let decimals = 4;
151        let initial_supply = Decimal::new(1000, 0);
152        let initial_price = Decimal::new(1, 0);
153
154        let users = User::generate(total_users, initial_supply, initial_price, decimals);
155
156        assert_eq!(users.len(), total_users as usize);
157
158        let total_balance = users
159            .iter()
160            .map(|user| user.balance.round_dp(decimals))
161            .sum::<Decimal>();
162
163        assert_eq!(total_balance, initial_supply);
164    }
165}