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