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}