1#![allow(clippy::arithmetic_side_effects)]
2#![deny(clippy::wildcard_enum_match_arm)]
3#![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#[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 pub const fn size_of() -> usize {
131 200 }
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 pub const fn size_of() -> usize {
249 200 }
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 #[cfg_attr(feature = "codama", codama(display(label = "Locked Until")))]
350 pub unix_timestamp: UnixTimestamp,
351 #[cfg_attr(feature = "codama", codama(display(label = "Locked Until Epoch")))]
354 pub epoch: Epoch,
355 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 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 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 #[cfg_attr(feature = "codama", codama(display(label = "Vote Account")))]
557 pub voter_pubkey: Pubkey,
558 #[cfg_attr(
560 feature = "codama",
561 codama(display(label = "Delegated Amount", amount(decimals = 9, unit = "SOL")))
562 )]
563 pub stake: u64,
564 pub activation_epoch: Epoch,
566 pub deactivation_epoch: Epoch,
568 #[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 #[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 #[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 let (effective_stake, activating_stake) =
626 self.stake_and_activating(target_epoch, history, new_rate_activation_epoch);
627
628 if target_epoch < self.deactivation_epoch {
630 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 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 let mut current_epoch;
657 let mut current_effective_stake = effective_stake;
658 loop {
659 current_epoch = prev_epoch + 1;
660 if prev_cluster_stake.deactivating == 0 {
663 break;
664 }
665
666 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 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 StakeActivationStatus::with_deactivating(current_effective_stake)
698 } else {
699 StakeActivationStatus::default()
701 }
702 }
703
704 #[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 (delegated_stake, 0)
717 } else if self.activation_epoch == self.deactivation_epoch {
718 (0, 0)
721 } else if target_epoch == self.activation_epoch {
722 (0, delegated_stake)
724 } else if target_epoch < self.activation_epoch {
725 (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 let mut current_epoch;
742 let mut current_effective_stake = 0;
743 loop {
744 current_epoch = prev_epoch + 1;
745 if prev_cluster_stake.activating == 0 {
748 break;
749 }
750
751 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 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 (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 let (effective_stake, activating_stake) =
810 self.stake_and_activating_v2(target_epoch, history, new_rate_activation_epoch);
811
812 if target_epoch < self.deactivation_epoch {
814 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 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 let mut current_epoch;
843 let mut remaining_deactivating_stake = effective_stake;
844 loop {
845 current_epoch = prev_epoch + 1;
846 if prev_cluster_stake.deactivating == 0 {
849 break;
850 }
851
852 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 remaining_deactivating_stake =
863 remaining_deactivating_stake.saturating_sub(newly_deactivated_stake.max(1));
864
865 if remaining_deactivating_stake == 0 {
867 break;
868 }
869
870 if current_epoch >= target_epoch {
872 break;
873 }
874
875 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 break;
882 }
883 }
884
885 StakeActivationStatus::with_deactivating(remaining_deactivating_stake)
887 } else {
888 StakeActivationStatus::default()
890 }
891 }
892
893 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 (delegated_stake, 0)
905 } else if self.activation_epoch == self.deactivation_epoch {
906 (0, 0)
909 } else if target_epoch == self.activation_epoch {
910 (0, delegated_stake)
912 } else if target_epoch < self.activation_epoch {
913 (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 let mut current_epoch;
932 let mut activated_stake_amount = 0;
933 loop {
934 current_epoch = prev_epoch + 1;
935 if prev_cluster_stake.activating == 0 {
938 break;
939 }
940
941 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 activated_stake_amount += newly_effective_stake.max(1);
952
953 if activated_stake_amount >= delegated_stake {
955 activated_stake_amount = delegated_stake;
956 break;
957 }
958
959 if current_epoch >= target_epoch || current_epoch >= self.deactivation_epoch {
961 break;
962 }
963
964 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 break;
971 }
972 }
973
974 (
976 activated_stake_amount,
977 delegated_stake - activated_stake_amount,
978 )
979 } else {
980 (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 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 assert_eq!(
1173 authorized.authorize(
1174 &signers,
1175 &staker,
1176 StakeAuthorize::Withdrawer,
1177 Some((&Lockup::default(), &clock, None))
1178 ),
1179 Ok(())
1180 );
1181
1182 assert_eq!(
1184 authorized.authorize(
1185 &signers,
1186 &staker,
1187 StakeAuthorize::Withdrawer,
1188 Some((&Lockup::default(), &clock, Some(&invalid_custodian)))
1189 ),
1190 Ok(()) );
1192
1193 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 assert_eq!(
1208 authorized.authorize(
1209 &signers,
1210 &staker,
1211 StakeAuthorize::Withdrawer,
1212 Some((&Lockup::default(), &clock, Some(&invalid_custodian)))
1213 ),
1214 Ok(()) );
1216
1217 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()), );
1228
1229 signers.remove(&invalid_custodian);
1230
1231 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 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 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, deactivation_epoch: 5,
1298 ..Delegation::default()
1299 };
1300
1301 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_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_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_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, StakeHistoryEntry {
1343 effective: 1_000,
1344 ..StakeHistoryEntry::default()
1345 },
1346 );
1347 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, StakeHistoryEntry {
1356 effective: 1_000,
1357 activating: 1_000,
1358 ..StakeHistoryEntry::default()
1359 },
1360 );
1362 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 let mut stake_history = StakeHistory::default();
1373
1374 stake_history.add(
1375 stake.deactivation_epoch, StakeHistoryEntry {
1377 effective: 1_000,
1378 ..StakeHistoryEntry::default()
1379 },
1380 );
1381 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 stake_history.add(
1393 stake.deactivation_epoch, StakeHistoryEntry {
1395 effective: 1_000,
1396 deactivating: 1_000,
1397 ..StakeHistoryEntry::default()
1398 },
1399 );
1400 assert_eq!(
1402 stake.stake_activating_and_deactivating_v2(
1403 stake.deactivation_epoch + 2,
1404 &stake_history,
1405 None,
1406 ),
1407 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 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 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 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 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), StakeActivationStatus::with_effective(700),
1632 StakeActivationStatus::with_deactivating(700),
1633 StakeActivationStatus::with_deactivating(275 + 1), StakeActivationStatus::default(),
1635 ];
1636
1637 assert_eq!(
1639 expected_staking_status_transition,
1640 calculate_each_staking_status(&stake, expected_staking_status_transition.len())
1641 );
1642
1643 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 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 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, deactivation_epoch: 5,
1740 ..Delegation::default()
1741 }];
1742 let epochs = 7;
1744 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 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 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 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 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 assert!(lockup.is_in_force(
1864 &Clock {
1865 epoch: 0,
1866 unix_timestamp: 0,
1867 ..Clock::default()
1868 },
1869 None
1870 ));
1871 assert!(lockup.is_in_force(
1873 &Clock {
1874 epoch: 2,
1875 unix_timestamp: 0,
1876 ..Clock::default()
1877 },
1878 None
1879 ));
1880 assert!(lockup.is_in_force(
1882 &Clock {
1883 epoch: 0,
1884 unix_timestamp: 2,
1885 ..Clock::default()
1886 },
1887 None
1888 ));
1889 assert!(!lockup.is_in_force(
1891 &Clock {
1892 epoch: 1,
1893 unix_timestamp: 1,
1894 ..Clock::default()
1895 },
1896 None
1897 ));
1898 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 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 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 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 let new_delegation = Delegation::try_from_slice(&serialized_data).unwrap();
2214
2215 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_eq!(
2229 new_delegation._reserved,
2230 NEW_WARMUP_COOLDOWN_RATE.to_le_bytes()
2231 );
2232 }
2233 }
2234}