gemachain_account_decoder/
parse_stake.rs

1use crate::{
2    parse_account_data::{ParsableAccount, ParseAccountError},
3    StringAmount,
4};
5use bincode::deserialize;
6use gemachain_sdk::clock::{Epoch, UnixTimestamp};
7use gemachain_sdk::stake::state::{Authorized, Delegation, Lockup, Meta, Stake, StakeState};
8
9pub fn parse_stake(data: &[u8]) -> Result<StakeAccountType, ParseAccountError> {
10    let stake_state: StakeState = deserialize(data)
11        .map_err(|_| ParseAccountError::AccountNotParsable(ParsableAccount::Stake))?;
12    let parsed_account = match stake_state {
13        StakeState::Uninitialized => StakeAccountType::Uninitialized,
14        StakeState::Initialized(meta) => StakeAccountType::Initialized(UiStakeAccount {
15            meta: meta.into(),
16            stake: None,
17        }),
18        StakeState::Stake(meta, stake) => StakeAccountType::Delegated(UiStakeAccount {
19            meta: meta.into(),
20            stake: Some(stake.into()),
21        }),
22        StakeState::RewardsPool => StakeAccountType::RewardsPool,
23    };
24    Ok(parsed_account)
25}
26
27#[derive(Debug, Serialize, Deserialize, PartialEq)]
28#[serde(rename_all = "camelCase", tag = "type", content = "info")]
29pub enum StakeAccountType {
30    Uninitialized,
31    Initialized(UiStakeAccount),
32    Delegated(UiStakeAccount),
33    RewardsPool,
34}
35
36#[derive(Debug, Serialize, Deserialize, PartialEq)]
37#[serde(rename_all = "camelCase")]
38pub struct UiStakeAccount {
39    pub meta: UiMeta,
40    pub stake: Option<UiStake>,
41}
42
43#[derive(Debug, Serialize, Deserialize, PartialEq)]
44#[serde(rename_all = "camelCase")]
45pub struct UiMeta {
46    pub rent_exempt_reserve: StringAmount,
47    pub authorized: UiAuthorized,
48    pub lockup: UiLockup,
49}
50
51impl From<Meta> for UiMeta {
52    fn from(meta: Meta) -> Self {
53        Self {
54            rent_exempt_reserve: meta.rent_exempt_reserve.to_string(),
55            authorized: meta.authorized.into(),
56            lockup: meta.lockup.into(),
57        }
58    }
59}
60
61#[derive(Debug, Serialize, Deserialize, PartialEq)]
62#[serde(rename_all = "camelCase")]
63pub struct UiLockup {
64    pub unix_timestamp: UnixTimestamp,
65    pub epoch: Epoch,
66    pub custodian: String,
67}
68
69impl From<Lockup> for UiLockup {
70    fn from(lockup: Lockup) -> Self {
71        Self {
72            unix_timestamp: lockup.unix_timestamp,
73            epoch: lockup.epoch,
74            custodian: lockup.custodian.to_string(),
75        }
76    }
77}
78
79#[derive(Debug, Serialize, Deserialize, PartialEq)]
80#[serde(rename_all = "camelCase")]
81pub struct UiAuthorized {
82    pub staker: String,
83    pub withdrawer: String,
84}
85
86impl From<Authorized> for UiAuthorized {
87    fn from(authorized: Authorized) -> Self {
88        Self {
89            staker: authorized.staker.to_string(),
90            withdrawer: authorized.withdrawer.to_string(),
91        }
92    }
93}
94
95#[derive(Debug, Serialize, Deserialize, PartialEq)]
96#[serde(rename_all = "camelCase")]
97pub struct UiStake {
98    pub delegation: UiDelegation,
99    pub credits_observed: u64,
100}
101
102impl From<Stake> for UiStake {
103    fn from(stake: Stake) -> Self {
104        Self {
105            delegation: stake.delegation.into(),
106            credits_observed: stake.credits_observed,
107        }
108    }
109}
110
111#[derive(Debug, Serialize, Deserialize, PartialEq)]
112#[serde(rename_all = "camelCase")]
113pub struct UiDelegation {
114    pub voter: String,
115    pub stake: StringAmount,
116    pub activation_epoch: StringAmount,
117    pub deactivation_epoch: StringAmount,
118    pub warmup_cooldown_rate: f64,
119}
120
121impl From<Delegation> for UiDelegation {
122    fn from(delegation: Delegation) -> Self {
123        Self {
124            voter: delegation.voter_pubkey.to_string(),
125            stake: delegation.stake.to_string(),
126            activation_epoch: delegation.activation_epoch.to_string(),
127            deactivation_epoch: delegation.deactivation_epoch.to_string(),
128            warmup_cooldown_rate: delegation.warmup_cooldown_rate,
129        }
130    }
131}
132
133#[cfg(test)]
134mod test {
135    use super::*;
136    use bincode::serialize;
137
138    #[test]
139    fn test_parse_stake() {
140        let stake_state = StakeState::Uninitialized;
141        let stake_data = serialize(&stake_state).unwrap();
142        assert_eq!(
143            parse_stake(&stake_data).unwrap(),
144            StakeAccountType::Uninitialized
145        );
146
147        let pubkey = gemachain_sdk::pubkey::new_rand();
148        let custodian = gemachain_sdk::pubkey::new_rand();
149        let authorized = Authorized::auto(&pubkey);
150        let lockup = Lockup {
151            unix_timestamp: 0,
152            epoch: 1,
153            custodian,
154        };
155        let meta = Meta {
156            rent_exempt_reserve: 42,
157            authorized,
158            lockup,
159        };
160
161        let stake_state = StakeState::Initialized(meta);
162        let stake_data = serialize(&stake_state).unwrap();
163        assert_eq!(
164            parse_stake(&stake_data).unwrap(),
165            StakeAccountType::Initialized(UiStakeAccount {
166                meta: UiMeta {
167                    rent_exempt_reserve: 42.to_string(),
168                    authorized: UiAuthorized {
169                        staker: pubkey.to_string(),
170                        withdrawer: pubkey.to_string(),
171                    },
172                    lockup: UiLockup {
173                        unix_timestamp: 0,
174                        epoch: 1,
175                        custodian: custodian.to_string(),
176                    }
177                },
178                stake: None,
179            })
180        );
181
182        let voter_pubkey = gemachain_sdk::pubkey::new_rand();
183        let stake = Stake {
184            delegation: Delegation {
185                voter_pubkey,
186                stake: 20,
187                activation_epoch: 2,
188                deactivation_epoch: std::u64::MAX,
189                warmup_cooldown_rate: 0.25,
190            },
191            credits_observed: 10,
192        };
193
194        let stake_state = StakeState::Stake(meta, stake);
195        let stake_data = serialize(&stake_state).unwrap();
196        assert_eq!(
197            parse_stake(&stake_data).unwrap(),
198            StakeAccountType::Delegated(UiStakeAccount {
199                meta: UiMeta {
200                    rent_exempt_reserve: 42.to_string(),
201                    authorized: UiAuthorized {
202                        staker: pubkey.to_string(),
203                        withdrawer: pubkey.to_string(),
204                    },
205                    lockup: UiLockup {
206                        unix_timestamp: 0,
207                        epoch: 1,
208                        custodian: custodian.to_string(),
209                    }
210                },
211                stake: Some(UiStake {
212                    delegation: UiDelegation {
213                        voter: voter_pubkey.to_string(),
214                        stake: 20.to_string(),
215                        activation_epoch: 2.to_string(),
216                        deactivation_epoch: std::u64::MAX.to_string(),
217                        warmup_cooldown_rate: 0.25,
218                    },
219                    credits_observed: 10,
220                })
221            })
222        );
223
224        let stake_state = StakeState::RewardsPool;
225        let stake_data = serialize(&stake_state).unwrap();
226        assert_eq!(
227            parse_stake(&stake_data).unwrap(),
228            StakeAccountType::RewardsPool
229        );
230
231        let bad_data = vec![1, 2, 3, 4];
232        assert!(parse_stake(&bad_data).is_err());
233    }
234}