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