Skip to main content

solana_stake_interface/
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};
9#[cfg(feature = "codama")]
10use codama_macros::CodamaType;
11use {
12    crate::{
13        error::StakeError,
14        instruction::LockupArgs,
15        stake_flags::StakeFlags,
16        stake_history::{StakeHistoryEntry, StakeHistoryGetEntry},
17        warmup_cooldown_allowance::{
18            calculate_activation_allowance, calculate_deactivation_allowance,
19        },
20    },
21    solana_clock::{Clock, Epoch, UnixTimestamp},
22    solana_instruction::error::InstructionError,
23    solana_pubkey::Pubkey,
24    std::collections::HashSet,
25};
26
27pub type StakeActivationStatus = StakeHistoryEntry;
28
29// Means that no more than RATE of current effective stake may be added or subtracted per
30// epoch.
31#[deprecated(
32    since = "3.2.0",
33    note = "Use `warmup_cooldown_allowance::ORIGINAL_WARMUP_COOLDOWN_RATE_BPS` instead"
34)]
35pub const DEFAULT_WARMUP_COOLDOWN_RATE: f64 = 0.25;
36#[deprecated(
37    since = "3.2.0",
38    note = "Use `warmup_cooldown_allowance::TOWER_WARMUP_COOLDOWN_RATE_BPS` instead"
39)]
40pub const NEW_WARMUP_COOLDOWN_RATE: f64 = 0.09;
41pub const DEFAULT_SLASH_PENALTY: u8 = ((5 * u8::MAX as usize) / 100) as u8;
42
43#[deprecated(since = "3.2.0", note = "Use warmup_cooldown_rate_bps() instead")]
44pub fn warmup_cooldown_rate(current_epoch: Epoch, new_rate_activation_epoch: Option<Epoch>) -> f64 {
45    if current_epoch < new_rate_activation_epoch.unwrap_or(u64::MAX) {
46        DEFAULT_WARMUP_COOLDOWN_RATE
47    } else {
48        NEW_WARMUP_COOLDOWN_RATE
49    }
50}
51
52#[cfg(feature = "borsh")]
53macro_rules! impl_borsh_stake_state {
54    ($borsh:ident) => {
55        impl $borsh::BorshDeserialize for StakeState {
56            fn deserialize_reader<R: io::Read>(reader: &mut R) -> io::Result<Self> {
57                let enum_value: u32 = $borsh::BorshDeserialize::deserialize_reader(reader)?;
58                match enum_value {
59                    0 => Ok(StakeState::Uninitialized),
60                    1 => {
61                        let meta: Meta = $borsh::BorshDeserialize::deserialize_reader(reader)?;
62                        Ok(StakeState::Initialized(meta))
63                    }
64                    2 => {
65                        let meta: Meta = $borsh::BorshDeserialize::deserialize_reader(reader)?;
66                        let stake: Stake = $borsh::BorshDeserialize::deserialize_reader(reader)?;
67                        Ok(StakeState::Stake(meta, stake))
68                    }
69                    3 => Ok(StakeState::RewardsPool),
70                    _ => Err(io::Error::new(
71                        io::ErrorKind::InvalidData,
72                        "Invalid enum value",
73                    )),
74                }
75            }
76        }
77        impl $borsh::BorshSerialize for StakeState {
78            fn serialize<W: io::Write>(&self, writer: &mut W) -> io::Result<()> {
79                match self {
80                    StakeState::Uninitialized => writer.write_all(&0u32.to_le_bytes()),
81                    StakeState::Initialized(meta) => {
82                        writer.write_all(&1u32.to_le_bytes())?;
83                        $borsh::BorshSerialize::serialize(&meta, writer)
84                    }
85                    StakeState::Stake(meta, stake) => {
86                        writer.write_all(&2u32.to_le_bytes())?;
87                        $borsh::BorshSerialize::serialize(&meta, writer)?;
88                        $borsh::BorshSerialize::serialize(&stake, writer)
89                    }
90                    StakeState::RewardsPool => writer.write_all(&3u32.to_le_bytes()),
91                }
92            }
93        }
94    };
95}
96#[cfg_attr(
97    feature = "codama",
98    derive(CodamaType),
99    codama(enum_discriminator(size = number(u32)))
100)]
101#[derive(Debug, Default, PartialEq, Clone, Copy)]
102#[cfg_attr(feature = "frozen-abi", derive(solana_frozen_abi_macro::AbiExample))]
103#[cfg_attr(
104    feature = "serde",
105    derive(serde_derive::Deserialize, serde_derive::Serialize)
106)]
107#[allow(clippy::large_enum_variant)]
108#[deprecated(
109    since = "1.17.0",
110    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)`."
111)]
112pub enum StakeState {
113    #[default]
114    Uninitialized,
115    Initialized(Meta),
116    Stake(Meta, Stake),
117    RewardsPool,
118}
119#[cfg(feature = "borsh")]
120impl_borsh_stake_state!(borsh);
121impl StakeState {
122    /// The fixed number of bytes used to serialize each stake account
123    pub const fn size_of() -> usize {
124        200 // see test_size_of
125    }
126
127    pub fn stake(&self) -> Option<Stake> {
128        match self {
129            Self::Stake(_meta, stake) => Some(*stake),
130            Self::Uninitialized | Self::Initialized(_) | Self::RewardsPool => None,
131        }
132    }
133
134    pub fn delegation(&self) -> Option<Delegation> {
135        match self {
136            Self::Stake(_meta, stake) => Some(stake.delegation),
137            Self::Uninitialized | Self::Initialized(_) | Self::RewardsPool => None,
138        }
139    }
140
141    pub fn authorized(&self) -> Option<Authorized> {
142        match self {
143            Self::Stake(meta, _stake) => Some(meta.authorized),
144            Self::Initialized(meta) => Some(meta.authorized),
145            Self::Uninitialized | Self::RewardsPool => None,
146        }
147    }
148
149    pub fn lockup(&self) -> Option<Lockup> {
150        self.meta().map(|meta| meta.lockup)
151    }
152
153    pub fn meta(&self) -> Option<Meta> {
154        match self {
155            Self::Stake(meta, _stake) => Some(*meta),
156            Self::Initialized(meta) => Some(*meta),
157            Self::Uninitialized | Self::RewardsPool => None,
158        }
159    }
160}
161
162#[cfg_attr(
163    feature = "codama",
164    derive(CodamaType),
165    codama(enum_discriminator(size = number(u32)))
166)]
167#[derive(Debug, Default, PartialEq, Clone, Copy)]
168#[cfg_attr(feature = "frozen-abi", derive(solana_frozen_abi_macro::AbiExample))]
169#[cfg_attr(
170    feature = "serde",
171    derive(serde_derive::Deserialize, serde_derive::Serialize)
172)]
173#[cfg_attr(feature = "wincode", derive(wincode::SchemaRead, wincode::SchemaWrite))]
174#[allow(clippy::large_enum_variant)]
175pub enum StakeStateV2 {
176    #[default]
177    Uninitialized,
178    Initialized(Meta),
179    Stake(Meta, Stake, StakeFlags),
180    RewardsPool,
181}
182#[cfg(feature = "borsh")]
183macro_rules! impl_borsh_stake_state_v2 {
184    ($borsh:ident) => {
185        impl $borsh::BorshDeserialize for StakeStateV2 {
186            fn deserialize_reader<R: io::Read>(reader: &mut R) -> io::Result<Self> {
187                let enum_value: u32 = $borsh::BorshDeserialize::deserialize_reader(reader)?;
188                match enum_value {
189                    0 => Ok(StakeStateV2::Uninitialized),
190                    1 => {
191                        let meta: Meta = $borsh::BorshDeserialize::deserialize_reader(reader)?;
192                        Ok(StakeStateV2::Initialized(meta))
193                    }
194                    2 => {
195                        let meta: Meta = $borsh::BorshDeserialize::deserialize_reader(reader)?;
196                        let stake: Stake = $borsh::BorshDeserialize::deserialize_reader(reader)?;
197                        let stake_flags: StakeFlags =
198                            $borsh::BorshDeserialize::deserialize_reader(reader)?;
199                        Ok(StakeStateV2::Stake(meta, stake, stake_flags))
200                    }
201                    3 => Ok(StakeStateV2::RewardsPool),
202                    _ => Err(io::Error::new(
203                        io::ErrorKind::InvalidData,
204                        "Invalid enum value",
205                    )),
206                }
207            }
208        }
209        impl $borsh::BorshSerialize for StakeStateV2 {
210            fn serialize<W: io::Write>(&self, writer: &mut W) -> io::Result<()> {
211                match self {
212                    StakeStateV2::Uninitialized => writer.write_all(&0u32.to_le_bytes()),
213                    StakeStateV2::Initialized(meta) => {
214                        writer.write_all(&1u32.to_le_bytes())?;
215                        $borsh::BorshSerialize::serialize(&meta, writer)
216                    }
217                    StakeStateV2::Stake(meta, stake, stake_flags) => {
218                        writer.write_all(&2u32.to_le_bytes())?;
219                        $borsh::BorshSerialize::serialize(&meta, writer)?;
220                        $borsh::BorshSerialize::serialize(&stake, writer)?;
221                        $borsh::BorshSerialize::serialize(&stake_flags, writer)
222                    }
223                    StakeStateV2::RewardsPool => writer.write_all(&3u32.to_le_bytes()),
224                }
225            }
226        }
227    };
228}
229#[cfg(feature = "borsh")]
230impl_borsh_stake_state_v2!(borsh);
231
232impl StakeStateV2 {
233    /// The fixed number of bytes used to serialize each stake account
234    pub const fn size_of() -> usize {
235        200 // see test_size_of
236    }
237
238    pub fn stake(&self) -> Option<Stake> {
239        match self {
240            Self::Stake(_meta, stake, _stake_flags) => Some(*stake),
241            Self::Uninitialized | Self::Initialized(_) | Self::RewardsPool => None,
242        }
243    }
244
245    pub fn stake_ref(&self) -> Option<&Stake> {
246        match self {
247            Self::Stake(_meta, stake, _stake_flags) => Some(stake),
248            Self::Uninitialized | Self::Initialized(_) | Self::RewardsPool => None,
249        }
250    }
251
252    pub fn delegation(&self) -> Option<Delegation> {
253        match self {
254            Self::Stake(_meta, stake, _stake_flags) => Some(stake.delegation),
255            Self::Uninitialized | Self::Initialized(_) | Self::RewardsPool => None,
256        }
257    }
258
259    pub fn delegation_ref(&self) -> Option<&Delegation> {
260        match self {
261            StakeStateV2::Stake(_meta, stake, _stake_flags) => Some(&stake.delegation),
262            Self::Uninitialized | Self::Initialized(_) | Self::RewardsPool => None,
263        }
264    }
265
266    pub fn authorized(&self) -> Option<Authorized> {
267        match self {
268            Self::Stake(meta, _stake, _stake_flags) => Some(meta.authorized),
269            Self::Initialized(meta) => Some(meta.authorized),
270            Self::Uninitialized | Self::RewardsPool => None,
271        }
272    }
273
274    pub fn lockup(&self) -> Option<Lockup> {
275        self.meta().map(|meta| meta.lockup)
276    }
277
278    pub fn meta(&self) -> Option<Meta> {
279        match self {
280            Self::Stake(meta, _stake, _stake_flags) => Some(*meta),
281            Self::Initialized(meta) => Some(*meta),
282            Self::Uninitialized | Self::RewardsPool => None,
283        }
284    }
285}
286
287#[cfg_attr(
288    feature = "codama",
289    derive(CodamaType),
290    codama(enum_discriminator(size = number(u32)))
291)]
292#[derive(Debug, PartialEq, Eq, Clone, Copy)]
293#[cfg_attr(feature = "frozen-abi", derive(solana_frozen_abi_macro::AbiExample))]
294#[cfg_attr(
295    feature = "serde",
296    derive(serde_derive::Deserialize, serde_derive::Serialize)
297)]
298#[cfg_attr(feature = "wincode", derive(wincode::SchemaRead, wincode::SchemaWrite))]
299pub enum StakeAuthorize {
300    Staker,
301    Withdrawer,
302}
303
304#[repr(C)]
305#[cfg_attr(feature = "codama", derive(CodamaType))]
306#[derive(Default, Debug, PartialEq, Eq, Clone, Copy)]
307#[cfg_attr(feature = "frozen-abi", derive(solana_frozen_abi_macro::AbiExample))]
308#[cfg_attr(
309    feature = "borsh",
310    derive(BorshSerialize, BorshDeserialize, BorshSchema),
311    borsh(crate = "borsh")
312)]
313#[cfg_attr(
314    feature = "serde",
315    derive(serde_derive::Deserialize, serde_derive::Serialize)
316)]
317#[cfg_attr(feature = "wincode", derive(wincode::SchemaRead, wincode::SchemaWrite))]
318pub struct Lockup {
319    /// `UnixTimestamp` at which this stake will allow withdrawal, unless the
320    ///   transaction is signed by the custodian
321    pub unix_timestamp: UnixTimestamp,
322    /// epoch height at which this stake will allow withdrawal, unless the
323    ///   transaction is signed by the custodian
324    pub epoch: Epoch,
325    /// custodian signature on a transaction exempts the operation from
326    ///  lockup constraints
327    pub custodian: Pubkey,
328}
329impl Lockup {
330    pub fn is_in_force(&self, clock: &Clock, custodian: Option<&Pubkey>) -> bool {
331        if custodian == Some(&self.custodian) {
332            return false;
333        }
334        self.unix_timestamp > clock.unix_timestamp || self.epoch > clock.epoch
335    }
336}
337
338#[repr(C)]
339#[cfg_attr(feature = "codama", derive(CodamaType))]
340#[derive(Default, Debug, PartialEq, Eq, Clone, Copy)]
341#[cfg_attr(feature = "frozen-abi", derive(solana_frozen_abi_macro::AbiExample))]
342#[cfg_attr(
343    feature = "borsh",
344    derive(BorshSerialize, BorshDeserialize, BorshSchema),
345    borsh(crate = "borsh")
346)]
347#[cfg_attr(
348    feature = "serde",
349    derive(serde_derive::Deserialize, serde_derive::Serialize)
350)]
351#[cfg_attr(feature = "wincode", derive(wincode::SchemaRead, wincode::SchemaWrite))]
352pub struct Authorized {
353    pub staker: Pubkey,
354    pub withdrawer: Pubkey,
355}
356
357impl Authorized {
358    pub fn auto(authorized: &Pubkey) -> Self {
359        Self {
360            staker: *authorized,
361            withdrawer: *authorized,
362        }
363    }
364    pub fn check(
365        &self,
366        signers: &HashSet<Pubkey>,
367        stake_authorize: StakeAuthorize,
368    ) -> Result<(), InstructionError> {
369        let authorized_signer = match stake_authorize {
370            StakeAuthorize::Staker => &self.staker,
371            StakeAuthorize::Withdrawer => &self.withdrawer,
372        };
373
374        if signers.contains(authorized_signer) {
375            Ok(())
376        } else {
377            Err(InstructionError::MissingRequiredSignature)
378        }
379    }
380
381    pub fn authorize(
382        &mut self,
383        signers: &HashSet<Pubkey>,
384        new_authorized: &Pubkey,
385        stake_authorize: StakeAuthorize,
386        lockup_custodian_args: Option<(&Lockup, &Clock, Option<&Pubkey>)>,
387    ) -> Result<(), InstructionError> {
388        match stake_authorize {
389            StakeAuthorize::Staker => {
390                // Allow either the staker or the withdrawer to change the staker key
391                if !signers.contains(&self.staker) && !signers.contains(&self.withdrawer) {
392                    return Err(InstructionError::MissingRequiredSignature);
393                }
394                self.staker = *new_authorized
395            }
396            StakeAuthorize::Withdrawer => {
397                if let Some((lockup, clock, custodian)) = lockup_custodian_args {
398                    if lockup.is_in_force(clock, None) {
399                        match custodian {
400                            None => {
401                                return Err(StakeError::CustodianMissing.into());
402                            }
403                            Some(custodian) => {
404                                if !signers.contains(custodian) {
405                                    return Err(StakeError::CustodianSignatureMissing.into());
406                                }
407
408                                if lockup.is_in_force(clock, Some(custodian)) {
409                                    return Err(StakeError::LockupInForce.into());
410                                }
411                            }
412                        }
413                    }
414                }
415                self.check(signers, stake_authorize)?;
416                self.withdrawer = *new_authorized
417            }
418        }
419        Ok(())
420    }
421}
422
423#[repr(C)]
424#[cfg_attr(feature = "codama", derive(CodamaType))]
425#[derive(Default, Debug, PartialEq, Eq, Clone, Copy)]
426#[cfg_attr(feature = "frozen-abi", derive(solana_frozen_abi_macro::AbiExample))]
427#[cfg_attr(
428    feature = "borsh",
429    derive(BorshSerialize, BorshDeserialize, BorshSchema),
430    borsh(crate = "borsh")
431)]
432#[cfg_attr(
433    feature = "serde",
434    derive(serde_derive::Deserialize, serde_derive::Serialize)
435)]
436#[cfg_attr(feature = "wincode", derive(wincode::SchemaRead, wincode::SchemaWrite))]
437pub struct Meta {
438    #[deprecated(
439        since = "3.0.1",
440        note = "Stake account rent must be calculated via the `Rent` sysvar. \
441        This value will cease to be correct once lamports-per-byte is adjusted."
442    )]
443    pub rent_exempt_reserve: u64,
444    pub authorized: Authorized,
445    pub lockup: Lockup,
446}
447
448impl Meta {
449    pub fn set_lockup(
450        &mut self,
451        lockup: &LockupArgs,
452        signers: &HashSet<Pubkey>,
453        clock: &Clock,
454    ) -> Result<(), InstructionError> {
455        // post-stake_program_v4 behavior:
456        // * custodian can update the lockup while in force
457        // * withdraw authority can set a new lockup
458        if self.lockup.is_in_force(clock, None) {
459            if !signers.contains(&self.lockup.custodian) {
460                return Err(InstructionError::MissingRequiredSignature);
461            }
462        } else if !signers.contains(&self.authorized.withdrawer) {
463            return Err(InstructionError::MissingRequiredSignature);
464        }
465        if let Some(unix_timestamp) = lockup.unix_timestamp {
466            self.lockup.unix_timestamp = unix_timestamp;
467        }
468        if let Some(epoch) = lockup.epoch {
469            self.lockup.epoch = epoch;
470        }
471        if let Some(custodian) = lockup.custodian {
472            self.lockup.custodian = custodian;
473        }
474        Ok(())
475    }
476
477    pub fn auto(authorized: &Pubkey) -> Self {
478        Self {
479            authorized: Authorized::auto(authorized),
480            ..Meta::default()
481        }
482    }
483}
484
485#[repr(C)]
486#[cfg_attr(feature = "codama", derive(CodamaType))]
487#[derive(Debug, PartialEq, Clone, Copy)]
488#[cfg_attr(feature = "frozen-abi", derive(solana_frozen_abi_macro::AbiExample))]
489#[cfg_attr(
490    feature = "borsh",
491    derive(BorshSerialize, BorshDeserialize, BorshSchema),
492    borsh(crate = "borsh")
493)]
494#[cfg_attr(
495    feature = "serde",
496    derive(serde_derive::Deserialize, serde_derive::Serialize)
497)]
498#[cfg_attr(feature = "wincode", derive(wincode::SchemaRead, wincode::SchemaWrite))]
499pub struct Delegation {
500    /// to whom the stake is delegated
501    pub voter_pubkey: Pubkey,
502    /// activated stake amount, set at delegate() time
503    pub stake: u64,
504    /// epoch at which this stake was activated, `std::u64::MAX` if is a bootstrap stake
505    pub activation_epoch: Epoch,
506    /// epoch the stake was deactivated, `std::u64::MAX` if not deactivated
507    pub deactivation_epoch: Epoch,
508    /// Formerly the `warmup_cooldown_rate: f64`, but floats are not eBPF-compatible.
509    /// It is unused, but this field is now reserved to maintain layout compatibility.
510    pub _reserved: [u8; 8],
511}
512
513impl Default for Delegation {
514    fn default() -> Self {
515        Self {
516            voter_pubkey: Pubkey::default(),
517            stake: 0,
518            activation_epoch: 0,
519            deactivation_epoch: u64::MAX,
520            _reserved: [0; 8],
521        }
522    }
523}
524
525impl Delegation {
526    pub fn new(voter_pubkey: &Pubkey, stake: u64, activation_epoch: Epoch) -> Self {
527        Self {
528            voter_pubkey: *voter_pubkey,
529            stake,
530            activation_epoch,
531            ..Delegation::default()
532        }
533    }
534    pub fn is_bootstrap(&self) -> bool {
535        self.activation_epoch == u64::MAX
536    }
537
538    /// Previous implementation that uses floats under the hood to calculate warmup/cooldown
539    /// rate-limiting. New `stake_v2()` uses integers (upstream eBPF-compatible).
540    #[deprecated(since = "3.2.0", note = "Use stake_v2() instead")]
541    pub fn stake<T: StakeHistoryGetEntry>(
542        &self,
543        epoch: Epoch,
544        history: &T,
545        new_rate_activation_epoch: Option<Epoch>,
546    ) -> u64 {
547        self.stake_activating_and_deactivating(epoch, history, new_rate_activation_epoch)
548            .effective
549    }
550
551    /// Previous implementation that uses floats under the hood to calculate warmup/cooldown
552    /// rate-limiting. New `stake_activating_and_deactivating_v2()` uses integers (upstream eBPF-compatible).
553    #[deprecated(
554        since = "3.2.0",
555        note = "Use stake_activating_and_deactivating_v2() instead"
556    )]
557    pub fn stake_activating_and_deactivating<T: StakeHistoryGetEntry>(
558        &self,
559        target_epoch: Epoch,
560        history: &T,
561        new_rate_activation_epoch: Option<Epoch>,
562    ) -> StakeActivationStatus {
563        // first, calculate an effective and activating stake
564        let (effective_stake, activating_stake) =
565            self.stake_and_activating(target_epoch, history, new_rate_activation_epoch);
566
567        // then de-activate some portion if necessary
568        if target_epoch < self.deactivation_epoch {
569            // not deactivated
570            if activating_stake == 0 {
571                StakeActivationStatus::with_effective(effective_stake)
572            } else {
573                StakeActivationStatus::with_effective_and_activating(
574                    effective_stake,
575                    activating_stake,
576                )
577            }
578        } else if target_epoch == self.deactivation_epoch {
579            // can only deactivate what's activated
580            StakeActivationStatus::with_deactivating(effective_stake)
581        } else if let Some((history, mut prev_epoch, mut prev_cluster_stake)) = history
582            .get_entry(self.deactivation_epoch)
583            .map(|cluster_stake_at_deactivation_epoch| {
584                (
585                    history,
586                    self.deactivation_epoch,
587                    cluster_stake_at_deactivation_epoch,
588                )
589            })
590        {
591            // target_epoch > self.deactivation_epoch
592
593            // loop from my deactivation epoch until the target epoch
594            // current effective stake is updated using its previous epoch's cluster stake
595            let mut current_epoch;
596            let mut current_effective_stake = effective_stake;
597            loop {
598                current_epoch = prev_epoch + 1;
599                // if there is no deactivating stake at prev epoch, we should have been
600                // fully undelegated at this moment
601                if prev_cluster_stake.deactivating == 0 {
602                    break;
603                }
604
605                // I'm trying to get to zero, how much of the deactivation in stake
606                //   this account is entitled to take
607                let weight =
608                    current_effective_stake as f64 / prev_cluster_stake.deactivating as f64;
609                let warmup_cooldown_rate =
610                    warmup_cooldown_rate(current_epoch, new_rate_activation_epoch);
611
612                // portion of newly not-effective cluster stake I'm entitled to at current epoch
613                let newly_not_effective_cluster_stake =
614                    prev_cluster_stake.effective as f64 * warmup_cooldown_rate;
615                let newly_not_effective_stake =
616                    ((weight * newly_not_effective_cluster_stake) as u64).max(1);
617
618                current_effective_stake =
619                    current_effective_stake.saturating_sub(newly_not_effective_stake);
620                if current_effective_stake == 0 {
621                    break;
622                }
623
624                if current_epoch >= target_epoch {
625                    break;
626                }
627                if let Some(current_cluster_stake) = history.get_entry(current_epoch) {
628                    prev_epoch = current_epoch;
629                    prev_cluster_stake = current_cluster_stake;
630                } else {
631                    break;
632                }
633            }
634
635            // deactivating stake should equal to all of currently remaining effective stake
636            StakeActivationStatus::with_deactivating(current_effective_stake)
637        } else {
638            // no history or I've dropped out of history, so assume fully deactivated
639            StakeActivationStatus::default()
640        }
641    }
642
643    // returned tuple is (effective, activating) stake
644    #[deprecated(since = "3.2.0", note = "Use stake_and_activating_v2() instead")]
645    fn stake_and_activating<T: StakeHistoryGetEntry>(
646        &self,
647        target_epoch: Epoch,
648        history: &T,
649        new_rate_activation_epoch: Option<Epoch>,
650    ) -> (u64, u64) {
651        let delegated_stake = self.stake;
652
653        if self.is_bootstrap() {
654            // fully effective immediately
655            (delegated_stake, 0)
656        } else if self.activation_epoch == self.deactivation_epoch {
657            // activated but instantly deactivated; no stake at all regardless of target_epoch
658            // this must be after the bootstrap check and before all-is-activating check
659            (0, 0)
660        } else if target_epoch == self.activation_epoch {
661            // all is activating
662            (0, delegated_stake)
663        } else if target_epoch < self.activation_epoch {
664            // not yet enabled
665            (0, 0)
666        } else if let Some((history, mut prev_epoch, mut prev_cluster_stake)) = history
667            .get_entry(self.activation_epoch)
668            .map(|cluster_stake_at_activation_epoch| {
669                (
670                    history,
671                    self.activation_epoch,
672                    cluster_stake_at_activation_epoch,
673                )
674            })
675        {
676            // target_epoch > self.activation_epoch
677
678            // loop from my activation epoch until the target epoch summing up my entitlement
679            // current effective stake is updated using its previous epoch's cluster stake
680            let mut current_epoch;
681            let mut current_effective_stake = 0;
682            loop {
683                current_epoch = prev_epoch + 1;
684                // if there is no activating stake at prev epoch, we should have been
685                // fully effective at this moment
686                if prev_cluster_stake.activating == 0 {
687                    break;
688                }
689
690                // how much of the growth in stake this account is
691                //  entitled to take
692                let remaining_activating_stake = delegated_stake - current_effective_stake;
693                let weight =
694                    remaining_activating_stake as f64 / prev_cluster_stake.activating as f64;
695                let warmup_cooldown_rate =
696                    warmup_cooldown_rate(current_epoch, new_rate_activation_epoch);
697
698                // portion of newly effective cluster stake I'm entitled to at current epoch
699                let newly_effective_cluster_stake =
700                    prev_cluster_stake.effective as f64 * warmup_cooldown_rate;
701                let newly_effective_stake =
702                    ((weight * newly_effective_cluster_stake) as u64).max(1);
703
704                current_effective_stake += newly_effective_stake;
705                if current_effective_stake >= delegated_stake {
706                    current_effective_stake = delegated_stake;
707                    break;
708                }
709
710                if current_epoch >= target_epoch || current_epoch >= self.deactivation_epoch {
711                    break;
712                }
713                if let Some(current_cluster_stake) = history.get_entry(current_epoch) {
714                    prev_epoch = current_epoch;
715                    prev_cluster_stake = current_cluster_stake;
716                } else {
717                    break;
718                }
719            }
720
721            (
722                current_effective_stake,
723                delegated_stake - current_effective_stake,
724            )
725        } else {
726            // no history or I've dropped out of history, so assume fully effective
727            (delegated_stake, 0)
728        }
729    }
730
731    pub fn stake_v2<T: StakeHistoryGetEntry>(
732        &self,
733        epoch: Epoch,
734        history: &T,
735        new_rate_activation_epoch: Option<Epoch>,
736    ) -> u64 {
737        self.stake_activating_and_deactivating_v2(epoch, history, new_rate_activation_epoch)
738            .effective
739    }
740
741    pub fn stake_activating_and_deactivating_v2<T: StakeHistoryGetEntry>(
742        &self,
743        target_epoch: Epoch,
744        history: &T,
745        new_rate_activation_epoch: Option<Epoch>,
746    ) -> StakeActivationStatus {
747        // first, calculate an effective and activating stake
748        let (effective_stake, activating_stake) =
749            self.stake_and_activating_v2(target_epoch, history, new_rate_activation_epoch);
750
751        // then de-activate some portion if necessary
752        if target_epoch < self.deactivation_epoch {
753            // not deactivated
754            if activating_stake == 0 {
755                StakeActivationStatus::with_effective(effective_stake)
756            } else {
757                StakeActivationStatus::with_effective_and_activating(
758                    effective_stake,
759                    activating_stake,
760                )
761            }
762        } else if target_epoch == self.deactivation_epoch {
763            // can only deactivate what's activated
764            StakeActivationStatus::with_deactivating(effective_stake)
765        } else if let Some((history, mut prev_epoch, mut prev_cluster_stake)) = history
766            .get_entry(self.deactivation_epoch)
767            .map(|cluster_stake_at_deactivation_epoch| {
768                (
769                    history,
770                    self.deactivation_epoch,
771                    cluster_stake_at_deactivation_epoch,
772                )
773            })
774        {
775            // target_epoch > self.deactivation_epoch
776            //
777            // We advance epoch-by-epoch from just after the deactivation epoch up to the target_epoch,
778            // removing (cooling down) the account's share of effective stake each epoch,
779            // potentially rate-limited by cluster history.
780
781            let mut current_epoch;
782            let mut remaining_deactivating_stake = effective_stake;
783            loop {
784                current_epoch = prev_epoch + 1;
785                // if there is no deactivating stake at prev epoch, we should have been
786                // fully undelegated at this moment
787                if prev_cluster_stake.deactivating == 0 {
788                    break;
789                }
790
791                // Compute how much of this account's stake cools down in `current_epoch`
792                let newly_deactivated_stake = calculate_deactivation_allowance(
793                    current_epoch,
794                    remaining_deactivating_stake,
795                    &prev_cluster_stake,
796                    new_rate_activation_epoch,
797                );
798
799                // Subtract the newly deactivated stake, clamping the per-epoch decrease to at
800                // least 1 lamport so cooldown always makes progress
801                remaining_deactivating_stake =
802                    remaining_deactivating_stake.saturating_sub(newly_deactivated_stake.max(1));
803
804                // Stop if we've fully cooled down this account
805                if remaining_deactivating_stake == 0 {
806                    break;
807                }
808
809                // Stop when we've reached the time bound for this query
810                if current_epoch >= target_epoch {
811                    break;
812                }
813
814                // Advance to the next epoch if we have history, otherwise we can't model further cooldown
815                if let Some(current_cluster_stake) = history.get_entry(current_epoch) {
816                    prev_epoch = current_epoch;
817                    prev_cluster_stake = current_cluster_stake;
818                } else {
819                    // No more history data, return the best-effort state as of the last known epoch
820                    break;
821                }
822            }
823
824            // Report how much stake remains in cooldown at `target_epoch`
825            StakeActivationStatus::with_deactivating(remaining_deactivating_stake)
826        } else {
827            // no history or I've dropped out of history, so assume fully deactivated
828            StakeActivationStatus::default()
829        }
830    }
831
832    // returned tuple is (effective, activating) stake
833    fn stake_and_activating_v2<T: StakeHistoryGetEntry>(
834        &self,
835        target_epoch: Epoch,
836        history: &T,
837        new_rate_activation_epoch: Option<Epoch>,
838    ) -> (u64, u64) {
839        let delegated_stake = self.stake;
840
841        if self.is_bootstrap() {
842            // fully effective immediately
843            (delegated_stake, 0)
844        } else if self.activation_epoch == self.deactivation_epoch {
845            // activated but instantly deactivated; no stake at all regardless of target_epoch
846            // this must be after the bootstrap check and before all-is-activating check
847            (0, 0)
848        } else if target_epoch == self.activation_epoch {
849            // all is activating
850            (0, delegated_stake)
851        } else if target_epoch < self.activation_epoch {
852            // not yet enabled
853            (0, 0)
854        } else if let Some((history, mut prev_epoch, mut prev_cluster_stake)) = history
855            .get_entry(self.activation_epoch)
856            .map(|cluster_stake_at_activation_epoch| {
857                (
858                    history,
859                    self.activation_epoch,
860                    cluster_stake_at_activation_epoch,
861                )
862            })
863        {
864            // target_epoch > self.activation_epoch
865            //
866            // We advance epoch-by-epoch from just after the activation epoch up to the target_epoch,
867            // accumulating (warming up) the account's share of effective stake each epoch,
868            // potentially rate-limited by cluster history.
869
870            let mut current_epoch;
871            let mut activated_stake_amount = 0;
872            loop {
873                current_epoch = prev_epoch + 1;
874                // if there is no activating stake at prev epoch, we should have been
875                // fully effective at this moment
876                if prev_cluster_stake.activating == 0 {
877                    break;
878                }
879
880                // Calculate how much of this account's remaining stake becomes effective in `current_epoch`.
881                let remaining_activating_stake = delegated_stake - activated_stake_amount;
882                let newly_effective_stake = calculate_activation_allowance(
883                    current_epoch,
884                    remaining_activating_stake,
885                    &prev_cluster_stake,
886                    new_rate_activation_epoch,
887                );
888
889                // Add the newly effective stake, clamping the per-epoch increase to at least 1 lamport so warmup always makes progress
890                activated_stake_amount += newly_effective_stake.max(1);
891
892                // Stop if we've fully warmed up this account's stake.
893                if activated_stake_amount >= delegated_stake {
894                    activated_stake_amount = delegated_stake;
895                    break;
896                }
897
898                // Stop when we've reached the time bound for this query
899                if current_epoch >= target_epoch || current_epoch >= self.deactivation_epoch {
900                    break;
901                }
902
903                // Advance to the next epoch if we have history, otherwise we can't model further warmup
904                if let Some(current_cluster_stake) = history.get_entry(current_epoch) {
905                    prev_epoch = current_epoch;
906                    prev_cluster_stake = current_cluster_stake;
907                } else {
908                    // No more history data, return the best-effort state as of the last known epoch
909                    break;
910                }
911            }
912
913            // Return the portion that has become effective and the portion still activating
914            (
915                activated_stake_amount,
916                delegated_stake - activated_stake_amount,
917            )
918        } else {
919            // no history or I've dropped out of history, so assume fully effective
920            (delegated_stake, 0)
921        }
922    }
923}
924
925#[repr(C)]
926#[cfg_attr(feature = "codama", derive(CodamaType))]
927#[derive(Debug, Default, PartialEq, Clone, Copy)]
928#[cfg_attr(feature = "frozen-abi", derive(solana_frozen_abi_macro::AbiExample))]
929#[cfg_attr(
930    feature = "borsh",
931    derive(BorshSerialize, BorshDeserialize, BorshSchema),
932    borsh(crate = "borsh")
933)]
934#[cfg_attr(
935    feature = "serde",
936    derive(serde_derive::Deserialize, serde_derive::Serialize)
937)]
938#[cfg_attr(feature = "wincode", derive(wincode::SchemaRead, wincode::SchemaWrite))]
939pub struct Stake {
940    pub delegation: Delegation,
941    /// credits observed is credits from vote account state when delegated or redeemed
942    pub credits_observed: u64,
943}
944
945impl Stake {
946    #[deprecated(since = "3.2.0", note = "Use stake_v2() instead")]
947    pub fn stake<T: StakeHistoryGetEntry>(
948        &self,
949        epoch: Epoch,
950        history: &T,
951        new_rate_activation_epoch: Option<Epoch>,
952    ) -> u64 {
953        self.delegation
954            .stake(epoch, history, new_rate_activation_epoch)
955    }
956
957    pub fn stake_v2<T: StakeHistoryGetEntry>(
958        &self,
959        epoch: Epoch,
960        history: &T,
961        new_rate_activation_epoch: Option<Epoch>,
962    ) -> u64 {
963        self.delegation
964            .stake_v2(epoch, history, new_rate_activation_epoch)
965    }
966
967    pub fn split(
968        &mut self,
969        remaining_stake_delta: u64,
970        split_stake_amount: u64,
971    ) -> Result<Self, StakeError> {
972        if remaining_stake_delta > self.delegation.stake {
973            return Err(StakeError::InsufficientStake);
974        }
975        self.delegation.stake -= remaining_stake_delta;
976        let new = Self {
977            delegation: Delegation {
978                stake: split_stake_amount,
979                ..self.delegation
980            },
981            ..*self
982        };
983        Ok(new)
984    }
985
986    pub fn deactivate(&mut self, epoch: Epoch) -> Result<(), StakeError> {
987        if self.delegation.deactivation_epoch != u64::MAX {
988            Err(StakeError::AlreadyDeactivated)
989        } else {
990            self.delegation.deactivation_epoch = epoch;
991            Ok(())
992        }
993    }
994}
995
996#[cfg(all(feature = "borsh", feature = "bincode"))]
997#[cfg(test)]
998mod tests {
999    use {
1000        super::*,
1001        crate::{stake_history::StakeHistory, warmup_cooldown_allowance::warmup_cooldown_rate_bps},
1002        assert_matches::assert_matches,
1003        bincode::serialize,
1004        solana_account::{state_traits::StateMut, AccountSharedData, ReadableAccount},
1005        solana_borsh::v1::try_from_slice_unchecked,
1006        solana_pubkey::Pubkey,
1007        test_case::test_case,
1008    };
1009
1010    fn from<T: ReadableAccount + StateMut<StakeStateV2>>(account: &T) -> Option<StakeStateV2> {
1011        account.state().ok()
1012    }
1013
1014    fn stake_from<T: ReadableAccount + StateMut<StakeStateV2>>(account: &T) -> Option<Stake> {
1015        from(account).and_then(|state: StakeStateV2| state.stake())
1016    }
1017
1018    fn new_stake_history_entry<'a, I>(
1019        epoch: Epoch,
1020        stakes: I,
1021        history: &StakeHistory,
1022        new_rate_activation_epoch: Option<Epoch>,
1023    ) -> StakeHistoryEntry
1024    where
1025        I: Iterator<Item = &'a Delegation>,
1026    {
1027        stakes.fold(StakeHistoryEntry::default(), |sum, stake| {
1028            sum + stake.stake_activating_and_deactivating_v2(
1029                epoch,
1030                history,
1031                new_rate_activation_epoch,
1032            )
1033        })
1034    }
1035
1036    fn create_stake_history_from_delegations(
1037        bootstrap: Option<u64>,
1038        epochs: std::ops::Range<Epoch>,
1039        delegations: &[Delegation],
1040        new_rate_activation_epoch: Option<Epoch>,
1041    ) -> StakeHistory {
1042        let mut stake_history = StakeHistory::default();
1043
1044        let bootstrap_delegation = if let Some(bootstrap) = bootstrap {
1045            vec![Delegation {
1046                activation_epoch: u64::MAX,
1047                stake: bootstrap,
1048                ..Delegation::default()
1049            }]
1050        } else {
1051            vec![]
1052        };
1053
1054        for epoch in epochs {
1055            let entry = new_stake_history_entry(
1056                epoch,
1057                delegations.iter().chain(bootstrap_delegation.iter()),
1058                &stake_history,
1059                new_rate_activation_epoch,
1060            );
1061            stake_history.add(epoch, entry);
1062        }
1063
1064        stake_history
1065    }
1066
1067    #[test]
1068    fn test_authorized_authorize() {
1069        let staker = Pubkey::new_unique();
1070        let mut authorized = Authorized::auto(&staker);
1071        let mut signers = HashSet::new();
1072        assert_eq!(
1073            authorized.authorize(&signers, &staker, StakeAuthorize::Staker, None),
1074            Err(InstructionError::MissingRequiredSignature)
1075        );
1076        signers.insert(staker);
1077        assert_eq!(
1078            authorized.authorize(&signers, &staker, StakeAuthorize::Staker, None),
1079            Ok(())
1080        );
1081    }
1082
1083    #[test]
1084    fn test_authorized_authorize_with_custodian() {
1085        let staker = Pubkey::new_unique();
1086        let custodian = Pubkey::new_unique();
1087        let invalid_custodian = Pubkey::new_unique();
1088        let mut authorized = Authorized::auto(&staker);
1089        let mut signers = HashSet::new();
1090        signers.insert(staker);
1091
1092        let lockup = Lockup {
1093            epoch: 1,
1094            unix_timestamp: 1,
1095            custodian,
1096        };
1097        let clock = Clock {
1098            epoch: 0,
1099            unix_timestamp: 0,
1100            ..Clock::default()
1101        };
1102
1103        // No lockup, no custodian
1104        assert_eq!(
1105            authorized.authorize(
1106                &signers,
1107                &staker,
1108                StakeAuthorize::Withdrawer,
1109                Some((&Lockup::default(), &clock, None))
1110            ),
1111            Ok(())
1112        );
1113
1114        // No lockup, invalid custodian not a signer
1115        assert_eq!(
1116            authorized.authorize(
1117                &signers,
1118                &staker,
1119                StakeAuthorize::Withdrawer,
1120                Some((&Lockup::default(), &clock, Some(&invalid_custodian)))
1121            ),
1122            Ok(()) // <== invalid custodian doesn't matter, there's no lockup
1123        );
1124
1125        // Lockup active, invalid custodian not a signer
1126        assert_eq!(
1127            authorized.authorize(
1128                &signers,
1129                &staker,
1130                StakeAuthorize::Withdrawer,
1131                Some((&lockup, &clock, Some(&invalid_custodian)))
1132            ),
1133            Err(StakeError::CustodianSignatureMissing.into()),
1134        );
1135
1136        signers.insert(invalid_custodian);
1137
1138        // No lockup, invalid custodian is a signer
1139        assert_eq!(
1140            authorized.authorize(
1141                &signers,
1142                &staker,
1143                StakeAuthorize::Withdrawer,
1144                Some((&Lockup::default(), &clock, Some(&invalid_custodian)))
1145            ),
1146            Ok(()) // <== invalid custodian doesn't matter, there's no lockup
1147        );
1148
1149        // Lockup active, invalid custodian is a signer
1150        signers.insert(invalid_custodian);
1151        assert_eq!(
1152            authorized.authorize(
1153                &signers,
1154                &staker,
1155                StakeAuthorize::Withdrawer,
1156                Some((&lockup, &clock, Some(&invalid_custodian)))
1157            ),
1158            Err(StakeError::LockupInForce.into()), // <== invalid custodian rejected
1159        );
1160
1161        signers.remove(&invalid_custodian);
1162
1163        // Lockup active, no custodian
1164        assert_eq!(
1165            authorized.authorize(
1166                &signers,
1167                &staker,
1168                StakeAuthorize::Withdrawer,
1169                Some((&lockup, &clock, None))
1170            ),
1171            Err(StakeError::CustodianMissing.into()),
1172        );
1173
1174        // Lockup active, custodian not a signer
1175        assert_eq!(
1176            authorized.authorize(
1177                &signers,
1178                &staker,
1179                StakeAuthorize::Withdrawer,
1180                Some((&lockup, &clock, Some(&custodian)))
1181            ),
1182            Err(StakeError::CustodianSignatureMissing.into()),
1183        );
1184
1185        // Lockup active, custodian is a signer
1186        signers.insert(custodian);
1187        assert_eq!(
1188            authorized.authorize(
1189                &signers,
1190                &staker,
1191                StakeAuthorize::Withdrawer,
1192                Some((&lockup, &clock, Some(&custodian)))
1193            ),
1194            Ok(())
1195        );
1196    }
1197
1198    #[test]
1199    fn test_stake_state_stake_from_fail() {
1200        let mut stake_account =
1201            AccountSharedData::new(0, StakeStateV2::size_of(), &crate::program::id());
1202
1203        stake_account
1204            .set_state(&StakeStateV2::default())
1205            .expect("set_state");
1206
1207        assert_eq!(stake_from(&stake_account), None);
1208    }
1209
1210    #[test]
1211    fn test_stake_is_bootstrap() {
1212        assert!(Delegation {
1213            activation_epoch: u64::MAX,
1214            ..Delegation::default()
1215        }
1216        .is_bootstrap());
1217        assert!(!Delegation {
1218            activation_epoch: 0,
1219            ..Delegation::default()
1220        }
1221        .is_bootstrap());
1222    }
1223
1224    #[test]
1225    fn test_stake_activating_and_deactivating() {
1226        let stake = Delegation {
1227            stake: 1_000,
1228            activation_epoch: 0, // activating at zero
1229            deactivation_epoch: 5,
1230            ..Delegation::default()
1231        };
1232
1233        // save this off so stake.config.warmup_rate changes don't break this test
1234        let rate_bps = warmup_cooldown_rate_bps(0, None);
1235        let increment = ((1_000u128 * rate_bps as u128) / 10_000) as u64;
1236
1237        let mut stake_history = StakeHistory::default();
1238        // assert that this stake follows step function if there's no history
1239        assert_eq!(
1240            stake.stake_activating_and_deactivating_v2(
1241                stake.activation_epoch,
1242                &stake_history,
1243                None
1244            ),
1245            StakeActivationStatus::with_effective_and_activating(0, stake.stake),
1246        );
1247        for epoch in stake.activation_epoch + 1..stake.deactivation_epoch {
1248            assert_eq!(
1249                stake.stake_activating_and_deactivating_v2(epoch, &stake_history, None),
1250                StakeActivationStatus::with_effective(stake.stake),
1251            );
1252        }
1253        // assert that this stake is full deactivating
1254        assert_eq!(
1255            stake.stake_activating_and_deactivating_v2(
1256                stake.deactivation_epoch,
1257                &stake_history,
1258                None
1259            ),
1260            StakeActivationStatus::with_deactivating(stake.stake),
1261        );
1262        // assert that this stake is fully deactivated if there's no history
1263        assert_eq!(
1264            stake.stake_activating_and_deactivating_v2(
1265                stake.deactivation_epoch + 1,
1266                &stake_history,
1267                None
1268            ),
1269            StakeActivationStatus::default(),
1270        );
1271
1272        stake_history.add(
1273            0u64, // entry for zero doesn't have my activating amount
1274            StakeHistoryEntry {
1275                effective: 1_000,
1276                ..StakeHistoryEntry::default()
1277            },
1278        );
1279        // assert that this stake is broken, because above setup is broken
1280        assert_eq!(
1281            stake.stake_activating_and_deactivating_v2(1, &stake_history, None),
1282            StakeActivationStatus::with_effective_and_activating(0, stake.stake),
1283        );
1284
1285        stake_history.add(
1286            0u64, // entry for zero has my activating amount
1287            StakeHistoryEntry {
1288                effective: 1_000,
1289                activating: 1_000,
1290                ..StakeHistoryEntry::default()
1291            },
1292            // no entry for 1, so this stake gets shorted
1293        );
1294        // assert that this stake is broken, because above setup is broken
1295        assert_eq!(
1296            stake.stake_activating_and_deactivating_v2(2, &stake_history, None),
1297            StakeActivationStatus::with_effective_and_activating(
1298                increment,
1299                stake.stake - increment
1300            ),
1301        );
1302
1303        // start over, test deactivation edge cases
1304        let mut stake_history = StakeHistory::default();
1305
1306        stake_history.add(
1307            stake.deactivation_epoch, // entry for zero doesn't have my de-activating amount
1308            StakeHistoryEntry {
1309                effective: 1_000,
1310                ..StakeHistoryEntry::default()
1311            },
1312        );
1313        // assert that this stake is broken, because above setup is broken
1314        assert_eq!(
1315            stake.stake_activating_and_deactivating_v2(
1316                stake.deactivation_epoch + 1,
1317                &stake_history,
1318                None,
1319            ),
1320            StakeActivationStatus::with_deactivating(stake.stake),
1321        );
1322
1323        // put in my initial deactivating amount, but don't put in an entry for next
1324        stake_history.add(
1325            stake.deactivation_epoch, // entry for zero has my de-activating amount
1326            StakeHistoryEntry {
1327                effective: 1_000,
1328                deactivating: 1_000,
1329                ..StakeHistoryEntry::default()
1330            },
1331        );
1332        // assert that this stake is broken, because above setup is broken
1333        assert_eq!(
1334            stake.stake_activating_and_deactivating_v2(
1335                stake.deactivation_epoch + 2,
1336                &stake_history,
1337                None,
1338            ),
1339            // hung, should be lower
1340            StakeActivationStatus::with_deactivating(stake.stake - increment),
1341        );
1342    }
1343
1344    mod same_epoch_activation_then_deactivation {
1345        use super::*;
1346
1347        enum OldDeactivationBehavior {
1348            Stuck,
1349            Slow,
1350        }
1351
1352        fn do_test(
1353            old_behavior: OldDeactivationBehavior,
1354            expected_stakes: &[StakeActivationStatus],
1355        ) {
1356            let cluster_stake = 1_000;
1357            let activating_stake = 10_000;
1358            let some_stake = 700;
1359            let some_epoch = 0;
1360
1361            let stake = Delegation {
1362                stake: some_stake,
1363                activation_epoch: some_epoch,
1364                deactivation_epoch: some_epoch,
1365                ..Delegation::default()
1366            };
1367
1368            let mut stake_history = StakeHistory::default();
1369            let cluster_deactivation_at_stake_modified_epoch = match old_behavior {
1370                OldDeactivationBehavior::Stuck => 0,
1371                OldDeactivationBehavior::Slow => 1000,
1372            };
1373
1374            let stake_history_entries = vec![
1375                (
1376                    cluster_stake,
1377                    activating_stake,
1378                    cluster_deactivation_at_stake_modified_epoch,
1379                ),
1380                (cluster_stake, activating_stake, 1000),
1381                (cluster_stake, activating_stake, 1000),
1382                (cluster_stake, activating_stake, 100),
1383                (cluster_stake, activating_stake, 100),
1384                (cluster_stake, activating_stake, 100),
1385                (cluster_stake, activating_stake, 100),
1386            ];
1387
1388            for (epoch, (effective, activating, deactivating)) in
1389                stake_history_entries.into_iter().enumerate()
1390            {
1391                stake_history.add(
1392                    epoch as Epoch,
1393                    StakeHistoryEntry {
1394                        effective,
1395                        activating,
1396                        deactivating,
1397                    },
1398                );
1399            }
1400
1401            assert_eq!(
1402                expected_stakes,
1403                (0..expected_stakes.len())
1404                    .map(|epoch| stake.stake_activating_and_deactivating_v2(
1405                        epoch as u64,
1406                        &stake_history,
1407                        None,
1408                    ))
1409                    .collect::<Vec<_>>()
1410            );
1411        }
1412
1413        #[test]
1414        fn test_new_behavior_previously_slow() {
1415            // any stake accounts activated and deactivated at the same epoch
1416            // shouldn't been activated (then deactivated) at all!
1417
1418            do_test(
1419                OldDeactivationBehavior::Slow,
1420                &[
1421                    StakeActivationStatus::default(),
1422                    StakeActivationStatus::default(),
1423                    StakeActivationStatus::default(),
1424                    StakeActivationStatus::default(),
1425                    StakeActivationStatus::default(),
1426                    StakeActivationStatus::default(),
1427                    StakeActivationStatus::default(),
1428                ],
1429            );
1430        }
1431
1432        #[test]
1433        fn test_new_behavior_previously_stuck() {
1434            // any stake accounts activated and deactivated at the same epoch
1435            // shouldn't been activated (then deactivated) at all!
1436
1437            do_test(
1438                OldDeactivationBehavior::Stuck,
1439                &[
1440                    StakeActivationStatus::default(),
1441                    StakeActivationStatus::default(),
1442                    StakeActivationStatus::default(),
1443                    StakeActivationStatus::default(),
1444                    StakeActivationStatus::default(),
1445                    StakeActivationStatus::default(),
1446                    StakeActivationStatus::default(),
1447                ],
1448            );
1449        }
1450    }
1451
1452    #[test]
1453    fn test_inflation_and_slashing_with_activating_and_deactivating_stake() {
1454        // some really boring delegation and stake_history setup
1455        let (delegated_stake, mut stake, stake_history) = {
1456            let cluster_stake = 1_000;
1457            let delegated_stake = 700;
1458
1459            let stake = Delegation {
1460                stake: delegated_stake,
1461                activation_epoch: 0,
1462                deactivation_epoch: 4,
1463                ..Delegation::default()
1464            };
1465
1466            let mut stake_history = StakeHistory::default();
1467            stake_history.add(
1468                0,
1469                StakeHistoryEntry {
1470                    effective: cluster_stake,
1471                    activating: delegated_stake,
1472                    ..StakeHistoryEntry::default()
1473                },
1474            );
1475            let newly_effective_at_epoch1 = (cluster_stake as f64 * 0.25) as u64;
1476            assert_eq!(newly_effective_at_epoch1, 250);
1477            stake_history.add(
1478                1,
1479                StakeHistoryEntry {
1480                    effective: cluster_stake + newly_effective_at_epoch1,
1481                    activating: delegated_stake - newly_effective_at_epoch1,
1482                    ..StakeHistoryEntry::default()
1483                },
1484            );
1485            let newly_effective_at_epoch2 =
1486                ((cluster_stake + newly_effective_at_epoch1) as f64 * 0.25) as u64;
1487            assert_eq!(newly_effective_at_epoch2, 312);
1488            stake_history.add(
1489                2,
1490                StakeHistoryEntry {
1491                    effective: cluster_stake
1492                        + newly_effective_at_epoch1
1493                        + newly_effective_at_epoch2,
1494                    activating: delegated_stake
1495                        - newly_effective_at_epoch1
1496                        - newly_effective_at_epoch2,
1497                    ..StakeHistoryEntry::default()
1498                },
1499            );
1500            stake_history.add(
1501                3,
1502                StakeHistoryEntry {
1503                    effective: cluster_stake + delegated_stake,
1504                    ..StakeHistoryEntry::default()
1505                },
1506            );
1507            stake_history.add(
1508                4,
1509                StakeHistoryEntry {
1510                    effective: cluster_stake + delegated_stake,
1511                    deactivating: delegated_stake,
1512                    ..StakeHistoryEntry::default()
1513                },
1514            );
1515            let newly_not_effective_stake_at_epoch5 =
1516                ((cluster_stake + delegated_stake) as f64 * 0.25) as u64;
1517            assert_eq!(newly_not_effective_stake_at_epoch5, 425);
1518            stake_history.add(
1519                5,
1520                StakeHistoryEntry {
1521                    effective: cluster_stake + delegated_stake
1522                        - newly_not_effective_stake_at_epoch5,
1523                    deactivating: delegated_stake - newly_not_effective_stake_at_epoch5,
1524                    ..StakeHistoryEntry::default()
1525                },
1526            );
1527
1528            (delegated_stake, stake, stake_history)
1529        };
1530
1531        // helper closures
1532        let calculate_each_staking_status = |stake: &Delegation, epoch_count: usize| -> Vec<_> {
1533            (0..epoch_count)
1534                .map(|epoch| {
1535                    stake.stake_activating_and_deactivating_v2(epoch as u64, &stake_history, None)
1536                })
1537                .collect::<Vec<_>>()
1538        };
1539        let adjust_staking_status = |rate: f64, status: &[StakeActivationStatus]| {
1540            status
1541                .iter()
1542                .map(|entry| StakeActivationStatus {
1543                    effective: (entry.effective as f64 * rate) as u64,
1544                    activating: (entry.activating as f64 * rate) as u64,
1545                    deactivating: (entry.deactivating as f64 * rate) as u64,
1546                })
1547                .collect::<Vec<_>>()
1548        };
1549
1550        let expected_staking_status_transition = vec![
1551            StakeActivationStatus::with_effective_and_activating(0, 700),
1552            StakeActivationStatus::with_effective_and_activating(250, 450),
1553            StakeActivationStatus::with_effective_and_activating(562, 138),
1554            StakeActivationStatus::with_effective(700),
1555            StakeActivationStatus::with_deactivating(700),
1556            StakeActivationStatus::with_deactivating(275),
1557            StakeActivationStatus::default(),
1558        ];
1559        let expected_staking_status_transition_base = vec![
1560            StakeActivationStatus::with_effective_and_activating(0, 700),
1561            StakeActivationStatus::with_effective_and_activating(250, 450),
1562            StakeActivationStatus::with_effective_and_activating(562, 138 + 1), // +1 is needed for rounding
1563            StakeActivationStatus::with_effective(700),
1564            StakeActivationStatus::with_deactivating(700),
1565            StakeActivationStatus::with_deactivating(275 + 1), // +1 is needed for rounding
1566            StakeActivationStatus::default(),
1567        ];
1568
1569        // normal stake activating and deactivating transition test, just in case
1570        assert_eq!(
1571            expected_staking_status_transition,
1572            calculate_each_staking_status(&stake, expected_staking_status_transition.len())
1573        );
1574
1575        // 10% inflation rewards assuming some sizable epochs passed!
1576        let rate = 1.10;
1577        stake.stake = (delegated_stake as f64 * rate) as u64;
1578        let expected_staking_status_transition =
1579            adjust_staking_status(rate, &expected_staking_status_transition_base);
1580
1581        assert_eq!(
1582            expected_staking_status_transition,
1583            calculate_each_staking_status(&stake, expected_staking_status_transition_base.len()),
1584        );
1585
1586        // 50% slashing!!!
1587        let rate = 0.5;
1588        stake.stake = (delegated_stake as f64 * rate) as u64;
1589        let expected_staking_status_transition =
1590            adjust_staking_status(rate, &expected_staking_status_transition_base);
1591
1592        assert_eq!(
1593            expected_staking_status_transition,
1594            calculate_each_staking_status(&stake, expected_staking_status_transition_base.len()),
1595        );
1596    }
1597
1598    #[test]
1599    fn test_stop_activating_after_deactivation() {
1600        let stake = Delegation {
1601            stake: 1_000,
1602            activation_epoch: 0,
1603            deactivation_epoch: 3,
1604            ..Delegation::default()
1605        };
1606
1607        let base_stake = 1_000;
1608        let mut stake_history = StakeHistory::default();
1609        let mut effective = base_stake;
1610        let other_activation = 100;
1611        let mut other_activations = vec![0];
1612        let rate_bps = warmup_cooldown_rate_bps(0, None);
1613
1614        // Build a stake history where the test staker always consumes all of the available warm
1615        // up and cool down stake. However, simulate other stakers beginning to activate during
1616        // the test staker's deactivation.
1617        for epoch in 0..=stake.deactivation_epoch + 1 {
1618            let (activating, deactivating) = if epoch < stake.deactivation_epoch {
1619                (stake.stake + base_stake - effective, 0)
1620            } else {
1621                let other_activation_sum: u64 = other_activations.iter().sum();
1622                let deactivating = effective - base_stake - other_activation_sum;
1623                (other_activation, deactivating)
1624            };
1625
1626            stake_history.add(
1627                epoch,
1628                StakeHistoryEntry {
1629                    effective,
1630                    activating,
1631                    deactivating,
1632                },
1633            );
1634
1635            let effective_rate_limited = ((effective as u128) * rate_bps as u128 / 10_000) as u64;
1636            if epoch < stake.deactivation_epoch {
1637                effective += effective_rate_limited.min(activating);
1638                other_activations.push(0);
1639            } else {
1640                effective -= effective_rate_limited.min(deactivating);
1641                effective += other_activation;
1642                other_activations.push(other_activation);
1643            }
1644        }
1645
1646        for epoch in 0..=stake.deactivation_epoch + 1 {
1647            let history = stake_history.get(epoch).unwrap();
1648            let other_activations: u64 = other_activations[..=epoch as usize].iter().sum();
1649            let expected_stake = history.effective - base_stake - other_activations;
1650            let (expected_activating, expected_deactivating) = if epoch < stake.deactivation_epoch {
1651                (history.activating, 0)
1652            } else {
1653                (0, history.deactivating)
1654            };
1655            assert_eq!(
1656                stake.stake_activating_and_deactivating_v2(epoch, &stake_history, None),
1657                StakeActivationStatus {
1658                    effective: expected_stake,
1659                    activating: expected_activating,
1660                    deactivating: expected_deactivating,
1661                },
1662            );
1663        }
1664    }
1665
1666    #[test]
1667    fn test_stake_warmup_cooldown_sub_integer_moves() {
1668        let delegations = [Delegation {
1669            stake: 2,
1670            activation_epoch: 0, // activating at zero
1671            deactivation_epoch: 5,
1672            ..Delegation::default()
1673        }];
1674        // give 2 epochs of cooldown
1675        let epochs = 7;
1676        // make bootstrap stake smaller than warmup so warmup/cooldownn
1677        //  increment is always smaller than 1
1678        let rate_bps = warmup_cooldown_rate_bps(0, None);
1679        let bootstrap = ((100u128 * rate_bps as u128) / (2u128 * 10_000)) as u64;
1680        let stake_history =
1681            create_stake_history_from_delegations(Some(bootstrap), 0..epochs, &delegations, None);
1682        let mut max_stake = 0;
1683        let mut min_stake = 2;
1684
1685        for epoch in 0..epochs {
1686            let stake = delegations
1687                .iter()
1688                .map(|delegation| delegation.stake_v2(epoch, &stake_history, None))
1689                .sum::<u64>();
1690            max_stake = max_stake.max(stake);
1691            min_stake = min_stake.min(stake);
1692        }
1693        assert_eq!(max_stake, 2);
1694        assert_eq!(min_stake, 0);
1695    }
1696
1697    #[test_case(None ; "old rate")]
1698    #[test_case(Some(1) ; "new rate activated in epoch 1")]
1699    #[test_case(Some(10) ; "new rate activated in epoch 10")]
1700    #[test_case(Some(30) ; "new rate activated in epoch 30")]
1701    #[test_case(Some(50) ; "new rate activated in epoch 50")]
1702    #[test_case(Some(60) ; "new rate activated in epoch 60")]
1703    fn test_stake_warmup_cooldown(new_rate_activation_epoch: Option<Epoch>) {
1704        let delegations = [
1705            Delegation {
1706                // never deactivates
1707                stake: 1_000,
1708                activation_epoch: u64::MAX,
1709                ..Delegation::default()
1710            },
1711            Delegation {
1712                stake: 1_000,
1713                activation_epoch: 0,
1714                deactivation_epoch: 9,
1715                ..Delegation::default()
1716            },
1717            Delegation {
1718                stake: 1_000,
1719                activation_epoch: 1,
1720                deactivation_epoch: 6,
1721                ..Delegation::default()
1722            },
1723            Delegation {
1724                stake: 1_000,
1725                activation_epoch: 2,
1726                deactivation_epoch: 5,
1727                ..Delegation::default()
1728            },
1729            Delegation {
1730                stake: 1_000,
1731                activation_epoch: 2,
1732                deactivation_epoch: 4,
1733                ..Delegation::default()
1734            },
1735            Delegation {
1736                stake: 1_000,
1737                activation_epoch: 4,
1738                deactivation_epoch: 4,
1739                ..Delegation::default()
1740            },
1741        ];
1742        // chosen to ensure that the last activated stake (at 4) finishes
1743        //  warming up and cooling down
1744        //  a stake takes 2.0f64.log(1.0 + STAKE_WARMUP_RATE) epochs to warm up or cool down
1745        //  when all alone, but the above overlap a lot
1746        let epochs = 60;
1747
1748        let stake_history = create_stake_history_from_delegations(
1749            None,
1750            0..epochs,
1751            &delegations,
1752            new_rate_activation_epoch,
1753        );
1754
1755        let mut prev_total_effective_stake = delegations
1756            .iter()
1757            .map(|delegation| delegation.stake_v2(0, &stake_history, new_rate_activation_epoch))
1758            .sum::<u64>();
1759
1760        // uncomment and add ! for fun with graphing
1761        // eprintln("\n{:8} {:8} {:8}", "   epoch", "   total", "   delta");
1762        for epoch in 1..epochs {
1763            let total_effective_stake = delegations
1764                .iter()
1765                .map(|delegation| {
1766                    delegation.stake_v2(epoch, &stake_history, new_rate_activation_epoch)
1767                })
1768                .sum::<u64>();
1769
1770            let delta = total_effective_stake.abs_diff(prev_total_effective_stake);
1771
1772            // uncomment and add ! for fun with graphing
1773            // eprint("{:8} {:8} {:8} ", epoch, total_effective_stake, delta);
1774            // (0..(total_effective_stake as usize / (delegations.len() * 5))).for_each(|_| eprint("#"));
1775            // eprintln();
1776
1777            let rate_bps = warmup_cooldown_rate_bps(epoch, new_rate_activation_epoch);
1778            let max_delta =
1779                ((prev_total_effective_stake as u128) * rate_bps as u128 / 10_000) as u64;
1780            assert!(delta <= max_delta.max(1));
1781
1782            prev_total_effective_stake = total_effective_stake;
1783        }
1784    }
1785
1786    #[test]
1787    fn test_lockup_is_expired() {
1788        let custodian = Pubkey::new_unique();
1789        let lockup = Lockup {
1790            epoch: 1,
1791            unix_timestamp: 1,
1792            custodian,
1793        };
1794        // neither time
1795        assert!(lockup.is_in_force(
1796            &Clock {
1797                epoch: 0,
1798                unix_timestamp: 0,
1799                ..Clock::default()
1800            },
1801            None
1802        ));
1803        // not timestamp
1804        assert!(lockup.is_in_force(
1805            &Clock {
1806                epoch: 2,
1807                unix_timestamp: 0,
1808                ..Clock::default()
1809            },
1810            None
1811        ));
1812        // not epoch
1813        assert!(lockup.is_in_force(
1814            &Clock {
1815                epoch: 0,
1816                unix_timestamp: 2,
1817                ..Clock::default()
1818            },
1819            None
1820        ));
1821        // both, no custodian
1822        assert!(!lockup.is_in_force(
1823            &Clock {
1824                epoch: 1,
1825                unix_timestamp: 1,
1826                ..Clock::default()
1827            },
1828            None
1829        ));
1830        // neither, but custodian
1831        assert!(!lockup.is_in_force(
1832            &Clock {
1833                epoch: 0,
1834                unix_timestamp: 0,
1835                ..Clock::default()
1836            },
1837            Some(&custodian),
1838        ));
1839    }
1840
1841    fn check_borsh_deserialization(stake: StakeStateV2) {
1842        let serialized = serialize(&stake).unwrap();
1843        let deserialized = StakeStateV2::try_from_slice(&serialized).unwrap();
1844        assert_eq!(stake, deserialized);
1845    }
1846
1847    fn check_borsh_serialization(stake: StakeStateV2) {
1848        let bincode_serialized = serialize(&stake).unwrap();
1849        let borsh_serialized = borsh::to_vec(&stake).unwrap();
1850        assert_eq!(bincode_serialized, borsh_serialized);
1851    }
1852
1853    #[test]
1854    fn test_size_of() {
1855        assert_eq!(StakeStateV2::size_of(), std::mem::size_of::<StakeStateV2>());
1856    }
1857
1858    #[test]
1859    fn bincode_vs_borsh_deserialization() {
1860        check_borsh_deserialization(StakeStateV2::Uninitialized);
1861        check_borsh_deserialization(StakeStateV2::RewardsPool);
1862        check_borsh_deserialization(StakeStateV2::Initialized(Meta {
1863            rent_exempt_reserve: u64::MAX,
1864            authorized: Authorized {
1865                staker: Pubkey::new_unique(),
1866                withdrawer: Pubkey::new_unique(),
1867            },
1868            lockup: Lockup::default(),
1869        }));
1870        check_borsh_deserialization(StakeStateV2::Stake(
1871            Meta {
1872                rent_exempt_reserve: 1,
1873                authorized: Authorized {
1874                    staker: Pubkey::new_unique(),
1875                    withdrawer: Pubkey::new_unique(),
1876                },
1877                lockup: Lockup::default(),
1878            },
1879            Stake {
1880                delegation: Delegation {
1881                    voter_pubkey: Pubkey::new_unique(),
1882                    stake: u64::MAX,
1883                    activation_epoch: Epoch::MAX,
1884                    deactivation_epoch: Epoch::MAX,
1885                    ..Delegation::default()
1886                },
1887                credits_observed: 1,
1888            },
1889            StakeFlags::empty(),
1890        ));
1891    }
1892
1893    #[test]
1894    fn bincode_vs_borsh_serialization() {
1895        check_borsh_serialization(StakeStateV2::Uninitialized);
1896        check_borsh_serialization(StakeStateV2::RewardsPool);
1897        check_borsh_serialization(StakeStateV2::Initialized(Meta {
1898            rent_exempt_reserve: u64::MAX,
1899            authorized: Authorized {
1900                staker: Pubkey::new_unique(),
1901                withdrawer: Pubkey::new_unique(),
1902            },
1903            lockup: Lockup::default(),
1904        }));
1905        #[allow(deprecated)]
1906        check_borsh_serialization(StakeStateV2::Stake(
1907            Meta {
1908                rent_exempt_reserve: 1,
1909                authorized: Authorized {
1910                    staker: Pubkey::new_unique(),
1911                    withdrawer: Pubkey::new_unique(),
1912                },
1913                lockup: Lockup::default(),
1914            },
1915            Stake {
1916                delegation: Delegation {
1917                    voter_pubkey: Pubkey::new_unique(),
1918                    stake: u64::MAX,
1919                    activation_epoch: Epoch::MAX,
1920                    deactivation_epoch: Epoch::MAX,
1921                    ..Default::default()
1922                },
1923                credits_observed: 1,
1924            },
1925            StakeFlags::MUST_FULLY_ACTIVATE_BEFORE_DEACTIVATION_IS_PERMITTED,
1926        ));
1927    }
1928
1929    #[test]
1930    fn borsh_deserialization_live_data() {
1931        let data = [
1932            1, 0, 0, 0, 128, 213, 34, 0, 0, 0, 0, 0, 133, 0, 79, 231, 141, 29, 73, 61, 232, 35,
1933            119, 124, 168, 12, 120, 216, 195, 29, 12, 166, 139, 28, 36, 182, 186, 154, 246, 149,
1934            224, 109, 52, 100, 133, 0, 79, 231, 141, 29, 73, 61, 232, 35, 119, 124, 168, 12, 120,
1935            216, 195, 29, 12, 166, 139, 28, 36, 182, 186, 154, 246, 149, 224, 109, 52, 100, 0, 0,
1936            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,
1937            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,
1938            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,
1939            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,
1940            0, 0, 0, 0, 0, 0,
1941        ];
1942        // As long as we get the 4-byte enum and the first field right, then
1943        // we're sure the rest works out
1944        let deserialized = try_from_slice_unchecked::<StakeStateV2>(&data).unwrap();
1945        assert_matches!(
1946            deserialized,
1947            StakeStateV2::Initialized(Meta {
1948                rent_exempt_reserve: 2282880,
1949                ..
1950            })
1951        );
1952    }
1953
1954    #[test]
1955    fn stake_flag_member_offset() {
1956        const FLAG_OFFSET: usize = 196;
1957        let check_flag = |flag, expected| {
1958            let stake = StakeStateV2::Stake(
1959                Meta {
1960                    rent_exempt_reserve: 1,
1961                    authorized: Authorized {
1962                        staker: Pubkey::new_unique(),
1963                        withdrawer: Pubkey::new_unique(),
1964                    },
1965                    lockup: Lockup::default(),
1966                },
1967                Stake {
1968                    delegation: Delegation {
1969                        voter_pubkey: Pubkey::new_unique(),
1970                        stake: u64::MAX,
1971                        activation_epoch: Epoch::MAX,
1972                        deactivation_epoch: Epoch::MAX,
1973                        _reserved: [0; 8],
1974                    },
1975                    credits_observed: 1,
1976                },
1977                flag,
1978            );
1979
1980            let bincode_serialized = serialize(&stake).unwrap();
1981            let borsh_serialized = borsh::to_vec(&stake).unwrap();
1982
1983            assert_eq!(bincode_serialized[FLAG_OFFSET], expected);
1984            assert_eq!(borsh_serialized[FLAG_OFFSET], expected);
1985        };
1986        #[allow(deprecated)]
1987        check_flag(
1988            StakeFlags::MUST_FULLY_ACTIVATE_BEFORE_DEACTIVATION_IS_PERMITTED,
1989            1,
1990        );
1991        check_flag(StakeFlags::empty(), 0);
1992    }
1993
1994    mod deprecated {
1995        use {
1996            super::*,
1997            static_assertions::{assert_eq_align, assert_eq_size},
1998        };
1999
2000        fn check_borsh_deserialization(stake: StakeState) {
2001            let serialized = serialize(&stake).unwrap();
2002            let deserialized = StakeState::try_from_slice(&serialized).unwrap();
2003            assert_eq!(stake, deserialized);
2004        }
2005
2006        fn check_borsh_serialization(stake: StakeState) {
2007            let bincode_serialized = serialize(&stake).unwrap();
2008            let borsh_serialized = borsh::to_vec(&stake).unwrap();
2009            assert_eq!(bincode_serialized, borsh_serialized);
2010        }
2011
2012        #[test]
2013        fn test_size_of() {
2014            assert_eq!(StakeState::size_of(), std::mem::size_of::<StakeState>());
2015        }
2016
2017        #[test]
2018        fn bincode_vs_borsh_deserialization() {
2019            check_borsh_deserialization(StakeState::Uninitialized);
2020            check_borsh_deserialization(StakeState::RewardsPool);
2021            check_borsh_deserialization(StakeState::Initialized(Meta {
2022                rent_exempt_reserve: u64::MAX,
2023                authorized: Authorized {
2024                    staker: Pubkey::new_unique(),
2025                    withdrawer: Pubkey::new_unique(),
2026                },
2027                lockup: Lockup::default(),
2028            }));
2029            check_borsh_deserialization(StakeState::Stake(
2030                Meta {
2031                    rent_exempt_reserve: 1,
2032                    authorized: Authorized {
2033                        staker: Pubkey::new_unique(),
2034                        withdrawer: Pubkey::new_unique(),
2035                    },
2036                    lockup: Lockup::default(),
2037                },
2038                Stake {
2039                    delegation: Delegation {
2040                        voter_pubkey: Pubkey::new_unique(),
2041                        stake: u64::MAX,
2042                        activation_epoch: Epoch::MAX,
2043                        deactivation_epoch: Epoch::MAX,
2044                        _reserved: [0; 8],
2045                    },
2046                    credits_observed: 1,
2047                },
2048            ));
2049        }
2050
2051        #[test]
2052        fn bincode_vs_borsh_serialization() {
2053            check_borsh_serialization(StakeState::Uninitialized);
2054            check_borsh_serialization(StakeState::RewardsPool);
2055            check_borsh_serialization(StakeState::Initialized(Meta {
2056                rent_exempt_reserve: u64::MAX,
2057                authorized: Authorized {
2058                    staker: Pubkey::new_unique(),
2059                    withdrawer: Pubkey::new_unique(),
2060                },
2061                lockup: Lockup::default(),
2062            }));
2063            check_borsh_serialization(StakeState::Stake(
2064                Meta {
2065                    rent_exempt_reserve: 1,
2066                    authorized: Authorized {
2067                        staker: Pubkey::new_unique(),
2068                        withdrawer: Pubkey::new_unique(),
2069                    },
2070                    lockup: Lockup::default(),
2071                },
2072                Stake {
2073                    delegation: Delegation {
2074                        voter_pubkey: Pubkey::new_unique(),
2075                        stake: u64::MAX,
2076                        activation_epoch: Epoch::MAX,
2077                        deactivation_epoch: Epoch::MAX,
2078                        _reserved: [0; 8],
2079                    },
2080                    credits_observed: 1,
2081                },
2082            ));
2083        }
2084
2085        #[test]
2086        fn borsh_deserialization_live_data() {
2087            let data = [
2088                1, 0, 0, 0, 128, 213, 34, 0, 0, 0, 0, 0, 133, 0, 79, 231, 141, 29, 73, 61, 232, 35,
2089                119, 124, 168, 12, 120, 216, 195, 29, 12, 166, 139, 28, 36, 182, 186, 154, 246,
2090                149, 224, 109, 52, 100, 133, 0, 79, 231, 141, 29, 73, 61, 232, 35, 119, 124, 168,
2091                12, 120, 216, 195, 29, 12, 166, 139, 28, 36, 182, 186, 154, 246, 149, 224, 109, 52,
2092                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,
2093                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,
2094                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,
2095                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,
2096                0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
2097            ];
2098            // As long as we get the 4-byte enum and the first field right, then
2099            // we're sure the rest works out
2100            let deserialized = try_from_slice_unchecked::<StakeState>(&data).unwrap();
2101            assert_matches!(
2102                deserialized,
2103                StakeState::Initialized(Meta {
2104                    rent_exempt_reserve: 2282880,
2105                    ..
2106                })
2107            );
2108        }
2109
2110        /// Contains legacy struct definitions to verify memory layout compatibility.
2111        mod legacy {
2112            use super::*;
2113
2114            #[derive(borsh::BorshSerialize, borsh::BorshDeserialize)]
2115            #[borsh(crate = "borsh")]
2116            pub struct Delegation {
2117                pub voter_pubkey: Pubkey,
2118                pub stake: u64,
2119                pub activation_epoch: Epoch,
2120                pub deactivation_epoch: Epoch,
2121                pub warmup_cooldown_rate: f64,
2122            }
2123        }
2124
2125        #[test]
2126        fn test_delegation_struct_layout_compatibility() {
2127            assert_eq_size!(Delegation, legacy::Delegation);
2128            assert_eq_align!(Delegation, legacy::Delegation);
2129        }
2130
2131        #[test]
2132        #[allow(clippy::used_underscore_binding)]
2133        fn test_delegation_deserialization_from_legacy_format() {
2134            let legacy_delegation = legacy::Delegation {
2135                voter_pubkey: Pubkey::new_unique(),
2136                stake: 12345,
2137                activation_epoch: 10,
2138                deactivation_epoch: 20,
2139                warmup_cooldown_rate: NEW_WARMUP_COOLDOWN_RATE,
2140            };
2141
2142            let serialized_data = borsh::to_vec(&legacy_delegation).unwrap();
2143
2144            // Deserialize into the new `Delegation` struct
2145            let new_delegation = Delegation::try_from_slice(&serialized_data).unwrap();
2146
2147            // Assert that the fields are identical
2148            assert_eq!(new_delegation.voter_pubkey, legacy_delegation.voter_pubkey);
2149            assert_eq!(new_delegation.stake, legacy_delegation.stake);
2150            assert_eq!(
2151                new_delegation.activation_epoch,
2152                legacy_delegation.activation_epoch
2153            );
2154            assert_eq!(
2155                new_delegation.deactivation_epoch,
2156                legacy_delegation.deactivation_epoch
2157            );
2158
2159            // Assert that the `reserved` bytes now contain the raw bits of the old f64
2160            assert_eq!(
2161                new_delegation._reserved,
2162                NEW_WARMUP_COOLDOWN_RATE.to_le_bytes()
2163            );
2164        }
2165    }
2166}