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