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