safecoin_account_decoder/
parse_stake.rs

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