1#![allow(clippy::integer_arithmetic)]
2use {
3 crate::{
4 clock::{Clock, Epoch, UnixTimestamp},
5 instruction::InstructionError,
6 pubkey::Pubkey,
7 stake::{
8 config::Config,
9 instruction::{LockupArgs, StakeError},
10 },
11 stake_history::{StakeHistory, StakeHistoryEntry},
12 },
13 borsh::{maybestd::io, BorshDeserialize, BorshSchema, BorshSerialize},
14 std::collections::HashSet,
15};
16
17pub type StakeActivationStatus = StakeHistoryEntry;
18
19#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, Copy, AbiExample)]
20#[allow(clippy::large_enum_variant)]
21pub enum StakeState {
22 Uninitialized,
23 Initialized(Meta),
24 Stake(Meta, Stake),
25 RewardsPool,
26}
27
28impl BorshDeserialize for StakeState {
29 fn deserialize(buf: &mut &[u8]) -> io::Result<Self> {
30 let enum_value: u32 = BorshDeserialize::deserialize(buf)?;
31 match enum_value {
32 0 => Ok(StakeState::Uninitialized),
33 1 => {
34 let meta: Meta = BorshDeserialize::deserialize(buf)?;
35 Ok(StakeState::Initialized(meta))
36 }
37 2 => {
38 let meta: Meta = BorshDeserialize::deserialize(buf)?;
39 let stake: Stake = BorshDeserialize::deserialize(buf)?;
40 Ok(StakeState::Stake(meta, stake))
41 }
42 3 => Ok(StakeState::RewardsPool),
43 _ => Err(io::Error::new(
44 io::ErrorKind::InvalidData,
45 "Invalid enum value",
46 )),
47 }
48 }
49}
50
51impl BorshSerialize for StakeState {
52 fn serialize<W: io::Write>(&self, writer: &mut W) -> io::Result<()> {
53 match self {
54 StakeState::Uninitialized => writer.write_all(&0u32.to_le_bytes()),
55 StakeState::Initialized(meta) => {
56 writer.write_all(&1u32.to_le_bytes())?;
57 meta.serialize(writer)
58 }
59 StakeState::Stake(meta, stake) => {
60 writer.write_all(&2u32.to_le_bytes())?;
61 meta.serialize(writer)?;
62 stake.serialize(writer)
63 }
64 StakeState::RewardsPool => writer.write_all(&3u32.to_le_bytes()),
65 }
66 }
67}
68
69impl Default for StakeState {
70 fn default() -> Self {
71 StakeState::Uninitialized
72 }
73}
74
75impl StakeState {
76 pub const fn size_of() -> usize {
78 200 }
80
81 pub fn stake(&self) -> Option<Stake> {
82 match self {
83 StakeState::Stake(_meta, stake) => Some(*stake),
84 _ => None,
85 }
86 }
87
88 pub fn delegation(&self) -> Option<Delegation> {
89 match self {
90 StakeState::Stake(_meta, stake) => Some(stake.delegation),
91 _ => None,
92 }
93 }
94
95 pub fn authorized(&self) -> Option<Authorized> {
96 match self {
97 StakeState::Stake(meta, _stake) => Some(meta.authorized),
98 StakeState::Initialized(meta) => Some(meta.authorized),
99 _ => None,
100 }
101 }
102
103 pub fn lockup(&self) -> Option<Lockup> {
104 self.meta().map(|meta| meta.lockup)
105 }
106
107 pub fn meta(&self) -> Option<Meta> {
108 match self {
109 StakeState::Stake(meta, _stake) => Some(*meta),
110 StakeState::Initialized(meta) => Some(*meta),
111 _ => None,
112 }
113 }
114}
115
116#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone, Copy, AbiExample)]
117pub enum StakeAuthorize {
118 Staker,
119 Withdrawer,
120}
121
122#[derive(
123 Default,
124 Debug,
125 Serialize,
126 Deserialize,
127 PartialEq,
128 Eq,
129 Clone,
130 Copy,
131 AbiExample,
132 BorshDeserialize,
133 BorshSchema,
134 BorshSerialize,
135)]
136pub struct Lockup {
137 pub unix_timestamp: UnixTimestamp,
140 pub epoch: Epoch,
143 pub custodian: Pubkey,
146}
147
148impl Lockup {
149 pub fn is_in_force(&self, clock: &Clock, custodian: Option<&Pubkey>) -> bool {
150 if custodian == Some(&self.custodian) {
151 return false;
152 }
153 self.unix_timestamp > clock.unix_timestamp || self.epoch > clock.epoch
154 }
155}
156
157#[derive(
158 Default,
159 Debug,
160 Serialize,
161 Deserialize,
162 PartialEq,
163 Eq,
164 Clone,
165 Copy,
166 AbiExample,
167 BorshDeserialize,
168 BorshSchema,
169 BorshSerialize,
170)]
171pub struct Authorized {
172 pub staker: Pubkey,
173 pub withdrawer: Pubkey,
174}
175
176impl Authorized {
177 pub fn auto(authorized: &Pubkey) -> Self {
178 Self {
179 staker: *authorized,
180 withdrawer: *authorized,
181 }
182 }
183 pub fn check(
184 &self,
185 signers: &HashSet<Pubkey>,
186 stake_authorize: StakeAuthorize,
187 ) -> Result<(), InstructionError> {
188 match stake_authorize {
189 StakeAuthorize::Staker if signers.contains(&self.staker) => Ok(()),
190 StakeAuthorize::Withdrawer if signers.contains(&self.withdrawer) => Ok(()),
191 _ => Err(InstructionError::MissingRequiredSignature),
192 }
193 }
194
195 pub fn authorize(
196 &mut self,
197 signers: &HashSet<Pubkey>,
198 new_authorized: &Pubkey,
199 stake_authorize: StakeAuthorize,
200 lockup_custodian_args: Option<(&Lockup, &Clock, Option<&Pubkey>)>,
201 ) -> Result<(), InstructionError> {
202 match stake_authorize {
203 StakeAuthorize::Staker => {
204 if !signers.contains(&self.staker) && !signers.contains(&self.withdrawer) {
206 return Err(InstructionError::MissingRequiredSignature);
207 }
208 self.staker = *new_authorized
209 }
210 StakeAuthorize::Withdrawer => {
211 if let Some((lockup, clock, custodian)) = lockup_custodian_args {
212 if lockup.is_in_force(clock, None) {
213 match custodian {
214 None => {
215 return Err(StakeError::CustodianMissing.into());
216 }
217 Some(custodian) => {
218 if !signers.contains(custodian) {
219 return Err(StakeError::CustodianSignatureMissing.into());
220 }
221
222 if lockup.is_in_force(clock, Some(custodian)) {
223 return Err(StakeError::LockupInForce.into());
224 }
225 }
226 }
227 }
228 }
229 self.check(signers, stake_authorize)?;
230 self.withdrawer = *new_authorized
231 }
232 }
233 Ok(())
234 }
235}
236
237#[derive(
238 Default,
239 Debug,
240 Serialize,
241 Deserialize,
242 PartialEq,
243 Eq,
244 Clone,
245 Copy,
246 AbiExample,
247 BorshDeserialize,
248 BorshSchema,
249 BorshSerialize,
250)]
251pub struct Meta {
252 pub rent_exempt_reserve: u64,
253 pub authorized: Authorized,
254 pub lockup: Lockup,
255}
256
257impl Meta {
258 pub fn set_lockup(
259 &mut self,
260 lockup: &LockupArgs,
261 signers: &HashSet<Pubkey>,
262 clock: &Clock,
263 ) -> Result<(), InstructionError> {
264 if self.lockup.is_in_force(clock, None) {
268 if !signers.contains(&self.lockup.custodian) {
269 return Err(InstructionError::MissingRequiredSignature);
270 }
271 } else if !signers.contains(&self.authorized.withdrawer) {
272 return Err(InstructionError::MissingRequiredSignature);
273 }
274 if let Some(unix_timestamp) = lockup.unix_timestamp {
275 self.lockup.unix_timestamp = unix_timestamp;
276 }
277 if let Some(epoch) = lockup.epoch {
278 self.lockup.epoch = epoch;
279 }
280 if let Some(custodian) = lockup.custodian {
281 self.lockup.custodian = custodian;
282 }
283 Ok(())
284 }
285
286 pub fn auto(authorized: &Pubkey) -> Self {
287 Self {
288 authorized: Authorized::auto(authorized),
289 ..Meta::default()
290 }
291 }
292}
293
294#[derive(
295 Debug,
296 Serialize,
297 Deserialize,
298 PartialEq,
299 Clone,
300 Copy,
301 AbiExample,
302 BorshDeserialize,
303 BorshSchema,
304 BorshSerialize,
305)]
306pub struct Delegation {
307 pub voter_pubkey: Pubkey,
309 pub stake: u64,
311 pub activation_epoch: Epoch,
313 pub deactivation_epoch: Epoch,
315 pub warmup_cooldown_rate: f64,
317}
318
319impl Default for Delegation {
320 fn default() -> Self {
321 Self {
322 voter_pubkey: Pubkey::default(),
323 stake: 0,
324 activation_epoch: 0,
325 deactivation_epoch: std::u64::MAX,
326 warmup_cooldown_rate: Config::default().warmup_cooldown_rate,
327 }
328 }
329}
330
331impl Delegation {
332 pub fn new(
333 voter_pubkey: &Pubkey,
334 stake: u64,
335 activation_epoch: Epoch,
336 warmup_cooldown_rate: f64,
337 ) -> Self {
338 Self {
339 voter_pubkey: *voter_pubkey,
340 stake,
341 activation_epoch,
342 warmup_cooldown_rate,
343 ..Delegation::default()
344 }
345 }
346 pub fn is_bootstrap(&self) -> bool {
347 self.activation_epoch == std::u64::MAX
348 }
349
350 pub fn stake(&self, epoch: Epoch, history: Option<&StakeHistory>) -> u64 {
351 self.stake_activating_and_deactivating(epoch, history)
352 .effective
353 }
354
355 #[allow(clippy::comparison_chain)]
356 pub fn stake_activating_and_deactivating(
357 &self,
358 target_epoch: Epoch,
359 history: Option<&StakeHistory>,
360 ) -> StakeActivationStatus {
361 let (effective_stake, activating_stake) = self.stake_and_activating(target_epoch, history);
363
364 if target_epoch < self.deactivation_epoch {
366 if activating_stake == 0 {
368 StakeActivationStatus::with_effective(effective_stake)
369 } else {
370 StakeActivationStatus::with_effective_and_activating(
371 effective_stake,
372 activating_stake,
373 )
374 }
375 } else if target_epoch == self.deactivation_epoch {
376 StakeActivationStatus::with_deactivating(effective_stake)
378 } else if let Some((history, mut prev_epoch, mut prev_cluster_stake)) =
379 history.and_then(|history| {
380 history
381 .get(self.deactivation_epoch)
382 .map(|cluster_stake_at_deactivation_epoch| {
383 (
384 history,
385 self.deactivation_epoch,
386 cluster_stake_at_deactivation_epoch,
387 )
388 })
389 })
390 {
391 let mut current_epoch;
396 let mut current_effective_stake = effective_stake;
397 loop {
398 current_epoch = prev_epoch + 1;
399 if prev_cluster_stake.deactivating == 0 {
402 break;
403 }
404
405 let weight =
408 current_effective_stake as f64 / prev_cluster_stake.deactivating as f64;
409
410 let newly_not_effective_cluster_stake =
412 prev_cluster_stake.effective as f64 * self.warmup_cooldown_rate;
413 let newly_not_effective_stake =
414 ((weight * newly_not_effective_cluster_stake) as u64).max(1);
415
416 current_effective_stake =
417 current_effective_stake.saturating_sub(newly_not_effective_stake);
418 if current_effective_stake == 0 {
419 break;
420 }
421
422 if current_epoch >= target_epoch {
423 break;
424 }
425 if let Some(current_cluster_stake) = history.get(current_epoch) {
426 prev_epoch = current_epoch;
427 prev_cluster_stake = current_cluster_stake;
428 } else {
429 break;
430 }
431 }
432
433 StakeActivationStatus::with_deactivating(current_effective_stake)
435 } else {
436 StakeActivationStatus::default()
438 }
439 }
440
441 fn stake_and_activating(
443 &self,
444 target_epoch: Epoch,
445 history: Option<&StakeHistory>,
446 ) -> (u64, u64) {
447 let delegated_stake = self.stake;
448
449 if self.is_bootstrap() {
450 (delegated_stake, 0)
452 } else if self.activation_epoch == self.deactivation_epoch {
453 (0, 0)
456 } else if target_epoch == self.activation_epoch {
457 (0, delegated_stake)
459 } else if target_epoch < self.activation_epoch {
460 (0, 0)
462 } else if let Some((history, mut prev_epoch, mut prev_cluster_stake)) =
463 history.and_then(|history| {
464 history
465 .get(self.activation_epoch)
466 .map(|cluster_stake_at_activation_epoch| {
467 (
468 history,
469 self.activation_epoch,
470 cluster_stake_at_activation_epoch,
471 )
472 })
473 })
474 {
475 let mut current_epoch;
480 let mut current_effective_stake = 0;
481 loop {
482 current_epoch = prev_epoch + 1;
483 if prev_cluster_stake.activating == 0 {
486 break;
487 }
488
489 let remaining_activating_stake = delegated_stake - current_effective_stake;
492 let weight =
493 remaining_activating_stake as f64 / prev_cluster_stake.activating as f64;
494
495 let newly_effective_cluster_stake =
497 prev_cluster_stake.effective as f64 * self.warmup_cooldown_rate;
498 let newly_effective_stake =
499 ((weight * newly_effective_cluster_stake) as u64).max(1);
500
501 current_effective_stake += newly_effective_stake;
502 if current_effective_stake >= delegated_stake {
503 current_effective_stake = delegated_stake;
504 break;
505 }
506
507 if current_epoch >= target_epoch || current_epoch >= self.deactivation_epoch {
508 break;
509 }
510 if let Some(current_cluster_stake) = history.get(current_epoch) {
511 prev_epoch = current_epoch;
512 prev_cluster_stake = current_cluster_stake;
513 } else {
514 break;
515 }
516 }
517
518 (
519 current_effective_stake,
520 delegated_stake - current_effective_stake,
521 )
522 } else {
523 (delegated_stake, 0)
525 }
526 }
527}
528
529#[derive(
530 Debug,
531 Default,
532 Serialize,
533 Deserialize,
534 PartialEq,
535 Clone,
536 Copy,
537 AbiExample,
538 BorshDeserialize,
539 BorshSchema,
540 BorshSerialize,
541)]
542pub struct Stake {
543 pub delegation: Delegation,
544 pub credits_observed: u64,
546}
547
548impl Stake {
549 pub fn stake(&self, epoch: Epoch, history: Option<&StakeHistory>) -> u64 {
550 self.delegation.stake(epoch, history)
551 }
552
553 pub fn split(
554 &mut self,
555 remaining_stake_delta: u64,
556 split_stake_amount: u64,
557 ) -> Result<Self, StakeError> {
558 if remaining_stake_delta > self.delegation.stake {
559 return Err(StakeError::InsufficientStake);
560 }
561 self.delegation.stake -= remaining_stake_delta;
562 let new = Self {
563 delegation: Delegation {
564 stake: split_stake_amount,
565 ..self.delegation
566 },
567 ..*self
568 };
569 Ok(new)
570 }
571
572 pub fn deactivate(&mut self, epoch: Epoch) -> Result<(), StakeError> {
573 if self.delegation.deactivation_epoch != std::u64::MAX {
574 Err(StakeError::AlreadyDeactivated)
575 } else {
576 self.delegation.deactivation_epoch = epoch;
577 Ok(())
578 }
579 }
580}
581
582#[cfg(test)]
583mod test {
584 use {
585 super::*, crate::borsh::try_from_slice_unchecked, assert_matches::assert_matches,
586 bincode::serialize,
587 };
588
589 fn check_borsh_deserialization(stake: StakeState) {
590 let serialized = serialize(&stake).unwrap();
591 let deserialized = StakeState::try_from_slice(&serialized).unwrap();
592 assert_eq!(stake, deserialized);
593 }
594
595 fn check_borsh_serialization(stake: StakeState) {
596 let bincode_serialized = serialize(&stake).unwrap();
597 let borsh_serialized = StakeState::try_to_vec(&stake).unwrap();
598 assert_eq!(bincode_serialized, borsh_serialized);
599 }
600
601 #[test]
602 fn test_size_of() {
603 assert_eq!(StakeState::size_of(), std::mem::size_of::<StakeState>());
604 }
605
606 #[test]
607 fn bincode_vs_borsh_deserialization() {
608 check_borsh_deserialization(StakeState::Uninitialized);
609 check_borsh_deserialization(StakeState::RewardsPool);
610 check_borsh_deserialization(StakeState::Initialized(Meta {
611 rent_exempt_reserve: u64::MAX,
612 authorized: Authorized {
613 staker: Pubkey::new_unique(),
614 withdrawer: Pubkey::new_unique(),
615 },
616 lockup: Lockup::default(),
617 }));
618 check_borsh_deserialization(StakeState::Stake(
619 Meta {
620 rent_exempt_reserve: 1,
621 authorized: Authorized {
622 staker: Pubkey::new_unique(),
623 withdrawer: Pubkey::new_unique(),
624 },
625 lockup: Lockup::default(),
626 },
627 Stake {
628 delegation: Delegation {
629 voter_pubkey: Pubkey::new_unique(),
630 stake: u64::MAX,
631 activation_epoch: Epoch::MAX,
632 deactivation_epoch: Epoch::MAX,
633 warmup_cooldown_rate: f64::MAX,
634 },
635 credits_observed: 1,
636 },
637 ));
638 }
639
640 #[test]
641 fn bincode_vs_borsh_serialization() {
642 check_borsh_serialization(StakeState::Uninitialized);
643 check_borsh_serialization(StakeState::RewardsPool);
644 check_borsh_serialization(StakeState::Initialized(Meta {
645 rent_exempt_reserve: u64::MAX,
646 authorized: Authorized {
647 staker: Pubkey::new_unique(),
648 withdrawer: Pubkey::new_unique(),
649 },
650 lockup: Lockup::default(),
651 }));
652 check_borsh_serialization(StakeState::Stake(
653 Meta {
654 rent_exempt_reserve: 1,
655 authorized: Authorized {
656 staker: Pubkey::new_unique(),
657 withdrawer: Pubkey::new_unique(),
658 },
659 lockup: Lockup::default(),
660 },
661 Stake {
662 delegation: Delegation {
663 voter_pubkey: Pubkey::new_unique(),
664 stake: u64::MAX,
665 activation_epoch: Epoch::MAX,
666 deactivation_epoch: Epoch::MAX,
667 warmup_cooldown_rate: f64::MAX,
668 },
669 credits_observed: 1,
670 },
671 ));
672 }
673
674 #[test]
675 fn borsh_deserialization_live_data() {
676 let data = [
677 1, 0, 0, 0, 128, 213, 34, 0, 0, 0, 0, 0, 133, 0, 79, 231, 141, 29, 73, 61, 232, 35,
678 119, 124, 168, 12, 120, 216, 195, 29, 12, 166, 139, 28, 36, 182, 186, 154, 246, 149,
679 224, 109, 52, 100, 133, 0, 79, 231, 141, 29, 73, 61, 232, 35, 119, 124, 168, 12, 120,
680 216, 195, 29, 12, 166, 139, 28, 36, 182, 186, 154, 246, 149, 224, 109, 52, 100, 0, 0,
681 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,
682 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,
683 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,
684 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,
685 0, 0, 0, 0, 0, 0,
686 ];
687 let deserialized = try_from_slice_unchecked::<StakeState>(&data).unwrap();
690 assert_matches!(
691 deserialized,
692 StakeState::Initialized(Meta {
693 rent_exempt_reserve: 2282880,
694 ..
695 })
696 );
697 }
698}