tokenomics_simulator/
user.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
// TODO: Allow users to define templates or profiles for different types of users,
// and then generate multiple users based on these templates.

// TODO: Provide an option for users to manually create a few specific users
// and then generate additional users programmatically to reach the desired number.

use rand::Rng;
use rust_decimal::{prelude::*, Decimal};
use serde::{Deserialize, Serialize};
use uuid::Uuid;

use crate::DECIMAL_PRECISION;

#[derive(Debug, Deserialize, Serialize)]
pub struct User {
    /// ID for the user.
    pub id: Uuid,

    /// Balance of the user.
    pub balance: Decimal,
}

impl User {
    /// Create a new user.
    ///
    /// # Arguments
    ///
    /// * `id` - ID for the user.
    /// * `balance` - Balance of the user.
    ///
    /// # Returns
    ///
    /// New user.
    pub fn new(id: Uuid, balance: Decimal) -> Self {
        User { id, balance }
    }

    /// Generate a list of users with random balances.
    ///
    /// # Arguments
    ///
    /// * `total_users` - Total number of users to generate.
    /// * `supply` - Initial supply of the token.
    /// * `price` - Initial price of the token.
    ///
    /// # Returns
    ///
    /// List of users with random balances.
    pub fn generate(total_users: u64, supply: Decimal, price: Option<Decimal>) -> Vec<User> {
        let mut rng = rand::thread_rng();
        let mut users = vec![];

        let mut total_balance = Decimal::default();
        for _ in 0..total_users {
            let balance = Decimal::from_f64(
                rng.gen_range(
                    0.0..(supply / Decimal::new(total_users as i64, 0))
                        .to_f64()
                        .unwrap(),
                ),
            )
            .unwrap()
            .round_dp(DECIMAL_PRECISION);
            total_balance += balance;

            users.push(User {
                id: Uuid::new_v4(),
                balance,
            });
        }

        // Normalize balances to ensure the total does not exceed initial supply
        let normalization_factor = supply / total_balance;
        for user in &mut users {
            user.balance *= normalization_factor;
            user.balance = user.balance.round_dp(DECIMAL_PRECISION);
        }

        // Adjust balances based on the initial price
        if let Some(price) = price {
            for user in &mut users {
                user.balance *= price;
                user.balance = user.balance.round_dp(DECIMAL_PRECISION);
            }
        }

        // Distribute any remaining balance to ensure total balance matches initial supply
        let mut remaining_balance = supply - users.iter().map(|u| u.balance).sum::<Decimal>();
        for user in &mut users {
            if remaining_balance.is_zero() {
                break;
            }

            let add_balance = Decimal::min(remaining_balance, Decimal::new(1, 4));
            user.balance += add_balance;
            remaining_balance -= add_balance;
        }

        users
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_user_new() {
        let id = Uuid::new_v4();
        let balance = Decimal::new(100, 0);

        let user = User::new(id, balance);

        assert_eq!(user.id, id);
        assert_eq!(user.balance, balance);
    }

    #[test]
    fn test_user_generate_with_initial_price() {
        let total_users = 10;
        let initial_supply = Decimal::new(1000, 0);
        let initial_price = Some(Decimal::new(1, 0));

        let users = User::generate(total_users, initial_supply, initial_price);

        assert_eq!(users.len(), total_users as usize);

        let total_balance = users
            .iter()
            .map(|user| user.balance.round_dp(DECIMAL_PRECISION))
            .sum::<Decimal>();

        assert_eq!(total_balance, initial_supply);
    }

    #[test]
    fn test_user_generate_without_initial_price() {
        let total_users = 10;
        let initial_supply = Decimal::new(1000, 0);
        let initial_price = None;

        let users = User::generate(total_users, initial_supply, initial_price);

        assert_eq!(users.len(), total_users as usize);

        let total_balance = users
            .iter()
            .map(|user| user.balance.round_dp(DECIMAL_PRECISION))
            .sum::<Decimal>();

        assert_eq!(total_balance, initial_supply);
    }
}