solana_stake_program/
stake_state.rs

1//! Stake state
2//! * delegate stakes to vote accounts
3//! * keep track of rewards
4//! * own mining pools
5
6#[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
72// utility function, used by Stakes, tests
73pub 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 is currently active:
108    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            // When a stake account is redelegated, the delegated lamports from the source stake
114            // account are transferred to a new stake account. Do not permit the deactivation of
115            // the source stake account to be rescinded, by more generally requiring the delegation
116            // be configured with the expected amount of stake lamports before rescinding.
117            stake_lamports >= stake.delegation.stake
118        } else {
119            true
120        };
121
122        // If pubkey of new voter is the same as current,
123        // and we are scheduled to start deactivating this epoch,
124        // we rescind deactivation
125        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            // can't redelegate to another pubkey if stake is active.
133            return Err(StakeError::TooSoonToRedelegate);
134        }
135    }
136    // Either the stake is freshly activated, is active but has been
137    // deactivated this epoch, or has fully de-activated.
138    // Redelegation implies either re-activation or un-deactivation
139
140    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/// captures a rewards round as lamports to be awarded
168///  and the total points over which those lamports
169///  are to be distributed
170//  basically read as rewards/points, but in integers instead of as an f64
171#[derive(Clone, Debug, PartialEq, Eq)]
172pub struct PointValue {
173    pub rewards: u64, // lamports to split
174    pub points: u128, // over these points
175}
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, // this is safe because this flag shouldn't affect the
229              // `points` field of the returned struct in any way
230    )
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
241/// for a given stake and vote_state, calculate how many
242///   points were earned (credits * stake) and new value
243///   for credits_observed were the points paid
244fn 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 there is no newer credits since observed, return no point
254    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            // Don't adjust stake.activation_epoch for simplicity:
260            //  - generally fast-forwarding stake.activation_epoch forcibly (for
261            //    artificial re-activation with re-warm-up) skews the stake
262            //    history sysvar. And properly handling all the cases
263            //    regarding deactivation epoch/warm-up/cool-down without
264            //    introducing incentive skew is hard.
265            //  - Conceptually, it should be acceptable for the staked SAFEs at
266            //    the recreated vote to receive rewards again immediately after
267            //    rewind even if it looks like instant activation. That's
268            //    because it must have passed the required warmed-up at least
269            //    once in the past already
270            //  - Also such a stake account remains to be a part of overall
271            //    effective stake calculation even while the vote account is
272            //    missing for (indefinite) time or remains to be pre-remove
273            //    credits score. It should be treated equally to staking with
274            //    delinquent validator with no differentiation.
275
276            // hint with true to indicate some exceptional credits handling is needed
277            return CalculatedStakePoints {
278                points: 0,
279                new_credits_observed: credits_in_vote,
280                force_credits_update_with_skipped_reward: true,
281            };
282        } else {
283            // change the above `else` to `else if credits_in_vote == credits_in_stake`
284            // (and remove the outermost enclosing `if`) when cleaning credits_auto_rewind
285            // after activation
286
287            if let Some(inflation_point_calc_tracer) = inflation_point_calc_tracer.as_ref() {
288                inflation_point_calc_tracer(&SkippedReason::ZeroCreditsAndReturnCurrent.into());
289            }
290            // don't hint the caller and return current value if credits_auto_rewind is off or
291            // credits remain to be unchanged (= delinquent)
292            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        // figure out how much this stake has seen that
309        //   for which the vote account has a record
310        let earned_credits = if credits_in_stake < initial_epoch_credits {
311            // the staker observed the entire epoch
312            final_epoch_credits - initial_epoch_credits
313        } else if credits_in_stake < final_epoch_credits {
314            // the staker registered sometime during the epoch, partial credit
315            final_epoch_credits - new_credits_observed
316        } else {
317            // the staker has already observed or been redeemed this epoch
318            //  or was activated after this epoch
319            0
320        };
321        let earned_credits = u128::from(earned_credits);
322
323        // don't want to assume anything about order of the iterator...
324        new_credits_observed = new_credits_observed.max(final_epoch_credits);
325
326        // finally calculate points for this epoch
327        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
354/// for a given stake and vote_state, calculate what distributions and what updates should be made
355/// returns a tuple in the case of a payout of:
356///   * staker_rewards to be distributed
357///   * voter_rewards to be distributed
358///   * new value for credits_observed in the stake
359/// returns None if there's no payout or if any deserved payout is < 1 lamport
360fn 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    // ensure to run to trigger (optional) inflation_point_calc_tracer
370    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    // Drive credits_observed forward unconditionally when rewards are disabled
383    // or when this is the stake's activation epoch
384    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        // not assert!()-ed; but points should be zero
391        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    // don't bother trying to split if fractional lamports got truncated
427    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        // don't collect if we lose a whole lamport somewhere
445        //  is_split means there should be tokens on both sides,
446        //  uncool to move credits_observed if one side didn't get paid
447        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        // when removing this feature, remove `minimum_balance` and just use `rent_exempt_reserve`
473        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
494/// Authorize the given pubkey to manage stake (deactivate, withdraw). This may be called
495/// multiple times, but will implicitly withdraw authorization from the previously authorized
496/// staker. The default staker is the owner of the stake account's pubkey.
497pub 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            // split the stake, subtract rent_exempt_balance unless
713            // the destination account already has those lamports
714            // in place.
715            // this means that the new stake account will have a stake equivalent to
716            // lamports minus rent_exempt_reserve if it starts out with a zero balance
717            let (remaining_stake_delta, split_stake_amount) =
718                if validated_split_info.source_remaining_balance == 0 {
719                    // If split amount equals the full source stake (as implied by 0
720                    // source_remaining_balance), the new split stake must equal the same
721                    // amount, regardless of any current lamport balance in the split account.
722                    // Since split accounts retain the state of their source account, this
723                    // prevents any magic activation of stake by prefunding the split account.
724                    //
725                    // The new split stake also needs to ignore any positive delta between the
726                    // original rent_exempt_reserve and the split_rent_exempt_reserve, in order
727                    // to prevent magic activation of stake by splitting between accounts of
728                    // different sizes.
729                    let remaining_stake_delta = lamports.saturating_sub(meta.rent_exempt_reserve);
730                    (remaining_stake_delta, remaining_stake_delta)
731                } else {
732                    // Otherwise, the new split stake should reflect the entire split
733                    // requested, less any lamports needed to cover the split_rent_exempt_reserve.
734                    (
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    // Deinitialize state upon zero balance
795    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    // Ensure source isn't spoofed
825    if *source_account.get_owner() != id() {
826        return Err(InstructionError::IncorrectProgramId);
827    }
828    // Close the stake_account-reference loophole
829    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    // Authorized staker is allowed to split/merge accounts
848    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 is about to be drained, deinitialize its state
868    source_account.set_state(&StakeState::Uninitialized)?;
869
870    // Drain the source stake account
871    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    // ensure `uninitialized_stake_account_index` is in the uninitialized state
890    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    // validate the provided vote account
922    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            // Deny redelegating to the same vote account. This is nonsensical and could be used to
948            // grief the global stake warm-up/cool-down rate
949            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`
964    //
965    // Note: This function also ensures `signers` contains the `StakeAuthorize::Staker`
966    deactivate(stake_account, &clock, signers)?;
967
968    // transfer the effective stake to the uninitialized stake account
969    stake_account.checked_sub_lamports(effective_stake)?;
970    uninitialized_stake_account.checked_add_lamports(effective_stake)?;
971
972    // initialize and schedule `uninitialized_stake_account` for activation
973    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            // if we have a deactivation epoch and we're in cooldown
1028            let staked = if clock.epoch >= stake.delegation.deactivation_epoch {
1029                stake.delegation.stake(clock.epoch, Some(stake_history))
1030            } else {
1031                // Assume full stake if the stake account hasn't been
1032                //  de-activated, because in the future the exposed stake
1033                //  might be higher than stake.stake() due to warmup
1034                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            // stake accounts must have a balance >= rent_exempt_reserve
1044            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) // no lockup, no restrictions
1060        }
1061        _ => return Err(InstructionError::InvalidAccountData),
1062    };
1063
1064    // verify that lockup has expired or that the withdrawal is signed by
1065    //   the custodian, both epoch and unix_timestamp must have passed
1066    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 the stake is active, we mustn't allow the account to go away
1086    if is_staked // line coverage for branch coverage
1087            && lamports_and_reserve > stake_account.get_lamports()
1088    {
1089        return Err(InstructionError::InsufficientFunds);
1090    }
1091
1092    if lamports != stake_account.get_lamports() // not a full withdrawal
1093            && lamports_and_reserve > stake_account.get_lamports()
1094    {
1095        assert!(!is_staked);
1096        return Err(InstructionError::InsufficientFunds);
1097    }
1098
1099    // Deinitialize state upon zero balance
1100    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        // Deactivate the stake account if its delegated vote account has never voted or has not
1152        // voted in the last `MINIMUM_DELINQUENT_EPOCHS_FOR_DEACTIVATION`
1153        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
1164/// After calling `validate_delegated_amount()`, this struct contains calculated values that are used
1165/// by the caller.
1166struct ValidatedDelegatedInfo {
1167    stake_amount: u64,
1168}
1169
1170/// Ensure the stake delegation amount is valid.  This checks that the account meets the minimum
1171/// balance requirements of delegated stake.  If not, return an error.
1172fn 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); // can't stake the rent
1180
1181    // Stake accounts may be initialized with a stake amount below the minimum delegation so check
1182    // that the minimum is met before delegation.
1183    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/// After calling `validate_split_amount()`, this struct contains calculated values that are used
1193/// by the caller.
1194#[derive(Copy, Clone, Debug, Default)]
1195struct ValidatedSplitInfo {
1196    source_remaining_balance: u64,
1197    destination_rent_exempt_reserve: u64,
1198}
1199
1200/// Ensure the split amount is valid.  This checks the source and destination accounts meet the
1201/// minimum balance requirements, which is the rent exempt reserve plus the minimum stake
1202/// delegation, and that the source account has enough lamports for the request split amount.  If
1203/// not, return an error.
1204fn 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    // Split amount has to be something
1227    if lamports == 0 {
1228        return Err(InstructionError::InsufficientFunds);
1229    }
1230
1231    // Obviously cannot split more than what the source account has
1232    if lamports > source_lamports {
1233        return Err(InstructionError::InsufficientFunds);
1234    }
1235
1236    // Verify that the source account still has enough lamports left after splitting:
1237    // EITHER at least the minimum balance, OR zero (in this case the source
1238    // account is transferring all lamports to new destination account, and the source
1239    // account will be closed)
1240    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        // full amount is a withdrawal
1246        // nothing to do here
1247    } else if source_remaining_balance < source_minimum_balance {
1248        // the remaining balance is too low to do the split
1249        return Err(InstructionError::InsufficientFunds);
1250    } else {
1251        // all clear!
1252        // nothing to do here
1253    }
1254
1255    // Verify the destination account meets the minimum balance requirements
1256    // This must handle:
1257    // 1. The destination account having a different rent exempt reserve due to data size changes
1258    // 2. The destination account being prefunded, which would lower the minimum split amount
1259    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 the source account is already staked, the destination will end up staked as well.  Verify
1281    // the destination account's delegation amount is at least the minimum delegation.
1282    //
1283    // The *delegation* requirements are different than the *balance* requirements.  If the
1284    // destination account is prefunded with a balance of `rent exempt reserve + minimum stake
1285    // delegation - 1`, the minimum split amount to satisfy the *balance* requirements is 1
1286    // lamport.  And since *only* the split amount is immediately staked in the destination
1287    // account, the split amount must be at least the minimum stake delegation.  So if the minimum
1288    // stake delegation was 10 lamports, then a split amount of 1 lamport would not meet the
1289    // *delegation* requirements.
1290    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                // stake must not be in a transient state. Transient here meaning
1334                // activating or deactivating with non-zero effective stake.
1335                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        // lockups may mismatch so long as both have expired
1362        let can_merge_lockups = stake.lockup == source.lockup
1363            || (!stake.lockup.is_in_force(clock, None) && !source.lockup.is_in_force(clock, None));
1364        // `rent_exempt_reserve` has no bearing on the mergeability of accounts,
1365        // as the source account will be culled by runtime once the operation
1366        // succeeds. Considering it here would needlessly prevent merging stake
1367        // accounts with differing data lengths, which already exist in the wild
1368        // due to an SDK bug
1369        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    // Remove this when the `stake_merge_with_unmatched_credits_observed` feature is removed
1397    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        // `credits_observed` MUST match to prevent earning multiple rewards
1404        // from a stake account by merging it into another stake account that
1405        // is small enough to not be paid out every epoch. This would effectively
1406        // reset the larger stake accounts `credits_observed` to that of the
1407        // smaller account.
1408        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                // Don't stake the source account's `rent_exempt_reserve` to
1468                // protect against the magic activation loophole. It will
1469                // instead be moved into the destination account as extra,
1470                // withdrawable `lamports`
1471                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
1503/// Calculate the effective credits observed for two stakes when merging
1504///
1505/// When merging two `ActivationEpoch` or `FullyActive` stakes, the credits
1506/// observed of the merged stake is the weighted average of the two stakes'
1507/// credits observed.
1508///
1509/// This is because we can derive the effective credits_observed by reversing the staking
1510/// rewards equation, _while keeping the rewards unchanged after merge (i.e. strong
1511/// requirement)_, like below:
1512///
1513/// a(N) => account, r => rewards, s => stake, c => credits:
1514/// assume:
1515///   a3 = merge(a1, a2)
1516/// then:
1517///   a3.s = a1.s + a2.s
1518///
1519/// Next, given:
1520///   aN.r = aN.c * aN.s (for every N)
1521/// finally:
1522///        a3.r = a1.r + a2.r
1523/// a3.c * a3.s = a1.c * a1.s + a2.c * a2.s
1524///        a3.c = (a1.c * a1.s + a2.c * a2.s) / (a1.s + a2.s)     // QED
1525///
1526/// (For this discussion, we omitted irrelevant variables, including distance
1527///  calculation against vote_account and point indirection.)
1528fn 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        // Discard fractional credits as a merge side-effect friction by taking
1542        // the ceiling, done by adding `denominator - 1` to the numerator.
1543        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// utility function, used by runtime
1552// returns a tuple of (stakers_reward,voters_reward)
1553#[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// utility function, used by runtime
1601#[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
1619// utility function, used by Split
1620//This emulates current Rent math in order to preserve backward compatibility. In the future, and
1621//to support variable rent, the Split instruction should pass in the Rent sysvar instead.
1622fn 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
1634// utility function, used by runtime::Stakes, tests
1635pub 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
1648// utility function, used by tests
1649pub 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
1678// genesis investor accounts
1679pub 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
1706// utility function, used by Bank, tests, genesis for bootstrap
1707pub 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
1724// utility function, used by tests
1725pub 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, // underflow is an error, is basically: assert!(lamports > rent_exempt_reserve);
1766                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        // Legacy behaviour when the `require_custodian_for_locked_stake_authorize` feature is
1829        // inactive
1830        assert_eq!(
1831            authorized.authorize(&signers, &staker, StakeAuthorize::Withdrawer, None),
1832            Ok(())
1833        );
1834
1835        // No lockup, no custodian
1836        assert_eq!(
1837            authorized.authorize(
1838                &signers,
1839                &staker,
1840                StakeAuthorize::Withdrawer,
1841                Some((&Lockup::default(), &clock, None))
1842            ),
1843            Ok(())
1844        );
1845
1846        // No lockup, invalid custodian not a signer
1847        assert_eq!(
1848            authorized.authorize(
1849                &signers,
1850                &staker,
1851                StakeAuthorize::Withdrawer,
1852                Some((&Lockup::default(), &clock, Some(&invalid_custodian)))
1853            ),
1854            Ok(()) // <== invalid custodian doesn't matter, there's no lockup
1855        );
1856
1857        // Lockup active, invalid custodian not a signer
1858        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        // No lockup, invalid custodian is a signer
1871        assert_eq!(
1872            authorized.authorize(
1873                &signers,
1874                &staker,
1875                StakeAuthorize::Withdrawer,
1876                Some((&Lockup::default(), &clock, Some(&invalid_custodian)))
1877            ),
1878            Ok(()) // <== invalid custodian doesn't matter, there's no lockup
1879        );
1880
1881        // Lockup active, invalid custodian is a signer
1882        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()), // <== invalid custodian rejected
1891        );
1892
1893        signers.remove(&invalid_custodian);
1894
1895        // Lockup active, no custodian
1896        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        // Lockup active, custodian not a signer
1907        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        // Lockup active, custodian is a signer
1918        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, // activating at zero
1960            deactivation_epoch: 5,
1961            ..Delegation::default()
1962        };
1963
1964        // save this off so stake.config.warmup_rate changes don't break this test
1965        let increment = (1_000_f64 * stake.warmup_cooldown_rate) as u64;
1966
1967        let mut stake_history = StakeHistory::default();
1968        // assert that this stake follows step function if there's no history
1969        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 that this stake is full deactivating
1980        assert_eq!(
1981            stake
1982                .stake_activating_and_deactivating(stake.deactivation_epoch, Some(&stake_history),),
1983            StakeActivationStatus::with_deactivating(stake.stake),
1984        );
1985        // assert that this stake is fully deactivated if there's no history
1986        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, // entry for zero doesn't have my activating amount
1996            StakeHistoryEntry {
1997                effective: 1_000,
1998                ..StakeHistoryEntry::default()
1999            },
2000        );
2001        // assert that this stake is broken, because above setup is broken
2002        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, // entry for zero has my activating amount
2009            StakeHistoryEntry {
2010                effective: 1_000,
2011                activating: 1_000,
2012                ..StakeHistoryEntry::default()
2013            },
2014            // no entry for 1, so this stake gets shorted
2015        );
2016        // assert that this stake is broken, because above setup is broken
2017        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        // start over, test deactivation edge cases
2026        let mut stake_history = StakeHistory::default();
2027
2028        stake_history.add(
2029            stake.deactivation_epoch, // entry for zero doesn't have my de-activating amount
2030            StakeHistoryEntry {
2031                effective: 1_000,
2032                ..StakeHistoryEntry::default()
2033            },
2034        );
2035        // assert that this stake is broken, because above setup is broken
2036        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        // put in my initial deactivating amount, but don't put in an entry for next
2045        stake_history.add(
2046            stake.deactivation_epoch, // entry for zero has my de-activating amount
2047            StakeHistoryEntry {
2048                effective: 1_000,
2049                deactivating: 1_000,
2050                ..StakeHistoryEntry::default()
2051            },
2052        );
2053        // assert that this stake is broken, because above setup is broken
2054        assert_eq!(
2055            stake.stake_activating_and_deactivating(
2056                stake.deactivation_epoch + 2,
2057                Some(&stake_history),
2058            ),
2059            // hung, should be lower
2060            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            // any stake accounts activated and deactivated at the same epoch
2133            // shouldn't been activated (then deactivated) at all!
2134
2135            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            // any stake accounts activated and deactivated at the same epoch
2152            // shouldn't been activated (then deactivated) at all!
2153
2154            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        // some really boring delegation and stake_history setup
2172        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        // helper closures
2249        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), // +1 is needed for rounding
2280            StakeActivationStatus::with_effective(700),
2281            StakeActivationStatus::with_deactivating(700),
2282            StakeActivationStatus::with_deactivating(275 + 1), // +1 is needed for rounding
2283            StakeActivationStatus::default(),
2284        ];
2285
2286        // normal stake activating and deactivating transition test, just in case
2287        assert_eq!(
2288            expected_staking_status_transition,
2289            calculate_each_staking_status(&stake, expected_staking_status_transition.len())
2290        );
2291
2292        // 10% inflation rewards assuming some sizable epochs passed!
2293        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        // 50% slashing!!!
2304        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        // Build a stake history where the test staker always consumes all of the available warm
2331        // up and cool down stake. However, simulate other stakers beginning to activate during
2332        // the test staker's deactivation.
2333        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, // activating at zero
2387            deactivation_epoch: 5,
2388            ..Delegation::default()
2389        }];
2390        // give 2 epochs of cooldown
2391        let epochs = 7;
2392        // make boostrap stake smaller than warmup so warmup/cooldownn
2393        //  increment is always smaller than 1
2394        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                // never deactivates
2417                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        // chosen to ensure that the last activated stake (at 4) finishes
2453        //  warming up and cooling down
2454        //  a stake takes 2.0f64.log(1.0 + STAKE_WARMUP_RATE) epochs to warm up or cool down
2455        //  when all alone, but the above overlap a lot
2456        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        // uncomment and add ! for fun with graphing
2466        // eprintln("\n{:8} {:8} {:8}", "   epoch", "   total", "   delta");
2467        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            // uncomment and add ! for fun with graphing
2480            //eprint("{:8} {:8} {:8} ", epoch, total_effective_stake, delta);
2481            //(0..(total_effective_stake as usize / (stakes.len() * 5))).for_each(|_| eprint("#"));
2482            //eprintln();
2483
2484            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        // assume stake.stake() is right
2498        // bootstrap means fully-vested stake at epoch 0
2499        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        // this one can't collect now, credits_observed == vote_state.credits()
2509        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        // put 2 credits in at epoch 0
2526        vote_state.increment_credits(0, 1);
2527        vote_state.increment_credits(0, 1);
2528
2529        // this one should be able to collect exactly 2
2530        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        // bootstrap means fully-vested stake at epoch 0 with
2558        //  10_000_000 SAFE is a big but not unreasaonable stake
2559        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        // this one can't collect now, credits_observed == vote_state.credits()
2568        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        // put 193,536,000 credits in at epoch 0, typical for a 14-day epoch
2586        //  this loop takes a few seconds...
2587        for _ in 0..epoch_slots {
2588            vote_state.increment_credits(0, 1);
2589        }
2590
2591        // no overflow on points
2592        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        // assume stake.stake() is right
2602        // bootstrap means fully-vested stake at epoch 0
2603        let mut stake = new_stake(
2604            1,
2605            &Pubkey::default(),
2606            &vote_state,
2607            std::u64::MAX,
2608            &Config::default(),
2609        );
2610
2611        // this one can't collect now, credits_observed == vote_state.credits()
2612        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        // put 2 credits in at epoch 0
2629        vote_state.increment_credits(0, 1);
2630        vote_state.increment_credits(0, 1);
2631
2632        // this one should be able to collect exactly 2
2633        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 // all his
2645                },
2646                &vote_state,
2647                None,
2648                null_tracer(),
2649                true,
2650            )
2651        );
2652
2653        stake.credits_observed = 1;
2654        // this one should be able to collect exactly 1 (already observed one)
2655        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        // put 1 credit in epoch 1
2676        vote_state.increment_credits(1, 1);
2677
2678        stake.credits_observed = 2;
2679        // this one should be able to collect the one just added
2680        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        // put 1 credit in epoch 2
2701        vote_state.increment_credits(2, 1);
2702        // this one should be able to collect 2 now
2703        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        // this one should be able to collect everything from t=0 a warmed up stake of 2
2725        // (2 credits at stake of 1) + (1 credit at a stake of 2)
2726        assert_eq!(
2727            Some(CalculatedStakeRewards {
2728                staker_rewards: stake.delegation.stake * 2 // epoch 0
2729                    + stake.delegation.stake // epoch 1
2730                    + stake.delegation.stake, // epoch 2
2731                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        // same as above, but is a really small commission out of 32 bits,
2749        //  verify that None comes back on small redemptions where no one gets paid
2750        vote_state.commission = 1;
2751        assert_eq!(
2752            None, // would be Some((0, 2 * 1 + 1 * 2, 4)),
2753            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, // would be Some((0, 2 * 1 + 1 * 2, 4)),
2769            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        // now one with inflation disabled. no one gets paid, but we still need
2784        // to advance the stake state's credits_observed field to prevent back-
2785        // paying rewards when inflation is turned on.
2786        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        // credits_observed remains at previous level when vote_state credits are
2807        // not advancing and inflation is disabled
2808        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        // credits_observed is auto-rewinded when vote_state credits are assumed to have been
2839        // recreated
2840        stake.credits_observed = 1000;
2841        // this is old behavior; return the pre-recreation (large) credits from stake account
2842        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        // this is new behavior 1; return the post-recreation rewinded credits from the vote account
2851        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        // this is new behavior 2; don't hint when credits both from stake and vote are identical
2860        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        // get rewards and credits observed when not the activation epoch
2871        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, // epoch 2
2877                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        // credits_observed is moved forward for the stake's activation epoch,
2895        // and no rewards are perceived
2896        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        // neither time
2940        assert!(lockup.is_in_force(
2941            &Clock {
2942                epoch: 0,
2943                unix_timestamp: 0,
2944                ..Clock::default()
2945            },
2946            None
2947        ));
2948        // not timestamp
2949        assert!(lockup.is_in_force(
2950            &Clock {
2951                epoch: 2,
2952                unix_timestamp: 0,
2953                ..Clock::default()
2954            },
2955            None
2956        ));
2957        // not epoch
2958        assert!(lockup.is_in_force(
2959            &Clock {
2960                epoch: 0,
2961                unix_timestamp: 2,
2962                ..Clock::default()
2963            },
2964            None
2965        ));
2966        // both, no custodian
2967        assert!(!lockup.is_in_force(
2968            &Clock {
2969                epoch: 1,
2970                unix_timestamp: 1,
2971                ..Clock::default()
2972            },
2973            None
2974        ));
2975        // neither, but custodian
2976        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        // Identical Metas can merge
3141        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        // Mismatched lockups fail when both in force
3220        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        // Mismatched lockups fail when either in force
3243        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        // Mismatched lockups succeed when both expired
3266        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        // Uninitialized state fails
3309        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        // RewardsPool state fails
3322        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        // Initialized state succeeds
3336        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        // activation_epoch succeeds
3388        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        // all paritially activated, transient epochs fail
3401        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        // all epochs for which we're fully active succeed
3432        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        // deactivation epoch fails, fully transient/deactivating
3466        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        // all transient, deactivating epochs fail
3479        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        // first fully-deactivated epoch succeeds
3510        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        // activating stake merge, match credits observed
3633        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        // active stake merge, match credits observed
3648        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        // activating stake merge, unmatched credits observed
3660        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        // active stake merge, unmatched credits observed
3688        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        // active stake merge, unmatched credits observed, no need to ceiling the calculation
3704        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            // calculated credits observed should always be between the credits of a and b
3769            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            // the difference of the combined weighted credits and the separate weighted credits
3778            // should be 1 or 0
3779            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}