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