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