tokenomics_simulator/
token.rs1use chrono::{DateTime, Utc};
6use rust_decimal::Decimal;
7#[cfg(feature = "serde")]
8use serde::{Deserialize, Serialize};
9use uuid::Uuid;
10
11#[derive(Debug, Clone, PartialEq)]
13#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
14pub struct Token {
15 pub id: Uuid,
17
18 pub name: String,
21
22 pub symbol: String,
25
26 #[cfg_attr(
29 feature = "serde",
30 serde(with = "rust_decimal::serde::arbitrary_precision")
31 )]
32 pub total_supply: Decimal,
33
34 #[cfg_attr(feature = "serde", serde(with = "rust_decimal::serde::float"))]
37 pub current_supply: Decimal,
38
39 #[cfg_attr(feature = "serde", serde(with = "rust_decimal::serde::float"))]
42 pub initial_supply_percentage: Decimal,
43
44 #[cfg_attr(feature = "serde", serde(with = "rust_decimal::serde::float_option"))]
47 pub inflation_rate: Option<Decimal>,
48
49 #[cfg_attr(feature = "serde", serde(with = "rust_decimal::serde::float_option"))]
52 pub burn_rate: Option<Decimal>,
53
54 #[cfg_attr(feature = "serde", serde(with = "rust_decimal::serde::float"))]
57 pub initial_price: Decimal,
58
59 #[cfg_attr(feature = "serde", serde(with = "rust_decimal::serde::float_option"))]
62 pub airdrop_percentage: Option<Decimal>,
63
64 pub unlock_schedule: Option<Vec<UnlockEvent>>,
67}
68
69#[derive(Debug, Clone, PartialEq)]
72#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
73pub struct UnlockEvent {
74 pub date: DateTime<Utc>,
76
77 #[cfg_attr(feature = "serde", serde(with = "rust_decimal::serde::float"))]
79 pub amount: Decimal,
80}
81
82impl Token {
83 pub fn airdrop(&mut self, percentage: Decimal) -> Decimal {
93 #[cfg(feature = "log")]
94 log::debug!(
95 "Airdropping {}% of total supply for token {}",
96 percentage,
97 self.name
98 );
99
100 let airdrop_amount = (self.total_supply * percentage / Decimal::new(100, 0)).round();
101 let remaining_supply = self.total_supply - self.current_supply;
102 let final_airdrop_amount = if airdrop_amount > remaining_supply {
103 remaining_supply
104 } else {
105 airdrop_amount
106 };
107
108 self.current_supply += final_airdrop_amount;
109
110 final_airdrop_amount
111 }
112
113 pub fn add_unlock_event(&mut self, date: DateTime<Utc>, amount: Decimal) {
121 #[cfg(feature = "log")]
122 log::debug!(
123 "Adding unlock event for token {} on {} for {} tokens",
124 self.name,
125 date,
126 amount
127 );
128
129 let event = UnlockEvent { date, amount };
130
131 if let Some(schedule) = &mut self.unlock_schedule {
132 schedule.push(event);
133 } else {
134 self.unlock_schedule = Some(vec![event]);
135 }
136 }
137
138 pub fn process_unlocks(&mut self, current_date: DateTime<Utc>) {
145 if let Some(schedule) = &mut self.unlock_schedule {
146 #[cfg(feature = "log")]
147 log::debug!("Processing unlock events for token {}", self.name);
148
149 schedule.retain(|event| {
150 if event.date <= current_date {
151 self.current_supply += event.amount;
152 false
153 } else {
154 true
155 }
156 });
157 }
158 }
159
160 pub fn initial_supply(&self) -> Decimal {
167 (self.total_supply * self.initial_supply_percentage / Decimal::new(100, 0)).round()
168 }
169}
170
171#[cfg(test)]
172mod tests {
173 use crate::TokenBuilder;
174
175 use super::*;
176
177 #[test]
178 fn test_token_airdrop() {
179 let mut token = TokenBuilder::new()
180 .name("Test Token".to_string())
181 .total_supply(1_000_000)
182 .build()
183 .unwrap();
184 let final_amount = Decimal::new(100000, 0);
185
186 let airdrop_amount = token.airdrop(Decimal::new(10, 0));
187
188 assert_eq!(airdrop_amount, final_amount);
189 assert_eq!(token.current_supply, final_amount);
190
191 let airdrop_amount = token.airdrop(Decimal::new(100, 0));
192
193 assert_eq!(airdrop_amount, Decimal::new(900000, 0));
194 assert_eq!(token.current_supply, Decimal::new(1_000_000, 0));
195 }
196
197 #[test]
198 fn test_add_unlock_event() {
199 let mut token = TokenBuilder::new()
200 .name("Test Token".to_string())
201 .total_supply(1_000_000)
202 .build()
203 .unwrap();
204 let date = Utc::now();
205 let amount = Decimal::new(100000, 0);
206
207 token.add_unlock_event(date, amount);
208 token.add_unlock_event(date, amount);
209
210 assert_eq!(token.unlock_schedule.unwrap().len(), 2);
211 }
212
213 #[test]
214 fn test_process_unlock() {
215 let mut token = TokenBuilder::new()
216 .name("Test Token".to_string())
217 .total_supply(1_000_000)
218 .build()
219 .unwrap();
220 let date = Utc::now();
221 let amount = Decimal::new(100000, 0);
222 token.add_unlock_event(date, amount);
223
224 let current_date = date + chrono::Duration::days(1);
225 token.process_unlocks(current_date);
226
227 assert_eq!(token.current_supply, amount);
228 assert!(token.unlock_schedule.unwrap().is_empty());
229 }
230}