mina_producer_dashboard/node/
epoch_ledgers.rs

1use 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// TODO(adonagy): remove dead_code
14#[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                // If the fractional part is longer than 9 digits, raise an error, should not happen
32                return Err(serde::de::Error::custom(
33                    "Fractional part longer than 9 digits is not supported",
34                ));
35            }
36            // Extend the fractional part to 9 digits if necessary
37            normalized.push_str(parts[1]);
38            (parts[1].len()..9).for_each(|_| normalized.push('0'))
39        } else {
40            // If no fractional part, just append 9 zeros
41            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        // Divide by 1_000_000_000 to get the integral part and remainder
56        let divisor = BigInt::from_u64(1_000_000_000).unwrap();
57        let (integral, fractional) = (&self.0 / &divisor, &self.0 % &divisor);
58
59        // Convert integral part to string
60        let integral_str = integral.to_string();
61
62        // For the fractional part, only proceed if it's not zero
63        let fractional_str = if !fractional.is_zero() {
64            // Convert fractional part to string
65            let mut fractional_str = fractional.to_string();
66            // Ensure the fractional part is not omitted due to leading zeros
67            while fractional_str.len() < 9 {
68                fractional_str.insert(0, '0');
69            }
70            // Trim trailing zeros if necessary
71            fractional_str.trim_end_matches('0').to_string()
72        } else {
73            String::new()
74        };
75
76        // Combine integral and fractional parts
77        let result = if !fractional_str.is_empty() {
78            format!("{}.{}", integral_str, fractional_str)
79        } else {
80            integral_str
81        };
82
83        // Serialize the combined string
84        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
145// impl From<usize> for NanoMina {
146//     fn from(value: usize) -> Self {
147//         Self(BigInt::from_usize(value).unwrap())
148//     }
149// }
150
151impl 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}