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::{BorrowedAccount, InstructionContext, TransactionContext},
32 },
33 solana_vote_program::vote_state::{VoteState, VoteStateVersions},
34 std::{collections::HashSet, convert::TryFrom},
35};
36
37#[derive(Debug)]
38pub enum SkippedReason {
39 DisabledInflation,
40 JustActivated,
41 TooEarlyUnfairSplit,
42 ZeroPoints,
43 ZeroPointValue,
44 ZeroReward,
45 ZeroCreditsAndReturnZero,
46 ZeroCreditsAndReturnCurrent,
47 ZeroCreditsAndReturnRewinded,
48}
49
50impl From<SkippedReason> for InflationPointCalculationEvent {
51 fn from(reason: SkippedReason) -> Self {
52 InflationPointCalculationEvent::Skipped(reason)
53 }
54}
55
56#[derive(Debug)]
57pub enum InflationPointCalculationEvent {
58 CalculatedPoints(u64, u128, u128, u128),
59 SplitRewards(u64, u64, u64, PointValue),
60 EffectiveStakeAtRewardedEpoch(u64),
61 RentExemptReserve(u64),
62 Delegation(Delegation, Pubkey),
63 Commission(u8),
64 CreditsObserved(u64, Option<u64>),
65 Skipped(SkippedReason),
66}
67
68pub(crate) fn null_tracer() -> Option<impl Fn(&InflationPointCalculationEvent)> {
69 None::<fn(&_)>
70}
71
72pub fn from<T: ReadableAccount + StateMut<StakeState>>(account: &T) -> Option<StakeState> {
74 account.state().ok()
75}
76
77pub fn stake_from<T: ReadableAccount + StateMut<StakeState>>(account: &T) -> Option<Stake> {
78 from(account).and_then(|state: StakeState| state.stake())
79}
80
81pub fn delegation_from(account: &AccountSharedData) -> Option<Delegation> {
82 from(account).and_then(|state: StakeState| state.delegation())
83}
84
85pub fn authorized_from(account: &AccountSharedData) -> Option<Authorized> {
86 from(account).and_then(|state: StakeState| state.authorized())
87}
88
89pub fn lockup_from<T: ReadableAccount + StateMut<StakeState>>(account: &T) -> Option<Lockup> {
90 from(account).and_then(|state: StakeState| state.lockup())
91}
92
93pub fn meta_from(account: &AccountSharedData) -> Option<Meta> {
94 from(account).and_then(|state: StakeState| state.meta())
95}
96
97fn redelegate_stake(
98 invoke_context: &InvokeContext,
99 stake: &mut Stake,
100 stake_lamports: u64,
101 voter_pubkey: &Pubkey,
102 vote_state: &VoteState,
103 clock: &Clock,
104 stake_history: &StakeHistory,
105 config: &Config,
106) -> Result<(), StakeError> {
107 if stake.stake(clock.epoch, Some(stake_history)) != 0 {
109 let stake_lamports_ok = if invoke_context
110 .feature_set
111 .is_active(&feature_set::stake_redelegate_instruction::id())
112 {
113 stake_lamports >= stake.delegation.stake
118 } else {
119 true
120 };
121
122 if stake.delegation.voter_pubkey == *voter_pubkey
126 && clock.epoch == stake.delegation.deactivation_epoch
127 && stake_lamports_ok
128 {
129 stake.delegation.deactivation_epoch = std::u64::MAX;
130 return Ok(());
131 } else {
132 return Err(StakeError::TooSoonToRedelegate);
134 }
135 }
136 stake.delegation.stake = stake_lamports;
141 stake.delegation.activation_epoch = clock.epoch;
142 stake.delegation.deactivation_epoch = std::u64::MAX;
143 stake.delegation.voter_pubkey = *voter_pubkey;
144 stake.delegation.warmup_cooldown_rate = config.warmup_cooldown_rate;
145 stake.credits_observed = vote_state.credits();
146 Ok(())
147}
148
149pub(crate) fn new_stake(
150 stake: u64,
151 voter_pubkey: &Pubkey,
152 vote_state: &VoteState,
153 activation_epoch: Epoch,
154 config: &Config,
155) -> Stake {
156 Stake {
157 delegation: Delegation::new(
158 voter_pubkey,
159 stake,
160 activation_epoch,
161 config.warmup_cooldown_rate,
162 ),
163 credits_observed: vote_state.credits(),
164 }
165}
166
167#[derive(Clone, Debug, PartialEq, Eq)]
172pub struct PointValue {
173 pub rewards: u64, pub points: u128, }
176
177fn redeem_stake_rewards(
178 rewarded_epoch: Epoch,
179 stake: &mut Stake,
180 point_value: &PointValue,
181 vote_state: &VoteState,
182 stake_history: Option<&StakeHistory>,
183 inflation_point_calc_tracer: Option<impl Fn(&InflationPointCalculationEvent)>,
184 credits_auto_rewind: bool,
185) -> Option<(u64, u64)> {
186 if let Some(inflation_point_calc_tracer) = inflation_point_calc_tracer.as_ref() {
187 inflation_point_calc_tracer(&InflationPointCalculationEvent::CreditsObserved(
188 stake.credits_observed,
189 None,
190 ));
191 }
192 calculate_stake_rewards(
193 rewarded_epoch,
194 stake,
195 point_value,
196 vote_state,
197 stake_history,
198 inflation_point_calc_tracer.as_ref(),
199 credits_auto_rewind,
200 )
201 .map(|calculated_stake_rewards| {
202 if let Some(inflation_point_calc_tracer) = inflation_point_calc_tracer {
203 inflation_point_calc_tracer(&InflationPointCalculationEvent::CreditsObserved(
204 stake.credits_observed,
205 Some(calculated_stake_rewards.new_credits_observed),
206 ));
207 }
208 stake.credits_observed = calculated_stake_rewards.new_credits_observed;
209 stake.delegation.stake += calculated_stake_rewards.staker_rewards;
210 (
211 calculated_stake_rewards.staker_rewards,
212 calculated_stake_rewards.voter_rewards,
213 )
214 })
215}
216
217fn calculate_stake_points(
218 stake: &Stake,
219 vote_state: &VoteState,
220 stake_history: Option<&StakeHistory>,
221 inflation_point_calc_tracer: Option<impl Fn(&InflationPointCalculationEvent)>,
222) -> u128 {
223 calculate_stake_points_and_credits(
224 stake,
225 vote_state,
226 stake_history,
227 inflation_point_calc_tracer,
228 true, )
231 .points
232}
233
234#[derive(Debug, PartialEq, Eq)]
235struct CalculatedStakePoints {
236 points: u128,
237 new_credits_observed: u64,
238 force_credits_update_with_skipped_reward: bool,
239}
240
241fn calculate_stake_points_and_credits(
245 stake: &Stake,
246 new_vote_state: &VoteState,
247 stake_history: Option<&StakeHistory>,
248 inflation_point_calc_tracer: Option<impl Fn(&InflationPointCalculationEvent)>,
249 credits_auto_rewind: bool,
250) -> CalculatedStakePoints {
251 let credits_in_stake = stake.credits_observed;
252 let credits_in_vote = new_vote_state.credits();
253 if credits_in_vote <= credits_in_stake {
255 if credits_auto_rewind && credits_in_vote < credits_in_stake {
256 if let Some(inflation_point_calc_tracer) = inflation_point_calc_tracer.as_ref() {
257 inflation_point_calc_tracer(&SkippedReason::ZeroCreditsAndReturnRewinded.into());
258 }
259 return CalculatedStakePoints {
278 points: 0,
279 new_credits_observed: credits_in_vote,
280 force_credits_update_with_skipped_reward: true,
281 };
282 } else {
283 if let Some(inflation_point_calc_tracer) = inflation_point_calc_tracer.as_ref() {
288 inflation_point_calc_tracer(&SkippedReason::ZeroCreditsAndReturnCurrent.into());
289 }
290 return CalculatedStakePoints {
293 points: 0,
294 new_credits_observed: credits_in_stake,
295 force_credits_update_with_skipped_reward: false,
296 };
297 };
298 }
299
300 let mut points = 0;
301 let mut new_credits_observed = credits_in_stake;
302
303 for (epoch, final_epoch_credits, initial_epoch_credits) in
304 new_vote_state.epoch_credits().iter().copied()
305 {
306 let stake_amount = u128::from(stake.delegation.stake(epoch, stake_history));
307
308 let earned_credits = if credits_in_stake < initial_epoch_credits {
311 final_epoch_credits - initial_epoch_credits
313 } else if credits_in_stake < final_epoch_credits {
314 final_epoch_credits - new_credits_observed
316 } else {
317 0
320 };
321 let earned_credits = u128::from(earned_credits);
322
323 new_credits_observed = new_credits_observed.max(final_epoch_credits);
325
326 let earned_points = stake_amount * earned_credits;
328 points += earned_points;
329
330 if let Some(inflation_point_calc_tracer) = inflation_point_calc_tracer.as_ref() {
331 inflation_point_calc_tracer(&InflationPointCalculationEvent::CalculatedPoints(
332 epoch,
333 stake_amount,
334 earned_credits,
335 earned_points,
336 ));
337 }
338 }
339
340 CalculatedStakePoints {
341 points,
342 new_credits_observed,
343 force_credits_update_with_skipped_reward: false,
344 }
345}
346
347#[derive(Debug, PartialEq, Eq)]
348struct CalculatedStakeRewards {
349 staker_rewards: u64,
350 voter_rewards: u64,
351 new_credits_observed: u64,
352}
353
354fn calculate_stake_rewards(
361 rewarded_epoch: Epoch,
362 stake: &Stake,
363 point_value: &PointValue,
364 vote_state: &VoteState,
365 stake_history: Option<&StakeHistory>,
366 inflation_point_calc_tracer: Option<impl Fn(&InflationPointCalculationEvent)>,
367 credits_auto_rewind: bool,
368) -> Option<CalculatedStakeRewards> {
369 let CalculatedStakePoints {
371 points,
372 new_credits_observed,
373 mut force_credits_update_with_skipped_reward,
374 } = calculate_stake_points_and_credits(
375 stake,
376 vote_state,
377 stake_history,
378 inflation_point_calc_tracer.as_ref(),
379 credits_auto_rewind,
380 );
381
382 if point_value.rewards == 0 {
385 if let Some(inflation_point_calc_tracer) = inflation_point_calc_tracer.as_ref() {
386 inflation_point_calc_tracer(&SkippedReason::DisabledInflation.into());
387 }
388 force_credits_update_with_skipped_reward = true;
389 } else if stake.delegation.activation_epoch == rewarded_epoch {
390 if let Some(inflation_point_calc_tracer) = inflation_point_calc_tracer.as_ref() {
392 inflation_point_calc_tracer(&SkippedReason::JustActivated.into());
393 }
394 force_credits_update_with_skipped_reward = true;
395 }
396
397 if force_credits_update_with_skipped_reward {
398 return Some(CalculatedStakeRewards {
399 staker_rewards: 0,
400 voter_rewards: 0,
401 new_credits_observed,
402 });
403 }
404
405 if points == 0 {
406 if let Some(inflation_point_calc_tracer) = inflation_point_calc_tracer.as_ref() {
407 inflation_point_calc_tracer(&SkippedReason::ZeroPoints.into());
408 }
409 return None;
410 }
411 if point_value.points == 0 {
412 if let Some(inflation_point_calc_tracer) = inflation_point_calc_tracer.as_ref() {
413 inflation_point_calc_tracer(&SkippedReason::ZeroPointValue.into());
414 }
415 return None;
416 }
417
418 let rewards = points
419 .checked_mul(u128::from(point_value.rewards))
420 .unwrap()
421 .checked_div(point_value.points)
422 .unwrap();
423
424 let rewards = u64::try_from(rewards).unwrap();
425
426 if rewards == 0 {
428 if let Some(inflation_point_calc_tracer) = inflation_point_calc_tracer.as_ref() {
429 inflation_point_calc_tracer(&SkippedReason::ZeroReward.into());
430 }
431 return None;
432 }
433 let (voter_rewards, staker_rewards, is_split) = vote_state.commission_split(rewards);
434 if let Some(inflation_point_calc_tracer) = inflation_point_calc_tracer.as_ref() {
435 inflation_point_calc_tracer(&InflationPointCalculationEvent::SplitRewards(
436 rewards,
437 voter_rewards,
438 staker_rewards,
439 (*point_value).clone(),
440 ));
441 }
442
443 if (voter_rewards == 0 || staker_rewards == 0) && is_split {
444 if let Some(inflation_point_calc_tracer) = inflation_point_calc_tracer.as_ref() {
448 inflation_point_calc_tracer(&SkippedReason::TooEarlyUnfairSplit.into());
449 }
450 return None;
451 }
452
453 Some(CalculatedStakeRewards {
454 staker_rewards,
455 voter_rewards,
456 new_credits_observed,
457 })
458}
459
460pub fn initialize(
461 stake_account: &mut BorrowedAccount,
462 authorized: &Authorized,
463 lockup: &Lockup,
464 rent: &Rent,
465 feature_set: &FeatureSet,
466) -> Result<(), InstructionError> {
467 if stake_account.get_data().len() != StakeState::size_of() {
468 return Err(InstructionError::InvalidAccountData);
469 }
470 if let StakeState::Uninitialized = stake_account.get_state()? {
471 let rent_exempt_reserve = rent.minimum_balance(stake_account.get_data().len());
472 let minimum_balance = if feature_set.is_active(&stake_allow_zero_undelegated_amount::id()) {
474 rent_exempt_reserve
475 } else {
476 let minimum_delegation = crate::get_minimum_delegation(feature_set);
477 rent_exempt_reserve + minimum_delegation
478 };
479
480 if stake_account.get_lamports() >= minimum_balance {
481 stake_account.set_state(&StakeState::Initialized(Meta {
482 rent_exempt_reserve,
483 authorized: *authorized,
484 lockup: *lockup,
485 }))
486 } else {
487 Err(InstructionError::InsufficientFunds)
488 }
489 } else {
490 Err(InstructionError::InvalidAccountData)
491 }
492}
493
494pub fn authorize(
498 stake_account: &mut BorrowedAccount,
499 signers: &HashSet<Pubkey>,
500 new_authority: &Pubkey,
501 stake_authorize: StakeAuthorize,
502 require_custodian_for_locked_stake_authorize: bool,
503 clock: &Clock,
504 custodian: Option<&Pubkey>,
505) -> Result<(), InstructionError> {
506 match stake_account.get_state()? {
507 StakeState::Stake(mut meta, stake) => {
508 meta.authorized.authorize(
509 signers,
510 new_authority,
511 stake_authorize,
512 if require_custodian_for_locked_stake_authorize {
513 Some((&meta.lockup, clock, custodian))
514 } else {
515 None
516 },
517 )?;
518 stake_account.set_state(&StakeState::Stake(meta, stake))
519 }
520 StakeState::Initialized(mut meta) => {
521 meta.authorized.authorize(
522 signers,
523 new_authority,
524 stake_authorize,
525 if require_custodian_for_locked_stake_authorize {
526 Some((&meta.lockup, clock, custodian))
527 } else {
528 None
529 },
530 )?;
531 stake_account.set_state(&StakeState::Initialized(meta))
532 }
533 _ => Err(InstructionError::InvalidAccountData),
534 }
535}
536
537#[allow(clippy::too_many_arguments)]
538pub fn authorize_with_seed(
539 transaction_context: &TransactionContext,
540 instruction_context: &InstructionContext,
541 stake_account: &mut BorrowedAccount,
542 authority_base_index: usize,
543 authority_seed: &str,
544 authority_owner: &Pubkey,
545 new_authority: &Pubkey,
546 stake_authorize: StakeAuthorize,
547 require_custodian_for_locked_stake_authorize: bool,
548 clock: &Clock,
549 custodian: Option<&Pubkey>,
550) -> Result<(), InstructionError> {
551 let mut signers = HashSet::default();
552 if instruction_context.is_instruction_account_signer(authority_base_index)? {
553 let base_pubkey = transaction_context.get_key_of_account_at_index(
554 instruction_context
555 .get_index_of_instruction_account_in_transaction(authority_base_index)?,
556 )?;
557 signers.insert(Pubkey::create_with_seed(
558 base_pubkey,
559 authority_seed,
560 authority_owner,
561 )?);
562 }
563 authorize(
564 stake_account,
565 &signers,
566 new_authority,
567 stake_authorize,
568 require_custodian_for_locked_stake_authorize,
569 clock,
570 custodian,
571 )
572}
573
574#[allow(clippy::too_many_arguments)]
575pub fn delegate(
576 invoke_context: &InvokeContext,
577 transaction_context: &TransactionContext,
578 instruction_context: &InstructionContext,
579 stake_account_index: usize,
580 vote_account_index: usize,
581 clock: &Clock,
582 stake_history: &StakeHistory,
583 config: &Config,
584 signers: &HashSet<Pubkey>,
585 feature_set: &FeatureSet,
586) -> Result<(), InstructionError> {
587 let vote_account = instruction_context
588 .try_borrow_instruction_account(transaction_context, vote_account_index)?;
589 if *vote_account.get_owner() != solana_vote_program::id() {
590 return Err(InstructionError::IncorrectProgramId);
591 }
592 let vote_pubkey = *vote_account.get_key();
593 let vote_state = vote_account.get_state::<VoteStateVersions>();
594 drop(vote_account);
595
596 let mut stake_account = instruction_context
597 .try_borrow_instruction_account(transaction_context, stake_account_index)?;
598 match stake_account.get_state()? {
599 StakeState::Initialized(meta) => {
600 meta.authorized.check(signers, StakeAuthorize::Staker)?;
601 let ValidatedDelegatedInfo { stake_amount } =
602 validate_delegated_amount(&stake_account, &meta, feature_set)?;
603 let stake = new_stake(
604 stake_amount,
605 &vote_pubkey,
606 &vote_state?.convert_to_current(),
607 clock.epoch,
608 config,
609 );
610 stake_account.set_state(&StakeState::Stake(meta, stake))
611 }
612 StakeState::Stake(meta, mut stake) => {
613 meta.authorized.check(signers, StakeAuthorize::Staker)?;
614 let ValidatedDelegatedInfo { stake_amount } =
615 validate_delegated_amount(&stake_account, &meta, feature_set)?;
616 redelegate_stake(
617 invoke_context,
618 &mut stake,
619 stake_amount,
620 &vote_pubkey,
621 &vote_state?.convert_to_current(),
622 clock,
623 stake_history,
624 config,
625 )?;
626 stake_account.set_state(&StakeState::Stake(meta, stake))
627 }
628 _ => Err(InstructionError::InvalidAccountData),
629 }
630}
631
632pub fn deactivate(
633 stake_account: &mut BorrowedAccount,
634 clock: &Clock,
635 signers: &HashSet<Pubkey>,
636) -> Result<(), InstructionError> {
637 if let StakeState::Stake(meta, mut stake) = stake_account.get_state()? {
638 meta.authorized.check(signers, StakeAuthorize::Staker)?;
639 stake.deactivate(clock.epoch)?;
640
641 stake_account.set_state(&StakeState::Stake(meta, stake))
642 } else {
643 Err(InstructionError::InvalidAccountData)
644 }
645}
646
647pub fn set_lockup(
648 stake_account: &mut BorrowedAccount,
649 lockup: &LockupArgs,
650 signers: &HashSet<Pubkey>,
651 clock: &Clock,
652) -> Result<(), InstructionError> {
653 match stake_account.get_state()? {
654 StakeState::Initialized(mut meta) => {
655 meta.set_lockup(lockup, signers, clock)?;
656 stake_account.set_state(&StakeState::Initialized(meta))
657 }
658 StakeState::Stake(mut meta, stake) => {
659 meta.set_lockup(lockup, signers, clock)?;
660 stake_account.set_state(&StakeState::Stake(meta, stake))
661 }
662 _ => Err(InstructionError::InvalidAccountData),
663 }
664}
665
666pub fn split(
667 invoke_context: &InvokeContext,
668 transaction_context: &TransactionContext,
669 instruction_context: &InstructionContext,
670 stake_account_index: usize,
671 lamports: u64,
672 split_index: usize,
673 signers: &HashSet<Pubkey>,
674) -> Result<(), InstructionError> {
675 let split =
676 instruction_context.try_borrow_instruction_account(transaction_context, split_index)?;
677 if *split.get_owner() != id() {
678 return Err(InstructionError::IncorrectProgramId);
679 }
680 if split.get_data().len() != StakeState::size_of() {
681 return Err(InstructionError::InvalidAccountData);
682 }
683 if !matches!(split.get_state()?, StakeState::Uninitialized) {
684 return Err(InstructionError::InvalidAccountData);
685 }
686 let split_lamport_balance = split.get_lamports();
687 drop(split);
688 let stake_account = instruction_context
689 .try_borrow_instruction_account(transaction_context, stake_account_index)?;
690 if lamports > stake_account.get_lamports() {
691 return Err(InstructionError::InsufficientFunds);
692 }
693 let stake_state = stake_account.get_state()?;
694 drop(stake_account);
695
696 match stake_state {
697 StakeState::Stake(meta, mut stake) => {
698 meta.authorized.check(signers, StakeAuthorize::Staker)?;
699 let minimum_delegation = crate::get_minimum_delegation(&invoke_context.feature_set);
700 let validated_split_info = validate_split_amount(
701 invoke_context,
702 transaction_context,
703 instruction_context,
704 stake_account_index,
705 split_index,
706 lamports,
707 &meta,
708 Some(&stake),
709 minimum_delegation,
710 )?;
711
712 let (remaining_stake_delta, split_stake_amount) =
718 if validated_split_info.source_remaining_balance == 0 {
719 let remaining_stake_delta = lamports.saturating_sub(meta.rent_exempt_reserve);
730 (remaining_stake_delta, remaining_stake_delta)
731 } else {
732 (
735 lamports,
736 lamports.saturating_sub(
737 validated_split_info
738 .destination_rent_exempt_reserve
739 .saturating_sub(split_lamport_balance),
740 ),
741 )
742 };
743 let split_stake = stake.split(remaining_stake_delta, split_stake_amount)?;
744 let mut split_meta = meta;
745 split_meta.rent_exempt_reserve = validated_split_info.destination_rent_exempt_reserve;
746
747 let mut stake_account = instruction_context
748 .try_borrow_instruction_account(transaction_context, stake_account_index)?;
749 stake_account.set_state(&StakeState::Stake(meta, stake))?;
750 drop(stake_account);
751 let mut split = instruction_context
752 .try_borrow_instruction_account(transaction_context, split_index)?;
753 split.set_state(&StakeState::Stake(split_meta, split_stake))?;
754 }
755 StakeState::Initialized(meta) => {
756 meta.authorized.check(signers, StakeAuthorize::Staker)?;
757 let additional_required_lamports = if invoke_context
758 .feature_set
759 .is_active(&stake_allow_zero_undelegated_amount::id())
760 {
761 0
762 } else {
763 crate::get_minimum_delegation(&invoke_context.feature_set)
764 };
765 let validated_split_info = validate_split_amount(
766 invoke_context,
767 transaction_context,
768 instruction_context,
769 stake_account_index,
770 split_index,
771 lamports,
772 &meta,
773 None,
774 additional_required_lamports,
775 )?;
776 let mut split_meta = meta;
777 split_meta.rent_exempt_reserve = validated_split_info.destination_rent_exempt_reserve;
778 let mut split = instruction_context
779 .try_borrow_instruction_account(transaction_context, split_index)?;
780 split.set_state(&StakeState::Initialized(split_meta))?;
781 }
782 StakeState::Uninitialized => {
783 let stake_pubkey = transaction_context.get_key_of_account_at_index(
784 instruction_context
785 .get_index_of_instruction_account_in_transaction(stake_account_index)?,
786 )?;
787 if !signers.contains(stake_pubkey) {
788 return Err(InstructionError::MissingRequiredSignature);
789 }
790 }
791 _ => return Err(InstructionError::InvalidAccountData),
792 }
793
794 let mut stake_account = instruction_context
796 .try_borrow_instruction_account(transaction_context, stake_account_index)?;
797 if lamports == stake_account.get_lamports() {
798 stake_account.set_state(&StakeState::Uninitialized)?;
799 }
800 drop(stake_account);
801
802 let mut split =
803 instruction_context.try_borrow_instruction_account(transaction_context, split_index)?;
804 split.checked_add_lamports(lamports)?;
805 drop(split);
806 let mut stake_account = instruction_context
807 .try_borrow_instruction_account(transaction_context, stake_account_index)?;
808 stake_account.checked_sub_lamports(lamports)?;
809 Ok(())
810}
811
812pub fn merge(
813 invoke_context: &InvokeContext,
814 transaction_context: &TransactionContext,
815 instruction_context: &InstructionContext,
816 stake_account_index: usize,
817 source_account_index: usize,
818 clock: &Clock,
819 stake_history: &StakeHistory,
820 signers: &HashSet<Pubkey>,
821) -> Result<(), InstructionError> {
822 let mut source_account = instruction_context
823 .try_borrow_instruction_account(transaction_context, source_account_index)?;
824 if *source_account.get_owner() != id() {
826 return Err(InstructionError::IncorrectProgramId);
827 }
828 if instruction_context.get_index_of_instruction_account_in_transaction(stake_account_index)?
830 == instruction_context
831 .get_index_of_instruction_account_in_transaction(source_account_index)?
832 {
833 return Err(InstructionError::InvalidArgument);
834 }
835 let mut stake_account = instruction_context
836 .try_borrow_instruction_account(transaction_context, stake_account_index)?;
837
838 ic_msg!(invoke_context, "Checking if destination stake is mergeable");
839 let stake_merge_kind = MergeKind::get_if_mergeable(
840 invoke_context,
841 &stake_account.get_state()?,
842 stake_account.get_lamports(),
843 clock,
844 stake_history,
845 )?;
846
847 stake_merge_kind
849 .meta()
850 .authorized
851 .check(signers, StakeAuthorize::Staker)?;
852
853 ic_msg!(invoke_context, "Checking if source stake is mergeable");
854 let source_merge_kind = MergeKind::get_if_mergeable(
855 invoke_context,
856 &source_account.get_state()?,
857 source_account.get_lamports(),
858 clock,
859 stake_history,
860 )?;
861
862 ic_msg!(invoke_context, "Merging stake accounts");
863 if let Some(merged_state) = stake_merge_kind.merge(invoke_context, source_merge_kind, clock)? {
864 stake_account.set_state(&merged_state)?;
865 }
866
867 source_account.set_state(&StakeState::Uninitialized)?;
869
870 let lamports = source_account.get_lamports();
872 source_account.checked_sub_lamports(lamports)?;
873 stake_account.checked_add_lamports(lamports)?;
874 Ok(())
875}
876
877pub fn redelegate(
878 invoke_context: &InvokeContext,
879 transaction_context: &TransactionContext,
880 instruction_context: &InstructionContext,
881 stake_account: &mut BorrowedAccount,
882 uninitialized_stake_account_index: usize,
883 vote_account_index: usize,
884 config: &Config,
885 signers: &HashSet<Pubkey>,
886) -> Result<(), InstructionError> {
887 let clock = invoke_context.get_sysvar_cache().get_clock()?;
888
889 let mut uninitialized_stake_account = instruction_context
891 .try_borrow_instruction_account(transaction_context, uninitialized_stake_account_index)?;
892 if *uninitialized_stake_account.get_owner() != id() {
893 ic_msg!(
894 invoke_context,
895 "expected uninitialized stake account owner to be {}, not {}",
896 id(),
897 *uninitialized_stake_account.get_owner()
898 );
899 return Err(InstructionError::IncorrectProgramId);
900 }
901 if uninitialized_stake_account.get_data().len() != StakeState::size_of() {
902 ic_msg!(
903 invoke_context,
904 "expected uninitialized stake account data len to be {}, not {}",
905 StakeState::size_of(),
906 uninitialized_stake_account.get_data().len()
907 );
908 return Err(InstructionError::InvalidAccountData);
909 }
910 if !matches!(
911 uninitialized_stake_account.get_state()?,
912 StakeState::Uninitialized
913 ) {
914 ic_msg!(
915 invoke_context,
916 "expected uninitialized stake account to be uninitialized",
917 );
918 return Err(InstructionError::AccountAlreadyInitialized);
919 }
920
921 let vote_account = instruction_context
923 .try_borrow_instruction_account(transaction_context, vote_account_index)?;
924 if *vote_account.get_owner() != solana_vote_program::id() {
925 ic_msg!(
926 invoke_context,
927 "expected vote account owner to be {}, not {}",
928 solana_vote_program::id(),
929 *vote_account.get_owner()
930 );
931 return Err(InstructionError::IncorrectProgramId);
932 }
933 let vote_pubkey = *vote_account.get_key();
934 let vote_state = vote_account.get_state::<VoteStateVersions>()?;
935
936 let (stake_meta, effective_stake) =
937 if let StakeState::Stake(meta, stake) = stake_account.get_state()? {
938 let stake_history = invoke_context.get_sysvar_cache().get_stake_history()?;
939 let status = stake
940 .delegation
941 .stake_activating_and_deactivating(clock.epoch, Some(&stake_history));
942 if status.effective == 0 || status.activating != 0 || status.deactivating != 0 {
943 ic_msg!(invoke_context, "stake is not active");
944 return Err(StakeError::RedelegateTransientOrInactiveStake.into());
945 }
946
947 if stake.delegation.voter_pubkey == vote_pubkey {
950 ic_msg!(
951 invoke_context,
952 "redelegating to the same vote account not permitted"
953 );
954 return Err(StakeError::RedelegateToSameVoteAccount.into());
955 }
956
957 (meta, status.effective)
958 } else {
959 ic_msg!(invoke_context, "invalid stake account data",);
960 return Err(InstructionError::InvalidAccountData);
961 };
962
963 deactivate(stake_account, &clock, signers)?;
967
968 stake_account.checked_sub_lamports(effective_stake)?;
970 uninitialized_stake_account.checked_add_lamports(effective_stake)?;
971
972 let sysvar_cache = invoke_context.get_sysvar_cache();
974 let rent = sysvar_cache.get_rent()?;
975 let mut uninitialized_stake_meta = stake_meta;
976 uninitialized_stake_meta.rent_exempt_reserve =
977 rent.minimum_balance(uninitialized_stake_account.get_data().len());
978
979 let ValidatedDelegatedInfo { stake_amount } = validate_delegated_amount(
980 &uninitialized_stake_account,
981 &uninitialized_stake_meta,
982 &invoke_context.feature_set,
983 )?;
984 uninitialized_stake_account.set_state(&StakeState::Stake(
985 uninitialized_stake_meta,
986 new_stake(
987 stake_amount,
988 &vote_pubkey,
989 &vote_state.convert_to_current(),
990 clock.epoch,
991 config,
992 ),
993 ))?;
994
995 Ok(())
996}
997
998#[allow(clippy::too_many_arguments)]
999pub fn withdraw(
1000 transaction_context: &TransactionContext,
1001 instruction_context: &InstructionContext,
1002 stake_account_index: usize,
1003 lamports: u64,
1004 to_index: usize,
1005 clock: &Clock,
1006 stake_history: &StakeHistory,
1007 withdraw_authority_index: usize,
1008 custodian_index: Option<usize>,
1009 feature_set: &FeatureSet,
1010) -> Result<(), InstructionError> {
1011 let withdraw_authority_pubkey = transaction_context.get_key_of_account_at_index(
1012 instruction_context
1013 .get_index_of_instruction_account_in_transaction(withdraw_authority_index)?,
1014 )?;
1015 if !instruction_context.is_instruction_account_signer(withdraw_authority_index)? {
1016 return Err(InstructionError::MissingRequiredSignature);
1017 }
1018 let mut signers = HashSet::new();
1019 signers.insert(*withdraw_authority_pubkey);
1020
1021 let mut stake_account = instruction_context
1022 .try_borrow_instruction_account(transaction_context, stake_account_index)?;
1023 let (lockup, reserve, is_staked) = match stake_account.get_state()? {
1024 StakeState::Stake(meta, stake) => {
1025 meta.authorized
1026 .check(&signers, StakeAuthorize::Withdrawer)?;
1027 let staked = if clock.epoch >= stake.delegation.deactivation_epoch {
1029 stake.delegation.stake(clock.epoch, Some(stake_history))
1030 } else {
1031 stake.delegation.stake
1035 };
1036
1037 let staked_and_reserve = checked_add(staked, meta.rent_exempt_reserve)?;
1038 (meta.lockup, staked_and_reserve, staked != 0)
1039 }
1040 StakeState::Initialized(meta) => {
1041 meta.authorized
1042 .check(&signers, StakeAuthorize::Withdrawer)?;
1043 let reserve = if feature_set.is_active(&stake_allow_zero_undelegated_amount::id()) {
1045 meta.rent_exempt_reserve
1046 } else {
1047 checked_add(
1048 meta.rent_exempt_reserve,
1049 crate::get_minimum_delegation(feature_set),
1050 )?
1051 };
1052
1053 (meta.lockup, reserve, false)
1054 }
1055 StakeState::Uninitialized => {
1056 if !signers.contains(stake_account.get_key()) {
1057 return Err(InstructionError::MissingRequiredSignature);
1058 }
1059 (Lockup::default(), 0, false) }
1061 _ => return Err(InstructionError::InvalidAccountData),
1062 };
1063
1064 let custodian_pubkey = if let Some(custodian_index) = custodian_index {
1067 if instruction_context.is_instruction_account_signer(custodian_index)? {
1068 Some(
1069 transaction_context.get_key_of_account_at_index(
1070 instruction_context
1071 .get_index_of_instruction_account_in_transaction(custodian_index)?,
1072 )?,
1073 )
1074 } else {
1075 None
1076 }
1077 } else {
1078 None
1079 };
1080 if lockup.is_in_force(clock, custodian_pubkey) {
1081 return Err(StakeError::LockupInForce.into());
1082 }
1083
1084 let lamports_and_reserve = checked_add(lamports, reserve)?;
1085 if is_staked && lamports_and_reserve > stake_account.get_lamports()
1088 {
1089 return Err(InstructionError::InsufficientFunds);
1090 }
1091
1092 if lamports != stake_account.get_lamports() && lamports_and_reserve > stake_account.get_lamports()
1094 {
1095 assert!(!is_staked);
1096 return Err(InstructionError::InsufficientFunds);
1097 }
1098
1099 if lamports == stake_account.get_lamports() {
1101 stake_account.set_state(&StakeState::Uninitialized)?;
1102 }
1103
1104 stake_account.checked_sub_lamports(lamports)?;
1105 drop(stake_account);
1106 let mut to =
1107 instruction_context.try_borrow_instruction_account(transaction_context, to_index)?;
1108 to.checked_add_lamports(lamports)?;
1109 Ok(())
1110}
1111
1112pub(crate) fn deactivate_delinquent(
1113 transaction_context: &TransactionContext,
1114 instruction_context: &InstructionContext,
1115 stake_account: &mut BorrowedAccount,
1116 delinquent_vote_account_index: usize,
1117 reference_vote_account_index: usize,
1118 current_epoch: Epoch,
1119) -> Result<(), InstructionError> {
1120 let delinquent_vote_account_pubkey = transaction_context.get_key_of_account_at_index(
1121 instruction_context
1122 .get_index_of_instruction_account_in_transaction(delinquent_vote_account_index)?,
1123 )?;
1124 let delinquent_vote_account = instruction_context
1125 .try_borrow_instruction_account(transaction_context, delinquent_vote_account_index)?;
1126 if *delinquent_vote_account.get_owner() != solana_vote_program::id() {
1127 return Err(InstructionError::IncorrectProgramId);
1128 }
1129 let delinquent_vote_state = delinquent_vote_account
1130 .get_state::<VoteStateVersions>()?
1131 .convert_to_current();
1132
1133 let reference_vote_account = instruction_context
1134 .try_borrow_instruction_account(transaction_context, reference_vote_account_index)?;
1135 if *reference_vote_account.get_owner() != solana_vote_program::id() {
1136 return Err(InstructionError::IncorrectProgramId);
1137 }
1138 let reference_vote_state = reference_vote_account
1139 .get_state::<VoteStateVersions>()?
1140 .convert_to_current();
1141
1142 if !acceptable_reference_epoch_credits(&reference_vote_state.epoch_credits, current_epoch) {
1143 return Err(StakeError::InsufficientReferenceVotes.into());
1144 }
1145
1146 if let StakeState::Stake(meta, mut stake) = stake_account.get_state()? {
1147 if stake.delegation.voter_pubkey != *delinquent_vote_account_pubkey {
1148 return Err(StakeError::VoteAddressMismatch.into());
1149 }
1150
1151 if eligible_for_deactivate_delinquent(&delinquent_vote_state.epoch_credits, current_epoch) {
1154 stake.deactivate(current_epoch)?;
1155 stake_account.set_state(&StakeState::Stake(meta, stake))
1156 } else {
1157 Err(StakeError::MinimumDelinquentEpochsForDeactivationNotMet.into())
1158 }
1159 } else {
1160 Err(InstructionError::InvalidAccountData)
1161 }
1162}
1163
1164struct ValidatedDelegatedInfo {
1167 stake_amount: u64,
1168}
1169
1170fn validate_delegated_amount(
1173 account: &BorrowedAccount,
1174 meta: &Meta,
1175 feature_set: &FeatureSet,
1176) -> Result<ValidatedDelegatedInfo, InstructionError> {
1177 let stake_amount = account
1178 .get_lamports()
1179 .saturating_sub(meta.rent_exempt_reserve); if (feature_set.is_active(&stake_allow_zero_undelegated_amount::id())
1184 || feature_set.is_active(&feature_set::stake_raise_minimum_delegation_to_1_sol::id()))
1185 && stake_amount < crate::get_minimum_delegation(feature_set)
1186 {
1187 return Err(StakeError::InsufficientDelegation.into());
1188 }
1189 Ok(ValidatedDelegatedInfo { stake_amount })
1190}
1191
1192#[derive(Copy, Clone, Debug, Default)]
1195struct ValidatedSplitInfo {
1196 source_remaining_balance: u64,
1197 destination_rent_exempt_reserve: u64,
1198}
1199
1200fn validate_split_amount(
1205 invoke_context: &InvokeContext,
1206 transaction_context: &TransactionContext,
1207 instruction_context: &InstructionContext,
1208 source_account_index: usize,
1209 destination_account_index: usize,
1210 lamports: u64,
1211 source_meta: &Meta,
1212 source_stake: Option<&Stake>,
1213 additional_required_lamports: u64,
1214) -> Result<ValidatedSplitInfo, InstructionError> {
1215 let source_account = instruction_context
1216 .try_borrow_instruction_account(transaction_context, source_account_index)?;
1217 let source_lamports = source_account.get_lamports();
1218 let source_data_len = source_account.get_data().len();
1219 drop(source_account);
1220 let destination_account = instruction_context
1221 .try_borrow_instruction_account(transaction_context, destination_account_index)?;
1222 let destination_lamports = destination_account.get_lamports();
1223 let destination_data_len = destination_account.get_data().len();
1224 drop(destination_account);
1225
1226 if lamports == 0 {
1228 return Err(InstructionError::InsufficientFunds);
1229 }
1230
1231 if lamports > source_lamports {
1233 return Err(InstructionError::InsufficientFunds);
1234 }
1235
1236 let source_minimum_balance = source_meta
1241 .rent_exempt_reserve
1242 .saturating_add(additional_required_lamports);
1243 let source_remaining_balance = source_lamports.saturating_sub(lamports);
1244 if source_remaining_balance == 0 {
1245 } else if source_remaining_balance < source_minimum_balance {
1248 return Err(InstructionError::InsufficientFunds);
1250 } else {
1251 }
1254
1255 let destination_rent_exempt_reserve = if invoke_context
1260 .feature_set
1261 .is_active(&stake_split_uses_rent_sysvar::ID)
1262 {
1263 let rent = invoke_context.get_sysvar_cache().get_rent()?;
1264 rent.minimum_balance(destination_data_len)
1265 } else {
1266 calculate_split_rent_exempt_reserve(
1267 source_meta.rent_exempt_reserve,
1268 source_data_len as u64,
1269 destination_data_len as u64,
1270 )
1271 };
1272 let destination_minimum_balance =
1273 destination_rent_exempt_reserve.saturating_add(additional_required_lamports);
1274 let destination_balance_deficit =
1275 destination_minimum_balance.saturating_sub(destination_lamports);
1276 if lamports < destination_balance_deficit {
1277 return Err(InstructionError::InsufficientFunds);
1278 }
1279
1280 if source_stake.is_some() && lamports < additional_required_lamports {
1291 return Err(InstructionError::InsufficientFunds);
1292 }
1293
1294 Ok(ValidatedSplitInfo {
1295 source_remaining_balance,
1296 destination_rent_exempt_reserve,
1297 })
1298}
1299
1300#[derive(Clone, Debug, PartialEq)]
1301enum MergeKind {
1302 Inactive(Meta, u64),
1303 ActivationEpoch(Meta, Stake),
1304 FullyActive(Meta, Stake),
1305}
1306
1307impl MergeKind {
1308 fn meta(&self) -> &Meta {
1309 match self {
1310 Self::Inactive(meta, _) => meta,
1311 Self::ActivationEpoch(meta, _) => meta,
1312 Self::FullyActive(meta, _) => meta,
1313 }
1314 }
1315
1316 fn active_stake(&self) -> Option<&Stake> {
1317 match self {
1318 Self::Inactive(_, _) => None,
1319 Self::ActivationEpoch(_, stake) => Some(stake),
1320 Self::FullyActive(_, stake) => Some(stake),
1321 }
1322 }
1323
1324 fn get_if_mergeable(
1325 invoke_context: &InvokeContext,
1326 stake_state: &StakeState,
1327 stake_lamports: u64,
1328 clock: &Clock,
1329 stake_history: &StakeHistory,
1330 ) -> Result<Self, InstructionError> {
1331 match stake_state {
1332 StakeState::Stake(meta, stake) => {
1333 let status = stake
1336 .delegation
1337 .stake_activating_and_deactivating(clock.epoch, Some(stake_history));
1338
1339 match (status.effective, status.activating, status.deactivating) {
1340 (0, 0, 0) => Ok(Self::Inactive(*meta, stake_lamports)),
1341 (0, _, _) => Ok(Self::ActivationEpoch(*meta, *stake)),
1342 (_, 0, 0) => Ok(Self::FullyActive(*meta, *stake)),
1343 _ => {
1344 let err = StakeError::MergeTransientStake;
1345 ic_msg!(invoke_context, "{}", err);
1346 Err(err.into())
1347 }
1348 }
1349 }
1350 StakeState::Initialized(meta) => Ok(Self::Inactive(*meta, stake_lamports)),
1351 _ => Err(InstructionError::InvalidAccountData),
1352 }
1353 }
1354
1355 fn metas_can_merge(
1356 invoke_context: &InvokeContext,
1357 stake: &Meta,
1358 source: &Meta,
1359 clock: &Clock,
1360 ) -> Result<(), InstructionError> {
1361 let can_merge_lockups = stake.lockup == source.lockup
1363 || (!stake.lockup.is_in_force(clock, None) && !source.lockup.is_in_force(clock, None));
1364 if stake.authorized == source.authorized && can_merge_lockups {
1370 Ok(())
1371 } else {
1372 ic_msg!(invoke_context, "Unable to merge due to metadata mismatch");
1373 Err(StakeError::MergeMismatch.into())
1374 }
1375 }
1376
1377 fn active_delegations_can_merge(
1378 invoke_context: &InvokeContext,
1379 stake: &Delegation,
1380 source: &Delegation,
1381 ) -> Result<(), InstructionError> {
1382 if stake.voter_pubkey != source.voter_pubkey {
1383 ic_msg!(invoke_context, "Unable to merge due to voter mismatch");
1384 Err(StakeError::MergeMismatch.into())
1385 } else if (stake.warmup_cooldown_rate - source.warmup_cooldown_rate).abs() < f64::EPSILON
1386 && stake.deactivation_epoch == Epoch::MAX
1387 && source.deactivation_epoch == Epoch::MAX
1388 {
1389 Ok(())
1390 } else {
1391 ic_msg!(invoke_context, "Unable to merge due to stake deactivation");
1392 Err(StakeError::MergeMismatch.into())
1393 }
1394 }
1395
1396 fn active_stakes_can_merge(
1398 invoke_context: &InvokeContext,
1399 stake: &Stake,
1400 source: &Stake,
1401 ) -> Result<(), InstructionError> {
1402 Self::active_delegations_can_merge(invoke_context, &stake.delegation, &source.delegation)?;
1403 if stake.credits_observed == source.credits_observed {
1409 Ok(())
1410 } else {
1411 ic_msg!(
1412 invoke_context,
1413 "Unable to merge due to credits observed mismatch"
1414 );
1415 Err(StakeError::MergeMismatch.into())
1416 }
1417 }
1418
1419 fn merge(
1420 self,
1421 invoke_context: &InvokeContext,
1422 source: Self,
1423 clock: &Clock,
1424 ) -> Result<Option<StakeState>, InstructionError> {
1425 Self::metas_can_merge(invoke_context, self.meta(), source.meta(), clock)?;
1426 self.active_stake()
1427 .zip(source.active_stake())
1428 .map(|(stake, source)| {
1429 if invoke_context
1430 .feature_set
1431 .is_active(&stake_merge_with_unmatched_credits_observed::id())
1432 {
1433 Self::active_delegations_can_merge(
1434 invoke_context,
1435 &stake.delegation,
1436 &source.delegation,
1437 )
1438 } else {
1439 Self::active_stakes_can_merge(invoke_context, stake, source)
1440 }
1441 })
1442 .unwrap_or(Ok(()))?;
1443 let merged_state = match (self, source) {
1444 (Self::Inactive(_, _), Self::Inactive(_, _)) => None,
1445 (Self::Inactive(_, _), Self::ActivationEpoch(_, _)) => None,
1446 (Self::ActivationEpoch(meta, mut stake), Self::Inactive(_, source_lamports)) => {
1447 stake.delegation.stake = checked_add(stake.delegation.stake, source_lamports)?;
1448 Some(StakeState::Stake(meta, stake))
1449 }
1450 (
1451 Self::ActivationEpoch(meta, mut stake),
1452 Self::ActivationEpoch(source_meta, source_stake),
1453 ) => {
1454 let source_lamports = checked_add(
1455 source_meta.rent_exempt_reserve,
1456 source_stake.delegation.stake,
1457 )?;
1458 merge_delegation_stake_and_credits_observed(
1459 invoke_context,
1460 &mut stake,
1461 source_lamports,
1462 source_stake.credits_observed,
1463 )?;
1464 Some(StakeState::Stake(meta, stake))
1465 }
1466 (Self::FullyActive(meta, mut stake), Self::FullyActive(_, source_stake)) => {
1467 merge_delegation_stake_and_credits_observed(
1472 invoke_context,
1473 &mut stake,
1474 source_stake.delegation.stake,
1475 source_stake.credits_observed,
1476 )?;
1477 Some(StakeState::Stake(meta, stake))
1478 }
1479 _ => return Err(StakeError::MergeMismatch.into()),
1480 };
1481 Ok(merged_state)
1482 }
1483}
1484
1485fn merge_delegation_stake_and_credits_observed(
1486 invoke_context: &InvokeContext,
1487 stake: &mut Stake,
1488 absorbed_lamports: u64,
1489 absorbed_credits_observed: u64,
1490) -> Result<(), InstructionError> {
1491 if invoke_context
1492 .feature_set
1493 .is_active(&stake_merge_with_unmatched_credits_observed::id())
1494 {
1495 stake.credits_observed =
1496 stake_weighted_credits_observed(stake, absorbed_lamports, absorbed_credits_observed)
1497 .ok_or(InstructionError::ArithmeticOverflow)?;
1498 }
1499 stake.delegation.stake = checked_add(stake.delegation.stake, absorbed_lamports)?;
1500 Ok(())
1501}
1502
1503fn stake_weighted_credits_observed(
1529 stake: &Stake,
1530 absorbed_lamports: u64,
1531 absorbed_credits_observed: u64,
1532) -> Option<u64> {
1533 if stake.credits_observed == absorbed_credits_observed {
1534 Some(stake.credits_observed)
1535 } else {
1536 let total_stake = u128::from(stake.delegation.stake.checked_add(absorbed_lamports)?);
1537 let stake_weighted_credits =
1538 u128::from(stake.credits_observed).checked_mul(u128::from(stake.delegation.stake))?;
1539 let absorbed_weighted_credits =
1540 u128::from(absorbed_credits_observed).checked_mul(u128::from(absorbed_lamports))?;
1541 let total_weighted_credits = stake_weighted_credits
1544 .checked_add(absorbed_weighted_credits)?
1545 .checked_add(total_stake)?
1546 .checked_sub(1)?;
1547 u64::try_from(total_weighted_credits.checked_div(total_stake)?).ok()
1548 }
1549}
1550
1551#[doc(hidden)]
1554pub fn redeem_rewards(
1555 rewarded_epoch: Epoch,
1556 stake_state: StakeState,
1557 stake_account: &mut AccountSharedData,
1558 vote_state: &VoteState,
1559 point_value: &PointValue,
1560 stake_history: Option<&StakeHistory>,
1561 inflation_point_calc_tracer: Option<impl Fn(&InflationPointCalculationEvent)>,
1562 credits_auto_rewind: bool,
1563) -> Result<(u64, u64), InstructionError> {
1564 if let StakeState::Stake(meta, mut stake) = stake_state {
1565 if let Some(inflation_point_calc_tracer) = inflation_point_calc_tracer.as_ref() {
1566 inflation_point_calc_tracer(
1567 &InflationPointCalculationEvent::EffectiveStakeAtRewardedEpoch(
1568 stake.stake(rewarded_epoch, stake_history),
1569 ),
1570 );
1571 inflation_point_calc_tracer(&InflationPointCalculationEvent::RentExemptReserve(
1572 meta.rent_exempt_reserve,
1573 ));
1574 inflation_point_calc_tracer(&InflationPointCalculationEvent::Commission(
1575 vote_state.commission,
1576 ));
1577 }
1578
1579 if let Some((stakers_reward, voters_reward)) = redeem_stake_rewards(
1580 rewarded_epoch,
1581 &mut stake,
1582 point_value,
1583 vote_state,
1584 stake_history,
1585 inflation_point_calc_tracer,
1586 credits_auto_rewind,
1587 ) {
1588 stake_account.checked_add_lamports(stakers_reward)?;
1589 stake_account.set_state(&StakeState::Stake(meta, stake))?;
1590
1591 Ok((stakers_reward, voters_reward))
1592 } else {
1593 Err(StakeError::NoCreditsToRedeem.into())
1594 }
1595 } else {
1596 Err(InstructionError::InvalidAccountData)
1597 }
1598}
1599
1600#[doc(hidden)]
1602pub fn calculate_points(
1603 stake_state: &StakeState,
1604 vote_state: &VoteState,
1605 stake_history: Option<&StakeHistory>,
1606) -> Result<u128, InstructionError> {
1607 if let StakeState::Stake(_meta, stake) = stake_state {
1608 Ok(calculate_stake_points(
1609 stake,
1610 vote_state,
1611 stake_history,
1612 null_tracer(),
1613 ))
1614 } else {
1615 Err(InstructionError::InvalidAccountData)
1616 }
1617}
1618
1619fn calculate_split_rent_exempt_reserve(
1623 source_rent_exempt_reserve: u64,
1624 source_data_len: u64,
1625 split_data_len: u64,
1626) -> u64 {
1627 let lamports_per_byte_year =
1628 source_rent_exempt_reserve / (source_data_len + ACCOUNT_STORAGE_OVERHEAD);
1629 lamports_per_byte_year * (split_data_len + ACCOUNT_STORAGE_OVERHEAD)
1630}
1631
1632pub type RewriteStakeStatus = (&'static str, (u64, u64), (u64, u64));
1633
1634pub fn new_stake_history_entry<'a, I>(
1636 epoch: Epoch,
1637 stakes: I,
1638 history: Option<&StakeHistory>,
1639) -> StakeHistoryEntry
1640where
1641 I: Iterator<Item = &'a Delegation>,
1642{
1643 stakes.fold(StakeHistoryEntry::default(), |sum, stake| {
1644 sum + stake.stake_activating_and_deactivating(epoch, history)
1645 })
1646}
1647
1648pub fn create_stake_history_from_delegations(
1650 bootstrap: Option<u64>,
1651 epochs: std::ops::Range<Epoch>,
1652 delegations: &[Delegation],
1653) -> StakeHistory {
1654 let mut stake_history = StakeHistory::default();
1655
1656 let bootstrap_delegation = if let Some(bootstrap) = bootstrap {
1657 vec![Delegation {
1658 activation_epoch: std::u64::MAX,
1659 stake: bootstrap,
1660 ..Delegation::default()
1661 }]
1662 } else {
1663 vec![]
1664 };
1665
1666 for epoch in epochs {
1667 let entry = new_stake_history_entry(
1668 epoch,
1669 delegations.iter().chain(bootstrap_delegation.iter()),
1670 Some(&stake_history),
1671 );
1672 stake_history.add(epoch, entry);
1673 }
1674
1675 stake_history
1676}
1677
1678pub fn create_lockup_stake_account(
1680 authorized: &Authorized,
1681 lockup: &Lockup,
1682 rent: &Rent,
1683 lamports: u64,
1684) -> AccountSharedData {
1685 let mut stake_account = AccountSharedData::new(lamports, StakeState::size_of(), &id());
1686
1687 let rent_exempt_reserve = rent.minimum_balance(stake_account.data().len());
1688 assert!(
1689 lamports >= rent_exempt_reserve,
1690 "lamports: {} is less than rent_exempt_reserve {}",
1691 lamports,
1692 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 = VoteState::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, {} SAFE",
2993 minimum_balance,
2994 minimum_balance as f64 / solana_sdk::native_token::LAMPORTS_PER_SAFE 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}