solana_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, StakeStateV2},
10    },
11};
12
13pub fn parse_stake(data: &[u8]) -> Result<StakeAccountType, ParseAccountError> {
14    let stake_state: StakeStateV2 = deserialize(data)
15        .map_err(|_| ParseAccountError::AccountNotParsable(ParsableAccount::Stake))?;
16    let parsed_account = match stake_state {
17        StakeStateV2::Uninitialized => StakeAccountType::Uninitialized,
18        StakeStateV2::Initialized(meta) => StakeAccountType::Initialized(UiStakeAccount {
19            meta: meta.into(),
20            stake: None,
21        }),
22        StakeStateV2::Stake(meta, stake, _) => StakeAccountType::Delegated(UiStakeAccount {
23            meta: meta.into(),
24            stake: Some(stake.into()),
25        }),
26        StakeStateV2::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    #[deprecated(
123        since = "1.16.7",
124        note = "Please use `solana_sdk::stake::stake::warmup_cooldown_rate()` instead"
125    )]
126    pub warmup_cooldown_rate: f64,
127}
128
129impl From<Delegation> for UiDelegation {
130    fn from(delegation: Delegation) -> Self {
131        #[allow(deprecated)]
132        Self {
133            voter: delegation.voter_pubkey.to_string(),
134            stake: delegation.stake.to_string(),
135            activation_epoch: delegation.activation_epoch.to_string(),
136            deactivation_epoch: delegation.deactivation_epoch.to_string(),
137            warmup_cooldown_rate: delegation.warmup_cooldown_rate,
138        }
139    }
140}
141
142#[cfg(test)]
143mod test {
144    use {super::*, bincode::serialize, solana_sdk::stake::stake_flags::StakeFlags};
145
146    #[test]
147    #[allow(deprecated)]
148    fn test_parse_stake() {
149        let stake_state = StakeStateV2::Uninitialized;
150        let stake_data = serialize(&stake_state).unwrap();
151        assert_eq!(
152            parse_stake(&stake_data).unwrap(),
153            StakeAccountType::Uninitialized
154        );
155
156        let pubkey = solana_sdk::pubkey::new_rand();
157        let custodian = solana_sdk::pubkey::new_rand();
158        let authorized = Authorized::auto(&pubkey);
159        let lockup = Lockup {
160            unix_timestamp: 0,
161            epoch: 1,
162            custodian,
163        };
164        let meta = Meta {
165            rent_exempt_reserve: 42,
166            authorized,
167            lockup,
168        };
169
170        let stake_state = StakeStateV2::Initialized(meta);
171        let stake_data = serialize(&stake_state).unwrap();
172        assert_eq!(
173            parse_stake(&stake_data).unwrap(),
174            StakeAccountType::Initialized(UiStakeAccount {
175                meta: UiMeta {
176                    rent_exempt_reserve: 42.to_string(),
177                    authorized: UiAuthorized {
178                        staker: pubkey.to_string(),
179                        withdrawer: pubkey.to_string(),
180                    },
181                    lockup: UiLockup {
182                        unix_timestamp: 0,
183                        epoch: 1,
184                        custodian: custodian.to_string(),
185                    }
186                },
187                stake: None,
188            })
189        );
190
191        let voter_pubkey = solana_sdk::pubkey::new_rand();
192        let stake = Stake {
193            delegation: Delegation {
194                voter_pubkey,
195                stake: 20,
196                activation_epoch: 2,
197                deactivation_epoch: u64::MAX,
198                warmup_cooldown_rate: 0.25,
199            },
200            credits_observed: 10,
201        };
202
203        let stake_state = StakeStateV2::Stake(meta, stake, StakeFlags::empty());
204        let stake_data = serialize(&stake_state).unwrap();
205        assert_eq!(
206            parse_stake(&stake_data).unwrap(),
207            StakeAccountType::Delegated(UiStakeAccount {
208                meta: UiMeta {
209                    rent_exempt_reserve: 42.to_string(),
210                    authorized: UiAuthorized {
211                        staker: pubkey.to_string(),
212                        withdrawer: pubkey.to_string(),
213                    },
214                    lockup: UiLockup {
215                        unix_timestamp: 0,
216                        epoch: 1,
217                        custodian: custodian.to_string(),
218                    }
219                },
220                stake: Some(UiStake {
221                    delegation: UiDelegation {
222                        voter: voter_pubkey.to_string(),
223                        stake: 20.to_string(),
224                        activation_epoch: 2.to_string(),
225                        deactivation_epoch: u64::MAX.to_string(),
226                        warmup_cooldown_rate: 0.25,
227                    },
228                    credits_observed: 10,
229                })
230            })
231        );
232
233        let stake_state = StakeStateV2::RewardsPool;
234        let stake_data = serialize(&stake_state).unwrap();
235        assert_eq!(
236            parse_stake(&stake_data).unwrap(),
237            StakeAccountType::RewardsPool
238        );
239
240        let bad_data = vec![1, 2, 3, 4];
241        assert!(parse_stake(&bad_data).is_err());
242    }
243}