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 #[deprecated(
419 since = "3.0.1",
420 note = "Stake account rent must be calculated via the `Rent` sysvar. \
421 This value will cease to be correct once lamports-per-byte is adjusted."
422 )]
423 pub rent_exempt_reserve: u64,
424 pub authorized: Authorized,
425 pub lockup: Lockup,
426}
427
428impl Meta {
429 pub fn set_lockup(
430 &mut self,
431 lockup: &LockupArgs,
432 signers: &HashSet<Pubkey>,
433 clock: &Clock,
434 ) -> Result<(), InstructionError> {
435 if self.lockup.is_in_force(clock, None) {
439 if !signers.contains(&self.lockup.custodian) {
440 return Err(InstructionError::MissingRequiredSignature);
441 }
442 } else if !signers.contains(&self.authorized.withdrawer) {
443 return Err(InstructionError::MissingRequiredSignature);
444 }
445 if let Some(unix_timestamp) = lockup.unix_timestamp {
446 self.lockup.unix_timestamp = unix_timestamp;
447 }
448 if let Some(epoch) = lockup.epoch {
449 self.lockup.epoch = epoch;
450 }
451 if let Some(custodian) = lockup.custodian {
452 self.lockup.custodian = custodian;
453 }
454 Ok(())
455 }
456
457 pub fn auto(authorized: &Pubkey) -> Self {
458 Self {
459 authorized: Authorized::auto(authorized),
460 ..Meta::default()
461 }
462 }
463}
464
465#[cfg_attr(feature = "codama", derive(CodamaType))]
466#[derive(Debug, PartialEq, Clone, Copy)]
467#[cfg_attr(feature = "frozen-abi", derive(solana_frozen_abi_macro::AbiExample))]
468#[cfg_attr(
469 feature = "borsh",
470 derive(BorshSerialize, BorshDeserialize, BorshSchema),
471 borsh(crate = "borsh")
472)]
473#[cfg_attr(
474 feature = "serde",
475 derive(serde_derive::Deserialize, serde_derive::Serialize)
476)]
477pub struct Delegation {
478 pub voter_pubkey: Pubkey,
480 pub stake: u64,
482 pub activation_epoch: Epoch,
484 pub deactivation_epoch: Epoch,
486 #[deprecated(
488 since = "1.16.7",
489 note = "Please use `solana_sdk::stake::state::warmup_cooldown_rate()` instead"
490 )]
491 pub warmup_cooldown_rate: f64,
492}
493
494impl Default for Delegation {
495 fn default() -> Self {
496 #[allow(deprecated)]
497 Self {
498 voter_pubkey: Pubkey::default(),
499 stake: 0,
500 activation_epoch: 0,
501 deactivation_epoch: u64::MAX,
502 warmup_cooldown_rate: DEFAULT_WARMUP_COOLDOWN_RATE,
503 }
504 }
505}
506
507impl Delegation {
508 pub fn new(voter_pubkey: &Pubkey, stake: u64, activation_epoch: Epoch) -> Self {
509 Self {
510 voter_pubkey: *voter_pubkey,
511 stake,
512 activation_epoch,
513 ..Delegation::default()
514 }
515 }
516 pub fn is_bootstrap(&self) -> bool {
517 self.activation_epoch == u64::MAX
518 }
519
520 pub fn stake<T: StakeHistoryGetEntry>(
521 &self,
522 epoch: Epoch,
523 history: &T,
524 new_rate_activation_epoch: Option<Epoch>,
525 ) -> u64 {
526 self.stake_activating_and_deactivating(epoch, history, new_rate_activation_epoch)
527 .effective
528 }
529
530 #[allow(clippy::comparison_chain)]
531 pub fn stake_activating_and_deactivating<T: StakeHistoryGetEntry>(
532 &self,
533 target_epoch: Epoch,
534 history: &T,
535 new_rate_activation_epoch: Option<Epoch>,
536 ) -> StakeActivationStatus {
537 let (effective_stake, activating_stake) =
539 self.stake_and_activating(target_epoch, history, new_rate_activation_epoch);
540
541 if target_epoch < self.deactivation_epoch {
543 if activating_stake == 0 {
545 StakeActivationStatus::with_effective(effective_stake)
546 } else {
547 StakeActivationStatus::with_effective_and_activating(
548 effective_stake,
549 activating_stake,
550 )
551 }
552 } else if target_epoch == self.deactivation_epoch {
553 StakeActivationStatus::with_deactivating(effective_stake)
555 } else if let Some((history, mut prev_epoch, mut prev_cluster_stake)) = history
556 .get_entry(self.deactivation_epoch)
557 .map(|cluster_stake_at_deactivation_epoch| {
558 (
559 history,
560 self.deactivation_epoch,
561 cluster_stake_at_deactivation_epoch,
562 )
563 })
564 {
565 let mut current_epoch;
570 let mut current_effective_stake = effective_stake;
571 loop {
572 current_epoch = prev_epoch + 1;
573 if prev_cluster_stake.deactivating == 0 {
576 break;
577 }
578
579 let weight =
582 current_effective_stake as f64 / prev_cluster_stake.deactivating as f64;
583 let warmup_cooldown_rate =
584 warmup_cooldown_rate(current_epoch, new_rate_activation_epoch);
585
586 let newly_not_effective_cluster_stake =
588 prev_cluster_stake.effective as f64 * warmup_cooldown_rate;
589 let newly_not_effective_stake =
590 ((weight * newly_not_effective_cluster_stake) as u64).max(1);
591
592 current_effective_stake =
593 current_effective_stake.saturating_sub(newly_not_effective_stake);
594 if current_effective_stake == 0 {
595 break;
596 }
597
598 if current_epoch >= target_epoch {
599 break;
600 }
601 if let Some(current_cluster_stake) = history.get_entry(current_epoch) {
602 prev_epoch = current_epoch;
603 prev_cluster_stake = current_cluster_stake;
604 } else {
605 break;
606 }
607 }
608
609 StakeActivationStatus::with_deactivating(current_effective_stake)
611 } else {
612 StakeActivationStatus::default()
614 }
615 }
616
617 fn stake_and_activating<T: StakeHistoryGetEntry>(
619 &self,
620 target_epoch: Epoch,
621 history: &T,
622 new_rate_activation_epoch: Option<Epoch>,
623 ) -> (u64, u64) {
624 let delegated_stake = self.stake;
625
626 if self.is_bootstrap() {
627 (delegated_stake, 0)
629 } else if self.activation_epoch == self.deactivation_epoch {
630 (0, 0)
633 } else if target_epoch == self.activation_epoch {
634 (0, delegated_stake)
636 } else if target_epoch < self.activation_epoch {
637 (0, 0)
639 } else if let Some((history, mut prev_epoch, mut prev_cluster_stake)) = history
640 .get_entry(self.activation_epoch)
641 .map(|cluster_stake_at_activation_epoch| {
642 (
643 history,
644 self.activation_epoch,
645 cluster_stake_at_activation_epoch,
646 )
647 })
648 {
649 let mut current_epoch;
654 let mut current_effective_stake = 0;
655 loop {
656 current_epoch = prev_epoch + 1;
657 if prev_cluster_stake.activating == 0 {
660 break;
661 }
662
663 let remaining_activating_stake = delegated_stake - current_effective_stake;
666 let weight =
667 remaining_activating_stake as f64 / prev_cluster_stake.activating as f64;
668 let warmup_cooldown_rate =
669 warmup_cooldown_rate(current_epoch, new_rate_activation_epoch);
670
671 let newly_effective_cluster_stake =
673 prev_cluster_stake.effective as f64 * warmup_cooldown_rate;
674 let newly_effective_stake =
675 ((weight * newly_effective_cluster_stake) as u64).max(1);
676
677 current_effective_stake += newly_effective_stake;
678 if current_effective_stake >= delegated_stake {
679 current_effective_stake = delegated_stake;
680 break;
681 }
682
683 if current_epoch >= target_epoch || current_epoch >= self.deactivation_epoch {
684 break;
685 }
686 if let Some(current_cluster_stake) = history.get_entry(current_epoch) {
687 prev_epoch = current_epoch;
688 prev_cluster_stake = current_cluster_stake;
689 } else {
690 break;
691 }
692 }
693
694 (
695 current_effective_stake,
696 delegated_stake - current_effective_stake,
697 )
698 } else {
699 (delegated_stake, 0)
701 }
702 }
703}
704
705#[cfg_attr(feature = "codama", derive(CodamaType))]
706#[derive(Debug, Default, PartialEq, Clone, Copy)]
707#[cfg_attr(feature = "frozen-abi", derive(solana_frozen_abi_macro::AbiExample))]
708#[cfg_attr(
709 feature = "borsh",
710 derive(BorshSerialize, BorshDeserialize, BorshSchema),
711 borsh(crate = "borsh")
712)]
713#[cfg_attr(
714 feature = "serde",
715 derive(serde_derive::Deserialize, serde_derive::Serialize)
716)]
717pub struct Stake {
718 pub delegation: Delegation,
719 pub credits_observed: u64,
721}
722
723impl Stake {
724 pub fn stake<T: StakeHistoryGetEntry>(
725 &self,
726 epoch: Epoch,
727 history: &T,
728 new_rate_activation_epoch: Option<Epoch>,
729 ) -> u64 {
730 self.delegation
731 .stake(epoch, history, new_rate_activation_epoch)
732 }
733
734 pub fn split(
735 &mut self,
736 remaining_stake_delta: u64,
737 split_stake_amount: u64,
738 ) -> Result<Self, StakeError> {
739 if remaining_stake_delta > self.delegation.stake {
740 return Err(StakeError::InsufficientStake);
741 }
742 self.delegation.stake -= remaining_stake_delta;
743 let new = Self {
744 delegation: Delegation {
745 stake: split_stake_amount,
746 ..self.delegation
747 },
748 ..*self
749 };
750 Ok(new)
751 }
752
753 pub fn deactivate(&mut self, epoch: Epoch) -> Result<(), StakeError> {
754 if self.delegation.deactivation_epoch != u64::MAX {
755 Err(StakeError::AlreadyDeactivated)
756 } else {
757 self.delegation.deactivation_epoch = epoch;
758 Ok(())
759 }
760 }
761}
762
763#[cfg(all(feature = "borsh", feature = "bincode"))]
764#[cfg(test)]
765mod tests {
766 use {
767 super::*,
768 crate::stake_history::StakeHistory,
769 assert_matches::assert_matches,
770 bincode::serialize,
771 solana_account::{state_traits::StateMut, AccountSharedData, ReadableAccount},
772 solana_borsh::v1::try_from_slice_unchecked,
773 solana_pubkey::Pubkey,
774 test_case::test_case,
775 };
776
777 fn from<T: ReadableAccount + StateMut<StakeStateV2>>(account: &T) -> Option<StakeStateV2> {
778 account.state().ok()
779 }
780
781 fn stake_from<T: ReadableAccount + StateMut<StakeStateV2>>(account: &T) -> Option<Stake> {
782 from(account).and_then(|state: StakeStateV2| state.stake())
783 }
784
785 fn new_stake_history_entry<'a, I>(
786 epoch: Epoch,
787 stakes: I,
788 history: &StakeHistory,
789 new_rate_activation_epoch: Option<Epoch>,
790 ) -> StakeHistoryEntry
791 where
792 I: Iterator<Item = &'a Delegation>,
793 {
794 stakes.fold(StakeHistoryEntry::default(), |sum, stake| {
795 sum + stake.stake_activating_and_deactivating(epoch, history, new_rate_activation_epoch)
796 })
797 }
798
799 fn create_stake_history_from_delegations(
800 bootstrap: Option<u64>,
801 epochs: std::ops::Range<Epoch>,
802 delegations: &[Delegation],
803 new_rate_activation_epoch: Option<Epoch>,
804 ) -> StakeHistory {
805 let mut stake_history = StakeHistory::default();
806
807 let bootstrap_delegation = if let Some(bootstrap) = bootstrap {
808 vec![Delegation {
809 activation_epoch: u64::MAX,
810 stake: bootstrap,
811 ..Delegation::default()
812 }]
813 } else {
814 vec![]
815 };
816
817 for epoch in epochs {
818 let entry = new_stake_history_entry(
819 epoch,
820 delegations.iter().chain(bootstrap_delegation.iter()),
821 &stake_history,
822 new_rate_activation_epoch,
823 );
824 stake_history.add(epoch, entry);
825 }
826
827 stake_history
828 }
829
830 #[test]
831 fn test_authorized_authorize() {
832 let staker = Pubkey::new_unique();
833 let mut authorized = Authorized::auto(&staker);
834 let mut signers = HashSet::new();
835 assert_eq!(
836 authorized.authorize(&signers, &staker, StakeAuthorize::Staker, None),
837 Err(InstructionError::MissingRequiredSignature)
838 );
839 signers.insert(staker);
840 assert_eq!(
841 authorized.authorize(&signers, &staker, StakeAuthorize::Staker, None),
842 Ok(())
843 );
844 }
845
846 #[test]
847 fn test_authorized_authorize_with_custodian() {
848 let staker = Pubkey::new_unique();
849 let custodian = Pubkey::new_unique();
850 let invalid_custodian = Pubkey::new_unique();
851 let mut authorized = Authorized::auto(&staker);
852 let mut signers = HashSet::new();
853 signers.insert(staker);
854
855 let lockup = Lockup {
856 epoch: 1,
857 unix_timestamp: 1,
858 custodian,
859 };
860 let clock = Clock {
861 epoch: 0,
862 unix_timestamp: 0,
863 ..Clock::default()
864 };
865
866 assert_eq!(
868 authorized.authorize(
869 &signers,
870 &staker,
871 StakeAuthorize::Withdrawer,
872 Some((&Lockup::default(), &clock, None))
873 ),
874 Ok(())
875 );
876
877 assert_eq!(
879 authorized.authorize(
880 &signers,
881 &staker,
882 StakeAuthorize::Withdrawer,
883 Some((&Lockup::default(), &clock, Some(&invalid_custodian)))
884 ),
885 Ok(()) );
887
888 assert_eq!(
890 authorized.authorize(
891 &signers,
892 &staker,
893 StakeAuthorize::Withdrawer,
894 Some((&lockup, &clock, Some(&invalid_custodian)))
895 ),
896 Err(StakeError::CustodianSignatureMissing.into()),
897 );
898
899 signers.insert(invalid_custodian);
900
901 assert_eq!(
903 authorized.authorize(
904 &signers,
905 &staker,
906 StakeAuthorize::Withdrawer,
907 Some((&Lockup::default(), &clock, Some(&invalid_custodian)))
908 ),
909 Ok(()) );
911
912 signers.insert(invalid_custodian);
914 assert_eq!(
915 authorized.authorize(
916 &signers,
917 &staker,
918 StakeAuthorize::Withdrawer,
919 Some((&lockup, &clock, Some(&invalid_custodian)))
920 ),
921 Err(StakeError::LockupInForce.into()), );
923
924 signers.remove(&invalid_custodian);
925
926 assert_eq!(
928 authorized.authorize(
929 &signers,
930 &staker,
931 StakeAuthorize::Withdrawer,
932 Some((&lockup, &clock, None))
933 ),
934 Err(StakeError::CustodianMissing.into()),
935 );
936
937 assert_eq!(
939 authorized.authorize(
940 &signers,
941 &staker,
942 StakeAuthorize::Withdrawer,
943 Some((&lockup, &clock, Some(&custodian)))
944 ),
945 Err(StakeError::CustodianSignatureMissing.into()),
946 );
947
948 signers.insert(custodian);
950 assert_eq!(
951 authorized.authorize(
952 &signers,
953 &staker,
954 StakeAuthorize::Withdrawer,
955 Some((&lockup, &clock, Some(&custodian)))
956 ),
957 Ok(())
958 );
959 }
960
961 #[test]
962 fn test_stake_state_stake_from_fail() {
963 let mut stake_account =
964 AccountSharedData::new(0, StakeStateV2::size_of(), &crate::program::id());
965
966 stake_account
967 .set_state(&StakeStateV2::default())
968 .expect("set_state");
969
970 assert_eq!(stake_from(&stake_account), None);
971 }
972
973 #[test]
974 fn test_stake_is_bootstrap() {
975 assert!(Delegation {
976 activation_epoch: u64::MAX,
977 ..Delegation::default()
978 }
979 .is_bootstrap());
980 assert!(!Delegation {
981 activation_epoch: 0,
982 ..Delegation::default()
983 }
984 .is_bootstrap());
985 }
986
987 #[test]
988 fn test_stake_activating_and_deactivating() {
989 let stake = Delegation {
990 stake: 1_000,
991 activation_epoch: 0, deactivation_epoch: 5,
993 ..Delegation::default()
994 };
995
996 let increment = (1_000_f64 * warmup_cooldown_rate(0, None)) as u64;
998
999 let mut stake_history = StakeHistory::default();
1000 assert_eq!(
1002 stake.stake_activating_and_deactivating(stake.activation_epoch, &stake_history, None),
1003 StakeActivationStatus::with_effective_and_activating(0, stake.stake),
1004 );
1005 for epoch in stake.activation_epoch + 1..stake.deactivation_epoch {
1006 assert_eq!(
1007 stake.stake_activating_and_deactivating(epoch, &stake_history, None),
1008 StakeActivationStatus::with_effective(stake.stake),
1009 );
1010 }
1011 assert_eq!(
1013 stake.stake_activating_and_deactivating(stake.deactivation_epoch, &stake_history, None),
1014 StakeActivationStatus::with_deactivating(stake.stake),
1015 );
1016 assert_eq!(
1018 stake.stake_activating_and_deactivating(
1019 stake.deactivation_epoch + 1,
1020 &stake_history,
1021 None
1022 ),
1023 StakeActivationStatus::default(),
1024 );
1025
1026 stake_history.add(
1027 0u64, StakeHistoryEntry {
1029 effective: 1_000,
1030 ..StakeHistoryEntry::default()
1031 },
1032 );
1033 assert_eq!(
1035 stake.stake_activating_and_deactivating(1, &stake_history, None),
1036 StakeActivationStatus::with_effective_and_activating(0, stake.stake),
1037 );
1038
1039 stake_history.add(
1040 0u64, StakeHistoryEntry {
1042 effective: 1_000,
1043 activating: 1_000,
1044 ..StakeHistoryEntry::default()
1045 },
1046 );
1048 assert_eq!(
1050 stake.stake_activating_and_deactivating(2, &stake_history, None),
1051 StakeActivationStatus::with_effective_and_activating(
1052 increment,
1053 stake.stake - increment
1054 ),
1055 );
1056
1057 let mut stake_history = StakeHistory::default();
1059
1060 stake_history.add(
1061 stake.deactivation_epoch, StakeHistoryEntry {
1063 effective: 1_000,
1064 ..StakeHistoryEntry::default()
1065 },
1066 );
1067 assert_eq!(
1069 stake.stake_activating_and_deactivating(
1070 stake.deactivation_epoch + 1,
1071 &stake_history,
1072 None,
1073 ),
1074 StakeActivationStatus::with_deactivating(stake.stake),
1075 );
1076
1077 stake_history.add(
1079 stake.deactivation_epoch, StakeHistoryEntry {
1081 effective: 1_000,
1082 deactivating: 1_000,
1083 ..StakeHistoryEntry::default()
1084 },
1085 );
1086 assert_eq!(
1088 stake.stake_activating_and_deactivating(
1089 stake.deactivation_epoch + 2,
1090 &stake_history,
1091 None,
1092 ),
1093 StakeActivationStatus::with_deactivating(stake.stake - increment),
1095 );
1096 }
1097
1098 mod same_epoch_activation_then_deactivation {
1099 use super::*;
1100
1101 enum OldDeactivationBehavior {
1102 Stuck,
1103 Slow,
1104 }
1105
1106 fn do_test(
1107 old_behavior: OldDeactivationBehavior,
1108 expected_stakes: &[StakeActivationStatus],
1109 ) {
1110 let cluster_stake = 1_000;
1111 let activating_stake = 10_000;
1112 let some_stake = 700;
1113 let some_epoch = 0;
1114
1115 let stake = Delegation {
1116 stake: some_stake,
1117 activation_epoch: some_epoch,
1118 deactivation_epoch: some_epoch,
1119 ..Delegation::default()
1120 };
1121
1122 let mut stake_history = StakeHistory::default();
1123 let cluster_deactivation_at_stake_modified_epoch = match old_behavior {
1124 OldDeactivationBehavior::Stuck => 0,
1125 OldDeactivationBehavior::Slow => 1000,
1126 };
1127
1128 let stake_history_entries = vec![
1129 (
1130 cluster_stake,
1131 activating_stake,
1132 cluster_deactivation_at_stake_modified_epoch,
1133 ),
1134 (cluster_stake, activating_stake, 1000),
1135 (cluster_stake, activating_stake, 1000),
1136 (cluster_stake, activating_stake, 100),
1137 (cluster_stake, activating_stake, 100),
1138 (cluster_stake, activating_stake, 100),
1139 (cluster_stake, activating_stake, 100),
1140 ];
1141
1142 for (epoch, (effective, activating, deactivating)) in
1143 stake_history_entries.into_iter().enumerate()
1144 {
1145 stake_history.add(
1146 epoch as Epoch,
1147 StakeHistoryEntry {
1148 effective,
1149 activating,
1150 deactivating,
1151 },
1152 );
1153 }
1154
1155 assert_eq!(
1156 expected_stakes,
1157 (0..expected_stakes.len())
1158 .map(|epoch| stake.stake_activating_and_deactivating(
1159 epoch as u64,
1160 &stake_history,
1161 None,
1162 ))
1163 .collect::<Vec<_>>()
1164 );
1165 }
1166
1167 #[test]
1168 fn test_new_behavior_previously_slow() {
1169 do_test(
1173 OldDeactivationBehavior::Slow,
1174 &[
1175 StakeActivationStatus::default(),
1176 StakeActivationStatus::default(),
1177 StakeActivationStatus::default(),
1178 StakeActivationStatus::default(),
1179 StakeActivationStatus::default(),
1180 StakeActivationStatus::default(),
1181 StakeActivationStatus::default(),
1182 ],
1183 );
1184 }
1185
1186 #[test]
1187 fn test_new_behavior_previously_stuck() {
1188 do_test(
1192 OldDeactivationBehavior::Stuck,
1193 &[
1194 StakeActivationStatus::default(),
1195 StakeActivationStatus::default(),
1196 StakeActivationStatus::default(),
1197 StakeActivationStatus::default(),
1198 StakeActivationStatus::default(),
1199 StakeActivationStatus::default(),
1200 StakeActivationStatus::default(),
1201 ],
1202 );
1203 }
1204 }
1205
1206 #[test]
1207 fn test_inflation_and_slashing_with_activating_and_deactivating_stake() {
1208 let (delegated_stake, mut stake, stake_history) = {
1210 let cluster_stake = 1_000;
1211 let delegated_stake = 700;
1212
1213 let stake = Delegation {
1214 stake: delegated_stake,
1215 activation_epoch: 0,
1216 deactivation_epoch: 4,
1217 ..Delegation::default()
1218 };
1219
1220 let mut stake_history = StakeHistory::default();
1221 stake_history.add(
1222 0,
1223 StakeHistoryEntry {
1224 effective: cluster_stake,
1225 activating: delegated_stake,
1226 ..StakeHistoryEntry::default()
1227 },
1228 );
1229 let newly_effective_at_epoch1 = (cluster_stake as f64 * 0.25) as u64;
1230 assert_eq!(newly_effective_at_epoch1, 250);
1231 stake_history.add(
1232 1,
1233 StakeHistoryEntry {
1234 effective: cluster_stake + newly_effective_at_epoch1,
1235 activating: delegated_stake - newly_effective_at_epoch1,
1236 ..StakeHistoryEntry::default()
1237 },
1238 );
1239 let newly_effective_at_epoch2 =
1240 ((cluster_stake + newly_effective_at_epoch1) as f64 * 0.25) as u64;
1241 assert_eq!(newly_effective_at_epoch2, 312);
1242 stake_history.add(
1243 2,
1244 StakeHistoryEntry {
1245 effective: cluster_stake
1246 + newly_effective_at_epoch1
1247 + newly_effective_at_epoch2,
1248 activating: delegated_stake
1249 - newly_effective_at_epoch1
1250 - newly_effective_at_epoch2,
1251 ..StakeHistoryEntry::default()
1252 },
1253 );
1254 stake_history.add(
1255 3,
1256 StakeHistoryEntry {
1257 effective: cluster_stake + delegated_stake,
1258 ..StakeHistoryEntry::default()
1259 },
1260 );
1261 stake_history.add(
1262 4,
1263 StakeHistoryEntry {
1264 effective: cluster_stake + delegated_stake,
1265 deactivating: delegated_stake,
1266 ..StakeHistoryEntry::default()
1267 },
1268 );
1269 let newly_not_effective_stake_at_epoch5 =
1270 ((cluster_stake + delegated_stake) as f64 * 0.25) as u64;
1271 assert_eq!(newly_not_effective_stake_at_epoch5, 425);
1272 stake_history.add(
1273 5,
1274 StakeHistoryEntry {
1275 effective: cluster_stake + delegated_stake
1276 - newly_not_effective_stake_at_epoch5,
1277 deactivating: delegated_stake - newly_not_effective_stake_at_epoch5,
1278 ..StakeHistoryEntry::default()
1279 },
1280 );
1281
1282 (delegated_stake, stake, stake_history)
1283 };
1284
1285 let calculate_each_staking_status = |stake: &Delegation, epoch_count: usize| -> Vec<_> {
1287 (0..epoch_count)
1288 .map(|epoch| {
1289 stake.stake_activating_and_deactivating(epoch as u64, &stake_history, None)
1290 })
1291 .collect::<Vec<_>>()
1292 };
1293 let adjust_staking_status = |rate: f64, status: &[StakeActivationStatus]| {
1294 status
1295 .iter()
1296 .map(|entry| StakeActivationStatus {
1297 effective: (entry.effective as f64 * rate) as u64,
1298 activating: (entry.activating as f64 * rate) as u64,
1299 deactivating: (entry.deactivating as f64 * rate) as u64,
1300 })
1301 .collect::<Vec<_>>()
1302 };
1303
1304 let expected_staking_status_transition = vec![
1305 StakeActivationStatus::with_effective_and_activating(0, 700),
1306 StakeActivationStatus::with_effective_and_activating(250, 450),
1307 StakeActivationStatus::with_effective_and_activating(562, 138),
1308 StakeActivationStatus::with_effective(700),
1309 StakeActivationStatus::with_deactivating(700),
1310 StakeActivationStatus::with_deactivating(275),
1311 StakeActivationStatus::default(),
1312 ];
1313 let expected_staking_status_transition_base = vec![
1314 StakeActivationStatus::with_effective_and_activating(0, 700),
1315 StakeActivationStatus::with_effective_and_activating(250, 450),
1316 StakeActivationStatus::with_effective_and_activating(562, 138 + 1), StakeActivationStatus::with_effective(700),
1318 StakeActivationStatus::with_deactivating(700),
1319 StakeActivationStatus::with_deactivating(275 + 1), StakeActivationStatus::default(),
1321 ];
1322
1323 assert_eq!(
1325 expected_staking_status_transition,
1326 calculate_each_staking_status(&stake, expected_staking_status_transition.len())
1327 );
1328
1329 let rate = 1.10;
1331 stake.stake = (delegated_stake as f64 * rate) as u64;
1332 let expected_staking_status_transition =
1333 adjust_staking_status(rate, &expected_staking_status_transition_base);
1334
1335 assert_eq!(
1336 expected_staking_status_transition,
1337 calculate_each_staking_status(&stake, expected_staking_status_transition_base.len()),
1338 );
1339
1340 let rate = 0.5;
1342 stake.stake = (delegated_stake as f64 * rate) as u64;
1343 let expected_staking_status_transition =
1344 adjust_staking_status(rate, &expected_staking_status_transition_base);
1345
1346 assert_eq!(
1347 expected_staking_status_transition,
1348 calculate_each_staking_status(&stake, expected_staking_status_transition_base.len()),
1349 );
1350 }
1351
1352 #[test]
1353 fn test_stop_activating_after_deactivation() {
1354 let stake = Delegation {
1355 stake: 1_000,
1356 activation_epoch: 0,
1357 deactivation_epoch: 3,
1358 ..Delegation::default()
1359 };
1360
1361 let base_stake = 1_000;
1362 let mut stake_history = StakeHistory::default();
1363 let mut effective = base_stake;
1364 let other_activation = 100;
1365 let mut other_activations = vec![0];
1366
1367 for epoch in 0..=stake.deactivation_epoch + 1 {
1371 let (activating, deactivating) = if epoch < stake.deactivation_epoch {
1372 (stake.stake + base_stake - effective, 0)
1373 } else {
1374 let other_activation_sum: u64 = other_activations.iter().sum();
1375 let deactivating = effective - base_stake - other_activation_sum;
1376 (other_activation, deactivating)
1377 };
1378
1379 stake_history.add(
1380 epoch,
1381 StakeHistoryEntry {
1382 effective,
1383 activating,
1384 deactivating,
1385 },
1386 );
1387
1388 let effective_rate_limited = (effective as f64 * warmup_cooldown_rate(0, None)) as u64;
1389 if epoch < stake.deactivation_epoch {
1390 effective += effective_rate_limited.min(activating);
1391 other_activations.push(0);
1392 } else {
1393 effective -= effective_rate_limited.min(deactivating);
1394 effective += other_activation;
1395 other_activations.push(other_activation);
1396 }
1397 }
1398
1399 for epoch in 0..=stake.deactivation_epoch + 1 {
1400 let history = stake_history.get(epoch).unwrap();
1401 let other_activations: u64 = other_activations[..=epoch as usize].iter().sum();
1402 let expected_stake = history.effective - base_stake - other_activations;
1403 let (expected_activating, expected_deactivating) = if epoch < stake.deactivation_epoch {
1404 (history.activating, 0)
1405 } else {
1406 (0, history.deactivating)
1407 };
1408 assert_eq!(
1409 stake.stake_activating_and_deactivating(epoch, &stake_history, None),
1410 StakeActivationStatus {
1411 effective: expected_stake,
1412 activating: expected_activating,
1413 deactivating: expected_deactivating,
1414 },
1415 );
1416 }
1417 }
1418
1419 #[test]
1420 fn test_stake_warmup_cooldown_sub_integer_moves() {
1421 let delegations = [Delegation {
1422 stake: 2,
1423 activation_epoch: 0, deactivation_epoch: 5,
1425 ..Delegation::default()
1426 }];
1427 let epochs = 7;
1429 let bootstrap = (warmup_cooldown_rate(0, None) * 100.0 / 2.0) as u64;
1432 let stake_history =
1433 create_stake_history_from_delegations(Some(bootstrap), 0..epochs, &delegations, None);
1434 let mut max_stake = 0;
1435 let mut min_stake = 2;
1436
1437 for epoch in 0..epochs {
1438 let stake = delegations
1439 .iter()
1440 .map(|delegation| delegation.stake(epoch, &stake_history, None))
1441 .sum::<u64>();
1442 max_stake = max_stake.max(stake);
1443 min_stake = min_stake.min(stake);
1444 }
1445 assert_eq!(max_stake, 2);
1446 assert_eq!(min_stake, 0);
1447 }
1448
1449 #[test_case(None ; "old rate")]
1450 #[test_case(Some(1) ; "new rate activated in epoch 1")]
1451 #[test_case(Some(10) ; "new rate activated in epoch 10")]
1452 #[test_case(Some(30) ; "new rate activated in epoch 30")]
1453 #[test_case(Some(50) ; "new rate activated in epoch 50")]
1454 #[test_case(Some(60) ; "new rate activated in epoch 60")]
1455 fn test_stake_warmup_cooldown(new_rate_activation_epoch: Option<Epoch>) {
1456 let delegations = [
1457 Delegation {
1458 stake: 1_000,
1460 activation_epoch: u64::MAX,
1461 ..Delegation::default()
1462 },
1463 Delegation {
1464 stake: 1_000,
1465 activation_epoch: 0,
1466 deactivation_epoch: 9,
1467 ..Delegation::default()
1468 },
1469 Delegation {
1470 stake: 1_000,
1471 activation_epoch: 1,
1472 deactivation_epoch: 6,
1473 ..Delegation::default()
1474 },
1475 Delegation {
1476 stake: 1_000,
1477 activation_epoch: 2,
1478 deactivation_epoch: 5,
1479 ..Delegation::default()
1480 },
1481 Delegation {
1482 stake: 1_000,
1483 activation_epoch: 2,
1484 deactivation_epoch: 4,
1485 ..Delegation::default()
1486 },
1487 Delegation {
1488 stake: 1_000,
1489 activation_epoch: 4,
1490 deactivation_epoch: 4,
1491 ..Delegation::default()
1492 },
1493 ];
1494 let epochs = 60;
1499
1500 let stake_history = create_stake_history_from_delegations(
1501 None,
1502 0..epochs,
1503 &delegations,
1504 new_rate_activation_epoch,
1505 );
1506
1507 let mut prev_total_effective_stake = delegations
1508 .iter()
1509 .map(|delegation| delegation.stake(0, &stake_history, new_rate_activation_epoch))
1510 .sum::<u64>();
1511
1512 for epoch in 1..epochs {
1515 let total_effective_stake = delegations
1516 .iter()
1517 .map(|delegation| {
1518 delegation.stake(epoch, &stake_history, new_rate_activation_epoch)
1519 })
1520 .sum::<u64>();
1521
1522 let delta = total_effective_stake.abs_diff(prev_total_effective_stake);
1523
1524 assert!(
1530 delta
1531 <= ((prev_total_effective_stake as f64
1532 * warmup_cooldown_rate(epoch, new_rate_activation_epoch))
1533 as u64)
1534 .max(1)
1535 );
1536
1537 prev_total_effective_stake = total_effective_stake;
1538 }
1539 }
1540
1541 #[test]
1542 fn test_lockup_is_expired() {
1543 let custodian = Pubkey::new_unique();
1544 let lockup = Lockup {
1545 epoch: 1,
1546 unix_timestamp: 1,
1547 custodian,
1548 };
1549 assert!(lockup.is_in_force(
1551 &Clock {
1552 epoch: 0,
1553 unix_timestamp: 0,
1554 ..Clock::default()
1555 },
1556 None
1557 ));
1558 assert!(lockup.is_in_force(
1560 &Clock {
1561 epoch: 2,
1562 unix_timestamp: 0,
1563 ..Clock::default()
1564 },
1565 None
1566 ));
1567 assert!(lockup.is_in_force(
1569 &Clock {
1570 epoch: 0,
1571 unix_timestamp: 2,
1572 ..Clock::default()
1573 },
1574 None
1575 ));
1576 assert!(!lockup.is_in_force(
1578 &Clock {
1579 epoch: 1,
1580 unix_timestamp: 1,
1581 ..Clock::default()
1582 },
1583 None
1584 ));
1585 assert!(!lockup.is_in_force(
1587 &Clock {
1588 epoch: 0,
1589 unix_timestamp: 0,
1590 ..Clock::default()
1591 },
1592 Some(&custodian),
1593 ));
1594 }
1595
1596 fn check_borsh_deserialization(stake: StakeStateV2) {
1597 let serialized = serialize(&stake).unwrap();
1598 let deserialized = StakeStateV2::try_from_slice(&serialized).unwrap();
1599 assert_eq!(stake, deserialized);
1600 }
1601
1602 fn check_borsh_serialization(stake: StakeStateV2) {
1603 let bincode_serialized = serialize(&stake).unwrap();
1604 let borsh_serialized = borsh::to_vec(&stake).unwrap();
1605 assert_eq!(bincode_serialized, borsh_serialized);
1606 }
1607
1608 #[test]
1609 fn test_size_of() {
1610 assert_eq!(StakeStateV2::size_of(), std::mem::size_of::<StakeStateV2>());
1611 }
1612
1613 #[test]
1614 fn bincode_vs_borsh_deserialization() {
1615 check_borsh_deserialization(StakeStateV2::Uninitialized);
1616 check_borsh_deserialization(StakeStateV2::RewardsPool);
1617 check_borsh_deserialization(StakeStateV2::Initialized(Meta {
1618 rent_exempt_reserve: u64::MAX,
1619 authorized: Authorized {
1620 staker: Pubkey::new_unique(),
1621 withdrawer: Pubkey::new_unique(),
1622 },
1623 lockup: Lockup::default(),
1624 }));
1625 check_borsh_deserialization(StakeStateV2::Stake(
1626 Meta {
1627 rent_exempt_reserve: 1,
1628 authorized: Authorized {
1629 staker: Pubkey::new_unique(),
1630 withdrawer: Pubkey::new_unique(),
1631 },
1632 lockup: Lockup::default(),
1633 },
1634 Stake {
1635 delegation: Delegation {
1636 voter_pubkey: Pubkey::new_unique(),
1637 stake: u64::MAX,
1638 activation_epoch: Epoch::MAX,
1639 deactivation_epoch: Epoch::MAX,
1640 ..Delegation::default()
1641 },
1642 credits_observed: 1,
1643 },
1644 StakeFlags::empty(),
1645 ));
1646 }
1647
1648 #[test]
1649 fn bincode_vs_borsh_serialization() {
1650 check_borsh_serialization(StakeStateV2::Uninitialized);
1651 check_borsh_serialization(StakeStateV2::RewardsPool);
1652 check_borsh_serialization(StakeStateV2::Initialized(Meta {
1653 rent_exempt_reserve: u64::MAX,
1654 authorized: Authorized {
1655 staker: Pubkey::new_unique(),
1656 withdrawer: Pubkey::new_unique(),
1657 },
1658 lockup: Lockup::default(),
1659 }));
1660 #[allow(deprecated)]
1661 check_borsh_serialization(StakeStateV2::Stake(
1662 Meta {
1663 rent_exempt_reserve: 1,
1664 authorized: Authorized {
1665 staker: Pubkey::new_unique(),
1666 withdrawer: Pubkey::new_unique(),
1667 },
1668 lockup: Lockup::default(),
1669 },
1670 Stake {
1671 delegation: Delegation {
1672 voter_pubkey: Pubkey::new_unique(),
1673 stake: u64::MAX,
1674 activation_epoch: Epoch::MAX,
1675 deactivation_epoch: Epoch::MAX,
1676 ..Default::default()
1677 },
1678 credits_observed: 1,
1679 },
1680 StakeFlags::MUST_FULLY_ACTIVATE_BEFORE_DEACTIVATION_IS_PERMITTED,
1681 ));
1682 }
1683
1684 #[test]
1685 fn borsh_deserialization_live_data() {
1686 let data = [
1687 1, 0, 0, 0, 128, 213, 34, 0, 0, 0, 0, 0, 133, 0, 79, 231, 141, 29, 73, 61, 232, 35,
1688 119, 124, 168, 12, 120, 216, 195, 29, 12, 166, 139, 28, 36, 182, 186, 154, 246, 149,
1689 224, 109, 52, 100, 133, 0, 79, 231, 141, 29, 73, 61, 232, 35, 119, 124, 168, 12, 120,
1690 216, 195, 29, 12, 166, 139, 28, 36, 182, 186, 154, 246, 149, 224, 109, 52, 100, 0, 0,
1691 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1692 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1693 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1694 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1695 0, 0, 0, 0, 0, 0,
1696 ];
1697 let deserialized = try_from_slice_unchecked::<StakeStateV2>(&data).unwrap();
1700 assert_matches!(
1701 deserialized,
1702 StakeStateV2::Initialized(Meta {
1703 rent_exempt_reserve: 2282880,
1704 ..
1705 })
1706 );
1707 }
1708
1709 #[test]
1710 fn stake_flag_member_offset() {
1711 const FLAG_OFFSET: usize = 196;
1712 let check_flag = |flag, expected| {
1713 let stake = StakeStateV2::Stake(
1714 Meta {
1715 rent_exempt_reserve: 1,
1716 authorized: Authorized {
1717 staker: Pubkey::new_unique(),
1718 withdrawer: Pubkey::new_unique(),
1719 },
1720 lockup: Lockup::default(),
1721 },
1722 Stake {
1723 delegation: Delegation {
1724 voter_pubkey: Pubkey::new_unique(),
1725 stake: u64::MAX,
1726 activation_epoch: Epoch::MAX,
1727 deactivation_epoch: Epoch::MAX,
1728 warmup_cooldown_rate: f64::MAX,
1729 },
1730 credits_observed: 1,
1731 },
1732 flag,
1733 );
1734
1735 let bincode_serialized = serialize(&stake).unwrap();
1736 let borsh_serialized = borsh::to_vec(&stake).unwrap();
1737
1738 assert_eq!(bincode_serialized[FLAG_OFFSET], expected);
1739 assert_eq!(borsh_serialized[FLAG_OFFSET], expected);
1740 };
1741 #[allow(deprecated)]
1742 check_flag(
1743 StakeFlags::MUST_FULLY_ACTIVATE_BEFORE_DEACTIVATION_IS_PERMITTED,
1744 1,
1745 );
1746 check_flag(StakeFlags::empty(), 0);
1747 }
1748
1749 mod deprecated {
1750 use super::*;
1751
1752 fn check_borsh_deserialization(stake: StakeState) {
1753 let serialized = serialize(&stake).unwrap();
1754 let deserialized = StakeState::try_from_slice(&serialized).unwrap();
1755 assert_eq!(stake, deserialized);
1756 }
1757
1758 fn check_borsh_serialization(stake: StakeState) {
1759 let bincode_serialized = serialize(&stake).unwrap();
1760 let borsh_serialized = borsh::to_vec(&stake).unwrap();
1761 assert_eq!(bincode_serialized, borsh_serialized);
1762 }
1763
1764 #[test]
1765 fn test_size_of() {
1766 assert_eq!(StakeState::size_of(), std::mem::size_of::<StakeState>());
1767 }
1768
1769 #[test]
1770 fn bincode_vs_borsh_deserialization() {
1771 check_borsh_deserialization(StakeState::Uninitialized);
1772 check_borsh_deserialization(StakeState::RewardsPool);
1773 check_borsh_deserialization(StakeState::Initialized(Meta {
1774 rent_exempt_reserve: u64::MAX,
1775 authorized: Authorized {
1776 staker: Pubkey::new_unique(),
1777 withdrawer: Pubkey::new_unique(),
1778 },
1779 lockup: Lockup::default(),
1780 }));
1781 check_borsh_deserialization(StakeState::Stake(
1782 Meta {
1783 rent_exempt_reserve: 1,
1784 authorized: Authorized {
1785 staker: Pubkey::new_unique(),
1786 withdrawer: Pubkey::new_unique(),
1787 },
1788 lockup: Lockup::default(),
1789 },
1790 Stake {
1791 delegation: Delegation {
1792 voter_pubkey: Pubkey::new_unique(),
1793 stake: u64::MAX,
1794 activation_epoch: Epoch::MAX,
1795 deactivation_epoch: Epoch::MAX,
1796 warmup_cooldown_rate: f64::MAX,
1797 },
1798 credits_observed: 1,
1799 },
1800 ));
1801 }
1802
1803 #[test]
1804 fn bincode_vs_borsh_serialization() {
1805 check_borsh_serialization(StakeState::Uninitialized);
1806 check_borsh_serialization(StakeState::RewardsPool);
1807 check_borsh_serialization(StakeState::Initialized(Meta {
1808 rent_exempt_reserve: u64::MAX,
1809 authorized: Authorized {
1810 staker: Pubkey::new_unique(),
1811 withdrawer: Pubkey::new_unique(),
1812 },
1813 lockup: Lockup::default(),
1814 }));
1815 check_borsh_serialization(StakeState::Stake(
1816 Meta {
1817 rent_exempt_reserve: 1,
1818 authorized: Authorized {
1819 staker: Pubkey::new_unique(),
1820 withdrawer: Pubkey::new_unique(),
1821 },
1822 lockup: Lockup::default(),
1823 },
1824 Stake {
1825 delegation: Delegation {
1826 voter_pubkey: Pubkey::new_unique(),
1827 stake: u64::MAX,
1828 activation_epoch: Epoch::MAX,
1829 deactivation_epoch: Epoch::MAX,
1830 warmup_cooldown_rate: f64::MAX,
1831 },
1832 credits_observed: 1,
1833 },
1834 ));
1835 }
1836
1837 #[test]
1838 fn borsh_deserialization_live_data() {
1839 let data = [
1840 1, 0, 0, 0, 128, 213, 34, 0, 0, 0, 0, 0, 133, 0, 79, 231, 141, 29, 73, 61, 232, 35,
1841 119, 124, 168, 12, 120, 216, 195, 29, 12, 166, 139, 28, 36, 182, 186, 154, 246,
1842 149, 224, 109, 52, 100, 133, 0, 79, 231, 141, 29, 73, 61, 232, 35, 119, 124, 168,
1843 12, 120, 216, 195, 29, 12, 166, 139, 28, 36, 182, 186, 154, 246, 149, 224, 109, 52,
1844 100, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1845 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1846 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1847 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1848 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1849 ];
1850 let deserialized = try_from_slice_unchecked::<StakeState>(&data).unwrap();
1853 assert_matches!(
1854 deserialized,
1855 StakeState::Initialized(Meta {
1856 rent_exempt_reserve: 2282880,
1857 ..
1858 })
1859 );
1860 }
1861 }
1862}