tokenomics_simulator/
token_builder.rs

1//! # Token builder module
2//!
3//! The module provides a builder for creating a new token with the specified parameters.
4
5use rust_decimal::{prelude::FromPrimitive, Decimal};
6#[cfg(feature = "serde")]
7use serde::{Deserialize, Serialize};
8use uuid::Uuid;
9
10use crate::{SimulationError, Token, UnlockEvent};
11
12/// Builder for creating a new token.
13/// The builder allows to configure the token with the following parameters.
14#[derive(Debug, Default, PartialEq)]
15#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
16pub struct TokenBuilder {
17    /// Name of the token.
18    /// Required field.
19    pub name: Option<String>,
20
21    /// Symbol of the token.
22    /// Default value: "TKN".
23    pub symbol: Option<String>,
24
25    /// Total supply of the token.
26    /// Default value: 1,000,000.
27    pub total_supply: Option<i64>,
28
29    /// Current supply of the token.
30    /// Default value: 0.
31    pub current_supply: Option<f64>,
32
33    /// Initial supply of the token, in percentage of total supply.
34    /// Default value: 100%.
35    pub initial_supply_percentage: Option<f64>,
36
37    /// Annual percentage increase in supply, if supply is inflationary.
38    /// Optional field.
39    pub inflation_rate: Option<f64>,
40
41    /// Percentage of tokens burned during each transaction, if deflationary.
42    /// Optional field.
43    pub burn_rate: Option<f64>,
44
45    /// Initial price of the token in simulation.
46    /// Default value: 1.
47    pub initial_price: Option<f64>,
48
49    /// Airdrop amount of the token, in percentage of total supply.
50    /// Optional field.
51    pub airdrop_percentage: Option<f64>,
52
53    /// Unlock schedule.
54    /// Optional field.
55    pub unlock_schedule: Option<Vec<UnlockEvent>>,
56}
57
58impl TokenBuilder {
59    /// Create a new token builder to configure the token.
60    ///
61    /// # Returns
62    ///
63    /// New token builder.
64    pub fn new() -> Self {
65        TokenBuilder::default()
66    }
67
68    /// Set the name of the token.
69    ///
70    /// # Arguments
71    ///
72    /// * `name` - Name of the token.
73    ///
74    /// # Returns
75    ///
76    /// The token builder.
77    pub fn name(mut self, name: String) -> Self {
78        self.name = Some(name);
79        self
80    }
81
82    /// Set the symbol of the token.
83    ///
84    /// # Arguments
85    ///
86    /// * `symbol` - Symbol of the token.
87    ///
88    /// # Returns
89    ///
90    /// The token builder.
91    pub fn symbol(mut self, symbol: String) -> Self {
92        self.symbol = Some(symbol);
93        self
94    }
95
96    /// Set the total supply of the token.
97    ///
98    /// # Arguments
99    ///
100    /// * `total_supply` - Total supply of the token.
101    ///
102    /// # Returns
103    ///
104    /// The token builder.
105    pub fn total_supply(mut self, total_supply: i64) -> Self {
106        self.total_supply = Some(total_supply);
107        self
108    }
109
110    /// Set the current supply of the token.
111    ///
112    /// # Arguments
113    ///
114    /// * `current_supply` - Current supply of the token.
115    ///
116    /// # Returns
117    ///
118    /// The token builder.
119    pub fn current_supply(mut self, current_supply: f64) -> Self {
120        self.current_supply = Some(current_supply);
121        self
122    }
123
124    /// Set the initial supply of the token, in percentage of total supply.
125    ///
126    /// # Arguments
127    ///
128    /// * `initial_supply_percentage` - Initial supply of the token.
129    ///
130    /// # Returns
131    ///
132    /// The token builder.
133    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    /// Set the annual percentage increase in supply, if supply is inflationary.
139    ///
140    /// # Arguments
141    ///
142    /// * `inflation_rate` - Annual percentage increase in supply.
143    ///
144    /// # Returns
145    ///
146    /// The token builder.
147    pub fn inflation_rate(mut self, inflation_rate: f64) -> Self {
148        self.inflation_rate = Some(inflation_rate);
149        self
150    }
151
152    /// Set the percentage of tokens burned during each transaction, if deflationary.
153    ///
154    /// # Arguments
155    ///
156    /// * `burn_rate` - Percentage of tokens burned during each transaction.
157    ///
158    /// # Returns
159    ///
160    /// The token builder.
161    pub fn burn_rate(mut self, burn_rate: f64) -> Self {
162        self.burn_rate = Some(burn_rate);
163        self
164    }
165
166    /// Set the initial price of the token in simulation.
167    ///
168    ///
169    /// # Arguments
170    ///
171    /// * `initial_price` - Initial price of the token.
172    ///
173    /// # Returns
174    ///
175    /// The token builder.
176    pub fn initial_price(mut self, initial_price: f64) -> Self {
177        self.initial_price = Some(initial_price);
178        self
179    }
180
181    /// Set the airdrop amount of the token, in percentage of total supply.
182    ///
183    /// # Arguments
184    ///
185    /// * `airdrop_percentage` - Airdrop amount of the token.
186    ///
187    /// # Returns
188    ///
189    /// The token builder.
190    pub fn airdrop_percentage(mut self, airdrop_percentage: f64) -> Self {
191        self.airdrop_percentage = Some(airdrop_percentage);
192        self
193    }
194
195    /// Set the unlock schedule.
196    ///
197    /// # Arguments
198    ///
199    /// * `unlock_schedule` - List of unlock events.
200    ///
201    /// # Returns
202    ///
203    /// The token builder.
204    pub fn unlock_schedule(mut self, unlock_schedule: Vec<UnlockEvent>) -> Self {
205        self.unlock_schedule = Some(unlock_schedule);
206        self
207    }
208
209    /// Build the token.
210    ///
211    /// # Returns
212    ///
213    /// Token with the configured parameters.
214    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}