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