mina_producer_dashboard/node/
epoch_ledgers.rs1use std::{
2 ops::{Add, AddAssign},
3 path::PathBuf,
4};
5
6use num_bigint::BigInt;
7use num_traits::{identities::Zero, FromPrimitive};
8use serde::{Deserialize, Deserializer, Serialize, Serializer};
9use std::str::FromStr;
10
11use crate::StakingToolError;
12
13#[allow(dead_code)]
15#[derive(Debug, Clone, PartialEq)]
16pub struct MinaLedgerDumpBalanceStringNumber(BigInt);
17
18impl<'de> Deserialize<'de> for MinaLedgerDumpBalanceStringNumber {
19 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
20 where
21 D: Deserializer<'de>,
22 {
23 let s = String::deserialize(deserializer)?;
24
25 let parts: Vec<&str> = s.split('.').collect();
26 let mut normalized = String::new();
27 normalized.push_str(parts[0]);
28
29 if parts.len() > 1 {
30 if parts[1].len() > 9 {
31 return Err(serde::de::Error::custom(
33 "Fractional part longer than 9 digits is not supported",
34 ));
35 }
36 normalized.push_str(parts[1]);
38 (parts[1].len()..9).for_each(|_| normalized.push('0'))
39 } else {
40 normalized.extend(std::iter::repeat('0').take(9));
42 }
43
44 BigInt::from_str(&normalized)
45 .map(MinaLedgerDumpBalanceStringNumber)
46 .map_err(serde::de::Error::custom)
47 }
48}
49
50impl Serialize for MinaLedgerDumpBalanceStringNumber {
51 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
52 where
53 S: Serializer,
54 {
55 let divisor = BigInt::from_u64(1_000_000_000).unwrap();
57 let (integral, fractional) = (&self.0 / &divisor, &self.0 % &divisor);
58
59 let integral_str = integral.to_string();
61
62 let fractional_str = if !fractional.is_zero() {
64 let mut fractional_str = fractional.to_string();
66 while fractional_str.len() < 9 {
68 fractional_str.insert(0, '0');
69 }
70 fractional_str.trim_end_matches('0').to_string()
72 } else {
73 String::new()
74 };
75
76 let result = if !fractional_str.is_empty() {
78 format!("{}.{}", integral_str, fractional_str)
79 } else {
80 integral_str
81 };
82
83 serializer.serialize_str(&result)
85 }
86}
87
88impl From<MinaLedgerDumpBalanceStringNumber> for BigInt {
89 fn from(value: MinaLedgerDumpBalanceStringNumber) -> Self {
90 value.0
91 }
92}
93
94impl From<&MinaLedgerDumpBalanceStringNumber> for BigInt {
95 fn from(value: &MinaLedgerDumpBalanceStringNumber) -> Self {
96 value.0.clone()
97 }
98}
99
100impl From<MinaLedgerDumpBalanceStringNumber> for NanoMina {
101 fn from(value: MinaLedgerDumpBalanceStringNumber) -> Self {
102 NanoMina(value.0)
103 }
104}
105
106#[derive(Debug, Clone, PartialEq, Default)]
107pub struct NanoMina(BigInt);
108
109impl Serialize for NanoMina {
110 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
111 where
112 S: Serializer,
113 {
114 serializer.serialize_str(&self.0.to_string())
115 }
116}
117
118impl<'de> Deserialize<'de> for NanoMina {
119 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
120 where
121 D: Deserializer<'de>,
122 {
123 let s = String::deserialize(deserializer)?;
124
125 BigInt::from_str(&s)
126 .map(NanoMina)
127 .map_err(serde::de::Error::custom)
128 }
129}
130
131impl AddAssign for NanoMina {
132 fn add_assign(&mut self, rhs: Self) {
133 *self = Self(self.0.clone() + rhs.0)
134 }
135}
136
137impl Add for NanoMina {
138 type Output = Self;
139
140 fn add(self, rhs: Self) -> Self::Output {
141 Self(self.0 + rhs.0)
142 }
143}
144
145impl NanoMina {
152 pub fn new(value: BigInt) -> Self {
153 let nano_factor: BigInt = 1_000_000_000.into();
154 Self(value * nano_factor)
155 }
156}
157
158#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
159pub struct LedgerEntry {
160 pub pk: String,
161 pub balance: MinaLedgerDumpBalanceStringNumber,
162 pub token: String,
163 pub token_symbol: String,
164 pub delegate: Option<String>,
165}
166
167#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
168pub struct Ledger {
169 inner: Vec<LedgerEntry>,
170}
171
172#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
173pub struct Balances {
174 balance_producer: NanoMina,
175 balance_delegated: NanoMina,
176 balance_staked: NanoMina,
177}
178
179#[allow(dead_code)]
180impl Ledger {
181 pub fn new(inner: Vec<LedgerEntry>) -> Self {
182 Self { inner }
183 }
184
185 pub fn load_from_file(path: PathBuf) -> Result<Self, StakingToolError> {
186 let f = std::fs::File::open(path)?;
187
188 Ok(Self {
189 inner: serde_json::from_reader(f)?,
190 })
191 }
192
193 pub fn gather_producer_and_delegates(&self, producer: &str) -> Vec<(usize, &LedgerEntry)> {
194 self.inner
195 .iter()
196 .enumerate()
197 .filter_map(|(index, entry)| {
198 if entry.pk == producer || entry.delegate.as_deref() == Some(producer) {
199 Some((index, entry))
200 } else {
201 None
202 }
203 })
204 .collect()
205 }
206
207 pub fn total_currency(&self) -> BigInt {
208 self.inner
209 .iter()
210 .filter(|entry| entry.token == "wSHV2S4qX9jFsLjQo8r1BsMLH2ZRKsZx6EJd1sbozGPieEC4Jf")
211 .map(|entry| &entry.balance.0)
212 .sum()
213 }
214
215 pub fn producer_balances(&self, producer: &str) -> Balances {
216 let mut balances = Balances::default();
217
218 for entry in &self.inner {
219 if entry.pk == producer {
220 balances.balance_producer = entry.balance.clone().into();
221 } else if let Some(delegate) = &entry.delegate {
222 if delegate == producer {
223 balances.balance_delegated += entry.balance.clone().into();
224 }
225 }
226 }
227 balances.balance_staked =
228 balances.balance_delegated.clone() + balances.balance_producer.clone();
229 balances
230 }
231}