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