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