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