1#[deprecated(
7 since = "1.8.0",
8 note = "Please use `solana_sdk::stake::state` or `solana_program::stake::state` instead"
9)]
10pub use solana_sdk::stake::state::*;
11use {
12 solana_program_runtime::{ic_msg, invoke_context::InvokeContext},
13 solana_sdk::{
14 account::{AccountSharedData, ReadableAccount, WritableAccount},
15 account_utils::StateMut,
16 clock::{Clock, Epoch},
17 feature_set::{
18 self, stake_allow_zero_undelegated_amount, stake_merge_with_unmatched_credits_observed,
19 stake_split_uses_rent_sysvar, FeatureSet,
20 },
21 instruction::{checked_add, InstructionError},
22 pubkey::Pubkey,
23 rent::{Rent, ACCOUNT_STORAGE_OVERHEAD},
24 stake::{
25 config::Config,
26 instruction::{LockupArgs, StakeError},
27 program::id,
28 tools::{acceptable_reference_epoch_credits, eligible_for_deactivate_delinquent},
29 },
30 stake_history::{StakeHistory, StakeHistoryEntry},
31 transaction_context::{
32 BorrowedAccount, IndexOfAccount, InstructionContext, TransactionContext,
33 },
34 },
35 solana_vote_program::vote_state::{self, VoteState, VoteStateVersions},
36 std::{collections::HashSet, convert::TryFrom},
37};
38
39#[derive(Debug)]
40pub enum SkippedReason {
41 DisabledInflation,
42 JustActivated,
43 TooEarlyUnfairSplit,
44 ZeroPoints,
45 ZeroPointValue,
46 ZeroReward,
47 ZeroCreditsAndReturnZero,
48 ZeroCreditsAndReturnCurrent,
49 ZeroCreditsAndReturnRewinded,
50}
51
52impl From<SkippedReason> for InflationPointCalculationEvent {
53 fn from(reason: SkippedReason) -> Self {
54 InflationPointCalculationEvent::Skipped(reason)
55 }
56}
57
58#[derive(Debug)]
59pub enum InflationPointCalculationEvent {
60 CalculatedPoints(u64, u128, u128, u128),
61 SplitRewards(u64, u64, u64, PointValue),
62 EffectiveStakeAtRewardedEpoch(u64),
63 RentExemptReserve(u64),
64 Delegation(Delegation, Pubkey),
65 Commission(u8),
66 CreditsObserved(u64, Option<u64>),
67 Skipped(SkippedReason),
68}
69
70pub(crate) fn null_tracer() -> Option<impl Fn(&InflationPointCalculationEvent)> {
71 None::<fn(&_)>
72}
73
74pub fn from<T: ReadableAccount + StateMut<StakeState>>(account: &T) -> Option<StakeState> {
76 account.state().ok()
77}
78
79pub fn stake_from<T: ReadableAccount + StateMut<StakeState>>(account: &T) -> Option<Stake> {
80 from(account).and_then(|state: StakeState| state.stake())
81}
82
83pub fn delegation_from(account: &AccountSharedData) -> Option<Delegation> {
84 from(account).and_then(|state: StakeState| state.delegation())
85}
86
87pub fn authorized_from(account: &AccountSharedData) -> Option<Authorized> {
88 from(account).and_then(|state: StakeState| state.authorized())
89}
90
91pub fn lockup_from<T: ReadableAccount + StateMut<StakeState>>(account: &T) -> Option<Lockup> {
92 from(account).and_then(|state: StakeState| state.lockup())
93}
94
95pub fn meta_from(account: &AccountSharedData) -> Option<Meta> {
96 from(account).and_then(|state: StakeState| state.meta())
97}
98
99fn redelegate_stake(
100 invoke_context: &InvokeContext,
101 stake: &mut Stake,
102 stake_lamports: u64,
103 voter_pubkey: &Pubkey,
104 vote_state: &VoteState,
105 clock: &Clock,
106 stake_history: &StakeHistory,
107 config: &Config,
108) -> Result<(), StakeError> {
109 if stake.stake(clock.epoch, Some(stake_history)) != 0 {
111 let stake_lamports_ok = if invoke_context
112 .feature_set
113 .is_active(&feature_set::stake_redelegate_instruction::id())
114 {
115 stake_lamports >= stake.delegation.stake
120 } else {
121 true
122 };
123
124 if stake.delegation.voter_pubkey == *voter_pubkey
128 && clock.epoch == stake.delegation.deactivation_epoch
129 && stake_lamports_ok
130 {
131 stake.delegation.deactivation_epoch = std::u64::MAX;
132 return Ok(());
133 } else {
134 return Err(StakeError::TooSoonToRedelegate);
136 }
137 }
138 stake.delegation.stake = stake_lamports;
143 stake.delegation.activation_epoch = clock.epoch;
144 stake.delegation.deactivation_epoch = std::u64::MAX;
145 stake.delegation.voter_pubkey = *voter_pubkey;
146 stake.delegation.warmup_cooldown_rate = config.warmup_cooldown_rate;
147 stake.credits_observed = vote_state.credits();
148 Ok(())
149}
150
151pub(crate) fn new_stake(
152 stake: u64,
153 voter_pubkey: &Pubkey,
154 vote_state: &VoteState,
155 activation_epoch: Epoch,
156 config: &Config,
157) -> Stake {
158 Stake {
159 delegation: Delegation::new(
160 voter_pubkey,
161 stake,
162 activation_epoch,
163 config.warmup_cooldown_rate,
164 ),
165 credits_observed: vote_state.credits(),
166 }
167}
168
169#[derive(Clone, Debug, PartialEq, Eq)]
174pub struct PointValue {
175 pub rewards: u64, pub points: u128, }
178
179fn redeem_stake_rewards(
180 rewarded_epoch: Epoch,
181 stake: &mut Stake,
182 point_value: &PointValue,
183 vote_state: &VoteState,
184 stake_history: Option<&StakeHistory>,
185 inflation_point_calc_tracer: Option<impl Fn(&InflationPointCalculationEvent)>,
186 credits_auto_rewind: bool,
187) -> Option<(u64, u64)> {
188 if let Some(inflation_point_calc_tracer) = inflation_point_calc_tracer.as_ref() {
189 inflation_point_calc_tracer(&InflationPointCalculationEvent::CreditsObserved(
190 stake.credits_observed,
191 None,
192 ));
193 }
194 calculate_stake_rewards(
195 rewarded_epoch,
196 stake,
197 point_value,
198 vote_state,
199 stake_history,
200 inflation_point_calc_tracer.as_ref(),
201 credits_auto_rewind,
202 )
203 .map(|calculated_stake_rewards| {
204 if let Some(inflation_point_calc_tracer) = inflation_point_calc_tracer {
205 inflation_point_calc_tracer(&InflationPointCalculationEvent::CreditsObserved(
206 stake.credits_observed,
207 Some(calculated_stake_rewards.new_credits_observed),
208 ));
209 }
210 stake.credits_observed = calculated_stake_rewards.new_credits_observed;
211 stake.delegation.stake += calculated_stake_rewards.staker_rewards;
212 (
213 calculated_stake_rewards.staker_rewards,
214 calculated_stake_rewards.voter_rewards,
215 )
216 })
217}
218
219fn calculate_stake_points(
220 stake: &Stake,
221 vote_state: &VoteState,
222 stake_history: Option<&StakeHistory>,
223 inflation_point_calc_tracer: Option<impl Fn(&InflationPointCalculationEvent)>,
224) -> u128 {
225 calculate_stake_points_and_credits(
226 stake,
227 vote_state,
228 stake_history,
229 inflation_point_calc_tracer,
230 true, )
233 .points
234}
235
236#[derive(Debug, PartialEq, Eq)]
237struct CalculatedStakePoints {
238 points: u128,
239 new_credits_observed: u64,
240 force_credits_update_with_skipped_reward: bool,
241}
242
243fn calculate_stake_points_and_credits(
247 stake: &Stake,
248 new_vote_state: &VoteState,
249 stake_history: Option<&StakeHistory>,
250 inflation_point_calc_tracer: Option<impl Fn(&InflationPointCalculationEvent)>,
251 credits_auto_rewind: bool,
252) -> CalculatedStakePoints {
253 let credits_in_stake = stake.credits_observed;
254 let credits_in_vote = new_vote_state.credits();
255 if credits_in_vote <= credits_in_stake {
257 if credits_auto_rewind && credits_in_vote < credits_in_stake {
258 if let Some(inflation_point_calc_tracer) = inflation_point_calc_tracer.as_ref() {
259 inflation_point_calc_tracer(&SkippedReason::ZeroCreditsAndReturnRewinded.into());
260 }
261 return CalculatedStakePoints {
280 points: 0,
281 new_credits_observed: credits_in_vote,
282 force_credits_update_with_skipped_reward: true,
283 };
284 } else {
285 if let Some(inflation_point_calc_tracer) = inflation_point_calc_tracer.as_ref() {
290 inflation_point_calc_tracer(&SkippedReason::ZeroCreditsAndReturnCurrent.into());
291 }
292 return CalculatedStakePoints {
295 points: 0,
296 new_credits_observed: credits_in_stake,
297 force_credits_update_with_skipped_reward: false,
298 };
299 };
300 }
301
302 let mut points = 0;
303 let mut new_credits_observed = credits_in_stake;
304
305 for (epoch, final_epoch_credits, initial_epoch_credits) in
306 new_vote_state.epoch_credits().iter().copied()
307 {
308 let stake_amount = u128::from(stake.delegation.stake(epoch, stake_history));
309
310 let earned_credits = if credits_in_stake < initial_epoch_credits {
313 final_epoch_credits - initial_epoch_credits
315 } else if credits_in_stake < final_epoch_credits {
316 final_epoch_credits - new_credits_observed
318 } else {
319 0
322 };
323 let earned_credits = u128::from(earned_credits);
324
325 new_credits_observed = new_credits_observed.max(final_epoch_credits);
327
328 let earned_points = stake_amount * earned_credits;
330 points += earned_points;
331
332 if let Some(inflation_point_calc_tracer) = inflation_point_calc_tracer.as_ref() {
333 inflation_point_calc_tracer(&InflationPointCalculationEvent::CalculatedPoints(
334 epoch,
335 stake_amount,
336 earned_credits,
337 earned_points,
338 ));
339 }
340 }
341
342 CalculatedStakePoints {
343 points,
344 new_credits_observed,
345 force_credits_update_with_skipped_reward: false,
346 }
347}
348
349#[derive(Debug, PartialEq, Eq)]
350struct CalculatedStakeRewards {
351 staker_rewards: u64,
352 voter_rewards: u64,
353 new_credits_observed: u64,
354}
355
356fn calculate_stake_rewards(
363 rewarded_epoch: Epoch,
364 stake: &Stake,
365 point_value: &PointValue,
366 vote_state: &VoteState,
367 stake_history: Option<&StakeHistory>,
368 inflation_point_calc_tracer: Option<impl Fn(&InflationPointCalculationEvent)>,
369 credits_auto_rewind: bool,
370) -> Option<CalculatedStakeRewards> {
371 let CalculatedStakePoints {
373 points,
374 new_credits_observed,
375 mut force_credits_update_with_skipped_reward,
376 } = calculate_stake_points_and_credits(
377 stake,
378 vote_state,
379 stake_history,
380 inflation_point_calc_tracer.as_ref(),
381 credits_auto_rewind,
382 );
383
384 if point_value.rewards == 0 {
387 if let Some(inflation_point_calc_tracer) = inflation_point_calc_tracer.as_ref() {
388 inflation_point_calc_tracer(&SkippedReason::DisabledInflation.into());
389 }
390 force_credits_update_with_skipped_reward = true;
391 } else if stake.delegation.activation_epoch == rewarded_epoch {
392 if let Some(inflation_point_calc_tracer) = inflation_point_calc_tracer.as_ref() {
394 inflation_point_calc_tracer(&SkippedReason::JustActivated.into());
395 }
396 force_credits_update_with_skipped_reward = true;
397 }
398
399 if force_credits_update_with_skipped_reward {
400 return Some(CalculatedStakeRewards {
401 staker_rewards: 0,
402 voter_rewards: 0,
403 new_credits_observed,
404 });
405 }
406
407 if points == 0 {
408 if let Some(inflation_point_calc_tracer) = inflation_point_calc_tracer.as_ref() {
409 inflation_point_calc_tracer(&SkippedReason::ZeroPoints.into());
410 }
411 return None;
412 }
413 if point_value.points == 0 {
414 if let Some(inflation_point_calc_tracer) = inflation_point_calc_tracer.as_ref() {
415 inflation_point_calc_tracer(&SkippedReason::ZeroPointValue.into());
416 }
417 return None;
418 }
419
420 let rewards = points
421 .checked_mul(u128::from(point_value.rewards))
422 .unwrap()
423 .checked_div(point_value.points)
424 .unwrap();
425
426 let rewards = u64::try_from(rewards).unwrap();
427
428 if rewards == 0 {
430 if let Some(inflation_point_calc_tracer) = inflation_point_calc_tracer.as_ref() {
431 inflation_point_calc_tracer(&SkippedReason::ZeroReward.into());
432 }
433 return None;
434 }
435 let (voter_rewards, staker_rewards, is_split) = vote_state.commission_split(rewards);
436 if let Some(inflation_point_calc_tracer) = inflation_point_calc_tracer.as_ref() {
437 inflation_point_calc_tracer(&InflationPointCalculationEvent::SplitRewards(
438 rewards,
439 voter_rewards,
440 staker_rewards,
441 (*point_value).clone(),
442 ));
443 }
444
445 if (voter_rewards == 0 || staker_rewards == 0) && is_split {
446 if let Some(inflation_point_calc_tracer) = inflation_point_calc_tracer.as_ref() {
450 inflation_point_calc_tracer(&SkippedReason::TooEarlyUnfairSplit.into());
451 }
452 return None;
453 }
454
455 Some(CalculatedStakeRewards {
456 staker_rewards,
457 voter_rewards,
458 new_credits_observed,
459 })
460}
461
462pub fn initialize(
463 stake_account: &mut BorrowedAccount,
464 authorized: &Authorized,
465 lockup: &Lockup,
466 rent: &Rent,
467 feature_set: &FeatureSet,
468) -> Result<(), InstructionError> {
469 if stake_account.get_data().len() != StakeState::size_of() {
470 return Err(InstructionError::InvalidAccountData);
471 }
472 if let StakeState::Uninitialized = stake_account.get_state()? {
473 let rent_exempt_reserve = rent.minimum_balance(stake_account.get_data().len());
474 let minimum_balance = if feature_set.is_active(&stake_allow_zero_undelegated_amount::id()) {
476 rent_exempt_reserve
477 } else {
478 let minimum_delegation = crate::get_minimum_delegation(feature_set);
479 rent_exempt_reserve + minimum_delegation
480 };
481
482 if stake_account.get_lamports() >= minimum_balance {
483 stake_account.set_state(&StakeState::Initialized(Meta {
484 rent_exempt_reserve,
485 authorized: *authorized,
486 lockup: *lockup,
487 }))
488 } else {
489 Err(InstructionError::InsufficientFunds)
490 }
491 } else {
492 Err(InstructionError::InvalidAccountData)
493 }
494}
495
496pub fn authorize(
500 stake_account: &mut BorrowedAccount,
501 signers: &HashSet<Pubkey>,
502 new_authority: &Pubkey,
503 stake_authorize: StakeAuthorize,
504 require_custodian_for_locked_stake_authorize: bool,
505 clock: &Clock,
506 custodian: Option<&Pubkey>,
507) -> Result<(), InstructionError> {
508 match stake_account.get_state()? {
509 StakeState::Stake(mut meta, stake) => {
510 meta.authorized.authorize(
511 signers,
512 new_authority,
513 stake_authorize,
514 if require_custodian_for_locked_stake_authorize {
515 Some((&meta.lockup, clock, custodian))
516 } else {
517 None
518 },
519 )?;
520 stake_account.set_state(&StakeState::Stake(meta, stake))
521 }
522 StakeState::Initialized(mut meta) => {
523 meta.authorized.authorize(
524 signers,
525 new_authority,
526 stake_authorize,
527 if require_custodian_for_locked_stake_authorize {
528 Some((&meta.lockup, clock, custodian))
529 } else {
530 None
531 },
532 )?;
533 stake_account.set_state(&StakeState::Initialized(meta))
534 }
535 _ => Err(InstructionError::InvalidAccountData),
536 }
537}
538
539#[allow(clippy::too_many_arguments)]
540pub fn authorize_with_seed(
541 transaction_context: &TransactionContext,
542 instruction_context: &InstructionContext,
543 stake_account: &mut BorrowedAccount,
544 authority_base_index: IndexOfAccount,
545 authority_seed: &str,
546 authority_owner: &Pubkey,
547 new_authority: &Pubkey,
548 stake_authorize: StakeAuthorize,
549 require_custodian_for_locked_stake_authorize: bool,
550 clock: &Clock,
551 custodian: Option<&Pubkey>,
552) -> Result<(), InstructionError> {
553 let mut signers = HashSet::default();
554 if instruction_context.is_instruction_account_signer(authority_base_index)? {
555 let base_pubkey = transaction_context.get_key_of_account_at_index(
556 instruction_context
557 .get_index_of_instruction_account_in_transaction(authority_base_index)?,
558 )?;
559 signers.insert(Pubkey::create_with_seed(
560 base_pubkey,
561 authority_seed,
562 authority_owner,
563 )?);
564 }
565 authorize(
566 stake_account,
567 &signers,
568 new_authority,
569 stake_authorize,
570 require_custodian_for_locked_stake_authorize,
571 clock,
572 custodian,
573 )
574}
575
576#[allow(clippy::too_many_arguments)]
577pub fn delegate(
578 invoke_context: &InvokeContext,
579 transaction_context: &TransactionContext,
580 instruction_context: &InstructionContext,
581 stake_account_index: IndexOfAccount,
582 vote_account_index: IndexOfAccount,
583 clock: &Clock,
584 stake_history: &StakeHistory,
585 config: &Config,
586 signers: &HashSet<Pubkey>,
587 feature_set: &FeatureSet,
588) -> Result<(), InstructionError> {
589 let vote_account = instruction_context
590 .try_borrow_instruction_account(transaction_context, vote_account_index)?;
591 if *vote_account.get_owner() != solana_vote_program::id() {
592 return Err(InstructionError::IncorrectProgramId);
593 }
594 let vote_pubkey = *vote_account.get_key();
595 let vote_state = vote_account.get_state::<VoteStateVersions>();
596 drop(vote_account);
597
598 let mut stake_account = instruction_context
599 .try_borrow_instruction_account(transaction_context, stake_account_index)?;
600 match stake_account.get_state()? {
601 StakeState::Initialized(meta) => {
602 meta.authorized.check(signers, StakeAuthorize::Staker)?;
603 let ValidatedDelegatedInfo { stake_amount } =
604 validate_delegated_amount(&stake_account, &meta, feature_set)?;
605 let stake = new_stake(
606 stake_amount,
607 &vote_pubkey,
608 &vote_state?.convert_to_current(),
609 clock.epoch,
610 config,
611 );
612 stake_account.set_state(&StakeState::Stake(meta, stake))
613 }
614 StakeState::Stake(meta, mut stake) => {
615 meta.authorized.check(signers, StakeAuthorize::Staker)?;
616 let ValidatedDelegatedInfo { stake_amount } =
617 validate_delegated_amount(&stake_account, &meta, feature_set)?;
618 redelegate_stake(
619 invoke_context,
620 &mut stake,
621 stake_amount,
622 &vote_pubkey,
623 &vote_state?.convert_to_current(),
624 clock,
625 stake_history,
626 config,
627 )?;
628 stake_account.set_state(&StakeState::Stake(meta, stake))
629 }
630 _ => Err(InstructionError::InvalidAccountData),
631 }
632}
633
634pub fn deactivate(
635 stake_account: &mut BorrowedAccount,
636 clock: &Clock,
637 signers: &HashSet<Pubkey>,
638) -> Result<(), InstructionError> {
639 if let StakeState::Stake(meta, mut stake) = stake_account.get_state()? {
640 meta.authorized.check(signers, StakeAuthorize::Staker)?;
641 stake.deactivate(clock.epoch)?;
642
643 stake_account.set_state(&StakeState::Stake(meta, stake))
644 } else {
645 Err(InstructionError::InvalidAccountData)
646 }
647}
648
649pub fn set_lockup(
650 stake_account: &mut BorrowedAccount,
651 lockup: &LockupArgs,
652 signers: &HashSet<Pubkey>,
653 clock: &Clock,
654) -> Result<(), InstructionError> {
655 match stake_account.get_state()? {
656 StakeState::Initialized(mut meta) => {
657 meta.set_lockup(lockup, signers, clock)?;
658 stake_account.set_state(&StakeState::Initialized(meta))
659 }
660 StakeState::Stake(mut meta, stake) => {
661 meta.set_lockup(lockup, signers, clock)?;
662 stake_account.set_state(&StakeState::Stake(meta, stake))
663 }
664 _ => Err(InstructionError::InvalidAccountData),
665 }
666}
667
668pub fn split(
669 invoke_context: &InvokeContext,
670 transaction_context: &TransactionContext,
671 instruction_context: &InstructionContext,
672 stake_account_index: IndexOfAccount,
673 lamports: u64,
674 split_index: IndexOfAccount,
675 signers: &HashSet<Pubkey>,
676) -> Result<(), InstructionError> {
677 let split =
678 instruction_context.try_borrow_instruction_account(transaction_context, split_index)?;
679 if *split.get_owner() != id() {
680 return Err(InstructionError::IncorrectProgramId);
681 }
682 if split.get_data().len() != StakeState::size_of() {
683 return Err(InstructionError::InvalidAccountData);
684 }
685 if !matches!(split.get_state()?, StakeState::Uninitialized) {
686 return Err(InstructionError::InvalidAccountData);
687 }
688 let split_lamport_balance = split.get_lamports();
689 drop(split);
690 let stake_account = instruction_context
691 .try_borrow_instruction_account(transaction_context, stake_account_index)?;
692 if lamports > stake_account.get_lamports() {
693 return Err(InstructionError::InsufficientFunds);
694 }
695 let stake_state = stake_account.get_state()?;
696 drop(stake_account);
697
698 match stake_state {
699 StakeState::Stake(meta, mut stake) => {
700 meta.authorized.check(signers, StakeAuthorize::Staker)?;
701 let minimum_delegation = crate::get_minimum_delegation(&invoke_context.feature_set);
702 let validated_split_info = validate_split_amount(
703 invoke_context,
704 transaction_context,
705 instruction_context,
706 stake_account_index,
707 split_index,
708 lamports,
709 &meta,
710 Some(&stake),
711 minimum_delegation,
712 )?;
713
714 let (remaining_stake_delta, split_stake_amount) =
720 if validated_split_info.source_remaining_balance == 0 {
721 let remaining_stake_delta = lamports.saturating_sub(meta.rent_exempt_reserve);
732 (remaining_stake_delta, remaining_stake_delta)
733 } else {
734 (
737 lamports,
738 lamports.saturating_sub(
739 validated_split_info
740 .destination_rent_exempt_reserve
741 .saturating_sub(split_lamport_balance),
742 ),
743 )
744 };
745 let split_stake = stake.split(remaining_stake_delta, split_stake_amount)?;
746 let mut split_meta = meta;
747 split_meta.rent_exempt_reserve = validated_split_info.destination_rent_exempt_reserve;
748
749 let mut stake_account = instruction_context
750 .try_borrow_instruction_account(transaction_context, stake_account_index)?;
751 stake_account.set_state(&StakeState::Stake(meta, stake))?;
752 drop(stake_account);
753 let mut split = instruction_context
754 .try_borrow_instruction_account(transaction_context, split_index)?;
755 split.set_state(&StakeState::Stake(split_meta, split_stake))?;
756 }
757 StakeState::Initialized(meta) => {
758 meta.authorized.check(signers, StakeAuthorize::Staker)?;
759 let additional_required_lamports = if invoke_context
760 .feature_set
761 .is_active(&stake_allow_zero_undelegated_amount::id())
762 {
763 0
764 } else {
765 crate::get_minimum_delegation(&invoke_context.feature_set)
766 };
767 let validated_split_info = validate_split_amount(
768 invoke_context,
769 transaction_context,
770 instruction_context,
771 stake_account_index,
772 split_index,
773 lamports,
774 &meta,
775 None,
776 additional_required_lamports,
777 )?;
778 let mut split_meta = meta;
779 split_meta.rent_exempt_reserve = validated_split_info.destination_rent_exempt_reserve;
780 let mut split = instruction_context
781 .try_borrow_instruction_account(transaction_context, split_index)?;
782 split.set_state(&StakeState::Initialized(split_meta))?;
783 }
784 StakeState::Uninitialized => {
785 let stake_pubkey = transaction_context.get_key_of_account_at_index(
786 instruction_context
787 .get_index_of_instruction_account_in_transaction(stake_account_index)?,
788 )?;
789 if !signers.contains(stake_pubkey) {
790 return Err(InstructionError::MissingRequiredSignature);
791 }
792 }
793 _ => return Err(InstructionError::InvalidAccountData),
794 }
795
796 let mut stake_account = instruction_context
798 .try_borrow_instruction_account(transaction_context, stake_account_index)?;
799 if lamports == stake_account.get_lamports() {
800 stake_account.set_state(&StakeState::Uninitialized)?;
801 }
802 drop(stake_account);
803
804 let mut split =
805 instruction_context.try_borrow_instruction_account(transaction_context, split_index)?;
806 split.checked_add_lamports(lamports)?;
807 drop(split);
808 let mut stake_account = instruction_context
809 .try_borrow_instruction_account(transaction_context, stake_account_index)?;
810 stake_account.checked_sub_lamports(lamports)?;
811 Ok(())
812}
813
814pub fn merge(
815 invoke_context: &InvokeContext,
816 transaction_context: &TransactionContext,
817 instruction_context: &InstructionContext,
818 stake_account_index: IndexOfAccount,
819 source_account_index: IndexOfAccount,
820 clock: &Clock,
821 stake_history: &StakeHistory,
822 signers: &HashSet<Pubkey>,
823) -> Result<(), InstructionError> {
824 let mut source_account = instruction_context
825 .try_borrow_instruction_account(transaction_context, source_account_index)?;
826 if *source_account.get_owner() != id() {
828 return Err(InstructionError::IncorrectProgramId);
829 }
830 if instruction_context.get_index_of_instruction_account_in_transaction(stake_account_index)?
832 == instruction_context
833 .get_index_of_instruction_account_in_transaction(source_account_index)?
834 {
835 return Err(InstructionError::InvalidArgument);
836 }
837 let mut stake_account = instruction_context
838 .try_borrow_instruction_account(transaction_context, stake_account_index)?;
839
840 ic_msg!(invoke_context, "Checking if destination stake is mergeable");
841 let stake_merge_kind = MergeKind::get_if_mergeable(
842 invoke_context,
843 &stake_account.get_state()?,
844 stake_account.get_lamports(),
845 clock,
846 stake_history,
847 )?;
848
849 stake_merge_kind
851 .meta()
852 .authorized
853 .check(signers, StakeAuthorize::Staker)?;
854
855 ic_msg!(invoke_context, "Checking if source stake is mergeable");
856 let source_merge_kind = MergeKind::get_if_mergeable(
857 invoke_context,
858 &source_account.get_state()?,
859 source_account.get_lamports(),
860 clock,
861 stake_history,
862 )?;
863
864 ic_msg!(invoke_context, "Merging stake accounts");
865 if let Some(merged_state) = stake_merge_kind.merge(invoke_context, source_merge_kind, clock)? {
866 stake_account.set_state(&merged_state)?;
867 }
868
869 source_account.set_state(&StakeState::Uninitialized)?;
871
872 let lamports = source_account.get_lamports();
874 source_account.checked_sub_lamports(lamports)?;
875 stake_account.checked_add_lamports(lamports)?;
876 Ok(())
877}
878
879pub fn redelegate(
880 invoke_context: &InvokeContext,
881 transaction_context: &TransactionContext,
882 instruction_context: &InstructionContext,
883 stake_account: &mut BorrowedAccount,
884 uninitialized_stake_account_index: IndexOfAccount,
885 vote_account_index: IndexOfAccount,
886 config: &Config,
887 signers: &HashSet<Pubkey>,
888) -> Result<(), InstructionError> {
889 let clock = invoke_context.get_sysvar_cache().get_clock()?;
890
891 let mut uninitialized_stake_account = instruction_context
893 .try_borrow_instruction_account(transaction_context, uninitialized_stake_account_index)?;
894 if *uninitialized_stake_account.get_owner() != id() {
895 ic_msg!(
896 invoke_context,
897 "expected uninitialized stake account owner to be {}, not {}",
898 id(),
899 *uninitialized_stake_account.get_owner()
900 );
901 return Err(InstructionError::IncorrectProgramId);
902 }
903 if uninitialized_stake_account.get_data().len() != StakeState::size_of() {
904 ic_msg!(
905 invoke_context,
906 "expected uninitialized stake account data len to be {}, not {}",
907 StakeState::size_of(),
908 uninitialized_stake_account.get_data().len()
909 );
910 return Err(InstructionError::InvalidAccountData);
911 }
912 if !matches!(
913 uninitialized_stake_account.get_state()?,
914 StakeState::Uninitialized
915 ) {
916 ic_msg!(
917 invoke_context,
918 "expected uninitialized stake account to be uninitialized",
919 );
920 return Err(InstructionError::AccountAlreadyInitialized);
921 }
922
923 let vote_account = instruction_context
925 .try_borrow_instruction_account(transaction_context, vote_account_index)?;
926 if *vote_account.get_owner() != solana_vote_program::id() {
927 ic_msg!(
928 invoke_context,
929 "expected vote account owner to be {}, not {}",
930 solana_vote_program::id(),
931 *vote_account.get_owner()
932 );
933 return Err(InstructionError::IncorrectProgramId);
934 }
935 let vote_pubkey = *vote_account.get_key();
936 let vote_state = vote_account.get_state::<VoteStateVersions>()?;
937
938 let (stake_meta, effective_stake) =
939 if let StakeState::Stake(meta, stake) = stake_account.get_state()? {
940 let stake_history = invoke_context.get_sysvar_cache().get_stake_history()?;
941 let status = stake
942 .delegation
943 .stake_activating_and_deactivating(clock.epoch, Some(&stake_history));
944 if status.effective == 0 || status.activating != 0 || status.deactivating != 0 {
945 ic_msg!(invoke_context, "stake is not active");
946 return Err(StakeError::RedelegateTransientOrInactiveStake.into());
947 }
948
949 if stake.delegation.voter_pubkey == vote_pubkey {
952 ic_msg!(
953 invoke_context,
954 "redelegating to the same vote account not permitted"
955 );
956 return Err(StakeError::RedelegateToSameVoteAccount.into());
957 }
958
959 (meta, status.effective)
960 } else {
961 ic_msg!(invoke_context, "invalid stake account data",);
962 return Err(InstructionError::InvalidAccountData);
963 };
964
965 deactivate(stake_account, &clock, signers)?;
969
970 stake_account.checked_sub_lamports(effective_stake)?;
972 uninitialized_stake_account.checked_add_lamports(effective_stake)?;
973
974 let sysvar_cache = invoke_context.get_sysvar_cache();
976 let rent = sysvar_cache.get_rent()?;
977 let mut uninitialized_stake_meta = stake_meta;
978 uninitialized_stake_meta.rent_exempt_reserve =
979 rent.minimum_balance(uninitialized_stake_account.get_data().len());
980
981 let ValidatedDelegatedInfo { stake_amount } = validate_delegated_amount(
982 &uninitialized_stake_account,
983 &uninitialized_stake_meta,
984 &invoke_context.feature_set,
985 )?;
986 uninitialized_stake_account.set_state(&StakeState::Stake(
987 uninitialized_stake_meta,
988 new_stake(
989 stake_amount,
990 &vote_pubkey,
991 &vote_state.convert_to_current(),
992 clock.epoch,
993 config,
994 ),
995 ))?;
996
997 Ok(())
998}
999
1000#[allow(clippy::too_many_arguments)]
1001pub fn withdraw(
1002 transaction_context: &TransactionContext,
1003 instruction_context: &InstructionContext,
1004 stake_account_index: IndexOfAccount,
1005 lamports: u64,
1006 to_index: IndexOfAccount,
1007 clock: &Clock,
1008 stake_history: &StakeHistory,
1009 withdraw_authority_index: IndexOfAccount,
1010 custodian_index: Option<IndexOfAccount>,
1011 feature_set: &FeatureSet,
1012) -> Result<(), InstructionError> {
1013 let withdraw_authority_pubkey = transaction_context.get_key_of_account_at_index(
1014 instruction_context
1015 .get_index_of_instruction_account_in_transaction(withdraw_authority_index)?,
1016 )?;
1017 if !instruction_context.is_instruction_account_signer(withdraw_authority_index)? {
1018 return Err(InstructionError::MissingRequiredSignature);
1019 }
1020 let mut signers = HashSet::new();
1021 signers.insert(*withdraw_authority_pubkey);
1022
1023 let mut stake_account = instruction_context
1024 .try_borrow_instruction_account(transaction_context, stake_account_index)?;
1025 let (lockup, reserve, is_staked) = match stake_account.get_state()? {
1026 StakeState::Stake(meta, stake) => {
1027 meta.authorized
1028 .check(&signers, StakeAuthorize::Withdrawer)?;
1029 let staked = if clock.epoch >= stake.delegation.deactivation_epoch {
1031 stake.delegation.stake(clock.epoch, Some(stake_history))
1032 } else {
1033 stake.delegation.stake
1037 };
1038
1039 let staked_and_reserve = checked_add(staked, meta.rent_exempt_reserve)?;
1040 (meta.lockup, staked_and_reserve, staked != 0)
1041 }
1042 StakeState::Initialized(meta) => {
1043 meta.authorized
1044 .check(&signers, StakeAuthorize::Withdrawer)?;
1045 let reserve = if feature_set.is_active(&stake_allow_zero_undelegated_amount::id()) {
1047 meta.rent_exempt_reserve
1048 } else {
1049 checked_add(
1050 meta.rent_exempt_reserve,
1051 crate::get_minimum_delegation(feature_set),
1052 )?
1053 };
1054
1055 (meta.lockup, reserve, false)
1056 }
1057 StakeState::Uninitialized => {
1058 if !signers.contains(stake_account.get_key()) {
1059 return Err(InstructionError::MissingRequiredSignature);
1060 }
1061 (Lockup::default(), 0, false) }
1063 _ => return Err(InstructionError::InvalidAccountData),
1064 };
1065
1066 let custodian_pubkey = if let Some(custodian_index) = custodian_index {
1069 if instruction_context.is_instruction_account_signer(custodian_index)? {
1070 Some(
1071 transaction_context.get_key_of_account_at_index(
1072 instruction_context
1073 .get_index_of_instruction_account_in_transaction(custodian_index)?,
1074 )?,
1075 )
1076 } else {
1077 None
1078 }
1079 } else {
1080 None
1081 };
1082 if lockup.is_in_force(clock, custodian_pubkey) {
1083 return Err(StakeError::LockupInForce.into());
1084 }
1085
1086 let lamports_and_reserve = checked_add(lamports, reserve)?;
1087 if is_staked && lamports_and_reserve > stake_account.get_lamports()
1090 {
1091 return Err(InstructionError::InsufficientFunds);
1092 }
1093
1094 if lamports != stake_account.get_lamports() && lamports_and_reserve > stake_account.get_lamports()
1096 {
1097 assert!(!is_staked);
1098 return Err(InstructionError::InsufficientFunds);
1099 }
1100
1101 if lamports == stake_account.get_lamports() {
1103 stake_account.set_state(&StakeState::Uninitialized)?;
1104 }
1105
1106 stake_account.checked_sub_lamports(lamports)?;
1107 drop(stake_account);
1108 let mut to =
1109 instruction_context.try_borrow_instruction_account(transaction_context, to_index)?;
1110 to.checked_add_lamports(lamports)?;
1111 Ok(())
1112}
1113
1114pub(crate) fn deactivate_delinquent(
1115 transaction_context: &TransactionContext,
1116 instruction_context: &InstructionContext,
1117 stake_account: &mut BorrowedAccount,
1118 delinquent_vote_account_index: IndexOfAccount,
1119 reference_vote_account_index: IndexOfAccount,
1120 current_epoch: Epoch,
1121) -> Result<(), InstructionError> {
1122 let delinquent_vote_account_pubkey = transaction_context.get_key_of_account_at_index(
1123 instruction_context
1124 .get_index_of_instruction_account_in_transaction(delinquent_vote_account_index)?,
1125 )?;
1126 let delinquent_vote_account = instruction_context
1127 .try_borrow_instruction_account(transaction_context, delinquent_vote_account_index)?;
1128 if *delinquent_vote_account.get_owner() != solana_vote_program::id() {
1129 return Err(InstructionError::IncorrectProgramId);
1130 }
1131 let delinquent_vote_state = delinquent_vote_account
1132 .get_state::<VoteStateVersions>()?
1133 .convert_to_current();
1134
1135 let reference_vote_account = instruction_context
1136 .try_borrow_instruction_account(transaction_context, reference_vote_account_index)?;
1137 if *reference_vote_account.get_owner() != solana_vote_program::id() {
1138 return Err(InstructionError::IncorrectProgramId);
1139 }
1140 let reference_vote_state = reference_vote_account
1141 .get_state::<VoteStateVersions>()?
1142 .convert_to_current();
1143
1144 if !acceptable_reference_epoch_credits(&reference_vote_state.epoch_credits, current_epoch) {
1145 return Err(StakeError::InsufficientReferenceVotes.into());
1146 }
1147
1148 if let StakeState::Stake(meta, mut stake) = stake_account.get_state()? {
1149 if stake.delegation.voter_pubkey != *delinquent_vote_account_pubkey {
1150 return Err(StakeError::VoteAddressMismatch.into());
1151 }
1152
1153 if eligible_for_deactivate_delinquent(&delinquent_vote_state.epoch_credits, current_epoch) {
1156 stake.deactivate(current_epoch)?;
1157 stake_account.set_state(&StakeState::Stake(meta, stake))
1158 } else {
1159 Err(StakeError::MinimumDelinquentEpochsForDeactivationNotMet.into())
1160 }
1161 } else {
1162 Err(InstructionError::InvalidAccountData)
1163 }
1164}
1165
1166struct ValidatedDelegatedInfo {
1169 stake_amount: u64,
1170}
1171
1172fn validate_delegated_amount(
1175 account: &BorrowedAccount,
1176 meta: &Meta,
1177 feature_set: &FeatureSet,
1178) -> Result<ValidatedDelegatedInfo, InstructionError> {
1179 let stake_amount = account
1180 .get_lamports()
1181 .saturating_sub(meta.rent_exempt_reserve); if (feature_set.is_active(&stake_allow_zero_undelegated_amount::id())
1186 || feature_set.is_active(&feature_set::stake_raise_minimum_delegation_to_1_sol::id()))
1187 && stake_amount < crate::get_minimum_delegation(feature_set)
1188 {
1189 return Err(StakeError::InsufficientDelegation.into());
1190 }
1191 Ok(ValidatedDelegatedInfo { stake_amount })
1192}
1193
1194#[derive(Copy, Clone, Debug, Default)]
1197struct ValidatedSplitInfo {
1198 source_remaining_balance: u64,
1199 destination_rent_exempt_reserve: u64,
1200}
1201
1202fn validate_split_amount(
1207 invoke_context: &InvokeContext,
1208 transaction_context: &TransactionContext,
1209 instruction_context: &InstructionContext,
1210 source_account_index: IndexOfAccount,
1211 destination_account_index: IndexOfAccount,
1212 lamports: u64,
1213 source_meta: &Meta,
1214 source_stake: Option<&Stake>,
1215 additional_required_lamports: u64,
1216) -> Result<ValidatedSplitInfo, InstructionError> {
1217 let source_account = instruction_context
1218 .try_borrow_instruction_account(transaction_context, source_account_index)?;
1219 let source_lamports = source_account.get_lamports();
1220 let source_data_len = source_account.get_data().len();
1221 drop(source_account);
1222 let destination_account = instruction_context
1223 .try_borrow_instruction_account(transaction_context, destination_account_index)?;
1224 let destination_lamports = destination_account.get_lamports();
1225 let destination_data_len = destination_account.get_data().len();
1226 drop(destination_account);
1227
1228 if lamports == 0 {
1230 return Err(InstructionError::InsufficientFunds);
1231 }
1232
1233 if lamports > source_lamports {
1235 return Err(InstructionError::InsufficientFunds);
1236 }
1237
1238 let source_minimum_balance = source_meta
1243 .rent_exempt_reserve
1244 .saturating_add(additional_required_lamports);
1245 let source_remaining_balance = source_lamports.saturating_sub(lamports);
1246 if source_remaining_balance == 0 {
1247 } else if source_remaining_balance < source_minimum_balance {
1250 return Err(InstructionError::InsufficientFunds);
1252 } else {
1253 }
1256
1257 let destination_rent_exempt_reserve = if invoke_context
1262 .feature_set
1263 .is_active(&stake_split_uses_rent_sysvar::ID)
1264 {
1265 let rent = invoke_context.get_sysvar_cache().get_rent()?;
1266 rent.minimum_balance(destination_data_len)
1267 } else {
1268 calculate_split_rent_exempt_reserve(
1269 source_meta.rent_exempt_reserve,
1270 source_data_len as u64,
1271 destination_data_len as u64,
1272 )
1273 };
1274 let destination_minimum_balance =
1275 destination_rent_exempt_reserve.saturating_add(additional_required_lamports);
1276 let destination_balance_deficit =
1277 destination_minimum_balance.saturating_sub(destination_lamports);
1278 if lamports < destination_balance_deficit {
1279 return Err(InstructionError::InsufficientFunds);
1280 }
1281
1282 if source_stake.is_some() && lamports < additional_required_lamports {
1293 return Err(InstructionError::InsufficientFunds);
1294 }
1295
1296 Ok(ValidatedSplitInfo {
1297 source_remaining_balance,
1298 destination_rent_exempt_reserve,
1299 })
1300}
1301
1302#[derive(Clone, Debug, PartialEq)]
1303enum MergeKind {
1304 Inactive(Meta, u64),
1305 ActivationEpoch(Meta, Stake),
1306 FullyActive(Meta, Stake),
1307}
1308
1309impl MergeKind {
1310 fn meta(&self) -> &Meta {
1311 match self {
1312 Self::Inactive(meta, _) => meta,
1313 Self::ActivationEpoch(meta, _) => meta,
1314 Self::FullyActive(meta, _) => meta,
1315 }
1316 }
1317
1318 fn active_stake(&self) -> Option<&Stake> {
1319 match self {
1320 Self::Inactive(_, _) => None,
1321 Self::ActivationEpoch(_, stake) => Some(stake),
1322 Self::FullyActive(_, stake) => Some(stake),
1323 }
1324 }
1325
1326 fn get_if_mergeable(
1327 invoke_context: &InvokeContext,
1328 stake_state: &StakeState,
1329 stake_lamports: u64,
1330 clock: &Clock,
1331 stake_history: &StakeHistory,
1332 ) -> Result<Self, InstructionError> {
1333 match stake_state {
1334 StakeState::Stake(meta, stake) => {
1335 let status = stake
1338 .delegation
1339 .stake_activating_and_deactivating(clock.epoch, Some(stake_history));
1340
1341 match (status.effective, status.activating, status.deactivating) {
1342 (0, 0, 0) => Ok(Self::Inactive(*meta, stake_lamports)),
1343 (0, _, _) => Ok(Self::ActivationEpoch(*meta, *stake)),
1344 (_, 0, 0) => Ok(Self::FullyActive(*meta, *stake)),
1345 _ => {
1346 let err = StakeError::MergeTransientStake;
1347 ic_msg!(invoke_context, "{}", err);
1348 Err(err.into())
1349 }
1350 }
1351 }
1352 StakeState::Initialized(meta) => Ok(Self::Inactive(*meta, stake_lamports)),
1353 _ => Err(InstructionError::InvalidAccountData),
1354 }
1355 }
1356
1357 fn metas_can_merge(
1358 invoke_context: &InvokeContext,
1359 stake: &Meta,
1360 source: &Meta,
1361 clock: &Clock,
1362 ) -> Result<(), InstructionError> {
1363 let can_merge_lockups = stake.lockup == source.lockup
1365 || (!stake.lockup.is_in_force(clock, None) && !source.lockup.is_in_force(clock, None));
1366 if stake.authorized == source.authorized && can_merge_lockups {
1372 Ok(())
1373 } else {
1374 ic_msg!(invoke_context, "Unable to merge due to metadata mismatch");
1375 Err(StakeError::MergeMismatch.into())
1376 }
1377 }
1378
1379 fn active_delegations_can_merge(
1380 invoke_context: &InvokeContext,
1381 stake: &Delegation,
1382 source: &Delegation,
1383 ) -> Result<(), InstructionError> {
1384 if stake.voter_pubkey != source.voter_pubkey {
1385 ic_msg!(invoke_context, "Unable to merge due to voter mismatch");
1386 Err(StakeError::MergeMismatch.into())
1387 } else if (stake.warmup_cooldown_rate - source.warmup_cooldown_rate).abs() < f64::EPSILON
1388 && stake.deactivation_epoch == Epoch::MAX
1389 && source.deactivation_epoch == Epoch::MAX
1390 {
1391 Ok(())
1392 } else {
1393 ic_msg!(invoke_context, "Unable to merge due to stake deactivation");
1394 Err(StakeError::MergeMismatch.into())
1395 }
1396 }
1397
1398 fn active_stakes_can_merge(
1400 invoke_context: &InvokeContext,
1401 stake: &Stake,
1402 source: &Stake,
1403 ) -> Result<(), InstructionError> {
1404 Self::active_delegations_can_merge(invoke_context, &stake.delegation, &source.delegation)?;
1405 if stake.credits_observed == source.credits_observed {
1411 Ok(())
1412 } else {
1413 ic_msg!(
1414 invoke_context,
1415 "Unable to merge due to credits observed mismatch"
1416 );
1417 Err(StakeError::MergeMismatch.into())
1418 }
1419 }
1420
1421 fn merge(
1422 self,
1423 invoke_context: &InvokeContext,
1424 source: Self,
1425 clock: &Clock,
1426 ) -> Result<Option<StakeState>, InstructionError> {
1427 Self::metas_can_merge(invoke_context, self.meta(), source.meta(), clock)?;
1428 self.active_stake()
1429 .zip(source.active_stake())
1430 .map(|(stake, source)| {
1431 if invoke_context
1432 .feature_set
1433 .is_active(&stake_merge_with_unmatched_credits_observed::id())
1434 {
1435 Self::active_delegations_can_merge(
1436 invoke_context,
1437 &stake.delegation,
1438 &source.delegation,
1439 )
1440 } else {
1441 Self::active_stakes_can_merge(invoke_context, stake, source)
1442 }
1443 })
1444 .unwrap_or(Ok(()))?;
1445 let merged_state = match (self, source) {
1446 (Self::Inactive(_, _), Self::Inactive(_, _)) => None,
1447 (Self::Inactive(_, _), Self::ActivationEpoch(_, _)) => None,
1448 (Self::ActivationEpoch(meta, mut stake), Self::Inactive(_, source_lamports)) => {
1449 stake.delegation.stake = checked_add(stake.delegation.stake, source_lamports)?;
1450 Some(StakeState::Stake(meta, stake))
1451 }
1452 (
1453 Self::ActivationEpoch(meta, mut stake),
1454 Self::ActivationEpoch(source_meta, source_stake),
1455 ) => {
1456 let source_lamports = checked_add(
1457 source_meta.rent_exempt_reserve,
1458 source_stake.delegation.stake,
1459 )?;
1460 merge_delegation_stake_and_credits_observed(
1461 invoke_context,
1462 &mut stake,
1463 source_lamports,
1464 source_stake.credits_observed,
1465 )?;
1466 Some(StakeState::Stake(meta, stake))
1467 }
1468 (Self::FullyActive(meta, mut stake), Self::FullyActive(_, source_stake)) => {
1469 merge_delegation_stake_and_credits_observed(
1474 invoke_context,
1475 &mut stake,
1476 source_stake.delegation.stake,
1477 source_stake.credits_observed,
1478 )?;
1479 Some(StakeState::Stake(meta, stake))
1480 }
1481 _ => return Err(StakeError::MergeMismatch.into()),
1482 };
1483 Ok(merged_state)
1484 }
1485}
1486
1487fn merge_delegation_stake_and_credits_observed(
1488 invoke_context: &InvokeContext,
1489 stake: &mut Stake,
1490 absorbed_lamports: u64,
1491 absorbed_credits_observed: u64,
1492) -> Result<(), InstructionError> {
1493 if invoke_context
1494 .feature_set
1495 .is_active(&stake_merge_with_unmatched_credits_observed::id())
1496 {
1497 stake.credits_observed =
1498 stake_weighted_credits_observed(stake, absorbed_lamports, absorbed_credits_observed)
1499 .ok_or(InstructionError::ArithmeticOverflow)?;
1500 }
1501 stake.delegation.stake = checked_add(stake.delegation.stake, absorbed_lamports)?;
1502 Ok(())
1503}
1504
1505fn stake_weighted_credits_observed(
1531 stake: &Stake,
1532 absorbed_lamports: u64,
1533 absorbed_credits_observed: u64,
1534) -> Option<u64> {
1535 if stake.credits_observed == absorbed_credits_observed {
1536 Some(stake.credits_observed)
1537 } else {
1538 let total_stake = u128::from(stake.delegation.stake.checked_add(absorbed_lamports)?);
1539 let stake_weighted_credits =
1540 u128::from(stake.credits_observed).checked_mul(u128::from(stake.delegation.stake))?;
1541 let absorbed_weighted_credits =
1542 u128::from(absorbed_credits_observed).checked_mul(u128::from(absorbed_lamports))?;
1543 let total_weighted_credits = stake_weighted_credits
1546 .checked_add(absorbed_weighted_credits)?
1547 .checked_add(total_stake)?
1548 .checked_sub(1)?;
1549 u64::try_from(total_weighted_credits.checked_div(total_stake)?).ok()
1550 }
1551}
1552
1553#[doc(hidden)]
1556pub fn redeem_rewards(
1557 rewarded_epoch: Epoch,
1558 stake_state: StakeState,
1559 stake_account: &mut AccountSharedData,
1560 vote_state: &VoteState,
1561 point_value: &PointValue,
1562 stake_history: Option<&StakeHistory>,
1563 inflation_point_calc_tracer: Option<impl Fn(&InflationPointCalculationEvent)>,
1564 credits_auto_rewind: bool,
1565) -> Result<(u64, u64), InstructionError> {
1566 if let StakeState::Stake(meta, mut stake) = stake_state {
1567 if let Some(inflation_point_calc_tracer) = inflation_point_calc_tracer.as_ref() {
1568 inflation_point_calc_tracer(
1569 &InflationPointCalculationEvent::EffectiveStakeAtRewardedEpoch(
1570 stake.stake(rewarded_epoch, stake_history),
1571 ),
1572 );
1573 inflation_point_calc_tracer(&InflationPointCalculationEvent::RentExemptReserve(
1574 meta.rent_exempt_reserve,
1575 ));
1576 inflation_point_calc_tracer(&InflationPointCalculationEvent::Commission(
1577 vote_state.commission,
1578 ));
1579 }
1580
1581 if let Some((stakers_reward, voters_reward)) = redeem_stake_rewards(
1582 rewarded_epoch,
1583 &mut stake,
1584 point_value,
1585 vote_state,
1586 stake_history,
1587 inflation_point_calc_tracer,
1588 credits_auto_rewind,
1589 ) {
1590 stake_account.checked_add_lamports(stakers_reward)?;
1591 stake_account.set_state(&StakeState::Stake(meta, stake))?;
1592
1593 Ok((stakers_reward, voters_reward))
1594 } else {
1595 Err(StakeError::NoCreditsToRedeem.into())
1596 }
1597 } else {
1598 Err(InstructionError::InvalidAccountData)
1599 }
1600}
1601
1602#[doc(hidden)]
1604pub fn calculate_points(
1605 stake_state: &StakeState,
1606 vote_state: &VoteState,
1607 stake_history: Option<&StakeHistory>,
1608) -> Result<u128, InstructionError> {
1609 if let StakeState::Stake(_meta, stake) = stake_state {
1610 Ok(calculate_stake_points(
1611 stake,
1612 vote_state,
1613 stake_history,
1614 null_tracer(),
1615 ))
1616 } else {
1617 Err(InstructionError::InvalidAccountData)
1618 }
1619}
1620
1621fn calculate_split_rent_exempt_reserve(
1625 source_rent_exempt_reserve: u64,
1626 source_data_len: u64,
1627 split_data_len: u64,
1628) -> u64 {
1629 let lamports_per_byte_year =
1630 source_rent_exempt_reserve / (source_data_len + ACCOUNT_STORAGE_OVERHEAD);
1631 lamports_per_byte_year * (split_data_len + ACCOUNT_STORAGE_OVERHEAD)
1632}
1633
1634pub type RewriteStakeStatus = (&'static str, (u64, u64), (u64, u64));
1635
1636pub fn new_stake_history_entry<'a, I>(
1638 epoch: Epoch,
1639 stakes: I,
1640 history: Option<&StakeHistory>,
1641) -> StakeHistoryEntry
1642where
1643 I: Iterator<Item = &'a Delegation>,
1644{
1645 stakes.fold(StakeHistoryEntry::default(), |sum, stake| {
1646 sum + stake.stake_activating_and_deactivating(epoch, history)
1647 })
1648}
1649
1650pub fn create_stake_history_from_delegations(
1652 bootstrap: Option<u64>,
1653 epochs: std::ops::Range<Epoch>,
1654 delegations: &[Delegation],
1655) -> StakeHistory {
1656 let mut stake_history = StakeHistory::default();
1657
1658 let bootstrap_delegation = if let Some(bootstrap) = bootstrap {
1659 vec![Delegation {
1660 activation_epoch: std::u64::MAX,
1661 stake: bootstrap,
1662 ..Delegation::default()
1663 }]
1664 } else {
1665 vec![]
1666 };
1667
1668 for epoch in epochs {
1669 let entry = new_stake_history_entry(
1670 epoch,
1671 delegations.iter().chain(bootstrap_delegation.iter()),
1672 Some(&stake_history),
1673 );
1674 stake_history.add(epoch, entry);
1675 }
1676
1677 stake_history
1678}
1679
1680pub fn create_lockup_stake_account(
1682 authorized: &Authorized,
1683 lockup: &Lockup,
1684 rent: &Rent,
1685 lamports: u64,
1686) -> AccountSharedData {
1687 let mut stake_account = AccountSharedData::new(lamports, StakeState::size_of(), &id());
1688
1689 let rent_exempt_reserve = rent.minimum_balance(stake_account.data().len());
1690 assert!(
1691 lamports >= rent_exempt_reserve,
1692 "lamports: {lamports} is less than rent_exempt_reserve {rent_exempt_reserve}"
1693 );
1694
1695 stake_account
1696 .set_state(&StakeState::Initialized(Meta {
1697 authorized: *authorized,
1698 lockup: *lockup,
1699 rent_exempt_reserve,
1700 }))
1701 .expect("set_state");
1702
1703 stake_account
1704}
1705
1706pub fn create_account(
1708 authorized: &Pubkey,
1709 voter_pubkey: &Pubkey,
1710 vote_account: &AccountSharedData,
1711 rent: &Rent,
1712 lamports: u64,
1713) -> AccountSharedData {
1714 do_create_account(
1715 authorized,
1716 voter_pubkey,
1717 vote_account,
1718 rent,
1719 lamports,
1720 Epoch::MAX,
1721 )
1722}
1723
1724pub fn create_account_with_activation_epoch(
1726 authorized: &Pubkey,
1727 voter_pubkey: &Pubkey,
1728 vote_account: &AccountSharedData,
1729 rent: &Rent,
1730 lamports: u64,
1731 activation_epoch: Epoch,
1732) -> AccountSharedData {
1733 do_create_account(
1734 authorized,
1735 voter_pubkey,
1736 vote_account,
1737 rent,
1738 lamports,
1739 activation_epoch,
1740 )
1741}
1742
1743fn do_create_account(
1744 authorized: &Pubkey,
1745 voter_pubkey: &Pubkey,
1746 vote_account: &AccountSharedData,
1747 rent: &Rent,
1748 lamports: u64,
1749 activation_epoch: Epoch,
1750) -> AccountSharedData {
1751 let mut stake_account = AccountSharedData::new(lamports, StakeState::size_of(), &id());
1752
1753 let vote_state = vote_state::from(vote_account).expect("vote_state");
1754
1755 let rent_exempt_reserve = rent.minimum_balance(stake_account.data().len());
1756
1757 stake_account
1758 .set_state(&StakeState::Stake(
1759 Meta {
1760 authorized: Authorized::auto(authorized),
1761 rent_exempt_reserve,
1762 ..Meta::default()
1763 },
1764 new_stake(
1765 lamports - rent_exempt_reserve, voter_pubkey,
1767 &vote_state,
1768 activation_epoch,
1769 &Config::default(),
1770 ),
1771 ))
1772 .expect("set_state");
1773
1774 stake_account
1775}
1776
1777#[cfg(test)]
1778mod tests {
1779 use {
1780 super::*,
1781 proptest::prelude::*,
1782 solana_program_runtime::invoke_context::InvokeContext,
1783 solana_sdk::{
1784 account::{create_account_shared_data_for_test, AccountSharedData},
1785 native_token,
1786 pubkey::Pubkey,
1787 sysvar::SysvarId,
1788 transaction_context::TransactionContext,
1789 },
1790 };
1791
1792 #[test]
1793 fn test_authorized_authorize() {
1794 let staker = solana_sdk::pubkey::new_rand();
1795 let mut authorized = Authorized::auto(&staker);
1796 let mut signers = HashSet::new();
1797 assert_eq!(
1798 authorized.authorize(&signers, &staker, StakeAuthorize::Staker, None),
1799 Err(InstructionError::MissingRequiredSignature)
1800 );
1801 signers.insert(staker);
1802 assert_eq!(
1803 authorized.authorize(&signers, &staker, StakeAuthorize::Staker, None),
1804 Ok(())
1805 );
1806 }
1807
1808 #[test]
1809 fn test_authorized_authorize_with_custodian() {
1810 let staker = solana_sdk::pubkey::new_rand();
1811 let custodian = solana_sdk::pubkey::new_rand();
1812 let invalid_custodian = solana_sdk::pubkey::new_rand();
1813 let mut authorized = Authorized::auto(&staker);
1814 let mut signers = HashSet::new();
1815 signers.insert(staker);
1816
1817 let lockup = Lockup {
1818 epoch: 1,
1819 unix_timestamp: 1,
1820 custodian,
1821 };
1822 let clock = Clock {
1823 epoch: 0,
1824 unix_timestamp: 0,
1825 ..Clock::default()
1826 };
1827
1828 assert_eq!(
1831 authorized.authorize(&signers, &staker, StakeAuthorize::Withdrawer, None),
1832 Ok(())
1833 );
1834
1835 assert_eq!(
1837 authorized.authorize(
1838 &signers,
1839 &staker,
1840 StakeAuthorize::Withdrawer,
1841 Some((&Lockup::default(), &clock, None))
1842 ),
1843 Ok(())
1844 );
1845
1846 assert_eq!(
1848 authorized.authorize(
1849 &signers,
1850 &staker,
1851 StakeAuthorize::Withdrawer,
1852 Some((&Lockup::default(), &clock, Some(&invalid_custodian)))
1853 ),
1854 Ok(()) );
1856
1857 assert_eq!(
1859 authorized.authorize(
1860 &signers,
1861 &staker,
1862 StakeAuthorize::Withdrawer,
1863 Some((&lockup, &clock, Some(&invalid_custodian)))
1864 ),
1865 Err(StakeError::CustodianSignatureMissing.into()),
1866 );
1867
1868 signers.insert(invalid_custodian);
1869
1870 assert_eq!(
1872 authorized.authorize(
1873 &signers,
1874 &staker,
1875 StakeAuthorize::Withdrawer,
1876 Some((&Lockup::default(), &clock, Some(&invalid_custodian)))
1877 ),
1878 Ok(()) );
1880
1881 signers.insert(invalid_custodian);
1883 assert_eq!(
1884 authorized.authorize(
1885 &signers,
1886 &staker,
1887 StakeAuthorize::Withdrawer,
1888 Some((&lockup, &clock, Some(&invalid_custodian)))
1889 ),
1890 Err(StakeError::LockupInForce.into()), );
1892
1893 signers.remove(&invalid_custodian);
1894
1895 assert_eq!(
1897 authorized.authorize(
1898 &signers,
1899 &staker,
1900 StakeAuthorize::Withdrawer,
1901 Some((&lockup, &clock, None))
1902 ),
1903 Err(StakeError::CustodianMissing.into()),
1904 );
1905
1906 assert_eq!(
1908 authorized.authorize(
1909 &signers,
1910 &staker,
1911 StakeAuthorize::Withdrawer,
1912 Some((&lockup, &clock, Some(&custodian)))
1913 ),
1914 Err(StakeError::CustodianSignatureMissing.into()),
1915 );
1916
1917 signers.insert(custodian);
1919 assert_eq!(
1920 authorized.authorize(
1921 &signers,
1922 &staker,
1923 StakeAuthorize::Withdrawer,
1924 Some((&lockup, &clock, Some(&custodian)))
1925 ),
1926 Ok(())
1927 );
1928 }
1929
1930 #[test]
1931 fn test_stake_state_stake_from_fail() {
1932 let mut stake_account = AccountSharedData::new(0, StakeState::size_of(), &id());
1933
1934 stake_account
1935 .set_state(&StakeState::default())
1936 .expect("set_state");
1937
1938 assert_eq!(stake_from(&stake_account), None);
1939 }
1940
1941 #[test]
1942 fn test_stake_is_bootstrap() {
1943 assert!(Delegation {
1944 activation_epoch: std::u64::MAX,
1945 ..Delegation::default()
1946 }
1947 .is_bootstrap());
1948 assert!(!Delegation {
1949 activation_epoch: 0,
1950 ..Delegation::default()
1951 }
1952 .is_bootstrap());
1953 }
1954
1955 #[test]
1956 fn test_stake_activating_and_deactivating() {
1957 let stake = Delegation {
1958 stake: 1_000,
1959 activation_epoch: 0, deactivation_epoch: 5,
1961 ..Delegation::default()
1962 };
1963
1964 let increment = (1_000_f64 * stake.warmup_cooldown_rate) as u64;
1966
1967 let mut stake_history = StakeHistory::default();
1968 assert_eq!(
1970 stake.stake_activating_and_deactivating(stake.activation_epoch, Some(&stake_history),),
1971 StakeActivationStatus::with_effective_and_activating(0, stake.stake),
1972 );
1973 for epoch in stake.activation_epoch + 1..stake.deactivation_epoch {
1974 assert_eq!(
1975 stake.stake_activating_and_deactivating(epoch, Some(&stake_history)),
1976 StakeActivationStatus::with_effective(stake.stake),
1977 );
1978 }
1979 assert_eq!(
1981 stake
1982 .stake_activating_and_deactivating(stake.deactivation_epoch, Some(&stake_history),),
1983 StakeActivationStatus::with_deactivating(stake.stake),
1984 );
1985 assert_eq!(
1987 stake.stake_activating_and_deactivating(
1988 stake.deactivation_epoch + 1,
1989 Some(&stake_history),
1990 ),
1991 StakeActivationStatus::default(),
1992 );
1993
1994 stake_history.add(
1995 0u64, StakeHistoryEntry {
1997 effective: 1_000,
1998 ..StakeHistoryEntry::default()
1999 },
2000 );
2001 assert_eq!(
2003 stake.stake_activating_and_deactivating(1, Some(&stake_history)),
2004 StakeActivationStatus::with_effective_and_activating(0, stake.stake),
2005 );
2006
2007 stake_history.add(
2008 0u64, StakeHistoryEntry {
2010 effective: 1_000,
2011 activating: 1_000,
2012 ..StakeHistoryEntry::default()
2013 },
2014 );
2016 assert_eq!(
2018 stake.stake_activating_and_deactivating(2, Some(&stake_history)),
2019 StakeActivationStatus::with_effective_and_activating(
2020 increment,
2021 stake.stake - increment
2022 ),
2023 );
2024
2025 let mut stake_history = StakeHistory::default();
2027
2028 stake_history.add(
2029 stake.deactivation_epoch, StakeHistoryEntry {
2031 effective: 1_000,
2032 ..StakeHistoryEntry::default()
2033 },
2034 );
2035 assert_eq!(
2037 stake.stake_activating_and_deactivating(
2038 stake.deactivation_epoch + 1,
2039 Some(&stake_history),
2040 ),
2041 StakeActivationStatus::with_deactivating(stake.stake),
2042 );
2043
2044 stake_history.add(
2046 stake.deactivation_epoch, StakeHistoryEntry {
2048 effective: 1_000,
2049 deactivating: 1_000,
2050 ..StakeHistoryEntry::default()
2051 },
2052 );
2053 assert_eq!(
2055 stake.stake_activating_and_deactivating(
2056 stake.deactivation_epoch + 2,
2057 Some(&stake_history),
2058 ),
2059 StakeActivationStatus::with_deactivating(stake.stake - increment),
2061 );
2062 }
2063
2064 mod same_epoch_activation_then_deactivation {
2065 use super::*;
2066
2067 enum OldDeactivationBehavior {
2068 Stuck,
2069 Slow,
2070 }
2071
2072 fn do_test(
2073 old_behavior: OldDeactivationBehavior,
2074 expected_stakes: &[StakeActivationStatus],
2075 ) {
2076 let cluster_stake = 1_000;
2077 let activating_stake = 10_000;
2078 let some_stake = 700;
2079 let some_epoch = 0;
2080
2081 let stake = Delegation {
2082 stake: some_stake,
2083 activation_epoch: some_epoch,
2084 deactivation_epoch: some_epoch,
2085 ..Delegation::default()
2086 };
2087
2088 let mut stake_history = StakeHistory::default();
2089 let cluster_deactivation_at_stake_modified_epoch = match old_behavior {
2090 OldDeactivationBehavior::Stuck => 0,
2091 OldDeactivationBehavior::Slow => 1000,
2092 };
2093
2094 let stake_history_entries = vec![
2095 (
2096 cluster_stake,
2097 activating_stake,
2098 cluster_deactivation_at_stake_modified_epoch,
2099 ),
2100 (cluster_stake, activating_stake, 1000),
2101 (cluster_stake, activating_stake, 1000),
2102 (cluster_stake, activating_stake, 100),
2103 (cluster_stake, activating_stake, 100),
2104 (cluster_stake, activating_stake, 100),
2105 (cluster_stake, activating_stake, 100),
2106 ];
2107
2108 for (epoch, (effective, activating, deactivating)) in
2109 stake_history_entries.into_iter().enumerate()
2110 {
2111 stake_history.add(
2112 epoch as Epoch,
2113 StakeHistoryEntry {
2114 effective,
2115 activating,
2116 deactivating,
2117 },
2118 );
2119 }
2120
2121 assert_eq!(
2122 expected_stakes,
2123 (0..expected_stakes.len())
2124 .map(|epoch| stake
2125 .stake_activating_and_deactivating(epoch as u64, Some(&stake_history),))
2126 .collect::<Vec<_>>()
2127 );
2128 }
2129
2130 #[test]
2131 fn test_new_behavior_previously_slow() {
2132 do_test(
2136 OldDeactivationBehavior::Slow,
2137 &[
2138 StakeActivationStatus::default(),
2139 StakeActivationStatus::default(),
2140 StakeActivationStatus::default(),
2141 StakeActivationStatus::default(),
2142 StakeActivationStatus::default(),
2143 StakeActivationStatus::default(),
2144 StakeActivationStatus::default(),
2145 ],
2146 );
2147 }
2148
2149 #[test]
2150 fn test_new_behavior_previously_stuck() {
2151 do_test(
2155 OldDeactivationBehavior::Stuck,
2156 &[
2157 StakeActivationStatus::default(),
2158 StakeActivationStatus::default(),
2159 StakeActivationStatus::default(),
2160 StakeActivationStatus::default(),
2161 StakeActivationStatus::default(),
2162 StakeActivationStatus::default(),
2163 StakeActivationStatus::default(),
2164 ],
2165 );
2166 }
2167 }
2168
2169 #[test]
2170 fn test_inflation_and_slashing_with_activating_and_deactivating_stake() {
2171 let (delegated_stake, mut stake, stake_history) = {
2173 let cluster_stake = 1_000;
2174 let delegated_stake = 700;
2175
2176 let stake = Delegation {
2177 stake: delegated_stake,
2178 activation_epoch: 0,
2179 deactivation_epoch: 4,
2180 ..Delegation::default()
2181 };
2182
2183 let mut stake_history = StakeHistory::default();
2184 stake_history.add(
2185 0,
2186 StakeHistoryEntry {
2187 effective: cluster_stake,
2188 activating: delegated_stake,
2189 ..StakeHistoryEntry::default()
2190 },
2191 );
2192 let newly_effective_at_epoch1 = (cluster_stake as f64 * 0.25) as u64;
2193 assert_eq!(newly_effective_at_epoch1, 250);
2194 stake_history.add(
2195 1,
2196 StakeHistoryEntry {
2197 effective: cluster_stake + newly_effective_at_epoch1,
2198 activating: delegated_stake - newly_effective_at_epoch1,
2199 ..StakeHistoryEntry::default()
2200 },
2201 );
2202 let newly_effective_at_epoch2 =
2203 ((cluster_stake + newly_effective_at_epoch1) as f64 * 0.25) as u64;
2204 assert_eq!(newly_effective_at_epoch2, 312);
2205 stake_history.add(
2206 2,
2207 StakeHistoryEntry {
2208 effective: cluster_stake
2209 + newly_effective_at_epoch1
2210 + newly_effective_at_epoch2,
2211 activating: delegated_stake
2212 - newly_effective_at_epoch1
2213 - newly_effective_at_epoch2,
2214 ..StakeHistoryEntry::default()
2215 },
2216 );
2217 stake_history.add(
2218 3,
2219 StakeHistoryEntry {
2220 effective: cluster_stake + delegated_stake,
2221 ..StakeHistoryEntry::default()
2222 },
2223 );
2224 stake_history.add(
2225 4,
2226 StakeHistoryEntry {
2227 effective: cluster_stake + delegated_stake,
2228 deactivating: delegated_stake,
2229 ..StakeHistoryEntry::default()
2230 },
2231 );
2232 let newly_not_effective_stake_at_epoch5 =
2233 ((cluster_stake + delegated_stake) as f64 * 0.25) as u64;
2234 assert_eq!(newly_not_effective_stake_at_epoch5, 425);
2235 stake_history.add(
2236 5,
2237 StakeHistoryEntry {
2238 effective: cluster_stake + delegated_stake
2239 - newly_not_effective_stake_at_epoch5,
2240 deactivating: delegated_stake - newly_not_effective_stake_at_epoch5,
2241 ..StakeHistoryEntry::default()
2242 },
2243 );
2244
2245 (delegated_stake, stake, stake_history)
2246 };
2247
2248 let calculate_each_staking_status = |stake: &Delegation, epoch_count: usize| -> Vec<_> {
2250 (0..epoch_count)
2251 .map(|epoch| {
2252 stake.stake_activating_and_deactivating(epoch as u64, Some(&stake_history))
2253 })
2254 .collect::<Vec<_>>()
2255 };
2256 let adjust_staking_status = |rate: f64, status: &[StakeActivationStatus]| {
2257 status
2258 .iter()
2259 .map(|entry| StakeActivationStatus {
2260 effective: (entry.effective as f64 * rate) as u64,
2261 activating: (entry.activating as f64 * rate) as u64,
2262 deactivating: (entry.deactivating as f64 * rate) as u64,
2263 })
2264 .collect::<Vec<_>>()
2265 };
2266
2267 let expected_staking_status_transition = vec![
2268 StakeActivationStatus::with_effective_and_activating(0, 700),
2269 StakeActivationStatus::with_effective_and_activating(250, 450),
2270 StakeActivationStatus::with_effective_and_activating(562, 138),
2271 StakeActivationStatus::with_effective(700),
2272 StakeActivationStatus::with_deactivating(700),
2273 StakeActivationStatus::with_deactivating(275),
2274 StakeActivationStatus::default(),
2275 ];
2276 let expected_staking_status_transition_base = vec![
2277 StakeActivationStatus::with_effective_and_activating(0, 700),
2278 StakeActivationStatus::with_effective_and_activating(250, 450),
2279 StakeActivationStatus::with_effective_and_activating(562, 138 + 1), StakeActivationStatus::with_effective(700),
2281 StakeActivationStatus::with_deactivating(700),
2282 StakeActivationStatus::with_deactivating(275 + 1), StakeActivationStatus::default(),
2284 ];
2285
2286 assert_eq!(
2288 expected_staking_status_transition,
2289 calculate_each_staking_status(&stake, expected_staking_status_transition.len())
2290 );
2291
2292 let rate = 1.10;
2294 stake.stake = (delegated_stake as f64 * rate) as u64;
2295 let expected_staking_status_transition =
2296 adjust_staking_status(rate, &expected_staking_status_transition_base);
2297
2298 assert_eq!(
2299 expected_staking_status_transition,
2300 calculate_each_staking_status(&stake, expected_staking_status_transition_base.len()),
2301 );
2302
2303 let rate = 0.5;
2305 stake.stake = (delegated_stake as f64 * rate) as u64;
2306 let expected_staking_status_transition =
2307 adjust_staking_status(rate, &expected_staking_status_transition_base);
2308
2309 assert_eq!(
2310 expected_staking_status_transition,
2311 calculate_each_staking_status(&stake, expected_staking_status_transition_base.len()),
2312 );
2313 }
2314
2315 #[test]
2316 fn test_stop_activating_after_deactivation() {
2317 let stake = Delegation {
2318 stake: 1_000,
2319 activation_epoch: 0,
2320 deactivation_epoch: 3,
2321 ..Delegation::default()
2322 };
2323
2324 let base_stake = 1_000;
2325 let mut stake_history = StakeHistory::default();
2326 let mut effective = base_stake;
2327 let other_activation = 100;
2328 let mut other_activations = vec![0];
2329
2330 for epoch in 0..=stake.deactivation_epoch + 1 {
2334 let (activating, deactivating) = if epoch < stake.deactivation_epoch {
2335 (stake.stake + base_stake - effective, 0)
2336 } else {
2337 let other_activation_sum: u64 = other_activations.iter().sum();
2338 let deactivating = effective - base_stake - other_activation_sum;
2339 (other_activation, deactivating)
2340 };
2341
2342 stake_history.add(
2343 epoch,
2344 StakeHistoryEntry {
2345 effective,
2346 activating,
2347 deactivating,
2348 },
2349 );
2350
2351 let effective_rate_limited = (effective as f64 * stake.warmup_cooldown_rate) as u64;
2352 if epoch < stake.deactivation_epoch {
2353 effective += effective_rate_limited.min(activating);
2354 other_activations.push(0);
2355 } else {
2356 effective -= effective_rate_limited.min(deactivating);
2357 effective += other_activation;
2358 other_activations.push(other_activation);
2359 }
2360 }
2361
2362 for epoch in 0..=stake.deactivation_epoch + 1 {
2363 let history = stake_history.get(epoch).unwrap();
2364 let other_activations: u64 = other_activations[..=epoch as usize].iter().sum();
2365 let expected_stake = history.effective - base_stake - other_activations;
2366 let (expected_activating, expected_deactivating) = if epoch < stake.deactivation_epoch {
2367 (history.activating, 0)
2368 } else {
2369 (0, history.deactivating)
2370 };
2371 assert_eq!(
2372 stake.stake_activating_and_deactivating(epoch, Some(&stake_history)),
2373 StakeActivationStatus {
2374 effective: expected_stake,
2375 activating: expected_activating,
2376 deactivating: expected_deactivating,
2377 },
2378 );
2379 }
2380 }
2381
2382 #[test]
2383 fn test_stake_warmup_cooldown_sub_integer_moves() {
2384 let delegations = [Delegation {
2385 stake: 2,
2386 activation_epoch: 0, deactivation_epoch: 5,
2388 ..Delegation::default()
2389 }];
2390 let epochs = 7;
2392 let bootstrap = (delegations[0].warmup_cooldown_rate * 100.0 / 2.0) as u64;
2395 let stake_history =
2396 create_stake_history_from_delegations(Some(bootstrap), 0..epochs, &delegations);
2397 let mut max_stake = 0;
2398 let mut min_stake = 2;
2399
2400 for epoch in 0..epochs {
2401 let stake = delegations
2402 .iter()
2403 .map(|delegation| delegation.stake(epoch, Some(&stake_history)))
2404 .sum::<u64>();
2405 max_stake = max_stake.max(stake);
2406 min_stake = min_stake.min(stake);
2407 }
2408 assert_eq!(max_stake, 2);
2409 assert_eq!(min_stake, 0);
2410 }
2411
2412 #[test]
2413 fn test_stake_warmup_cooldown() {
2414 let delegations = [
2415 Delegation {
2416 stake: 1_000,
2418 activation_epoch: std::u64::MAX,
2419 ..Delegation::default()
2420 },
2421 Delegation {
2422 stake: 1_000,
2423 activation_epoch: 0,
2424 deactivation_epoch: 9,
2425 ..Delegation::default()
2426 },
2427 Delegation {
2428 stake: 1_000,
2429 activation_epoch: 1,
2430 deactivation_epoch: 6,
2431 ..Delegation::default()
2432 },
2433 Delegation {
2434 stake: 1_000,
2435 activation_epoch: 2,
2436 deactivation_epoch: 5,
2437 ..Delegation::default()
2438 },
2439 Delegation {
2440 stake: 1_000,
2441 activation_epoch: 2,
2442 deactivation_epoch: 4,
2443 ..Delegation::default()
2444 },
2445 Delegation {
2446 stake: 1_000,
2447 activation_epoch: 4,
2448 deactivation_epoch: 4,
2449 ..Delegation::default()
2450 },
2451 ];
2452 let epochs = 20;
2457
2458 let stake_history = create_stake_history_from_delegations(None, 0..epochs, &delegations);
2459
2460 let mut prev_total_effective_stake = delegations
2461 .iter()
2462 .map(|delegation| delegation.stake(0, Some(&stake_history)))
2463 .sum::<u64>();
2464
2465 for epoch in 1..epochs {
2468 let total_effective_stake = delegations
2469 .iter()
2470 .map(|delegation| delegation.stake(epoch, Some(&stake_history)))
2471 .sum::<u64>();
2472
2473 let delta = if total_effective_stake > prev_total_effective_stake {
2474 total_effective_stake - prev_total_effective_stake
2475 } else {
2476 prev_total_effective_stake - total_effective_stake
2477 };
2478
2479 assert!(
2485 delta
2486 <= ((prev_total_effective_stake as f64 * Config::default().warmup_cooldown_rate) as u64)
2487 .max(1)
2488 );
2489
2490 prev_total_effective_stake = total_effective_stake;
2491 }
2492 }
2493
2494 #[test]
2495 fn test_stake_state_redeem_rewards() {
2496 let mut vote_state = VoteState::default();
2497 let stake_lamports = 1;
2500 let mut stake = new_stake(
2501 stake_lamports,
2502 &Pubkey::default(),
2503 &vote_state,
2504 std::u64::MAX,
2505 &Config::default(),
2506 );
2507
2508 assert_eq!(
2510 None,
2511 redeem_stake_rewards(
2512 0,
2513 &mut stake,
2514 &PointValue {
2515 rewards: 1_000_000_000,
2516 points: 1
2517 },
2518 &vote_state,
2519 None,
2520 null_tracer(),
2521 true,
2522 )
2523 );
2524
2525 vote_state.increment_credits(0, 1);
2527 vote_state.increment_credits(0, 1);
2528
2529 assert_eq!(
2531 Some((stake_lamports * 2, 0)),
2532 redeem_stake_rewards(
2533 0,
2534 &mut stake,
2535 &PointValue {
2536 rewards: 1,
2537 points: 1
2538 },
2539 &vote_state,
2540 None,
2541 null_tracer(),
2542 true,
2543 )
2544 );
2545
2546 assert_eq!(
2547 stake.delegation.stake,
2548 stake_lamports + (stake_lamports * 2)
2549 );
2550 assert_eq!(stake.credits_observed, 2);
2551 }
2552
2553 #[test]
2554 fn test_stake_state_calculate_points_with_typical_values() {
2555 let mut vote_state = VoteState::default();
2556
2557 let stake = new_stake(
2560 native_token::sol_to_lamports(10_000_000f64),
2561 &Pubkey::default(),
2562 &vote_state,
2563 std::u64::MAX,
2564 &Config::default(),
2565 );
2566
2567 assert_eq!(
2569 None,
2570 calculate_stake_rewards(
2571 0,
2572 &stake,
2573 &PointValue {
2574 rewards: 1_000_000_000,
2575 points: 1
2576 },
2577 &vote_state,
2578 None,
2579 null_tracer(),
2580 true,
2581 )
2582 );
2583
2584 let epoch_slots: u128 = 14 * 24 * 3600 * 160;
2585 for _ in 0..epoch_slots {
2588 vote_state.increment_credits(0, 1);
2589 }
2590
2591 assert_eq!(
2593 u128::from(stake.delegation.stake) * epoch_slots,
2594 calculate_stake_points(&stake, &vote_state, None, null_tracer())
2595 );
2596 }
2597
2598 #[test]
2599 fn test_stake_state_calculate_rewards() {
2600 let mut vote_state = VoteState::default();
2601 let mut stake = new_stake(
2604 1,
2605 &Pubkey::default(),
2606 &vote_state,
2607 std::u64::MAX,
2608 &Config::default(),
2609 );
2610
2611 assert_eq!(
2613 None,
2614 calculate_stake_rewards(
2615 0,
2616 &stake,
2617 &PointValue {
2618 rewards: 1_000_000_000,
2619 points: 1
2620 },
2621 &vote_state,
2622 None,
2623 null_tracer(),
2624 true,
2625 )
2626 );
2627
2628 vote_state.increment_credits(0, 1);
2630 vote_state.increment_credits(0, 1);
2631
2632 assert_eq!(
2634 Some(CalculatedStakeRewards {
2635 staker_rewards: stake.delegation.stake * 2,
2636 voter_rewards: 0,
2637 new_credits_observed: 2,
2638 }),
2639 calculate_stake_rewards(
2640 0,
2641 &stake,
2642 &PointValue {
2643 rewards: 2,
2644 points: 2 },
2646 &vote_state,
2647 None,
2648 null_tracer(),
2649 true,
2650 )
2651 );
2652
2653 stake.credits_observed = 1;
2654 assert_eq!(
2656 Some(CalculatedStakeRewards {
2657 staker_rewards: stake.delegation.stake,
2658 voter_rewards: 0,
2659 new_credits_observed: 2,
2660 }),
2661 calculate_stake_rewards(
2662 0,
2663 &stake,
2664 &PointValue {
2665 rewards: 1,
2666 points: 1
2667 },
2668 &vote_state,
2669 None,
2670 null_tracer(),
2671 true,
2672 )
2673 );
2674
2675 vote_state.increment_credits(1, 1);
2677
2678 stake.credits_observed = 2;
2679 assert_eq!(
2681 Some(CalculatedStakeRewards {
2682 staker_rewards: stake.delegation.stake,
2683 voter_rewards: 0,
2684 new_credits_observed: 3,
2685 }),
2686 calculate_stake_rewards(
2687 1,
2688 &stake,
2689 &PointValue {
2690 rewards: 2,
2691 points: 2
2692 },
2693 &vote_state,
2694 None,
2695 null_tracer(),
2696 true,
2697 )
2698 );
2699
2700 vote_state.increment_credits(2, 1);
2702 assert_eq!(
2704 Some(CalculatedStakeRewards {
2705 staker_rewards: stake.delegation.stake * 2,
2706 voter_rewards: 0,
2707 new_credits_observed: 4,
2708 }),
2709 calculate_stake_rewards(
2710 2,
2711 &stake,
2712 &PointValue {
2713 rewards: 2,
2714 points: 2
2715 },
2716 &vote_state,
2717 None,
2718 null_tracer(),
2719 true,
2720 )
2721 );
2722
2723 stake.credits_observed = 0;
2724 assert_eq!(
2727 Some(CalculatedStakeRewards {
2728 staker_rewards: stake.delegation.stake * 2 + stake.delegation.stake + stake.delegation.stake, voter_rewards: 0,
2732 new_credits_observed: 4,
2733 }),
2734 calculate_stake_rewards(
2735 2,
2736 &stake,
2737 &PointValue {
2738 rewards: 4,
2739 points: 4
2740 },
2741 &vote_state,
2742 None,
2743 null_tracer(),
2744 true,
2745 )
2746 );
2747
2748 vote_state.commission = 1;
2751 assert_eq!(
2752 None, calculate_stake_rewards(
2754 2,
2755 &stake,
2756 &PointValue {
2757 rewards: 4,
2758 points: 4
2759 },
2760 &vote_state,
2761 None,
2762 null_tracer(),
2763 true,
2764 )
2765 );
2766 vote_state.commission = 99;
2767 assert_eq!(
2768 None, calculate_stake_rewards(
2770 2,
2771 &stake,
2772 &PointValue {
2773 rewards: 4,
2774 points: 4
2775 },
2776 &vote_state,
2777 None,
2778 null_tracer(),
2779 true,
2780 )
2781 );
2782
2783 assert_eq!(
2787 Some(CalculatedStakeRewards {
2788 staker_rewards: 0,
2789 voter_rewards: 0,
2790 new_credits_observed: 4,
2791 }),
2792 calculate_stake_rewards(
2793 2,
2794 &stake,
2795 &PointValue {
2796 rewards: 0,
2797 points: 4
2798 },
2799 &vote_state,
2800 None,
2801 null_tracer(),
2802 true,
2803 )
2804 );
2805
2806 stake.credits_observed = 4;
2809 assert_eq!(
2810 Some(CalculatedStakeRewards {
2811 staker_rewards: 0,
2812 voter_rewards: 0,
2813 new_credits_observed: 4,
2814 }),
2815 calculate_stake_rewards(
2816 2,
2817 &stake,
2818 &PointValue {
2819 rewards: 0,
2820 points: 4
2821 },
2822 &vote_state,
2823 None,
2824 null_tracer(),
2825 true,
2826 )
2827 );
2828
2829 assert_eq!(
2830 CalculatedStakePoints {
2831 points: 0,
2832 new_credits_observed: 4,
2833 force_credits_update_with_skipped_reward: false,
2834 },
2835 calculate_stake_points_and_credits(&stake, &vote_state, None, null_tracer(), true)
2836 );
2837
2838 stake.credits_observed = 1000;
2841 assert_eq!(
2843 CalculatedStakePoints {
2844 points: 0,
2845 new_credits_observed: 1000,
2846 force_credits_update_with_skipped_reward: false,
2847 },
2848 calculate_stake_points_and_credits(&stake, &vote_state, None, null_tracer(), false)
2849 );
2850 assert_eq!(
2852 CalculatedStakePoints {
2853 points: 0,
2854 new_credits_observed: 4,
2855 force_credits_update_with_skipped_reward: true,
2856 },
2857 calculate_stake_points_and_credits(&stake, &vote_state, None, null_tracer(), true)
2858 );
2859 stake.credits_observed = 4;
2861 assert_eq!(
2862 CalculatedStakePoints {
2863 points: 0,
2864 new_credits_observed: 4,
2865 force_credits_update_with_skipped_reward: false,
2866 },
2867 calculate_stake_points_and_credits(&stake, &vote_state, None, null_tracer(), true)
2868 );
2869
2870 vote_state.commission = 0;
2872 stake.credits_observed = 3;
2873 stake.delegation.activation_epoch = 1;
2874 assert_eq!(
2875 Some(CalculatedStakeRewards {
2876 staker_rewards: stake.delegation.stake, voter_rewards: 0,
2878 new_credits_observed: 4,
2879 }),
2880 calculate_stake_rewards(
2881 2,
2882 &stake,
2883 &PointValue {
2884 rewards: 1,
2885 points: 1
2886 },
2887 &vote_state,
2888 None,
2889 null_tracer(),
2890 true,
2891 )
2892 );
2893
2894 stake.delegation.activation_epoch = 2;
2897 stake.credits_observed = 3;
2898 assert_eq!(
2899 Some(CalculatedStakeRewards {
2900 staker_rewards: 0,
2901 voter_rewards: 0,
2902 new_credits_observed: 4,
2903 }),
2904 calculate_stake_rewards(
2905 2,
2906 &stake,
2907 &PointValue {
2908 rewards: 1,
2909 points: 1
2910 },
2911 &vote_state,
2912 None,
2913 null_tracer(),
2914 true,
2915 )
2916 );
2917 }
2918
2919 fn create_mock_tx_context() -> TransactionContext {
2920 TransactionContext::new(
2921 vec![(
2922 Rent::id(),
2923 create_account_shared_data_for_test(&Rent::default()),
2924 )],
2925 Some(Rent::default()),
2926 1,
2927 1,
2928 )
2929 }
2930
2931 #[test]
2932 fn test_lockup_is_expired() {
2933 let custodian = solana_sdk::pubkey::new_rand();
2934 let lockup = Lockup {
2935 epoch: 1,
2936 unix_timestamp: 1,
2937 custodian,
2938 };
2939 assert!(lockup.is_in_force(
2941 &Clock {
2942 epoch: 0,
2943 unix_timestamp: 0,
2944 ..Clock::default()
2945 },
2946 None
2947 ));
2948 assert!(lockup.is_in_force(
2950 &Clock {
2951 epoch: 2,
2952 unix_timestamp: 0,
2953 ..Clock::default()
2954 },
2955 None
2956 ));
2957 assert!(lockup.is_in_force(
2959 &Clock {
2960 epoch: 0,
2961 unix_timestamp: 2,
2962 ..Clock::default()
2963 },
2964 None
2965 ));
2966 assert!(!lockup.is_in_force(
2968 &Clock {
2969 epoch: 1,
2970 unix_timestamp: 1,
2971 ..Clock::default()
2972 },
2973 None
2974 ));
2975 assert!(!lockup.is_in_force(
2977 &Clock {
2978 epoch: 0,
2979 unix_timestamp: 0,
2980 ..Clock::default()
2981 },
2982 Some(&custodian),
2983 ));
2984 }
2985
2986 #[test]
2987 #[ignore]
2988 #[should_panic]
2989 fn test_dbg_stake_minimum_balance() {
2990 let minimum_balance = Rent::default().minimum_balance(StakeState::size_of());
2991 panic!(
2992 "stake minimum_balance: {} lamports, {} SOL",
2993 minimum_balance,
2994 minimum_balance as f64 / solana_sdk::native_token::LAMPORTS_PER_SOL as f64
2995 );
2996 }
2997
2998 #[test]
2999 fn test_calculate_lamports_per_byte_year() {
3000 let rent = Rent::default();
3001 let data_len = 200u64;
3002 let rent_exempt_reserve = rent.minimum_balance(data_len as usize);
3003 assert_eq!(
3004 calculate_split_rent_exempt_reserve(rent_exempt_reserve, data_len, data_len),
3005 rent_exempt_reserve
3006 );
3007
3008 let larger_data = 4008u64;
3009 let larger_rent_exempt_reserve = rent.minimum_balance(larger_data as usize);
3010 assert_eq!(
3011 calculate_split_rent_exempt_reserve(rent_exempt_reserve, data_len, larger_data),
3012 larger_rent_exempt_reserve
3013 );
3014 assert_eq!(
3015 calculate_split_rent_exempt_reserve(larger_rent_exempt_reserve, larger_data, data_len),
3016 rent_exempt_reserve
3017 );
3018
3019 let even_larger_data = solana_sdk::system_instruction::MAX_PERMITTED_DATA_LENGTH;
3020 let even_larger_rent_exempt_reserve = rent.minimum_balance(even_larger_data as usize);
3021 assert_eq!(
3022 calculate_split_rent_exempt_reserve(rent_exempt_reserve, data_len, even_larger_data),
3023 even_larger_rent_exempt_reserve
3024 );
3025 assert_eq!(
3026 calculate_split_rent_exempt_reserve(
3027 even_larger_rent_exempt_reserve,
3028 even_larger_data,
3029 data_len
3030 ),
3031 rent_exempt_reserve
3032 );
3033 }
3034
3035 #[test]
3036 fn test_things_can_merge() {
3037 let mut transaction_context =
3038 TransactionContext::new(Vec::new(), Some(Rent::default()), 1, 1);
3039 let invoke_context = InvokeContext::new_mock(&mut transaction_context, &[]);
3040 let good_stake = Stake {
3041 credits_observed: 4242,
3042 delegation: Delegation {
3043 voter_pubkey: Pubkey::new_unique(),
3044 stake: 424242424242,
3045 activation_epoch: 42,
3046 ..Delegation::default()
3047 },
3048 };
3049
3050 let identical = good_stake;
3051 assert!(
3052 MergeKind::active_stakes_can_merge(&invoke_context, &good_stake, &identical).is_ok()
3053 );
3054
3055 let bad_credits_observed = Stake {
3056 credits_observed: good_stake.credits_observed + 1,
3057 ..good_stake
3058 };
3059 assert!(MergeKind::active_stakes_can_merge(
3060 &invoke_context,
3061 &good_stake,
3062 &bad_credits_observed
3063 )
3064 .is_err());
3065
3066 let good_delegation = good_stake.delegation;
3067 let different_stake_ok = Delegation {
3068 stake: good_delegation.stake + 1,
3069 ..good_delegation
3070 };
3071 assert!(MergeKind::active_delegations_can_merge(
3072 &invoke_context,
3073 &good_delegation,
3074 &different_stake_ok
3075 )
3076 .is_ok());
3077
3078 let different_activation_epoch_ok = Delegation {
3079 activation_epoch: good_delegation.activation_epoch + 1,
3080 ..good_delegation
3081 };
3082 assert!(MergeKind::active_delegations_can_merge(
3083 &invoke_context,
3084 &good_delegation,
3085 &different_activation_epoch_ok
3086 )
3087 .is_ok());
3088
3089 let bad_voter = Delegation {
3090 voter_pubkey: Pubkey::new_unique(),
3091 ..good_delegation
3092 };
3093 assert!(MergeKind::active_delegations_can_merge(
3094 &invoke_context,
3095 &good_delegation,
3096 &bad_voter
3097 )
3098 .is_err());
3099
3100 let bad_warmup_cooldown_rate = Delegation {
3101 warmup_cooldown_rate: good_delegation.warmup_cooldown_rate + f64::EPSILON,
3102 ..good_delegation
3103 };
3104 assert!(MergeKind::active_delegations_can_merge(
3105 &invoke_context,
3106 &good_delegation,
3107 &bad_warmup_cooldown_rate
3108 )
3109 .is_err());
3110 assert!(MergeKind::active_delegations_can_merge(
3111 &invoke_context,
3112 &bad_warmup_cooldown_rate,
3113 &good_delegation
3114 )
3115 .is_err());
3116
3117 let bad_deactivation_epoch = Delegation {
3118 deactivation_epoch: 43,
3119 ..good_delegation
3120 };
3121 assert!(MergeKind::active_delegations_can_merge(
3122 &invoke_context,
3123 &good_delegation,
3124 &bad_deactivation_epoch
3125 )
3126 .is_err());
3127 assert!(MergeKind::active_delegations_can_merge(
3128 &invoke_context,
3129 &bad_deactivation_epoch,
3130 &good_delegation
3131 )
3132 .is_err());
3133 }
3134
3135 #[test]
3136 fn test_metas_can_merge() {
3137 let mut transaction_context =
3138 TransactionContext::new(Vec::new(), Some(Rent::default()), 1, 1);
3139 let invoke_context = InvokeContext::new_mock(&mut transaction_context, &[]);
3140 assert!(MergeKind::metas_can_merge(
3142 &invoke_context,
3143 &Meta::default(),
3144 &Meta::default(),
3145 &Clock::default()
3146 )
3147 .is_ok());
3148
3149 let mismatched_rent_exempt_reserve_ok = Meta {
3150 rent_exempt_reserve: 42,
3151 ..Meta::default()
3152 };
3153 assert_ne!(
3154 mismatched_rent_exempt_reserve_ok.rent_exempt_reserve,
3155 Meta::default().rent_exempt_reserve,
3156 );
3157 assert!(MergeKind::metas_can_merge(
3158 &invoke_context,
3159 &Meta::default(),
3160 &mismatched_rent_exempt_reserve_ok,
3161 &Clock::default()
3162 )
3163 .is_ok());
3164 assert!(MergeKind::metas_can_merge(
3165 &invoke_context,
3166 &mismatched_rent_exempt_reserve_ok,
3167 &Meta::default(),
3168 &Clock::default()
3169 )
3170 .is_ok());
3171
3172 let mismatched_authorized_fails = Meta {
3173 authorized: Authorized {
3174 staker: Pubkey::new_unique(),
3175 withdrawer: Pubkey::new_unique(),
3176 },
3177 ..Meta::default()
3178 };
3179 assert_ne!(
3180 mismatched_authorized_fails.authorized,
3181 Meta::default().authorized,
3182 );
3183 assert!(MergeKind::metas_can_merge(
3184 &invoke_context,
3185 &Meta::default(),
3186 &mismatched_authorized_fails,
3187 &Clock::default()
3188 )
3189 .is_err());
3190 assert!(MergeKind::metas_can_merge(
3191 &invoke_context,
3192 &mismatched_authorized_fails,
3193 &Meta::default(),
3194 &Clock::default()
3195 )
3196 .is_err());
3197
3198 let lockup1_timestamp = 42;
3199 let lockup2_timestamp = 4242;
3200 let lockup1_epoch = 4;
3201 let lockup2_epoch = 42;
3202 let metas_with_lockup1 = Meta {
3203 lockup: Lockup {
3204 unix_timestamp: lockup1_timestamp,
3205 epoch: lockup1_epoch,
3206 custodian: Pubkey::new_unique(),
3207 },
3208 ..Meta::default()
3209 };
3210 let metas_with_lockup2 = Meta {
3211 lockup: Lockup {
3212 unix_timestamp: lockup2_timestamp,
3213 epoch: lockup2_epoch,
3214 custodian: Pubkey::new_unique(),
3215 },
3216 ..Meta::default()
3217 };
3218
3219 assert_ne!(metas_with_lockup1.lockup, Meta::default().lockup);
3221 assert!(MergeKind::metas_can_merge(
3222 &invoke_context,
3223 &metas_with_lockup1,
3224 &metas_with_lockup2,
3225 &Clock::default()
3226 )
3227 .is_err());
3228 assert!(MergeKind::metas_can_merge(
3229 &invoke_context,
3230 &metas_with_lockup2,
3231 &metas_with_lockup1,
3232 &Clock::default()
3233 )
3234 .is_err());
3235
3236 let clock = Clock {
3237 epoch: lockup1_epoch + 1,
3238 unix_timestamp: lockup1_timestamp + 1,
3239 ..Clock::default()
3240 };
3241
3242 assert_ne!(metas_with_lockup1.lockup, Meta::default().lockup);
3244 assert!(MergeKind::metas_can_merge(
3245 &invoke_context,
3246 &metas_with_lockup1,
3247 &metas_with_lockup2,
3248 &clock
3249 )
3250 .is_err());
3251 assert!(MergeKind::metas_can_merge(
3252 &invoke_context,
3253 &metas_with_lockup2,
3254 &metas_with_lockup1,
3255 &clock
3256 )
3257 .is_err());
3258
3259 let clock = Clock {
3260 epoch: lockup2_epoch + 1,
3261 unix_timestamp: lockup2_timestamp + 1,
3262 ..Clock::default()
3263 };
3264
3265 assert_ne!(metas_with_lockup1.lockup, Meta::default().lockup);
3267 assert!(MergeKind::metas_can_merge(
3268 &invoke_context,
3269 &metas_with_lockup1,
3270 &metas_with_lockup2,
3271 &clock
3272 )
3273 .is_ok());
3274 assert!(MergeKind::metas_can_merge(
3275 &invoke_context,
3276 &metas_with_lockup2,
3277 &metas_with_lockup1,
3278 &clock
3279 )
3280 .is_ok());
3281 }
3282
3283 #[test]
3284 fn test_merge_kind_get_if_mergeable() {
3285 let mut transaction_context =
3286 TransactionContext::new(Vec::new(), Some(Rent::default()), 1, 1);
3287 let invoke_context = InvokeContext::new_mock(&mut transaction_context, &[]);
3288 let authority_pubkey = Pubkey::new_unique();
3289 let initial_lamports = 4242424242;
3290 let rent = Rent::default();
3291 let rent_exempt_reserve = rent.minimum_balance(StakeState::size_of());
3292 let stake_lamports = rent_exempt_reserve + initial_lamports;
3293
3294 let meta = Meta {
3295 rent_exempt_reserve,
3296 ..Meta::auto(&authority_pubkey)
3297 };
3298 let mut stake_account = AccountSharedData::new_data_with_space(
3299 stake_lamports,
3300 &StakeState::Uninitialized,
3301 StakeState::size_of(),
3302 &id(),
3303 )
3304 .expect("stake_account");
3305 let mut clock = Clock::default();
3306 let mut stake_history = StakeHistory::default();
3307
3308 assert_eq!(
3310 MergeKind::get_if_mergeable(
3311 &invoke_context,
3312 &stake_account.state().unwrap(),
3313 stake_account.lamports(),
3314 &clock,
3315 &stake_history
3316 )
3317 .unwrap_err(),
3318 InstructionError::InvalidAccountData
3319 );
3320
3321 stake_account.set_state(&StakeState::RewardsPool).unwrap();
3323 assert_eq!(
3324 MergeKind::get_if_mergeable(
3325 &invoke_context,
3326 &stake_account.state().unwrap(),
3327 stake_account.lamports(),
3328 &clock,
3329 &stake_history
3330 )
3331 .unwrap_err(),
3332 InstructionError::InvalidAccountData
3333 );
3334
3335 stake_account
3337 .set_state(&StakeState::Initialized(meta))
3338 .unwrap();
3339 assert_eq!(
3340 MergeKind::get_if_mergeable(
3341 &invoke_context,
3342 &stake_account.state().unwrap(),
3343 stake_account.lamports(),
3344 &clock,
3345 &stake_history
3346 )
3347 .unwrap(),
3348 MergeKind::Inactive(meta, stake_lamports)
3349 );
3350
3351 clock.epoch = 0;
3352 let mut effective = 2 * initial_lamports;
3353 let mut activating = 0;
3354 let mut deactivating = 0;
3355 stake_history.add(
3356 clock.epoch,
3357 StakeHistoryEntry {
3358 effective,
3359 activating,
3360 deactivating,
3361 },
3362 );
3363
3364 clock.epoch += 1;
3365 activating = initial_lamports;
3366 stake_history.add(
3367 clock.epoch,
3368 StakeHistoryEntry {
3369 effective,
3370 activating,
3371 deactivating,
3372 },
3373 );
3374
3375 let stake = Stake {
3376 delegation: Delegation {
3377 stake: initial_lamports,
3378 activation_epoch: 1,
3379 deactivation_epoch: 5,
3380 ..Delegation::default()
3381 },
3382 ..Stake::default()
3383 };
3384 stake_account
3385 .set_state(&StakeState::Stake(meta, stake))
3386 .unwrap();
3387 assert_eq!(
3389 MergeKind::get_if_mergeable(
3390 &invoke_context,
3391 &stake_account.state().unwrap(),
3392 stake_account.lamports(),
3393 &clock,
3394 &stake_history
3395 )
3396 .unwrap(),
3397 MergeKind::ActivationEpoch(meta, stake),
3398 );
3399
3400 loop {
3402 clock.epoch += 1;
3403 let delta =
3404 activating.min((effective as f64 * stake.delegation.warmup_cooldown_rate) as u64);
3405 effective += delta;
3406 activating -= delta;
3407 stake_history.add(
3408 clock.epoch,
3409 StakeHistoryEntry {
3410 effective,
3411 activating,
3412 deactivating,
3413 },
3414 );
3415 if activating == 0 {
3416 break;
3417 }
3418 assert_eq!(
3419 MergeKind::get_if_mergeable(
3420 &invoke_context,
3421 &stake_account.state().unwrap(),
3422 stake_account.lamports(),
3423 &clock,
3424 &stake_history
3425 )
3426 .unwrap_err(),
3427 InstructionError::from(StakeError::MergeTransientStake),
3428 );
3429 }
3430
3431 while clock.epoch < stake.delegation.deactivation_epoch - 1 {
3433 clock.epoch += 1;
3434 stake_history.add(
3435 clock.epoch,
3436 StakeHistoryEntry {
3437 effective,
3438 activating,
3439 deactivating,
3440 },
3441 );
3442 assert_eq!(
3443 MergeKind::get_if_mergeable(
3444 &invoke_context,
3445 &stake_account.state().unwrap(),
3446 stake_account.lamports(),
3447 &clock,
3448 &stake_history
3449 )
3450 .unwrap(),
3451 MergeKind::FullyActive(meta, stake),
3452 );
3453 }
3454
3455 clock.epoch += 1;
3456 deactivating = stake.delegation.stake;
3457 stake_history.add(
3458 clock.epoch,
3459 StakeHistoryEntry {
3460 effective,
3461 activating,
3462 deactivating,
3463 },
3464 );
3465 assert_eq!(
3467 MergeKind::get_if_mergeable(
3468 &invoke_context,
3469 &stake_account.state().unwrap(),
3470 stake_account.lamports(),
3471 &clock,
3472 &stake_history
3473 )
3474 .unwrap_err(),
3475 InstructionError::from(StakeError::MergeTransientStake),
3476 );
3477
3478 loop {
3480 clock.epoch += 1;
3481 let delta =
3482 deactivating.min((effective as f64 * stake.delegation.warmup_cooldown_rate) as u64);
3483 effective -= delta;
3484 deactivating -= delta;
3485 stake_history.add(
3486 clock.epoch,
3487 StakeHistoryEntry {
3488 effective,
3489 activating,
3490 deactivating,
3491 },
3492 );
3493 if deactivating == 0 {
3494 break;
3495 }
3496 assert_eq!(
3497 MergeKind::get_if_mergeable(
3498 &invoke_context,
3499 &stake_account.state().unwrap(),
3500 stake_account.lamports(),
3501 &clock,
3502 &stake_history
3503 )
3504 .unwrap_err(),
3505 InstructionError::from(StakeError::MergeTransientStake),
3506 );
3507 }
3508
3509 assert_eq!(
3511 MergeKind::get_if_mergeable(
3512 &invoke_context,
3513 &stake_account.state().unwrap(),
3514 stake_account.lamports(),
3515 &clock,
3516 &stake_history
3517 )
3518 .unwrap(),
3519 MergeKind::Inactive(meta, stake_lamports),
3520 );
3521 }
3522
3523 #[test]
3524 fn test_merge_kind_merge() {
3525 let mut transaction_context =
3526 TransactionContext::new(Vec::new(), Some(Rent::default()), 1, 1);
3527 let invoke_context = InvokeContext::new_mock(&mut transaction_context, &[]);
3528 let clock = Clock::default();
3529 let lamports = 424242;
3530 let meta = Meta {
3531 rent_exempt_reserve: 42,
3532 ..Meta::default()
3533 };
3534 let stake = Stake {
3535 delegation: Delegation {
3536 stake: 4242,
3537 ..Delegation::default()
3538 },
3539 ..Stake::default()
3540 };
3541 let inactive = MergeKind::Inactive(Meta::default(), lamports);
3542 let activation_epoch = MergeKind::ActivationEpoch(meta, stake);
3543 let fully_active = MergeKind::FullyActive(meta, stake);
3544
3545 assert_eq!(
3546 inactive
3547 .clone()
3548 .merge(&invoke_context, inactive.clone(), &clock)
3549 .unwrap(),
3550 None
3551 );
3552 assert_eq!(
3553 inactive
3554 .clone()
3555 .merge(&invoke_context, activation_epoch.clone(), &clock)
3556 .unwrap(),
3557 None
3558 );
3559 assert!(inactive
3560 .clone()
3561 .merge(&invoke_context, fully_active.clone(), &clock)
3562 .is_err());
3563 assert!(activation_epoch
3564 .clone()
3565 .merge(&invoke_context, fully_active.clone(), &clock)
3566 .is_err());
3567 assert!(fully_active
3568 .clone()
3569 .merge(&invoke_context, inactive.clone(), &clock)
3570 .is_err());
3571 assert!(fully_active
3572 .clone()
3573 .merge(&invoke_context, activation_epoch.clone(), &clock)
3574 .is_err());
3575
3576 let new_state = activation_epoch
3577 .clone()
3578 .merge(&invoke_context, inactive, &clock)
3579 .unwrap()
3580 .unwrap();
3581 let delegation = new_state.delegation().unwrap();
3582 assert_eq!(delegation.stake, stake.delegation.stake + lamports);
3583
3584 let new_state = activation_epoch
3585 .clone()
3586 .merge(&invoke_context, activation_epoch, &clock)
3587 .unwrap()
3588 .unwrap();
3589 let delegation = new_state.delegation().unwrap();
3590 assert_eq!(
3591 delegation.stake,
3592 2 * stake.delegation.stake + meta.rent_exempt_reserve
3593 );
3594
3595 let new_state = fully_active
3596 .clone()
3597 .merge(&invoke_context, fully_active, &clock)
3598 .unwrap()
3599 .unwrap();
3600 let delegation = new_state.delegation().unwrap();
3601 assert_eq!(delegation.stake, 2 * stake.delegation.stake);
3602 }
3603
3604 #[test]
3605 fn test_active_stake_merge() {
3606 let mut transaction_context = create_mock_tx_context();
3607 let invoke_context = InvokeContext::new_mock(&mut transaction_context, &[]);
3608 let clock = Clock::default();
3609 let delegation_a = 4_242_424_242u64;
3610 let delegation_b = 6_200_000_000u64;
3611 let credits_a = 124_521_000u64;
3612 let rent_exempt_reserve = 227_000_000u64;
3613 let meta = Meta {
3614 rent_exempt_reserve,
3615 ..Meta::default()
3616 };
3617 let stake_a = Stake {
3618 delegation: Delegation {
3619 stake: delegation_a,
3620 ..Delegation::default()
3621 },
3622 credits_observed: credits_a,
3623 };
3624 let stake_b = Stake {
3625 delegation: Delegation {
3626 stake: delegation_b,
3627 ..Delegation::default()
3628 },
3629 credits_observed: credits_a,
3630 };
3631
3632 let activation_epoch_a = MergeKind::ActivationEpoch(meta, stake_a);
3634 let activation_epoch_b = MergeKind::ActivationEpoch(meta, stake_b);
3635 let new_stake = activation_epoch_a
3636 .merge(&invoke_context, activation_epoch_b, &clock)
3637 .unwrap()
3638 .unwrap()
3639 .stake()
3640 .unwrap();
3641 assert_eq!(new_stake.credits_observed, credits_a);
3642 assert_eq!(
3643 new_stake.delegation.stake,
3644 delegation_a + delegation_b + rent_exempt_reserve
3645 );
3646
3647 let fully_active_a = MergeKind::FullyActive(meta, stake_a);
3649 let fully_active_b = MergeKind::FullyActive(meta, stake_b);
3650 let new_stake = fully_active_a
3651 .merge(&invoke_context, fully_active_b, &clock)
3652 .unwrap()
3653 .unwrap()
3654 .stake()
3655 .unwrap();
3656 assert_eq!(new_stake.credits_observed, credits_a);
3657 assert_eq!(new_stake.delegation.stake, delegation_a + delegation_b);
3658
3659 let credits_b = 125_124_521u64;
3661 let stake_b = Stake {
3662 delegation: Delegation {
3663 stake: delegation_b,
3664 ..Delegation::default()
3665 },
3666 credits_observed: credits_b,
3667 };
3668 let activation_epoch_a = MergeKind::ActivationEpoch(meta, stake_a);
3669 let activation_epoch_b = MergeKind::ActivationEpoch(meta, stake_b);
3670 let new_stake = activation_epoch_a
3671 .merge(&invoke_context, activation_epoch_b, &clock)
3672 .unwrap()
3673 .unwrap()
3674 .stake()
3675 .unwrap();
3676 assert_eq!(
3677 new_stake.credits_observed,
3678 (credits_a * delegation_a + credits_b * (delegation_b + rent_exempt_reserve))
3679 / (delegation_a + delegation_b + rent_exempt_reserve)
3680 + 1
3681 );
3682 assert_eq!(
3683 new_stake.delegation.stake,
3684 delegation_a + delegation_b + rent_exempt_reserve
3685 );
3686
3687 let fully_active_a = MergeKind::FullyActive(meta, stake_a);
3689 let fully_active_b = MergeKind::FullyActive(meta, stake_b);
3690 let new_stake = fully_active_a
3691 .merge(&invoke_context, fully_active_b, &clock)
3692 .unwrap()
3693 .unwrap()
3694 .stake()
3695 .unwrap();
3696 assert_eq!(
3697 new_stake.credits_observed,
3698 (credits_a * delegation_a + credits_b * delegation_b) / (delegation_a + delegation_b)
3699 + 1
3700 );
3701 assert_eq!(new_stake.delegation.stake, delegation_a + delegation_b);
3702
3703 let delegation = 1_000_000u64;
3705 let credits_a = 200_000_000u64;
3706 let credits_b = 100_000_000u64;
3707 let rent_exempt_reserve = 227_000_000u64;
3708 let meta = Meta {
3709 rent_exempt_reserve,
3710 ..Meta::default()
3711 };
3712 let stake_a = Stake {
3713 delegation: Delegation {
3714 stake: delegation,
3715 ..Delegation::default()
3716 },
3717 credits_observed: credits_a,
3718 };
3719 let stake_b = Stake {
3720 delegation: Delegation {
3721 stake: delegation,
3722 ..Delegation::default()
3723 },
3724 credits_observed: credits_b,
3725 };
3726 let fully_active_a = MergeKind::FullyActive(meta, stake_a);
3727 let fully_active_b = MergeKind::FullyActive(meta, stake_b);
3728 let new_stake = fully_active_a
3729 .merge(&invoke_context, fully_active_b, &clock)
3730 .unwrap()
3731 .unwrap()
3732 .stake()
3733 .unwrap();
3734 assert_eq!(
3735 new_stake.credits_observed,
3736 (credits_a * delegation + credits_b * delegation) / (delegation + delegation)
3737 );
3738 assert_eq!(new_stake.delegation.stake, delegation * 2);
3739 }
3740
3741 prop_compose! {
3742 pub fn sum_within(max: u64)(total in 1..max)
3743 (intermediate in 1..total, total in Just(total))
3744 -> (u64, u64) {
3745 (intermediate, total - intermediate)
3746 }
3747 }
3748
3749 proptest! {
3750 #[test]
3751 fn test_stake_weighted_credits_observed(
3752 (credits_a, credits_b) in sum_within(u64::MAX),
3753 (delegation_a, delegation_b) in sum_within(u64::MAX),
3754 ) {
3755 let stake = Stake {
3756 delegation: Delegation {
3757 stake: delegation_a,
3758 ..Delegation::default()
3759 },
3760 credits_observed: credits_a
3761 };
3762 let credits_observed = stake_weighted_credits_observed(
3763 &stake,
3764 delegation_b,
3765 credits_b,
3766 ).unwrap();
3767
3768 if credits_a < credits_b {
3770 assert!(credits_a < credits_observed);
3771 assert!(credits_observed <= credits_b);
3772 } else {
3773 assert!(credits_b <= credits_observed);
3774 assert!(credits_observed <= credits_a);
3775 }
3776
3777 let weighted_credits_total = credits_observed as u128 * (delegation_a + delegation_b) as u128;
3780 let weighted_credits_a = credits_a as u128 * delegation_a as u128;
3781 let weighted_credits_b = credits_b as u128 * delegation_b as u128;
3782 let raw_diff = weighted_credits_total - (weighted_credits_a + weighted_credits_b);
3783 let credits_observed_diff = raw_diff / (delegation_a + delegation_b) as u128;
3784 assert!(credits_observed_diff <= 1);
3785 }
3786 }
3787}