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