solana_program/stake/
state.rs

1#![allow(clippy::arithmetic_side_effects)]
2#![deny(clippy::wildcard_enum_match_arm)]
3// Remove the following `allow` when `StakeState` is removed, required to avoid
4// warnings from uses of deprecated types during trait derivations.
5#![allow(deprecated)]
6
7#[cfg(feature = "borsh")]
8use borsh::{io, BorshDeserialize, BorshSchema, BorshSerialize};
9use {
10    crate::{
11        instruction::InstructionError,
12        pubkey::Pubkey,
13        stake::{
14            instruction::{LockupArgs, StakeError},
15            stake_flags::StakeFlags,
16        },
17        stake_history::{StakeHistoryEntry, StakeHistoryGetEntry},
18    },
19    solana_clock::{Clock, Epoch, UnixTimestamp},
20    std::collections::HashSet,
21};
22
23pub type StakeActivationStatus = StakeHistoryEntry;
24
25// means that no more than RATE of current effective stake may be added or subtracted per
26// epoch
27pub const DEFAULT_WARMUP_COOLDOWN_RATE: f64 = 0.25;
28pub const NEW_WARMUP_COOLDOWN_RATE: f64 = 0.09;
29pub const DEFAULT_SLASH_PENALTY: u8 = ((5 * u8::MAX as usize) / 100) as u8;
30
31pub fn warmup_cooldown_rate(current_epoch: Epoch, new_rate_activation_epoch: Option<Epoch>) -> f64 {
32    if current_epoch < new_rate_activation_epoch.unwrap_or(u64::MAX) {
33        DEFAULT_WARMUP_COOLDOWN_RATE
34    } else {
35        NEW_WARMUP_COOLDOWN_RATE
36    }
37}
38
39#[cfg(feature = "borsh")]
40macro_rules! impl_borsh_stake_state {
41    ($borsh:ident) => {
42        impl $borsh::BorshDeserialize for StakeState {
43            fn deserialize_reader<R: io::Read>(reader: &mut R) -> io::Result<Self> {
44                let enum_value: u32 = $borsh::BorshDeserialize::deserialize_reader(reader)?;
45                match enum_value {
46                    0 => Ok(StakeState::Uninitialized),
47                    1 => {
48                        let meta: Meta = $borsh::BorshDeserialize::deserialize_reader(reader)?;
49                        Ok(StakeState::Initialized(meta))
50                    }
51                    2 => {
52                        let meta: Meta = $borsh::BorshDeserialize::deserialize_reader(reader)?;
53                        let stake: Stake = $borsh::BorshDeserialize::deserialize_reader(reader)?;
54                        Ok(StakeState::Stake(meta, stake))
55                    }
56                    3 => Ok(StakeState::RewardsPool),
57                    _ => Err(io::Error::new(
58                        io::ErrorKind::InvalidData,
59                        "Invalid enum value",
60                    )),
61                }
62            }
63        }
64        impl $borsh::BorshSerialize for StakeState {
65            fn serialize<W: io::Write>(&self, writer: &mut W) -> io::Result<()> {
66                match self {
67                    StakeState::Uninitialized => writer.write_all(&0u32.to_le_bytes()),
68                    StakeState::Initialized(meta) => {
69                        writer.write_all(&1u32.to_le_bytes())?;
70                        $borsh::BorshSerialize::serialize(&meta, writer)
71                    }
72                    StakeState::Stake(meta, stake) => {
73                        writer.write_all(&2u32.to_le_bytes())?;
74                        $borsh::BorshSerialize::serialize(&meta, writer)?;
75                        $borsh::BorshSerialize::serialize(&stake, writer)
76                    }
77                    StakeState::RewardsPool => writer.write_all(&3u32.to_le_bytes()),
78                }
79            }
80        }
81    };
82}
83#[cfg_attr(feature = "frozen-abi", derive(AbiExample))]
84#[derive(Debug, Default, Serialize, Deserialize, PartialEq, Clone, Copy)]
85#[allow(clippy::large_enum_variant)]
86#[deprecated(
87    since = "1.17.0",
88    note = "Please use `StakeStateV2` instead, and match the third `StakeFlags` field when matching `StakeStateV2::Stake` to resolve any breakage. For example, `if let StakeState::Stake(meta, stake)` becomes `if let StakeStateV2::Stake(meta, stake, _stake_flags)`."
89)]
90pub enum StakeState {
91    #[default]
92    Uninitialized,
93    Initialized(Meta),
94    Stake(Meta, Stake),
95    RewardsPool,
96}
97#[cfg(feature = "borsh")]
98impl_borsh_stake_state!(borsh);
99#[cfg(feature = "borsh")]
100impl_borsh_stake_state!(borsh0_10);
101impl StakeState {
102    /// The fixed number of bytes used to serialize each stake account
103    pub const fn size_of() -> usize {
104        200 // see test_size_of
105    }
106
107    pub fn stake(&self) -> Option<Stake> {
108        match self {
109            Self::Stake(_meta, stake) => Some(*stake),
110            Self::Uninitialized | Self::Initialized(_) | Self::RewardsPool => None,
111        }
112    }
113
114    pub fn delegation(&self) -> Option<Delegation> {
115        match self {
116            Self::Stake(_meta, stake) => Some(stake.delegation),
117            Self::Uninitialized | Self::Initialized(_) | Self::RewardsPool => None,
118        }
119    }
120
121    pub fn authorized(&self) -> Option<Authorized> {
122        match self {
123            Self::Stake(meta, _stake) => Some(meta.authorized),
124            Self::Initialized(meta) => Some(meta.authorized),
125            Self::Uninitialized | Self::RewardsPool => None,
126        }
127    }
128
129    pub fn lockup(&self) -> Option<Lockup> {
130        self.meta().map(|meta| meta.lockup)
131    }
132
133    pub fn meta(&self) -> Option<Meta> {
134        match self {
135            Self::Stake(meta, _stake) => Some(*meta),
136            Self::Initialized(meta) => Some(*meta),
137            Self::Uninitialized | Self::RewardsPool => None,
138        }
139    }
140}
141
142#[cfg_attr(feature = "frozen-abi", derive(AbiExample))]
143#[derive(Debug, Default, Serialize, Deserialize, PartialEq, Clone, Copy)]
144#[allow(clippy::large_enum_variant)]
145pub enum StakeStateV2 {
146    #[default]
147    Uninitialized,
148    Initialized(Meta),
149    Stake(Meta, Stake, StakeFlags),
150    RewardsPool,
151}
152#[cfg(feature = "borsh")]
153macro_rules! impl_borsh_stake_state_v2 {
154    ($borsh:ident) => {
155        impl $borsh::BorshDeserialize for StakeStateV2 {
156            fn deserialize_reader<R: io::Read>(reader: &mut R) -> io::Result<Self> {
157                let enum_value: u32 = $borsh::BorshDeserialize::deserialize_reader(reader)?;
158                match enum_value {
159                    0 => Ok(StakeStateV2::Uninitialized),
160                    1 => {
161                        let meta: Meta = $borsh::BorshDeserialize::deserialize_reader(reader)?;
162                        Ok(StakeStateV2::Initialized(meta))
163                    }
164                    2 => {
165                        let meta: Meta = $borsh::BorshDeserialize::deserialize_reader(reader)?;
166                        let stake: Stake = $borsh::BorshDeserialize::deserialize_reader(reader)?;
167                        let stake_flags: StakeFlags =
168                            $borsh::BorshDeserialize::deserialize_reader(reader)?;
169                        Ok(StakeStateV2::Stake(meta, stake, stake_flags))
170                    }
171                    3 => Ok(StakeStateV2::RewardsPool),
172                    _ => Err(io::Error::new(
173                        io::ErrorKind::InvalidData,
174                        "Invalid enum value",
175                    )),
176                }
177            }
178        }
179        impl $borsh::BorshSerialize for StakeStateV2 {
180            fn serialize<W: io::Write>(&self, writer: &mut W) -> io::Result<()> {
181                match self {
182                    StakeStateV2::Uninitialized => writer.write_all(&0u32.to_le_bytes()),
183                    StakeStateV2::Initialized(meta) => {
184                        writer.write_all(&1u32.to_le_bytes())?;
185                        $borsh::BorshSerialize::serialize(&meta, writer)
186                    }
187                    StakeStateV2::Stake(meta, stake, stake_flags) => {
188                        writer.write_all(&2u32.to_le_bytes())?;
189                        $borsh::BorshSerialize::serialize(&meta, writer)?;
190                        $borsh::BorshSerialize::serialize(&stake, writer)?;
191                        $borsh::BorshSerialize::serialize(&stake_flags, writer)
192                    }
193                    StakeStateV2::RewardsPool => writer.write_all(&3u32.to_le_bytes()),
194                }
195            }
196        }
197    };
198}
199#[cfg(feature = "borsh")]
200impl_borsh_stake_state_v2!(borsh);
201#[cfg(feature = "borsh")]
202impl_borsh_stake_state_v2!(borsh0_10);
203
204impl StakeStateV2 {
205    /// The fixed number of bytes used to serialize each stake account
206    pub const fn size_of() -> usize {
207        200 // see test_size_of
208    }
209
210    pub fn stake(&self) -> Option<Stake> {
211        match self {
212            Self::Stake(_meta, stake, _stake_flags) => Some(*stake),
213            Self::Uninitialized | Self::Initialized(_) | Self::RewardsPool => None,
214        }
215    }
216
217    pub fn stake_ref(&self) -> Option<&Stake> {
218        match self {
219            Self::Stake(_meta, stake, _stake_flags) => Some(stake),
220            Self::Uninitialized | Self::Initialized(_) | Self::RewardsPool => None,
221        }
222    }
223
224    pub fn delegation(&self) -> Option<Delegation> {
225        match self {
226            Self::Stake(_meta, stake, _stake_flags) => Some(stake.delegation),
227            Self::Uninitialized | Self::Initialized(_) | Self::RewardsPool => None,
228        }
229    }
230
231    pub fn delegation_ref(&self) -> Option<&Delegation> {
232        match self {
233            StakeStateV2::Stake(_meta, stake, _stake_flags) => Some(&stake.delegation),
234            Self::Uninitialized | Self::Initialized(_) | Self::RewardsPool => None,
235        }
236    }
237
238    pub fn authorized(&self) -> Option<Authorized> {
239        match self {
240            Self::Stake(meta, _stake, _stake_flags) => Some(meta.authorized),
241            Self::Initialized(meta) => Some(meta.authorized),
242            Self::Uninitialized | Self::RewardsPool => None,
243        }
244    }
245
246    pub fn lockup(&self) -> Option<Lockup> {
247        self.meta().map(|meta| meta.lockup)
248    }
249
250    pub fn meta(&self) -> Option<Meta> {
251        match self {
252            Self::Stake(meta, _stake, _stake_flags) => Some(*meta),
253            Self::Initialized(meta) => Some(*meta),
254            Self::Uninitialized | Self::RewardsPool => None,
255        }
256    }
257}
258
259#[cfg_attr(feature = "frozen-abi", derive(AbiExample))]
260#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone, Copy)]
261pub enum StakeAuthorize {
262    Staker,
263    Withdrawer,
264}
265
266#[cfg_attr(feature = "frozen-abi", derive(AbiExample))]
267#[cfg_attr(
268    feature = "borsh",
269    derive(BorshSerialize, BorshDeserialize, BorshSchema),
270    borsh(crate = "borsh")
271)]
272#[derive(Default, Debug, Serialize, Deserialize, PartialEq, Eq, Clone, Copy)]
273pub struct Lockup {
274    /// UnixTimestamp at which this stake will allow withdrawal, unless the
275    ///   transaction is signed by the custodian
276    pub unix_timestamp: UnixTimestamp,
277    /// epoch height at which this stake will allow withdrawal, unless the
278    ///   transaction is signed by the custodian
279    pub epoch: Epoch,
280    /// custodian signature on a transaction exempts the operation from
281    ///  lockup constraints
282    pub custodian: Pubkey,
283}
284impl Lockup {
285    pub fn is_in_force(&self, clock: &Clock, custodian: Option<&Pubkey>) -> bool {
286        if custodian == Some(&self.custodian) {
287            return false;
288        }
289        self.unix_timestamp > clock.unix_timestamp || self.epoch > clock.epoch
290    }
291}
292#[cfg(feature = "borsh")]
293impl borsh0_10::de::BorshDeserialize for Lockup {
294    fn deserialize_reader<R: borsh0_10::maybestd::io::Read>(
295        reader: &mut R,
296    ) -> ::core::result::Result<Self, borsh0_10::maybestd::io::Error> {
297        Ok(Self {
298            unix_timestamp: borsh0_10::BorshDeserialize::deserialize_reader(reader)?,
299            epoch: borsh0_10::BorshDeserialize::deserialize_reader(reader)?,
300            custodian: borsh0_10::BorshDeserialize::deserialize_reader(reader)?,
301        })
302    }
303}
304#[cfg(feature = "borsh")]
305impl borsh0_10::BorshSchema for Lockup {
306    fn declaration() -> borsh0_10::schema::Declaration {
307        "Lockup".to_string()
308    }
309    fn add_definitions_recursively(
310        definitions: &mut borsh0_10::maybestd::collections::HashMap<
311            borsh0_10::schema::Declaration,
312            borsh0_10::schema::Definition,
313        >,
314    ) {
315        let fields = borsh0_10::schema::Fields::NamedFields(<[_]>::into_vec(
316            borsh0_10::maybestd::boxed::Box::new([
317                (
318                    "unix_timestamp".to_string(),
319                    <UnixTimestamp as borsh0_10::BorshSchema>::declaration(),
320                ),
321                (
322                    "epoch".to_string(),
323                    <Epoch as borsh0_10::BorshSchema>::declaration(),
324                ),
325                (
326                    "custodian".to_string(),
327                    <Pubkey as borsh0_10::BorshSchema>::declaration(),
328                ),
329            ]),
330        ));
331        let definition = borsh0_10::schema::Definition::Struct { fields };
332        Self::add_definition(
333            <Self as borsh0_10::BorshSchema>::declaration(),
334            definition,
335            definitions,
336        );
337        <UnixTimestamp as borsh0_10::BorshSchema>::add_definitions_recursively(definitions);
338        <Epoch as borsh0_10::BorshSchema>::add_definitions_recursively(definitions);
339        <Pubkey as borsh0_10::BorshSchema>::add_definitions_recursively(definitions);
340    }
341}
342#[cfg(feature = "borsh")]
343impl borsh0_10::ser::BorshSerialize for Lockup {
344    fn serialize<W: borsh0_10::maybestd::io::Write>(
345        &self,
346        writer: &mut W,
347    ) -> ::core::result::Result<(), borsh0_10::maybestd::io::Error> {
348        borsh0_10::BorshSerialize::serialize(&self.unix_timestamp, writer)?;
349        borsh0_10::BorshSerialize::serialize(&self.epoch, writer)?;
350        borsh0_10::BorshSerialize::serialize(&self.custodian, writer)?;
351        Ok(())
352    }
353}
354
355#[cfg_attr(feature = "frozen-abi", derive(AbiExample))]
356#[cfg_attr(
357    feature = "borsh",
358    derive(BorshSerialize, BorshDeserialize, BorshSchema),
359    borsh(crate = "borsh")
360)]
361#[derive(Default, Debug, Serialize, Deserialize, PartialEq, Eq, Clone, Copy)]
362pub struct Authorized {
363    pub staker: Pubkey,
364    pub withdrawer: Pubkey,
365}
366
367impl Authorized {
368    pub fn auto(authorized: &Pubkey) -> Self {
369        Self {
370            staker: *authorized,
371            withdrawer: *authorized,
372        }
373    }
374    pub fn check(
375        &self,
376        signers: &HashSet<Pubkey>,
377        stake_authorize: StakeAuthorize,
378    ) -> Result<(), InstructionError> {
379        let authorized_signer = match stake_authorize {
380            StakeAuthorize::Staker => &self.staker,
381            StakeAuthorize::Withdrawer => &self.withdrawer,
382        };
383
384        if signers.contains(authorized_signer) {
385            Ok(())
386        } else {
387            Err(InstructionError::MissingRequiredSignature)
388        }
389    }
390
391    pub fn authorize(
392        &mut self,
393        signers: &HashSet<Pubkey>,
394        new_authorized: &Pubkey,
395        stake_authorize: StakeAuthorize,
396        lockup_custodian_args: Option<(&Lockup, &Clock, Option<&Pubkey>)>,
397    ) -> Result<(), InstructionError> {
398        match stake_authorize {
399            StakeAuthorize::Staker => {
400                // Allow either the staker or the withdrawer to change the staker key
401                if !signers.contains(&self.staker) && !signers.contains(&self.withdrawer) {
402                    return Err(InstructionError::MissingRequiredSignature);
403                }
404                self.staker = *new_authorized
405            }
406            StakeAuthorize::Withdrawer => {
407                if let Some((lockup, clock, custodian)) = lockup_custodian_args {
408                    if lockup.is_in_force(clock, None) {
409                        match custodian {
410                            None => {
411                                return Err(StakeError::CustodianMissing.into());
412                            }
413                            Some(custodian) => {
414                                if !signers.contains(custodian) {
415                                    return Err(StakeError::CustodianSignatureMissing.into());
416                                }
417
418                                if lockup.is_in_force(clock, Some(custodian)) {
419                                    return Err(StakeError::LockupInForce.into());
420                                }
421                            }
422                        }
423                    }
424                }
425                self.check(signers, stake_authorize)?;
426                self.withdrawer = *new_authorized
427            }
428        }
429        Ok(())
430    }
431}
432#[cfg(feature = "borsh")]
433impl borsh0_10::de::BorshDeserialize for Authorized {
434    fn deserialize_reader<R: borsh0_10::maybestd::io::Read>(
435        reader: &mut R,
436    ) -> ::core::result::Result<Self, borsh0_10::maybestd::io::Error> {
437        Ok(Self {
438            staker: borsh0_10::BorshDeserialize::deserialize_reader(reader)?,
439            withdrawer: borsh0_10::BorshDeserialize::deserialize_reader(reader)?,
440        })
441    }
442}
443#[cfg(feature = "borsh")]
444impl borsh0_10::BorshSchema for Authorized {
445    fn declaration() -> borsh0_10::schema::Declaration {
446        "Authorized".to_string()
447    }
448    fn add_definitions_recursively(
449        definitions: &mut borsh0_10::maybestd::collections::HashMap<
450            borsh0_10::schema::Declaration,
451            borsh0_10::schema::Definition,
452        >,
453    ) {
454        let fields = borsh0_10::schema::Fields::NamedFields(<[_]>::into_vec(
455            borsh0_10::maybestd::boxed::Box::new([
456                (
457                    "staker".to_string(),
458                    <Pubkey as borsh0_10::BorshSchema>::declaration(),
459                ),
460                (
461                    "withdrawer".to_string(),
462                    <Pubkey as borsh0_10::BorshSchema>::declaration(),
463                ),
464            ]),
465        ));
466        let definition = borsh0_10::schema::Definition::Struct { fields };
467        Self::add_definition(
468            <Self as borsh0_10::BorshSchema>::declaration(),
469            definition,
470            definitions,
471        );
472        <Pubkey as borsh0_10::BorshSchema>::add_definitions_recursively(definitions);
473        <Pubkey as borsh0_10::BorshSchema>::add_definitions_recursively(definitions);
474    }
475}
476#[cfg(feature = "borsh")]
477impl borsh0_10::ser::BorshSerialize for Authorized {
478    fn serialize<W: borsh0_10::maybestd::io::Write>(
479        &self,
480        writer: &mut W,
481    ) -> ::core::result::Result<(), borsh0_10::maybestd::io::Error> {
482        borsh0_10::BorshSerialize::serialize(&self.staker, writer)?;
483        borsh0_10::BorshSerialize::serialize(&self.withdrawer, writer)?;
484        Ok(())
485    }
486}
487
488#[cfg_attr(feature = "frozen-abi", derive(AbiExample))]
489#[cfg_attr(
490    feature = "borsh",
491    derive(BorshSerialize, BorshDeserialize, BorshSchema),
492    borsh(crate = "borsh")
493)]
494#[derive(Default, Debug, Serialize, Deserialize, PartialEq, Eq, Clone, Copy)]
495pub struct Meta {
496    pub rent_exempt_reserve: u64,
497    pub authorized: Authorized,
498    pub lockup: Lockup,
499}
500
501impl Meta {
502    pub fn set_lockup(
503        &mut self,
504        lockup: &LockupArgs,
505        signers: &HashSet<Pubkey>,
506        clock: &Clock,
507    ) -> Result<(), InstructionError> {
508        // post-stake_program_v4 behavior:
509        // * custodian can update the lockup while in force
510        // * withdraw authority can set a new lockup
511        if self.lockup.is_in_force(clock, None) {
512            if !signers.contains(&self.lockup.custodian) {
513                return Err(InstructionError::MissingRequiredSignature);
514            }
515        } else if !signers.contains(&self.authorized.withdrawer) {
516            return Err(InstructionError::MissingRequiredSignature);
517        }
518        if let Some(unix_timestamp) = lockup.unix_timestamp {
519            self.lockup.unix_timestamp = unix_timestamp;
520        }
521        if let Some(epoch) = lockup.epoch {
522            self.lockup.epoch = epoch;
523        }
524        if let Some(custodian) = lockup.custodian {
525            self.lockup.custodian = custodian;
526        }
527        Ok(())
528    }
529
530    pub fn auto(authorized: &Pubkey) -> Self {
531        Self {
532            authorized: Authorized::auto(authorized),
533            ..Meta::default()
534        }
535    }
536}
537#[cfg(feature = "borsh")]
538impl borsh0_10::de::BorshDeserialize for Meta {
539    fn deserialize_reader<R: borsh0_10::maybestd::io::Read>(
540        reader: &mut R,
541    ) -> ::core::result::Result<Self, borsh0_10::maybestd::io::Error> {
542        Ok(Self {
543            rent_exempt_reserve: borsh0_10::BorshDeserialize::deserialize_reader(reader)?,
544            authorized: borsh0_10::BorshDeserialize::deserialize_reader(reader)?,
545            lockup: borsh0_10::BorshDeserialize::deserialize_reader(reader)?,
546        })
547    }
548}
549#[cfg(feature = "borsh")]
550impl borsh0_10::BorshSchema for Meta {
551    fn declaration() -> borsh0_10::schema::Declaration {
552        "Meta".to_string()
553    }
554    fn add_definitions_recursively(
555        definitions: &mut borsh0_10::maybestd::collections::HashMap<
556            borsh0_10::schema::Declaration,
557            borsh0_10::schema::Definition,
558        >,
559    ) {
560        let fields = borsh0_10::schema::Fields::NamedFields(<[_]>::into_vec(
561            borsh0_10::maybestd::boxed::Box::new([
562                (
563                    "rent_exempt_reserve".to_string(),
564                    <u64 as borsh0_10::BorshSchema>::declaration(),
565                ),
566                (
567                    "authorized".to_string(),
568                    <Authorized as borsh0_10::BorshSchema>::declaration(),
569                ),
570                (
571                    "lockup".to_string(),
572                    <Lockup as borsh0_10::BorshSchema>::declaration(),
573                ),
574            ]),
575        ));
576        let definition = borsh0_10::schema::Definition::Struct { fields };
577        Self::add_definition(
578            <Self as borsh0_10::BorshSchema>::declaration(),
579            definition,
580            definitions,
581        );
582        <u64 as borsh0_10::BorshSchema>::add_definitions_recursively(definitions);
583        <Authorized as borsh0_10::BorshSchema>::add_definitions_recursively(definitions);
584        <Lockup as borsh0_10::BorshSchema>::add_definitions_recursively(definitions);
585    }
586}
587#[cfg(feature = "borsh")]
588impl borsh0_10::ser::BorshSerialize for Meta {
589    fn serialize<W: borsh0_10::maybestd::io::Write>(
590        &self,
591        writer: &mut W,
592    ) -> ::core::result::Result<(), borsh0_10::maybestd::io::Error> {
593        borsh0_10::BorshSerialize::serialize(&self.rent_exempt_reserve, writer)?;
594        borsh0_10::BorshSerialize::serialize(&self.authorized, writer)?;
595        borsh0_10::BorshSerialize::serialize(&self.lockup, writer)?;
596        Ok(())
597    }
598}
599
600#[cfg_attr(feature = "frozen-abi", derive(AbiExample))]
601#[cfg_attr(
602    feature = "borsh",
603    derive(BorshSerialize, BorshDeserialize, BorshSchema),
604    borsh(crate = "borsh")
605)]
606#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, Copy)]
607pub struct Delegation {
608    /// to whom the stake is delegated
609    pub voter_pubkey: Pubkey,
610    /// activated stake amount, set at delegate() time
611    pub stake: u64,
612    /// epoch at which this stake was activated, std::Epoch::MAX if is a bootstrap stake
613    pub activation_epoch: Epoch,
614    /// epoch the stake was deactivated, std::Epoch::MAX if not deactivated
615    pub deactivation_epoch: Epoch,
616    /// how much stake we can activate per-epoch as a fraction of currently effective stake
617    #[deprecated(
618        since = "1.16.7",
619        note = "Please use `solana_sdk::stake::state::warmup_cooldown_rate()` instead"
620    )]
621    pub warmup_cooldown_rate: f64,
622}
623
624impl Default for Delegation {
625    fn default() -> Self {
626        #[allow(deprecated)]
627        Self {
628            voter_pubkey: Pubkey::default(),
629            stake: 0,
630            activation_epoch: 0,
631            deactivation_epoch: u64::MAX,
632            warmup_cooldown_rate: DEFAULT_WARMUP_COOLDOWN_RATE,
633        }
634    }
635}
636
637impl Delegation {
638    pub fn new(voter_pubkey: &Pubkey, stake: u64, activation_epoch: Epoch) -> Self {
639        Self {
640            voter_pubkey: *voter_pubkey,
641            stake,
642            activation_epoch,
643            ..Delegation::default()
644        }
645    }
646    pub fn is_bootstrap(&self) -> bool {
647        self.activation_epoch == u64::MAX
648    }
649
650    pub fn stake<T: StakeHistoryGetEntry>(
651        &self,
652        epoch: Epoch,
653        history: &T,
654        new_rate_activation_epoch: Option<Epoch>,
655    ) -> u64 {
656        self.stake_activating_and_deactivating(epoch, history, new_rate_activation_epoch)
657            .effective
658    }
659
660    #[allow(clippy::comparison_chain)]
661    pub fn stake_activating_and_deactivating<T: StakeHistoryGetEntry>(
662        &self,
663        target_epoch: Epoch,
664        history: &T,
665        new_rate_activation_epoch: Option<Epoch>,
666    ) -> StakeActivationStatus {
667        // first, calculate an effective and activating stake
668        let (effective_stake, activating_stake) =
669            self.stake_and_activating(target_epoch, history, new_rate_activation_epoch);
670
671        // then de-activate some portion if necessary
672        if target_epoch < self.deactivation_epoch {
673            // not deactivated
674            if activating_stake == 0 {
675                StakeActivationStatus::with_effective(effective_stake)
676            } else {
677                StakeActivationStatus::with_effective_and_activating(
678                    effective_stake,
679                    activating_stake,
680                )
681            }
682        } else if target_epoch == self.deactivation_epoch {
683            // can only deactivate what's activated
684            StakeActivationStatus::with_deactivating(effective_stake)
685        } else if let Some((history, mut prev_epoch, mut prev_cluster_stake)) = history
686            .get_entry(self.deactivation_epoch)
687            .map(|cluster_stake_at_deactivation_epoch| {
688                (
689                    history,
690                    self.deactivation_epoch,
691                    cluster_stake_at_deactivation_epoch,
692                )
693            })
694        {
695            // target_epoch > self.deactivation_epoch
696
697            // loop from my deactivation epoch until the target epoch
698            // current effective stake is updated using its previous epoch's cluster stake
699            let mut current_epoch;
700            let mut current_effective_stake = effective_stake;
701            loop {
702                current_epoch = prev_epoch + 1;
703                // if there is no deactivating stake at prev epoch, we should have been
704                // fully undelegated at this moment
705                if prev_cluster_stake.deactivating == 0 {
706                    break;
707                }
708
709                // I'm trying to get to zero, how much of the deactivation in stake
710                //   this account is entitled to take
711                let weight =
712                    current_effective_stake as f64 / prev_cluster_stake.deactivating as f64;
713                let warmup_cooldown_rate =
714                    warmup_cooldown_rate(current_epoch, new_rate_activation_epoch);
715
716                // portion of newly not-effective cluster stake I'm entitled to at current epoch
717                let newly_not_effective_cluster_stake =
718                    prev_cluster_stake.effective as f64 * warmup_cooldown_rate;
719                let newly_not_effective_stake =
720                    ((weight * newly_not_effective_cluster_stake) as u64).max(1);
721
722                current_effective_stake =
723                    current_effective_stake.saturating_sub(newly_not_effective_stake);
724                if current_effective_stake == 0 {
725                    break;
726                }
727
728                if current_epoch >= target_epoch {
729                    break;
730                }
731                if let Some(current_cluster_stake) = history.get_entry(current_epoch) {
732                    prev_epoch = current_epoch;
733                    prev_cluster_stake = current_cluster_stake;
734                } else {
735                    break;
736                }
737            }
738
739            // deactivating stake should equal to all of currently remaining effective stake
740            StakeActivationStatus::with_deactivating(current_effective_stake)
741        } else {
742            // no history or I've dropped out of history, so assume fully deactivated
743            StakeActivationStatus::default()
744        }
745    }
746
747    // returned tuple is (effective, activating) stake
748    fn stake_and_activating<T: StakeHistoryGetEntry>(
749        &self,
750        target_epoch: Epoch,
751        history: &T,
752        new_rate_activation_epoch: Option<Epoch>,
753    ) -> (u64, u64) {
754        let delegated_stake = self.stake;
755
756        if self.is_bootstrap() {
757            // fully effective immediately
758            (delegated_stake, 0)
759        } else if self.activation_epoch == self.deactivation_epoch {
760            // activated but instantly deactivated; no stake at all regardless of target_epoch
761            // this must be after the bootstrap check and before all-is-activating check
762            (0, 0)
763        } else if target_epoch == self.activation_epoch {
764            // all is activating
765            (0, delegated_stake)
766        } else if target_epoch < self.activation_epoch {
767            // not yet enabled
768            (0, 0)
769        } else if let Some((history, mut prev_epoch, mut prev_cluster_stake)) = history
770            .get_entry(self.activation_epoch)
771            .map(|cluster_stake_at_activation_epoch| {
772                (
773                    history,
774                    self.activation_epoch,
775                    cluster_stake_at_activation_epoch,
776                )
777            })
778        {
779            // target_epoch > self.activation_epoch
780
781            // loop from my activation epoch until the target epoch summing up my entitlement
782            // current effective stake is updated using its previous epoch's cluster stake
783            let mut current_epoch;
784            let mut current_effective_stake = 0;
785            loop {
786                current_epoch = prev_epoch + 1;
787                // if there is no activating stake at prev epoch, we should have been
788                // fully effective at this moment
789                if prev_cluster_stake.activating == 0 {
790                    break;
791                }
792
793                // how much of the growth in stake this account is
794                //  entitled to take
795                let remaining_activating_stake = delegated_stake - current_effective_stake;
796                let weight =
797                    remaining_activating_stake as f64 / prev_cluster_stake.activating as f64;
798                let warmup_cooldown_rate =
799                    warmup_cooldown_rate(current_epoch, new_rate_activation_epoch);
800
801                // portion of newly effective cluster stake I'm entitled to at current epoch
802                let newly_effective_cluster_stake =
803                    prev_cluster_stake.effective as f64 * warmup_cooldown_rate;
804                let newly_effective_stake =
805                    ((weight * newly_effective_cluster_stake) as u64).max(1);
806
807                current_effective_stake += newly_effective_stake;
808                if current_effective_stake >= delegated_stake {
809                    current_effective_stake = delegated_stake;
810                    break;
811                }
812
813                if current_epoch >= target_epoch || current_epoch >= self.deactivation_epoch {
814                    break;
815                }
816                if let Some(current_cluster_stake) = history.get_entry(current_epoch) {
817                    prev_epoch = current_epoch;
818                    prev_cluster_stake = current_cluster_stake;
819                } else {
820                    break;
821                }
822            }
823
824            (
825                current_effective_stake,
826                delegated_stake - current_effective_stake,
827            )
828        } else {
829            // no history or I've dropped out of history, so assume fully effective
830            (delegated_stake, 0)
831        }
832    }
833}
834#[cfg(feature = "borsh")]
835impl borsh0_10::de::BorshDeserialize for Delegation {
836    fn deserialize_reader<R: borsh0_10::maybestd::io::Read>(
837        reader: &mut R,
838    ) -> ::core::result::Result<Self, borsh0_10::maybestd::io::Error> {
839        Ok(Self {
840            voter_pubkey: borsh0_10::BorshDeserialize::deserialize_reader(reader)?,
841            stake: borsh0_10::BorshDeserialize::deserialize_reader(reader)?,
842            activation_epoch: borsh0_10::BorshDeserialize::deserialize_reader(reader)?,
843            deactivation_epoch: borsh0_10::BorshDeserialize::deserialize_reader(reader)?,
844            warmup_cooldown_rate: borsh0_10::BorshDeserialize::deserialize_reader(reader)?,
845        })
846    }
847}
848#[cfg(feature = "borsh")]
849impl borsh0_10::BorshSchema for Delegation {
850    fn declaration() -> borsh0_10::schema::Declaration {
851        "Delegation".to_string()
852    }
853    fn add_definitions_recursively(
854        definitions: &mut borsh0_10::maybestd::collections::HashMap<
855            borsh0_10::schema::Declaration,
856            borsh0_10::schema::Definition,
857        >,
858    ) {
859        let fields = borsh0_10::schema::Fields::NamedFields(<[_]>::into_vec(
860            borsh0_10::maybestd::boxed::Box::new([
861                (
862                    "voter_pubkey".to_string(),
863                    <Pubkey as borsh0_10::BorshSchema>::declaration(),
864                ),
865                (
866                    "stake".to_string(),
867                    <u64 as borsh0_10::BorshSchema>::declaration(),
868                ),
869                (
870                    "activation_epoch".to_string(),
871                    <Epoch as borsh0_10::BorshSchema>::declaration(),
872                ),
873                (
874                    "deactivation_epoch".to_string(),
875                    <Epoch as borsh0_10::BorshSchema>::declaration(),
876                ),
877                (
878                    "warmup_cooldown_rate".to_string(),
879                    <f64 as borsh0_10::BorshSchema>::declaration(),
880                ),
881            ]),
882        ));
883        let definition = borsh0_10::schema::Definition::Struct { fields };
884        Self::add_definition(
885            <Self as borsh0_10::BorshSchema>::declaration(),
886            definition,
887            definitions,
888        );
889        <Pubkey as borsh0_10::BorshSchema>::add_definitions_recursively(definitions);
890        <u64 as borsh0_10::BorshSchema>::add_definitions_recursively(definitions);
891        <Epoch as borsh0_10::BorshSchema>::add_definitions_recursively(definitions);
892        <Epoch as borsh0_10::BorshSchema>::add_definitions_recursively(definitions);
893        <f64 as borsh0_10::BorshSchema>::add_definitions_recursively(definitions);
894    }
895}
896#[cfg(feature = "borsh")]
897impl borsh0_10::ser::BorshSerialize for Delegation {
898    fn serialize<W: borsh0_10::maybestd::io::Write>(
899        &self,
900        writer: &mut W,
901    ) -> ::core::result::Result<(), borsh0_10::maybestd::io::Error> {
902        borsh0_10::BorshSerialize::serialize(&self.voter_pubkey, writer)?;
903        borsh0_10::BorshSerialize::serialize(&self.stake, writer)?;
904        borsh0_10::BorshSerialize::serialize(&self.activation_epoch, writer)?;
905        borsh0_10::BorshSerialize::serialize(&self.deactivation_epoch, writer)?;
906        borsh0_10::BorshSerialize::serialize(&self.warmup_cooldown_rate, writer)?;
907        Ok(())
908    }
909}
910
911#[cfg_attr(feature = "frozen-abi", derive(AbiExample))]
912#[cfg_attr(
913    feature = "borsh",
914    derive(BorshSerialize, BorshDeserialize, BorshSchema),
915    borsh(crate = "borsh")
916)]
917#[derive(Debug, Default, Serialize, Deserialize, PartialEq, Clone, Copy)]
918pub struct Stake {
919    pub delegation: Delegation,
920    /// credits observed is credits from vote account state when delegated or redeemed
921    pub credits_observed: u64,
922}
923
924impl Stake {
925    pub fn stake<T: StakeHistoryGetEntry>(
926        &self,
927        epoch: Epoch,
928        history: &T,
929        new_rate_activation_epoch: Option<Epoch>,
930    ) -> u64 {
931        self.delegation
932            .stake(epoch, history, new_rate_activation_epoch)
933    }
934
935    pub fn split(
936        &mut self,
937        remaining_stake_delta: u64,
938        split_stake_amount: u64,
939    ) -> Result<Self, StakeError> {
940        if remaining_stake_delta > self.delegation.stake {
941            return Err(StakeError::InsufficientStake);
942        }
943        self.delegation.stake -= remaining_stake_delta;
944        let new = Self {
945            delegation: Delegation {
946                stake: split_stake_amount,
947                ..self.delegation
948            },
949            ..*self
950        };
951        Ok(new)
952    }
953
954    pub fn deactivate(&mut self, epoch: Epoch) -> Result<(), StakeError> {
955        if self.delegation.deactivation_epoch != u64::MAX {
956            Err(StakeError::AlreadyDeactivated)
957        } else {
958            self.delegation.deactivation_epoch = epoch;
959            Ok(())
960        }
961    }
962}
963#[cfg(feature = "borsh")]
964impl borsh0_10::de::BorshDeserialize for Stake {
965    fn deserialize_reader<R: borsh0_10::maybestd::io::Read>(
966        reader: &mut R,
967    ) -> ::core::result::Result<Self, borsh0_10::maybestd::io::Error> {
968        Ok(Self {
969            delegation: borsh0_10::BorshDeserialize::deserialize_reader(reader)?,
970            credits_observed: borsh0_10::BorshDeserialize::deserialize_reader(reader)?,
971        })
972    }
973}
974#[cfg(feature = "borsh")]
975impl borsh0_10::BorshSchema for Stake {
976    fn declaration() -> borsh0_10::schema::Declaration {
977        "Stake".to_string()
978    }
979    fn add_definitions_recursively(
980        definitions: &mut borsh0_10::maybestd::collections::HashMap<
981            borsh0_10::schema::Declaration,
982            borsh0_10::schema::Definition,
983        >,
984    ) {
985        let fields = borsh0_10::schema::Fields::NamedFields(<[_]>::into_vec(
986            borsh0_10::maybestd::boxed::Box::new([
987                (
988                    "delegation".to_string(),
989                    <Delegation as borsh0_10::BorshSchema>::declaration(),
990                ),
991                (
992                    "credits_observed".to_string(),
993                    <u64 as borsh0_10::BorshSchema>::declaration(),
994                ),
995            ]),
996        ));
997        let definition = borsh0_10::schema::Definition::Struct { fields };
998        Self::add_definition(
999            <Self as borsh0_10::BorshSchema>::declaration(),
1000            definition,
1001            definitions,
1002        );
1003        <Delegation as borsh0_10::BorshSchema>::add_definitions_recursively(definitions);
1004        <u64 as borsh0_10::BorshSchema>::add_definitions_recursively(definitions);
1005    }
1006}
1007#[cfg(feature = "borsh")]
1008impl borsh0_10::ser::BorshSerialize for Stake {
1009    fn serialize<W: borsh0_10::maybestd::io::Write>(
1010        &self,
1011        writer: &mut W,
1012    ) -> ::core::result::Result<(), borsh0_10::maybestd::io::Error> {
1013        borsh0_10::BorshSerialize::serialize(&self.delegation, writer)?;
1014        borsh0_10::BorshSerialize::serialize(&self.credits_observed, writer)?;
1015        Ok(())
1016    }
1017}
1018
1019#[cfg(test)]
1020mod test {
1021    #[cfg(feature = "borsh")]
1022    use crate::borsh1::try_from_slice_unchecked;
1023    use {super::*, assert_matches::assert_matches, bincode::serialize};
1024
1025    #[cfg(feature = "borsh")]
1026    fn check_borsh_deserialization(stake: StakeStateV2) {
1027        let serialized = serialize(&stake).unwrap();
1028        let deserialized = StakeStateV2::try_from_slice(&serialized).unwrap();
1029        assert_eq!(stake, deserialized);
1030    }
1031
1032    #[cfg(feature = "borsh")]
1033    fn check_borsh_serialization(stake: StakeStateV2) {
1034        let bincode_serialized = serialize(&stake).unwrap();
1035        let borsh_serialized = borsh::to_vec(&stake).unwrap();
1036        assert_eq!(bincode_serialized, borsh_serialized);
1037    }
1038
1039    #[cfg(feature = "borsh")]
1040    #[test]
1041    fn test_size_of() {
1042        assert_eq!(StakeStateV2::size_of(), std::mem::size_of::<StakeStateV2>());
1043    }
1044
1045    #[cfg(feature = "borsh")]
1046    #[test]
1047    fn bincode_vs_borsh_deserialization() {
1048        check_borsh_deserialization(StakeStateV2::Uninitialized);
1049        check_borsh_deserialization(StakeStateV2::RewardsPool);
1050        check_borsh_deserialization(StakeStateV2::Initialized(Meta {
1051            rent_exempt_reserve: u64::MAX,
1052            authorized: Authorized {
1053                staker: Pubkey::new_unique(),
1054                withdrawer: Pubkey::new_unique(),
1055            },
1056            lockup: Lockup::default(),
1057        }));
1058        check_borsh_deserialization(StakeStateV2::Stake(
1059            Meta {
1060                rent_exempt_reserve: 1,
1061                authorized: Authorized {
1062                    staker: Pubkey::new_unique(),
1063                    withdrawer: Pubkey::new_unique(),
1064                },
1065                lockup: Lockup::default(),
1066            },
1067            Stake {
1068                delegation: Delegation {
1069                    voter_pubkey: Pubkey::new_unique(),
1070                    stake: u64::MAX,
1071                    activation_epoch: Epoch::MAX,
1072                    deactivation_epoch: Epoch::MAX,
1073                    ..Delegation::default()
1074                },
1075                credits_observed: 1,
1076            },
1077            StakeFlags::empty(),
1078        ));
1079    }
1080
1081    #[cfg(feature = "borsh")]
1082    #[test]
1083    fn bincode_vs_borsh_serialization() {
1084        check_borsh_serialization(StakeStateV2::Uninitialized);
1085        check_borsh_serialization(StakeStateV2::RewardsPool);
1086        check_borsh_serialization(StakeStateV2::Initialized(Meta {
1087            rent_exempt_reserve: u64::MAX,
1088            authorized: Authorized {
1089                staker: Pubkey::new_unique(),
1090                withdrawer: Pubkey::new_unique(),
1091            },
1092            lockup: Lockup::default(),
1093        }));
1094        #[allow(deprecated)]
1095        check_borsh_serialization(StakeStateV2::Stake(
1096            Meta {
1097                rent_exempt_reserve: 1,
1098                authorized: Authorized {
1099                    staker: Pubkey::new_unique(),
1100                    withdrawer: Pubkey::new_unique(),
1101                },
1102                lockup: Lockup::default(),
1103            },
1104            Stake {
1105                delegation: Delegation {
1106                    voter_pubkey: Pubkey::new_unique(),
1107                    stake: u64::MAX,
1108                    activation_epoch: Epoch::MAX,
1109                    deactivation_epoch: Epoch::MAX,
1110                    ..Default::default()
1111                },
1112                credits_observed: 1,
1113            },
1114            StakeFlags::MUST_FULLY_ACTIVATE_BEFORE_DEACTIVATION_IS_PERMITTED,
1115        ));
1116    }
1117
1118    #[cfg(feature = "borsh")]
1119    #[test]
1120    fn borsh_deserialization_live_data() {
1121        let data = [
1122            1, 0, 0, 0, 128, 213, 34, 0, 0, 0, 0, 0, 133, 0, 79, 231, 141, 29, 73, 61, 232, 35,
1123            119, 124, 168, 12, 120, 216, 195, 29, 12, 166, 139, 28, 36, 182, 186, 154, 246, 149,
1124            224, 109, 52, 100, 133, 0, 79, 231, 141, 29, 73, 61, 232, 35, 119, 124, 168, 12, 120,
1125            216, 195, 29, 12, 166, 139, 28, 36, 182, 186, 154, 246, 149, 224, 109, 52, 100, 0, 0,
1126            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1127            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1128            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1129            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1130            0, 0, 0, 0, 0, 0,
1131        ];
1132        // As long as we get the 4-byte enum and the first field right, then
1133        // we're sure the rest works out
1134        let deserialized = try_from_slice_unchecked::<StakeStateV2>(&data).unwrap();
1135        assert_matches!(
1136            deserialized,
1137            StakeStateV2::Initialized(Meta {
1138                rent_exempt_reserve: 2282880,
1139                ..
1140            })
1141        );
1142    }
1143
1144    #[test]
1145    fn stake_flag_member_offset() {
1146        const FLAG_OFFSET: usize = 196;
1147        let check_flag = |flag, expected| {
1148            let stake = StakeStateV2::Stake(
1149                Meta {
1150                    rent_exempt_reserve: 1,
1151                    authorized: Authorized {
1152                        staker: Pubkey::new_unique(),
1153                        withdrawer: Pubkey::new_unique(),
1154                    },
1155                    lockup: Lockup::default(),
1156                },
1157                Stake {
1158                    delegation: Delegation {
1159                        voter_pubkey: Pubkey::new_unique(),
1160                        stake: u64::MAX,
1161                        activation_epoch: Epoch::MAX,
1162                        deactivation_epoch: Epoch::MAX,
1163                        warmup_cooldown_rate: f64::MAX,
1164                    },
1165                    credits_observed: 1,
1166                },
1167                flag,
1168            );
1169
1170            let bincode_serialized = serialize(&stake).unwrap();
1171            let borsh_serialized = borsh::to_vec(&stake).unwrap();
1172
1173            assert_eq!(bincode_serialized[FLAG_OFFSET], expected);
1174            assert_eq!(borsh_serialized[FLAG_OFFSET], expected);
1175        };
1176        #[allow(deprecated)]
1177        check_flag(
1178            StakeFlags::MUST_FULLY_ACTIVATE_BEFORE_DEACTIVATION_IS_PERMITTED,
1179            1,
1180        );
1181        check_flag(StakeFlags::empty(), 0);
1182    }
1183
1184    mod deprecated {
1185        use super::*;
1186        #[cfg(feature = "borsh")]
1187        fn check_borsh_deserialization(stake: StakeState) {
1188            let serialized = serialize(&stake).unwrap();
1189            let deserialized = StakeState::try_from_slice(&serialized).unwrap();
1190            assert_eq!(stake, deserialized);
1191        }
1192
1193        #[cfg(feature = "borsh")]
1194        fn check_borsh_serialization(stake: StakeState) {
1195            let bincode_serialized = serialize(&stake).unwrap();
1196            let borsh_serialized = borsh::to_vec(&stake).unwrap();
1197            assert_eq!(bincode_serialized, borsh_serialized);
1198        }
1199
1200        #[test]
1201        fn test_size_of() {
1202            assert_eq!(StakeState::size_of(), std::mem::size_of::<StakeState>());
1203        }
1204
1205        #[cfg(feature = "borsh")]
1206        #[test]
1207        fn bincode_vs_borsh_deserialization() {
1208            check_borsh_deserialization(StakeState::Uninitialized);
1209            check_borsh_deserialization(StakeState::RewardsPool);
1210            check_borsh_deserialization(StakeState::Initialized(Meta {
1211                rent_exempt_reserve: u64::MAX,
1212                authorized: Authorized {
1213                    staker: Pubkey::new_unique(),
1214                    withdrawer: Pubkey::new_unique(),
1215                },
1216                lockup: Lockup::default(),
1217            }));
1218            check_borsh_deserialization(StakeState::Stake(
1219                Meta {
1220                    rent_exempt_reserve: 1,
1221                    authorized: Authorized {
1222                        staker: Pubkey::new_unique(),
1223                        withdrawer: Pubkey::new_unique(),
1224                    },
1225                    lockup: Lockup::default(),
1226                },
1227                Stake {
1228                    delegation: Delegation {
1229                        voter_pubkey: Pubkey::new_unique(),
1230                        stake: u64::MAX,
1231                        activation_epoch: Epoch::MAX,
1232                        deactivation_epoch: Epoch::MAX,
1233                        warmup_cooldown_rate: f64::MAX,
1234                    },
1235                    credits_observed: 1,
1236                },
1237            ));
1238        }
1239
1240        #[cfg(feature = "borsh")]
1241        #[test]
1242        fn bincode_vs_borsh_serialization() {
1243            check_borsh_serialization(StakeState::Uninitialized);
1244            check_borsh_serialization(StakeState::RewardsPool);
1245            check_borsh_serialization(StakeState::Initialized(Meta {
1246                rent_exempt_reserve: u64::MAX,
1247                authorized: Authorized {
1248                    staker: Pubkey::new_unique(),
1249                    withdrawer: Pubkey::new_unique(),
1250                },
1251                lockup: Lockup::default(),
1252            }));
1253            check_borsh_serialization(StakeState::Stake(
1254                Meta {
1255                    rent_exempt_reserve: 1,
1256                    authorized: Authorized {
1257                        staker: Pubkey::new_unique(),
1258                        withdrawer: Pubkey::new_unique(),
1259                    },
1260                    lockup: Lockup::default(),
1261                },
1262                Stake {
1263                    delegation: Delegation {
1264                        voter_pubkey: Pubkey::new_unique(),
1265                        stake: u64::MAX,
1266                        activation_epoch: Epoch::MAX,
1267                        deactivation_epoch: Epoch::MAX,
1268                        warmup_cooldown_rate: f64::MAX,
1269                    },
1270                    credits_observed: 1,
1271                },
1272            ));
1273        }
1274
1275        #[cfg(feature = "borsh")]
1276        #[test]
1277        fn borsh_deserialization_live_data() {
1278            let data = [
1279                1, 0, 0, 0, 128, 213, 34, 0, 0, 0, 0, 0, 133, 0, 79, 231, 141, 29, 73, 61, 232, 35,
1280                119, 124, 168, 12, 120, 216, 195, 29, 12, 166, 139, 28, 36, 182, 186, 154, 246,
1281                149, 224, 109, 52, 100, 133, 0, 79, 231, 141, 29, 73, 61, 232, 35, 119, 124, 168,
1282                12, 120, 216, 195, 29, 12, 166, 139, 28, 36, 182, 186, 154, 246, 149, 224, 109, 52,
1283                100, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1284                0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1285                0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1286                0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1287                0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1288            ];
1289            // As long as we get the 4-byte enum and the first field right, then
1290            // we're sure the rest works out
1291            let deserialized = try_from_slice_unchecked::<StakeState>(&data).unwrap();
1292            assert_matches!(
1293                deserialized,
1294                StakeState::Initialized(Meta {
1295                    rent_exempt_reserve: 2282880,
1296                    ..
1297                })
1298            );
1299        }
1300    }
1301}