Skip to main content

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::{
32            BorrowedAccount, IndexOfAccount, InstructionContext, TransactionContext,
33        },
34    },
35    solana_vote_program::vote_state::{self, VoteState, VoteStateVersions},
36    std::{collections::HashSet, convert::TryFrom},
37};
38
39#[derive(Debug)]
40pub enum SkippedReason {
41    DisabledInflation,
42    JustActivated,
43    TooEarlyUnfairSplit,
44    ZeroPoints,
45    ZeroPointValue,
46    ZeroReward,
47    ZeroCreditsAndReturnZero,
48    ZeroCreditsAndReturnCurrent,
49    ZeroCreditsAndReturnRewinded,
50}
51
52impl From<SkippedReason> for InflationPointCalculationEvent {
53    fn from(reason: SkippedReason) -> Self {
54        InflationPointCalculationEvent::Skipped(reason)
55    }
56}
57
58#[derive(Debug)]
59pub enum InflationPointCalculationEvent {
60    CalculatedPoints(u64, u128, u128, u128),
61    SplitRewards(u64, u64, u64, PointValue),
62    EffectiveStakeAtRewardedEpoch(u64),
63    RentExemptReserve(u64),
64    Delegation(Delegation, Pubkey),
65    Commission(u8),
66    CreditsObserved(u64, Option<u64>),
67    Skipped(SkippedReason),
68}
69
70pub(crate) fn null_tracer() -> Option<impl Fn(&InflationPointCalculationEvent)> {
71    None::<fn(&_)>
72}
73
74// utility function, used by Stakes, tests
75pub fn from<T: ReadableAccount + StateMut<StakeState>>(account: &T) -> Option<StakeState> {
76    account.state().ok()
77}
78
79pub fn stake_from<T: ReadableAccount + StateMut<StakeState>>(account: &T) -> Option<Stake> {
80    from(account).and_then(|state: StakeState| state.stake())
81}
82
83pub fn delegation_from(account: &AccountSharedData) -> Option<Delegation> {
84    from(account).and_then(|state: StakeState| state.delegation())
85}
86
87pub fn authorized_from(account: &AccountSharedData) -> Option<Authorized> {
88    from(account).and_then(|state: StakeState| state.authorized())
89}
90
91pub fn lockup_from<T: ReadableAccount + StateMut<StakeState>>(account: &T) -> Option<Lockup> {
92    from(account).and_then(|state: StakeState| state.lockup())
93}
94
95pub fn meta_from(account: &AccountSharedData) -> Option<Meta> {
96    from(account).and_then(|state: StakeState| state.meta())
97}
98
99fn redelegate_stake(
100    invoke_context: &InvokeContext,
101    stake: &mut Stake,
102    stake_lamports: u64,
103    voter_pubkey: &Pubkey,
104    vote_state: &VoteState,
105    clock: &Clock,
106    stake_history: &StakeHistory,
107    config: &Config,
108) -> Result<(), StakeError> {
109    // If stake is currently active:
110    if stake.stake(clock.epoch, Some(stake_history)) != 0 {
111        let stake_lamports_ok = if invoke_context
112            .feature_set
113            .is_active(&feature_set::stake_redelegate_instruction::id())
114        {
115            // When a stake account is redelegated, the delegated lamports from the source stake
116            // account are transferred to a new stake account. Do not permit the deactivation of
117            // the source stake account to be rescinded, by more generally requiring the delegation
118            // be configured with the expected amount of stake lamports before rescinding.
119            stake_lamports >= stake.delegation.stake
120        } else {
121            true
122        };
123
124        // If pubkey of new voter is the same as current,
125        // and we are scheduled to start deactivating this epoch,
126        // we rescind deactivation
127        if stake.delegation.voter_pubkey == *voter_pubkey
128            && clock.epoch == stake.delegation.deactivation_epoch
129            && stake_lamports_ok
130        {
131            stake.delegation.deactivation_epoch = std::u64::MAX;
132            return Ok(());
133        } else {
134            // can't redelegate to another pubkey if stake is active.
135            return Err(StakeError::TooSoonToRedelegate);
136        }
137    }
138    // Either the stake is freshly activated, is active but has been
139    // deactivated this epoch, or has fully de-activated.
140    // Redelegation implies either re-activation or un-deactivation
141
142    stake.delegation.stake = stake_lamports;
143    stake.delegation.activation_epoch = clock.epoch;
144    stake.delegation.deactivation_epoch = std::u64::MAX;
145    stake.delegation.voter_pubkey = *voter_pubkey;
146    stake.delegation.warmup_cooldown_rate = config.warmup_cooldown_rate;
147    stake.credits_observed = vote_state.credits();
148    Ok(())
149}
150
151pub(crate) fn new_stake(
152    stake: u64,
153    voter_pubkey: &Pubkey,
154    vote_state: &VoteState,
155    activation_epoch: Epoch,
156    config: &Config,
157) -> Stake {
158    Stake {
159        delegation: Delegation::new(
160            voter_pubkey,
161            stake,
162            activation_epoch,
163            config.warmup_cooldown_rate,
164        ),
165        credits_observed: vote_state.credits(),
166    }
167}
168
169/// captures a rewards round as lamports to be awarded
170///  and the total points over which those lamports
171///  are to be distributed
172//  basically read as rewards/points, but in integers instead of as an f64
173#[derive(Clone, Debug, PartialEq, Eq)]
174pub struct PointValue {
175    pub rewards: u64, // lamports to split
176    pub points: u128, // over these points
177}
178
179fn redeem_stake_rewards(
180    rewarded_epoch: Epoch,
181    stake: &mut Stake,
182    point_value: &PointValue,
183    vote_state: &VoteState,
184    stake_history: Option<&StakeHistory>,
185    inflation_point_calc_tracer: Option<impl Fn(&InflationPointCalculationEvent)>,
186    credits_auto_rewind: bool,
187) -> Option<(u64, u64)> {
188    if let Some(inflation_point_calc_tracer) = inflation_point_calc_tracer.as_ref() {
189        inflation_point_calc_tracer(&InflationPointCalculationEvent::CreditsObserved(
190            stake.credits_observed,
191            None,
192        ));
193    }
194    calculate_stake_rewards(
195        rewarded_epoch,
196        stake,
197        point_value,
198        vote_state,
199        stake_history,
200        inflation_point_calc_tracer.as_ref(),
201        credits_auto_rewind,
202    )
203    .map(|calculated_stake_rewards| {
204        if let Some(inflation_point_calc_tracer) = inflation_point_calc_tracer {
205            inflation_point_calc_tracer(&InflationPointCalculationEvent::CreditsObserved(
206                stake.credits_observed,
207                Some(calculated_stake_rewards.new_credits_observed),
208            ));
209        }
210        stake.credits_observed = calculated_stake_rewards.new_credits_observed;
211        stake.delegation.stake += calculated_stake_rewards.staker_rewards;
212        (
213            calculated_stake_rewards.staker_rewards,
214            calculated_stake_rewards.voter_rewards,
215        )
216    })
217}
218
219fn calculate_stake_points(
220    stake: &Stake,
221    vote_state: &VoteState,
222    stake_history: Option<&StakeHistory>,
223    inflation_point_calc_tracer: Option<impl Fn(&InflationPointCalculationEvent)>,
224) -> u128 {
225    calculate_stake_points_and_credits(
226        stake,
227        vote_state,
228        stake_history,
229        inflation_point_calc_tracer,
230        true, // this is safe because this flag shouldn't affect the
231              // `points` field of the returned struct in any way
232    )
233    .points
234}
235
236#[derive(Debug, PartialEq, Eq)]
237struct CalculatedStakePoints {
238    points: u128,
239    new_credits_observed: u64,
240    force_credits_update_with_skipped_reward: bool,
241}
242
243/// for a given stake and vote_state, calculate how many
244///   points were earned (credits * stake) and new value
245///   for credits_observed were the points paid
246fn calculate_stake_points_and_credits(
247    stake: &Stake,
248    new_vote_state: &VoteState,
249    stake_history: Option<&StakeHistory>,
250    inflation_point_calc_tracer: Option<impl Fn(&InflationPointCalculationEvent)>,
251    credits_auto_rewind: bool,
252) -> CalculatedStakePoints {
253    let credits_in_stake = stake.credits_observed;
254    let credits_in_vote = new_vote_state.credits();
255    // if there is no newer credits since observed, return no point
256    if credits_in_vote <= credits_in_stake {
257        if credits_auto_rewind && credits_in_vote < credits_in_stake {
258            if let Some(inflation_point_calc_tracer) = inflation_point_calc_tracer.as_ref() {
259                inflation_point_calc_tracer(&SkippedReason::ZeroCreditsAndReturnRewinded.into());
260            }
261            // Don't adjust stake.activation_epoch for simplicity:
262            //  - generally fast-forwarding stake.activation_epoch forcibly (for
263            //    artificial re-activation with re-warm-up) skews the stake
264            //    history sysvar. And properly handling all the cases
265            //    regarding deactivation epoch/warm-up/cool-down without
266            //    introducing incentive skew is hard.
267            //  - Conceptually, it should be acceptable for the staked SOLs at
268            //    the recreated vote to receive rewards again immediately after
269            //    rewind even if it looks like instant activation. That's
270            //    because it must have passed the required warmed-up at least
271            //    once in the past already
272            //  - Also such a stake account remains to be a part of overall
273            //    effective stake calculation even while the vote account is
274            //    missing for (indefinite) time or remains to be pre-remove
275            //    credits score. It should be treated equally to staking with
276            //    delinquent validator with no differentiation.
277
278            // hint with true to indicate some exceptional credits handling is needed
279            return CalculatedStakePoints {
280                points: 0,
281                new_credits_observed: credits_in_vote,
282                force_credits_update_with_skipped_reward: true,
283            };
284        } else {
285            // change the above `else` to `else if credits_in_vote == credits_in_stake`
286            // (and remove the outermost enclosing `if`) when cleaning credits_auto_rewind
287            // after activation
288
289            if let Some(inflation_point_calc_tracer) = inflation_point_calc_tracer.as_ref() {
290                inflation_point_calc_tracer(&SkippedReason::ZeroCreditsAndReturnCurrent.into());
291            }
292            // don't hint the caller and return current value if credits_auto_rewind is off or
293            // credits remain to be unchanged (= delinquent)
294            return CalculatedStakePoints {
295                points: 0,
296                new_credits_observed: credits_in_stake,
297                force_credits_update_with_skipped_reward: false,
298            };
299        };
300    }
301
302    let mut points = 0;
303    let mut new_credits_observed = credits_in_stake;
304
305    for (epoch, final_epoch_credits, initial_epoch_credits) in
306        new_vote_state.epoch_credits().iter().copied()
307    {
308        let stake_amount = u128::from(stake.delegation.stake(epoch, stake_history));
309
310        // figure out how much this stake has seen that
311        //   for which the vote account has a record
312        let earned_credits = if credits_in_stake < initial_epoch_credits {
313            // the staker observed the entire epoch
314            final_epoch_credits - initial_epoch_credits
315        } else if credits_in_stake < final_epoch_credits {
316            // the staker registered sometime during the epoch, partial credit
317            final_epoch_credits - new_credits_observed
318        } else {
319            // the staker has already observed or been redeemed this epoch
320            //  or was activated after this epoch
321            0
322        };
323        let earned_credits = u128::from(earned_credits);
324
325        // don't want to assume anything about order of the iterator...
326        new_credits_observed = new_credits_observed.max(final_epoch_credits);
327
328        // finally calculate points for this epoch
329        let earned_points = stake_amount * earned_credits;
330        points += earned_points;
331
332        if let Some(inflation_point_calc_tracer) = inflation_point_calc_tracer.as_ref() {
333            inflation_point_calc_tracer(&InflationPointCalculationEvent::CalculatedPoints(
334                epoch,
335                stake_amount,
336                earned_credits,
337                earned_points,
338            ));
339        }
340    }
341
342    CalculatedStakePoints {
343        points,
344        new_credits_observed,
345        force_credits_update_with_skipped_reward: false,
346    }
347}
348
349#[derive(Debug, PartialEq, Eq)]
350struct CalculatedStakeRewards {
351    staker_rewards: u64,
352    voter_rewards: u64,
353    new_credits_observed: u64,
354}
355
356/// for a given stake and vote_state, calculate what distributions and what updates should be made
357/// returns a tuple in the case of a payout of:
358///   * staker_rewards to be distributed
359///   * voter_rewards to be distributed
360///   * new value for credits_observed in the stake
361/// returns None if there's no payout or if any deserved payout is < 1 lamport
362fn calculate_stake_rewards(
363    rewarded_epoch: Epoch,
364    stake: &Stake,
365    point_value: &PointValue,
366    vote_state: &VoteState,
367    stake_history: Option<&StakeHistory>,
368    inflation_point_calc_tracer: Option<impl Fn(&InflationPointCalculationEvent)>,
369    credits_auto_rewind: bool,
370) -> Option<CalculatedStakeRewards> {
371    // ensure to run to trigger (optional) inflation_point_calc_tracer
372    let CalculatedStakePoints {
373        points,
374        new_credits_observed,
375        mut force_credits_update_with_skipped_reward,
376    } = calculate_stake_points_and_credits(
377        stake,
378        vote_state,
379        stake_history,
380        inflation_point_calc_tracer.as_ref(),
381        credits_auto_rewind,
382    );
383
384    // Drive credits_observed forward unconditionally when rewards are disabled
385    // or when this is the stake's activation epoch
386    if point_value.rewards == 0 {
387        if let Some(inflation_point_calc_tracer) = inflation_point_calc_tracer.as_ref() {
388            inflation_point_calc_tracer(&SkippedReason::DisabledInflation.into());
389        }
390        force_credits_update_with_skipped_reward = true;
391    } else if stake.delegation.activation_epoch == rewarded_epoch {
392        // not assert!()-ed; but points should be zero
393        if let Some(inflation_point_calc_tracer) = inflation_point_calc_tracer.as_ref() {
394            inflation_point_calc_tracer(&SkippedReason::JustActivated.into());
395        }
396        force_credits_update_with_skipped_reward = true;
397    }
398
399    if force_credits_update_with_skipped_reward {
400        return Some(CalculatedStakeRewards {
401            staker_rewards: 0,
402            voter_rewards: 0,
403            new_credits_observed,
404        });
405    }
406
407    if points == 0 {
408        if let Some(inflation_point_calc_tracer) = inflation_point_calc_tracer.as_ref() {
409            inflation_point_calc_tracer(&SkippedReason::ZeroPoints.into());
410        }
411        return None;
412    }
413    if point_value.points == 0 {
414        if let Some(inflation_point_calc_tracer) = inflation_point_calc_tracer.as_ref() {
415            inflation_point_calc_tracer(&SkippedReason::ZeroPointValue.into());
416        }
417        return None;
418    }
419
420    let rewards = points
421        .checked_mul(u128::from(point_value.rewards))
422        .unwrap()
423        .checked_div(point_value.points)
424        .unwrap();
425
426    let rewards = u64::try_from(rewards).unwrap();
427
428    // don't bother trying to split if fractional lamports got truncated
429    if rewards == 0 {
430        if let Some(inflation_point_calc_tracer) = inflation_point_calc_tracer.as_ref() {
431            inflation_point_calc_tracer(&SkippedReason::ZeroReward.into());
432        }
433        return None;
434    }
435    let (voter_rewards, staker_rewards, is_split) = vote_state.commission_split(rewards);
436    if let Some(inflation_point_calc_tracer) = inflation_point_calc_tracer.as_ref() {
437        inflation_point_calc_tracer(&InflationPointCalculationEvent::SplitRewards(
438            rewards,
439            voter_rewards,
440            staker_rewards,
441            (*point_value).clone(),
442        ));
443    }
444
445    if (voter_rewards == 0 || staker_rewards == 0) && is_split {
446        // don't collect if we lose a whole lamport somewhere
447        //  is_split means there should be tokens on both sides,
448        //  uncool to move credits_observed if one side didn't get paid
449        if let Some(inflation_point_calc_tracer) = inflation_point_calc_tracer.as_ref() {
450            inflation_point_calc_tracer(&SkippedReason::TooEarlyUnfairSplit.into());
451        }
452        return None;
453    }
454
455    Some(CalculatedStakeRewards {
456        staker_rewards,
457        voter_rewards,
458        new_credits_observed,
459    })
460}
461
462pub fn initialize(
463    stake_account: &mut BorrowedAccount,
464    authorized: &Authorized,
465    lockup: &Lockup,
466    rent: &Rent,
467    feature_set: &FeatureSet,
468) -> Result<(), InstructionError> {
469    if stake_account.get_data().len() != StakeState::size_of() {
470        return Err(InstructionError::InvalidAccountData);
471    }
472    if let StakeState::Uninitialized = stake_account.get_state()? {
473        let rent_exempt_reserve = rent.minimum_balance(stake_account.get_data().len());
474        // when removing this feature, remove `minimum_balance` and just use `rent_exempt_reserve`
475        let minimum_balance = if feature_set.is_active(&stake_allow_zero_undelegated_amount::id()) {
476            rent_exempt_reserve
477        } else {
478            let minimum_delegation = crate::get_minimum_delegation(feature_set);
479            rent_exempt_reserve + minimum_delegation
480        };
481
482        if stake_account.get_lamports() >= minimum_balance {
483            stake_account.set_state(&StakeState::Initialized(Meta {
484                rent_exempt_reserve,
485                authorized: *authorized,
486                lockup: *lockup,
487            }))
488        } else {
489            Err(InstructionError::InsufficientFunds)
490        }
491    } else {
492        Err(InstructionError::InvalidAccountData)
493    }
494}
495
496/// Authorize the given pubkey to manage stake (deactivate, withdraw). This may be called
497/// multiple times, but will implicitly withdraw authorization from the previously authorized
498/// staker. The default staker is the owner of the stake account's pubkey.
499pub fn authorize(
500    stake_account: &mut BorrowedAccount,
501    signers: &HashSet<Pubkey>,
502    new_authority: &Pubkey,
503    stake_authorize: StakeAuthorize,
504    require_custodian_for_locked_stake_authorize: bool,
505    clock: &Clock,
506    custodian: Option<&Pubkey>,
507) -> Result<(), InstructionError> {
508    match stake_account.get_state()? {
509        StakeState::Stake(mut meta, stake) => {
510            meta.authorized.authorize(
511                signers,
512                new_authority,
513                stake_authorize,
514                if require_custodian_for_locked_stake_authorize {
515                    Some((&meta.lockup, clock, custodian))
516                } else {
517                    None
518                },
519            )?;
520            stake_account.set_state(&StakeState::Stake(meta, stake))
521        }
522        StakeState::Initialized(mut meta) => {
523            meta.authorized.authorize(
524                signers,
525                new_authority,
526                stake_authorize,
527                if require_custodian_for_locked_stake_authorize {
528                    Some((&meta.lockup, clock, custodian))
529                } else {
530                    None
531                },
532            )?;
533            stake_account.set_state(&StakeState::Initialized(meta))
534        }
535        _ => Err(InstructionError::InvalidAccountData),
536    }
537}
538
539#[allow(clippy::too_many_arguments)]
540pub fn authorize_with_seed(
541    transaction_context: &TransactionContext,
542    instruction_context: &InstructionContext,
543    stake_account: &mut BorrowedAccount,
544    authority_base_index: IndexOfAccount,
545    authority_seed: &str,
546    authority_owner: &Pubkey,
547    new_authority: &Pubkey,
548    stake_authorize: StakeAuthorize,
549    require_custodian_for_locked_stake_authorize: bool,
550    clock: &Clock,
551    custodian: Option<&Pubkey>,
552) -> Result<(), InstructionError> {
553    let mut signers = HashSet::default();
554    if instruction_context.is_instruction_account_signer(authority_base_index)? {
555        let base_pubkey = transaction_context.get_key_of_account_at_index(
556            instruction_context
557                .get_index_of_instruction_account_in_transaction(authority_base_index)?,
558        )?;
559        signers.insert(Pubkey::create_with_seed(
560            base_pubkey,
561            authority_seed,
562            authority_owner,
563        )?);
564    }
565    authorize(
566        stake_account,
567        &signers,
568        new_authority,
569        stake_authorize,
570        require_custodian_for_locked_stake_authorize,
571        clock,
572        custodian,
573    )
574}
575
576#[allow(clippy::too_many_arguments)]
577pub fn delegate(
578    invoke_context: &InvokeContext,
579    transaction_context: &TransactionContext,
580    instruction_context: &InstructionContext,
581    stake_account_index: IndexOfAccount,
582    vote_account_index: IndexOfAccount,
583    clock: &Clock,
584    stake_history: &StakeHistory,
585    config: &Config,
586    signers: &HashSet<Pubkey>,
587    feature_set: &FeatureSet,
588) -> Result<(), InstructionError> {
589    let vote_account = instruction_context
590        .try_borrow_instruction_account(transaction_context, vote_account_index)?;
591    if *vote_account.get_owner() != solana_vote_program::id() {
592        return Err(InstructionError::IncorrectProgramId);
593    }
594    let vote_pubkey = *vote_account.get_key();
595    let vote_state = vote_account.get_state::<VoteStateVersions>();
596    drop(vote_account);
597
598    let mut stake_account = instruction_context
599        .try_borrow_instruction_account(transaction_context, stake_account_index)?;
600    match stake_account.get_state()? {
601        StakeState::Initialized(meta) => {
602            meta.authorized.check(signers, StakeAuthorize::Staker)?;
603            let ValidatedDelegatedInfo { stake_amount } =
604                validate_delegated_amount(&stake_account, &meta, feature_set)?;
605            let stake = new_stake(
606                stake_amount,
607                &vote_pubkey,
608                &vote_state?.convert_to_current(),
609                clock.epoch,
610                config,
611            );
612            stake_account.set_state(&StakeState::Stake(meta, stake))
613        }
614        StakeState::Stake(meta, mut stake) => {
615            meta.authorized.check(signers, StakeAuthorize::Staker)?;
616            let ValidatedDelegatedInfo { stake_amount } =
617                validate_delegated_amount(&stake_account, &meta, feature_set)?;
618            redelegate_stake(
619                invoke_context,
620                &mut stake,
621                stake_amount,
622                &vote_pubkey,
623                &vote_state?.convert_to_current(),
624                clock,
625                stake_history,
626                config,
627            )?;
628            stake_account.set_state(&StakeState::Stake(meta, stake))
629        }
630        _ => Err(InstructionError::InvalidAccountData),
631    }
632}
633
634pub fn deactivate(
635    stake_account: &mut BorrowedAccount,
636    clock: &Clock,
637    signers: &HashSet<Pubkey>,
638) -> Result<(), InstructionError> {
639    if let StakeState::Stake(meta, mut stake) = stake_account.get_state()? {
640        meta.authorized.check(signers, StakeAuthorize::Staker)?;
641        stake.deactivate(clock.epoch)?;
642
643        stake_account.set_state(&StakeState::Stake(meta, stake))
644    } else {
645        Err(InstructionError::InvalidAccountData)
646    }
647}
648
649pub fn set_lockup(
650    stake_account: &mut BorrowedAccount,
651    lockup: &LockupArgs,
652    signers: &HashSet<Pubkey>,
653    clock: &Clock,
654) -> Result<(), InstructionError> {
655    match stake_account.get_state()? {
656        StakeState::Initialized(mut meta) => {
657            meta.set_lockup(lockup, signers, clock)?;
658            stake_account.set_state(&StakeState::Initialized(meta))
659        }
660        StakeState::Stake(mut meta, stake) => {
661            meta.set_lockup(lockup, signers, clock)?;
662            stake_account.set_state(&StakeState::Stake(meta, stake))
663        }
664        _ => Err(InstructionError::InvalidAccountData),
665    }
666}
667
668pub fn split(
669    invoke_context: &InvokeContext,
670    transaction_context: &TransactionContext,
671    instruction_context: &InstructionContext,
672    stake_account_index: IndexOfAccount,
673    lamports: u64,
674    split_index: IndexOfAccount,
675    signers: &HashSet<Pubkey>,
676) -> Result<(), InstructionError> {
677    let split =
678        instruction_context.try_borrow_instruction_account(transaction_context, split_index)?;
679    if *split.get_owner() != id() {
680        return Err(InstructionError::IncorrectProgramId);
681    }
682    if split.get_data().len() != StakeState::size_of() {
683        return Err(InstructionError::InvalidAccountData);
684    }
685    if !matches!(split.get_state()?, StakeState::Uninitialized) {
686        return Err(InstructionError::InvalidAccountData);
687    }
688    let split_lamport_balance = split.get_lamports();
689    drop(split);
690    let stake_account = instruction_context
691        .try_borrow_instruction_account(transaction_context, stake_account_index)?;
692    if lamports > stake_account.get_lamports() {
693        return Err(InstructionError::InsufficientFunds);
694    }
695    let stake_state = stake_account.get_state()?;
696    drop(stake_account);
697
698    match stake_state {
699        StakeState::Stake(meta, mut stake) => {
700            meta.authorized.check(signers, StakeAuthorize::Staker)?;
701            let minimum_delegation = crate::get_minimum_delegation(&invoke_context.feature_set);
702            let validated_split_info = validate_split_amount(
703                invoke_context,
704                transaction_context,
705                instruction_context,
706                stake_account_index,
707                split_index,
708                lamports,
709                &meta,
710                Some(&stake),
711                minimum_delegation,
712            )?;
713
714            // split the stake, subtract rent_exempt_balance unless
715            // the destination account already has those lamports
716            // in place.
717            // this means that the new stake account will have a stake equivalent to
718            // lamports minus rent_exempt_reserve if it starts out with a zero balance
719            let (remaining_stake_delta, split_stake_amount) =
720                if validated_split_info.source_remaining_balance == 0 {
721                    // If split amount equals the full source stake (as implied by 0
722                    // source_remaining_balance), the new split stake must equal the same
723                    // amount, regardless of any current lamport balance in the split account.
724                    // Since split accounts retain the state of their source account, this
725                    // prevents any magic activation of stake by prefunding the split account.
726                    //
727                    // The new split stake also needs to ignore any positive delta between the
728                    // original rent_exempt_reserve and the split_rent_exempt_reserve, in order
729                    // to prevent magic activation of stake by splitting between accounts of
730                    // different sizes.
731                    let remaining_stake_delta = lamports.saturating_sub(meta.rent_exempt_reserve);
732                    (remaining_stake_delta, remaining_stake_delta)
733                } else {
734                    // Otherwise, the new split stake should reflect the entire split
735                    // requested, less any lamports needed to cover the split_rent_exempt_reserve.
736                    (
737                        lamports,
738                        lamports.saturating_sub(
739                            validated_split_info
740                                .destination_rent_exempt_reserve
741                                .saturating_sub(split_lamport_balance),
742                        ),
743                    )
744                };
745            let split_stake = stake.split(remaining_stake_delta, split_stake_amount)?;
746            let mut split_meta = meta;
747            split_meta.rent_exempt_reserve = validated_split_info.destination_rent_exempt_reserve;
748
749            let mut stake_account = instruction_context
750                .try_borrow_instruction_account(transaction_context, stake_account_index)?;
751            stake_account.set_state(&StakeState::Stake(meta, stake))?;
752            drop(stake_account);
753            let mut split = instruction_context
754                .try_borrow_instruction_account(transaction_context, split_index)?;
755            split.set_state(&StakeState::Stake(split_meta, split_stake))?;
756        }
757        StakeState::Initialized(meta) => {
758            meta.authorized.check(signers, StakeAuthorize::Staker)?;
759            let additional_required_lamports = if invoke_context
760                .feature_set
761                .is_active(&stake_allow_zero_undelegated_amount::id())
762            {
763                0
764            } else {
765                crate::get_minimum_delegation(&invoke_context.feature_set)
766            };
767            let validated_split_info = validate_split_amount(
768                invoke_context,
769                transaction_context,
770                instruction_context,
771                stake_account_index,
772                split_index,
773                lamports,
774                &meta,
775                None,
776                additional_required_lamports,
777            )?;
778            let mut split_meta = meta;
779            split_meta.rent_exempt_reserve = validated_split_info.destination_rent_exempt_reserve;
780            let mut split = instruction_context
781                .try_borrow_instruction_account(transaction_context, split_index)?;
782            split.set_state(&StakeState::Initialized(split_meta))?;
783        }
784        StakeState::Uninitialized => {
785            let stake_pubkey = transaction_context.get_key_of_account_at_index(
786                instruction_context
787                    .get_index_of_instruction_account_in_transaction(stake_account_index)?,
788            )?;
789            if !signers.contains(stake_pubkey) {
790                return Err(InstructionError::MissingRequiredSignature);
791            }
792        }
793        _ => return Err(InstructionError::InvalidAccountData),
794    }
795
796    // Deinitialize state upon zero balance
797    let mut stake_account = instruction_context
798        .try_borrow_instruction_account(transaction_context, stake_account_index)?;
799    if lamports == stake_account.get_lamports() {
800        stake_account.set_state(&StakeState::Uninitialized)?;
801    }
802    drop(stake_account);
803
804    let mut split =
805        instruction_context.try_borrow_instruction_account(transaction_context, split_index)?;
806    split.checked_add_lamports(lamports)?;
807    drop(split);
808    let mut stake_account = instruction_context
809        .try_borrow_instruction_account(transaction_context, stake_account_index)?;
810    stake_account.checked_sub_lamports(lamports)?;
811    Ok(())
812}
813
814pub fn merge(
815    invoke_context: &InvokeContext,
816    transaction_context: &TransactionContext,
817    instruction_context: &InstructionContext,
818    stake_account_index: IndexOfAccount,
819    source_account_index: IndexOfAccount,
820    clock: &Clock,
821    stake_history: &StakeHistory,
822    signers: &HashSet<Pubkey>,
823) -> Result<(), InstructionError> {
824    let mut source_account = instruction_context
825        .try_borrow_instruction_account(transaction_context, source_account_index)?;
826    // Ensure source isn't spoofed
827    if *source_account.get_owner() != id() {
828        return Err(InstructionError::IncorrectProgramId);
829    }
830    // Close the stake_account-reference loophole
831    if instruction_context.get_index_of_instruction_account_in_transaction(stake_account_index)?
832        == instruction_context
833            .get_index_of_instruction_account_in_transaction(source_account_index)?
834    {
835        return Err(InstructionError::InvalidArgument);
836    }
837    let mut stake_account = instruction_context
838        .try_borrow_instruction_account(transaction_context, stake_account_index)?;
839
840    ic_msg!(invoke_context, "Checking if destination stake is mergeable");
841    let stake_merge_kind = MergeKind::get_if_mergeable(
842        invoke_context,
843        &stake_account.get_state()?,
844        stake_account.get_lamports(),
845        clock,
846        stake_history,
847    )?;
848
849    // Authorized staker is allowed to split/merge accounts
850    stake_merge_kind
851        .meta()
852        .authorized
853        .check(signers, StakeAuthorize::Staker)?;
854
855    ic_msg!(invoke_context, "Checking if source stake is mergeable");
856    let source_merge_kind = MergeKind::get_if_mergeable(
857        invoke_context,
858        &source_account.get_state()?,
859        source_account.get_lamports(),
860        clock,
861        stake_history,
862    )?;
863
864    ic_msg!(invoke_context, "Merging stake accounts");
865    if let Some(merged_state) = stake_merge_kind.merge(invoke_context, source_merge_kind, clock)? {
866        stake_account.set_state(&merged_state)?;
867    }
868
869    // Source is about to be drained, deinitialize its state
870    source_account.set_state(&StakeState::Uninitialized)?;
871
872    // Drain the source stake account
873    let lamports = source_account.get_lamports();
874    source_account.checked_sub_lamports(lamports)?;
875    stake_account.checked_add_lamports(lamports)?;
876    Ok(())
877}
878
879pub fn redelegate(
880    invoke_context: &InvokeContext,
881    transaction_context: &TransactionContext,
882    instruction_context: &InstructionContext,
883    stake_account: &mut BorrowedAccount,
884    uninitialized_stake_account_index: IndexOfAccount,
885    vote_account_index: IndexOfAccount,
886    config: &Config,
887    signers: &HashSet<Pubkey>,
888) -> Result<(), InstructionError> {
889    let clock = invoke_context.get_sysvar_cache().get_clock()?;
890
891    // ensure `uninitialized_stake_account_index` is in the uninitialized state
892    let mut uninitialized_stake_account = instruction_context
893        .try_borrow_instruction_account(transaction_context, uninitialized_stake_account_index)?;
894    if *uninitialized_stake_account.get_owner() != id() {
895        ic_msg!(
896            invoke_context,
897            "expected uninitialized stake account owner to be {}, not {}",
898            id(),
899            *uninitialized_stake_account.get_owner()
900        );
901        return Err(InstructionError::IncorrectProgramId);
902    }
903    if uninitialized_stake_account.get_data().len() != StakeState::size_of() {
904        ic_msg!(
905            invoke_context,
906            "expected uninitialized stake account data len to be {}, not {}",
907            StakeState::size_of(),
908            uninitialized_stake_account.get_data().len()
909        );
910        return Err(InstructionError::InvalidAccountData);
911    }
912    if !matches!(
913        uninitialized_stake_account.get_state()?,
914        StakeState::Uninitialized
915    ) {
916        ic_msg!(
917            invoke_context,
918            "expected uninitialized stake account to be uninitialized",
919        );
920        return Err(InstructionError::AccountAlreadyInitialized);
921    }
922
923    // validate the provided vote account
924    let vote_account = instruction_context
925        .try_borrow_instruction_account(transaction_context, vote_account_index)?;
926    if *vote_account.get_owner() != solana_vote_program::id() {
927        ic_msg!(
928            invoke_context,
929            "expected vote account owner to be {}, not {}",
930            solana_vote_program::id(),
931            *vote_account.get_owner()
932        );
933        return Err(InstructionError::IncorrectProgramId);
934    }
935    let vote_pubkey = *vote_account.get_key();
936    let vote_state = vote_account.get_state::<VoteStateVersions>()?;
937
938    let (stake_meta, effective_stake) =
939        if let StakeState::Stake(meta, stake) = stake_account.get_state()? {
940            let stake_history = invoke_context.get_sysvar_cache().get_stake_history()?;
941            let status = stake
942                .delegation
943                .stake_activating_and_deactivating(clock.epoch, Some(&stake_history));
944            if status.effective == 0 || status.activating != 0 || status.deactivating != 0 {
945                ic_msg!(invoke_context, "stake is not active");
946                return Err(StakeError::RedelegateTransientOrInactiveStake.into());
947            }
948
949            // Deny redelegating to the same vote account. This is nonsensical and could be used to
950            // grief the global stake warm-up/cool-down rate
951            if stake.delegation.voter_pubkey == vote_pubkey {
952                ic_msg!(
953                    invoke_context,
954                    "redelegating to the same vote account not permitted"
955                );
956                return Err(StakeError::RedelegateToSameVoteAccount.into());
957            }
958
959            (meta, status.effective)
960        } else {
961            ic_msg!(invoke_context, "invalid stake account data",);
962            return Err(InstructionError::InvalidAccountData);
963        };
964
965    // deactivate `stake_account`
966    //
967    // Note: This function also ensures `signers` contains the `StakeAuthorize::Staker`
968    deactivate(stake_account, &clock, signers)?;
969
970    // transfer the effective stake to the uninitialized stake account
971    stake_account.checked_sub_lamports(effective_stake)?;
972    uninitialized_stake_account.checked_add_lamports(effective_stake)?;
973
974    // initialize and schedule `uninitialized_stake_account` for activation
975    let sysvar_cache = invoke_context.get_sysvar_cache();
976    let rent = sysvar_cache.get_rent()?;
977    let mut uninitialized_stake_meta = stake_meta;
978    uninitialized_stake_meta.rent_exempt_reserve =
979        rent.minimum_balance(uninitialized_stake_account.get_data().len());
980
981    let ValidatedDelegatedInfo { stake_amount } = validate_delegated_amount(
982        &uninitialized_stake_account,
983        &uninitialized_stake_meta,
984        &invoke_context.feature_set,
985    )?;
986    uninitialized_stake_account.set_state(&StakeState::Stake(
987        uninitialized_stake_meta,
988        new_stake(
989            stake_amount,
990            &vote_pubkey,
991            &vote_state.convert_to_current(),
992            clock.epoch,
993            config,
994        ),
995    ))?;
996
997    Ok(())
998}
999
1000#[allow(clippy::too_many_arguments)]
1001pub fn withdraw(
1002    transaction_context: &TransactionContext,
1003    instruction_context: &InstructionContext,
1004    stake_account_index: IndexOfAccount,
1005    lamports: u64,
1006    to_index: IndexOfAccount,
1007    clock: &Clock,
1008    stake_history: &StakeHistory,
1009    withdraw_authority_index: IndexOfAccount,
1010    custodian_index: Option<IndexOfAccount>,
1011    feature_set: &FeatureSet,
1012) -> Result<(), InstructionError> {
1013    let withdraw_authority_pubkey = transaction_context.get_key_of_account_at_index(
1014        instruction_context
1015            .get_index_of_instruction_account_in_transaction(withdraw_authority_index)?,
1016    )?;
1017    if !instruction_context.is_instruction_account_signer(withdraw_authority_index)? {
1018        return Err(InstructionError::MissingRequiredSignature);
1019    }
1020    let mut signers = HashSet::new();
1021    signers.insert(*withdraw_authority_pubkey);
1022
1023    let mut stake_account = instruction_context
1024        .try_borrow_instruction_account(transaction_context, stake_account_index)?;
1025    let (lockup, reserve, is_staked) = match stake_account.get_state()? {
1026        StakeState::Stake(meta, stake) => {
1027            meta.authorized
1028                .check(&signers, StakeAuthorize::Withdrawer)?;
1029            // if we have a deactivation epoch and we're in cooldown
1030            let staked = if clock.epoch >= stake.delegation.deactivation_epoch {
1031                stake.delegation.stake(clock.epoch, Some(stake_history))
1032            } else {
1033                // Assume full stake if the stake account hasn't been
1034                //  de-activated, because in the future the exposed stake
1035                //  might be higher than stake.stake() due to warmup
1036                stake.delegation.stake
1037            };
1038
1039            let staked_and_reserve = checked_add(staked, meta.rent_exempt_reserve)?;
1040            (meta.lockup, staked_and_reserve, staked != 0)
1041        }
1042        StakeState::Initialized(meta) => {
1043            meta.authorized
1044                .check(&signers, StakeAuthorize::Withdrawer)?;
1045            // stake accounts must have a balance >= rent_exempt_reserve
1046            let reserve = if feature_set.is_active(&stake_allow_zero_undelegated_amount::id()) {
1047                meta.rent_exempt_reserve
1048            } else {
1049                checked_add(
1050                    meta.rent_exempt_reserve,
1051                    crate::get_minimum_delegation(feature_set),
1052                )?
1053            };
1054
1055            (meta.lockup, reserve, false)
1056        }
1057        StakeState::Uninitialized => {
1058            if !signers.contains(stake_account.get_key()) {
1059                return Err(InstructionError::MissingRequiredSignature);
1060            }
1061            (Lockup::default(), 0, false) // no lockup, no restrictions
1062        }
1063        _ => return Err(InstructionError::InvalidAccountData),
1064    };
1065
1066    // verify that lockup has expired or that the withdrawal is signed by
1067    //   the custodian, both epoch and unix_timestamp must have passed
1068    let custodian_pubkey = if let Some(custodian_index) = custodian_index {
1069        if instruction_context.is_instruction_account_signer(custodian_index)? {
1070            Some(
1071                transaction_context.get_key_of_account_at_index(
1072                    instruction_context
1073                        .get_index_of_instruction_account_in_transaction(custodian_index)?,
1074                )?,
1075            )
1076        } else {
1077            None
1078        }
1079    } else {
1080        None
1081    };
1082    if lockup.is_in_force(clock, custodian_pubkey) {
1083        return Err(StakeError::LockupInForce.into());
1084    }
1085
1086    let lamports_and_reserve = checked_add(lamports, reserve)?;
1087    // if the stake is active, we mustn't allow the account to go away
1088    if is_staked // line coverage for branch coverage
1089            && lamports_and_reserve > stake_account.get_lamports()
1090    {
1091        return Err(InstructionError::InsufficientFunds);
1092    }
1093
1094    if lamports != stake_account.get_lamports() // not a full withdrawal
1095            && lamports_and_reserve > stake_account.get_lamports()
1096    {
1097        assert!(!is_staked);
1098        return Err(InstructionError::InsufficientFunds);
1099    }
1100
1101    // Deinitialize state upon zero balance
1102    if lamports == stake_account.get_lamports() {
1103        stake_account.set_state(&StakeState::Uninitialized)?;
1104    }
1105
1106    stake_account.checked_sub_lamports(lamports)?;
1107    drop(stake_account);
1108    let mut to =
1109        instruction_context.try_borrow_instruction_account(transaction_context, to_index)?;
1110    to.checked_add_lamports(lamports)?;
1111    Ok(())
1112}
1113
1114pub(crate) fn deactivate_delinquent(
1115    transaction_context: &TransactionContext,
1116    instruction_context: &InstructionContext,
1117    stake_account: &mut BorrowedAccount,
1118    delinquent_vote_account_index: IndexOfAccount,
1119    reference_vote_account_index: IndexOfAccount,
1120    current_epoch: Epoch,
1121) -> Result<(), InstructionError> {
1122    let delinquent_vote_account_pubkey = transaction_context.get_key_of_account_at_index(
1123        instruction_context
1124            .get_index_of_instruction_account_in_transaction(delinquent_vote_account_index)?,
1125    )?;
1126    let delinquent_vote_account = instruction_context
1127        .try_borrow_instruction_account(transaction_context, delinquent_vote_account_index)?;
1128    if *delinquent_vote_account.get_owner() != solana_vote_program::id() {
1129        return Err(InstructionError::IncorrectProgramId);
1130    }
1131    let delinquent_vote_state = delinquent_vote_account
1132        .get_state::<VoteStateVersions>()?
1133        .convert_to_current();
1134
1135    let reference_vote_account = instruction_context
1136        .try_borrow_instruction_account(transaction_context, reference_vote_account_index)?;
1137    if *reference_vote_account.get_owner() != solana_vote_program::id() {
1138        return Err(InstructionError::IncorrectProgramId);
1139    }
1140    let reference_vote_state = reference_vote_account
1141        .get_state::<VoteStateVersions>()?
1142        .convert_to_current();
1143
1144    if !acceptable_reference_epoch_credits(&reference_vote_state.epoch_credits, current_epoch) {
1145        return Err(StakeError::InsufficientReferenceVotes.into());
1146    }
1147
1148    if let StakeState::Stake(meta, mut stake) = stake_account.get_state()? {
1149        if stake.delegation.voter_pubkey != *delinquent_vote_account_pubkey {
1150            return Err(StakeError::VoteAddressMismatch.into());
1151        }
1152
1153        // Deactivate the stake account if its delegated vote account has never voted or has not
1154        // voted in the last `MINIMUM_DELINQUENT_EPOCHS_FOR_DEACTIVATION`
1155        if eligible_for_deactivate_delinquent(&delinquent_vote_state.epoch_credits, current_epoch) {
1156            stake.deactivate(current_epoch)?;
1157            stake_account.set_state(&StakeState::Stake(meta, stake))
1158        } else {
1159            Err(StakeError::MinimumDelinquentEpochsForDeactivationNotMet.into())
1160        }
1161    } else {
1162        Err(InstructionError::InvalidAccountData)
1163    }
1164}
1165
1166/// After calling `validate_delegated_amount()`, this struct contains calculated values that are used
1167/// by the caller.
1168struct ValidatedDelegatedInfo {
1169    stake_amount: u64,
1170}
1171
1172/// Ensure the stake delegation amount is valid.  This checks that the account meets the minimum
1173/// balance requirements of delegated stake.  If not, return an error.
1174fn validate_delegated_amount(
1175    account: &BorrowedAccount,
1176    meta: &Meta,
1177    feature_set: &FeatureSet,
1178) -> Result<ValidatedDelegatedInfo, InstructionError> {
1179    let stake_amount = account
1180        .get_lamports()
1181        .saturating_sub(meta.rent_exempt_reserve); // can't stake the rent
1182
1183    // Stake accounts may be initialized with a stake amount below the minimum delegation so check
1184    // that the minimum is met before delegation.
1185    if (feature_set.is_active(&stake_allow_zero_undelegated_amount::id())
1186        || feature_set.is_active(&feature_set::stake_raise_minimum_delegation_to_1_sol::id()))
1187        && stake_amount < crate::get_minimum_delegation(feature_set)
1188    {
1189        return Err(StakeError::InsufficientDelegation.into());
1190    }
1191    Ok(ValidatedDelegatedInfo { stake_amount })
1192}
1193
1194/// After calling `validate_split_amount()`, this struct contains calculated values that are used
1195/// by the caller.
1196#[derive(Copy, Clone, Debug, Default)]
1197struct ValidatedSplitInfo {
1198    source_remaining_balance: u64,
1199    destination_rent_exempt_reserve: u64,
1200}
1201
1202/// Ensure the split amount is valid.  This checks the source and destination accounts meet the
1203/// minimum balance requirements, which is the rent exempt reserve plus the minimum stake
1204/// delegation, and that the source account has enough lamports for the request split amount.  If
1205/// not, return an error.
1206fn validate_split_amount(
1207    invoke_context: &InvokeContext,
1208    transaction_context: &TransactionContext,
1209    instruction_context: &InstructionContext,
1210    source_account_index: IndexOfAccount,
1211    destination_account_index: IndexOfAccount,
1212    lamports: u64,
1213    source_meta: &Meta,
1214    source_stake: Option<&Stake>,
1215    additional_required_lamports: u64,
1216) -> Result<ValidatedSplitInfo, InstructionError> {
1217    let source_account = instruction_context
1218        .try_borrow_instruction_account(transaction_context, source_account_index)?;
1219    let source_lamports = source_account.get_lamports();
1220    let source_data_len = source_account.get_data().len();
1221    drop(source_account);
1222    let destination_account = instruction_context
1223        .try_borrow_instruction_account(transaction_context, destination_account_index)?;
1224    let destination_lamports = destination_account.get_lamports();
1225    let destination_data_len = destination_account.get_data().len();
1226    drop(destination_account);
1227
1228    // Split amount has to be something
1229    if lamports == 0 {
1230        return Err(InstructionError::InsufficientFunds);
1231    }
1232
1233    // Obviously cannot split more than what the source account has
1234    if lamports > source_lamports {
1235        return Err(InstructionError::InsufficientFunds);
1236    }
1237
1238    // Verify that the source account still has enough lamports left after splitting:
1239    // EITHER at least the minimum balance, OR zero (in this case the source
1240    // account is transferring all lamports to new destination account, and the source
1241    // account will be closed)
1242    let source_minimum_balance = source_meta
1243        .rent_exempt_reserve
1244        .saturating_add(additional_required_lamports);
1245    let source_remaining_balance = source_lamports.saturating_sub(lamports);
1246    if source_remaining_balance == 0 {
1247        // full amount is a withdrawal
1248        // nothing to do here
1249    } else if source_remaining_balance < source_minimum_balance {
1250        // the remaining balance is too low to do the split
1251        return Err(InstructionError::InsufficientFunds);
1252    } else {
1253        // all clear!
1254        // nothing to do here
1255    }
1256
1257    // Verify the destination account meets the minimum balance requirements
1258    // This must handle:
1259    // 1. The destination account having a different rent exempt reserve due to data size changes
1260    // 2. The destination account being prefunded, which would lower the minimum split amount
1261    let destination_rent_exempt_reserve = if invoke_context
1262        .feature_set
1263        .is_active(&stake_split_uses_rent_sysvar::ID)
1264    {
1265        let rent = invoke_context.get_sysvar_cache().get_rent()?;
1266        rent.minimum_balance(destination_data_len)
1267    } else {
1268        calculate_split_rent_exempt_reserve(
1269            source_meta.rent_exempt_reserve,
1270            source_data_len as u64,
1271            destination_data_len as u64,
1272        )
1273    };
1274    let destination_minimum_balance =
1275        destination_rent_exempt_reserve.saturating_add(additional_required_lamports);
1276    let destination_balance_deficit =
1277        destination_minimum_balance.saturating_sub(destination_lamports);
1278    if lamports < destination_balance_deficit {
1279        return Err(InstructionError::InsufficientFunds);
1280    }
1281
1282    // If the source account is already staked, the destination will end up staked as well.  Verify
1283    // the destination account's delegation amount is at least the minimum delegation.
1284    //
1285    // The *delegation* requirements are different than the *balance* requirements.  If the
1286    // destination account is prefunded with a balance of `rent exempt reserve + minimum stake
1287    // delegation - 1`, the minimum split amount to satisfy the *balance* requirements is 1
1288    // lamport.  And since *only* the split amount is immediately staked in the destination
1289    // account, the split amount must be at least the minimum stake delegation.  So if the minimum
1290    // stake delegation was 10 lamports, then a split amount of 1 lamport would not meet the
1291    // *delegation* requirements.
1292    if source_stake.is_some() && lamports < additional_required_lamports {
1293        return Err(InstructionError::InsufficientFunds);
1294    }
1295
1296    Ok(ValidatedSplitInfo {
1297        source_remaining_balance,
1298        destination_rent_exempt_reserve,
1299    })
1300}
1301
1302#[derive(Clone, Debug, PartialEq)]
1303enum MergeKind {
1304    Inactive(Meta, u64),
1305    ActivationEpoch(Meta, Stake),
1306    FullyActive(Meta, Stake),
1307}
1308
1309impl MergeKind {
1310    fn meta(&self) -> &Meta {
1311        match self {
1312            Self::Inactive(meta, _) => meta,
1313            Self::ActivationEpoch(meta, _) => meta,
1314            Self::FullyActive(meta, _) => meta,
1315        }
1316    }
1317
1318    fn active_stake(&self) -> Option<&Stake> {
1319        match self {
1320            Self::Inactive(_, _) => None,
1321            Self::ActivationEpoch(_, stake) => Some(stake),
1322            Self::FullyActive(_, stake) => Some(stake),
1323        }
1324    }
1325
1326    fn get_if_mergeable(
1327        invoke_context: &InvokeContext,
1328        stake_state: &StakeState,
1329        stake_lamports: u64,
1330        clock: &Clock,
1331        stake_history: &StakeHistory,
1332    ) -> Result<Self, InstructionError> {
1333        match stake_state {
1334            StakeState::Stake(meta, stake) => {
1335                // stake must not be in a transient state. Transient here meaning
1336                // activating or deactivating with non-zero effective stake.
1337                let status = stake
1338                    .delegation
1339                    .stake_activating_and_deactivating(clock.epoch, Some(stake_history));
1340
1341                match (status.effective, status.activating, status.deactivating) {
1342                    (0, 0, 0) => Ok(Self::Inactive(*meta, stake_lamports)),
1343                    (0, _, _) => Ok(Self::ActivationEpoch(*meta, *stake)),
1344                    (_, 0, 0) => Ok(Self::FullyActive(*meta, *stake)),
1345                    _ => {
1346                        let err = StakeError::MergeTransientStake;
1347                        ic_msg!(invoke_context, "{}", err);
1348                        Err(err.into())
1349                    }
1350                }
1351            }
1352            StakeState::Initialized(meta) => Ok(Self::Inactive(*meta, stake_lamports)),
1353            _ => Err(InstructionError::InvalidAccountData),
1354        }
1355    }
1356
1357    fn metas_can_merge(
1358        invoke_context: &InvokeContext,
1359        stake: &Meta,
1360        source: &Meta,
1361        clock: &Clock,
1362    ) -> Result<(), InstructionError> {
1363        // lockups may mismatch so long as both have expired
1364        let can_merge_lockups = stake.lockup == source.lockup
1365            || (!stake.lockup.is_in_force(clock, None) && !source.lockup.is_in_force(clock, None));
1366        // `rent_exempt_reserve` has no bearing on the mergeability of accounts,
1367        // as the source account will be culled by runtime once the operation
1368        // succeeds. Considering it here would needlessly prevent merging stake
1369        // accounts with differing data lengths, which already exist in the wild
1370        // due to an SDK bug
1371        if stake.authorized == source.authorized && can_merge_lockups {
1372            Ok(())
1373        } else {
1374            ic_msg!(invoke_context, "Unable to merge due to metadata mismatch");
1375            Err(StakeError::MergeMismatch.into())
1376        }
1377    }
1378
1379    fn active_delegations_can_merge(
1380        invoke_context: &InvokeContext,
1381        stake: &Delegation,
1382        source: &Delegation,
1383    ) -> Result<(), InstructionError> {
1384        if stake.voter_pubkey != source.voter_pubkey {
1385            ic_msg!(invoke_context, "Unable to merge due to voter mismatch");
1386            Err(StakeError::MergeMismatch.into())
1387        } else if (stake.warmup_cooldown_rate - source.warmup_cooldown_rate).abs() < f64::EPSILON
1388            && stake.deactivation_epoch == Epoch::MAX
1389            && source.deactivation_epoch == Epoch::MAX
1390        {
1391            Ok(())
1392        } else {
1393            ic_msg!(invoke_context, "Unable to merge due to stake deactivation");
1394            Err(StakeError::MergeMismatch.into())
1395        }
1396    }
1397
1398    // Remove this when the `stake_merge_with_unmatched_credits_observed` feature is removed
1399    fn active_stakes_can_merge(
1400        invoke_context: &InvokeContext,
1401        stake: &Stake,
1402        source: &Stake,
1403    ) -> Result<(), InstructionError> {
1404        Self::active_delegations_can_merge(invoke_context, &stake.delegation, &source.delegation)?;
1405        // `credits_observed` MUST match to prevent earning multiple rewards
1406        // from a stake account by merging it into another stake account that
1407        // is small enough to not be paid out every epoch. This would effectively
1408        // reset the larger stake accounts `credits_observed` to that of the
1409        // smaller account.
1410        if stake.credits_observed == source.credits_observed {
1411            Ok(())
1412        } else {
1413            ic_msg!(
1414                invoke_context,
1415                "Unable to merge due to credits observed mismatch"
1416            );
1417            Err(StakeError::MergeMismatch.into())
1418        }
1419    }
1420
1421    fn merge(
1422        self,
1423        invoke_context: &InvokeContext,
1424        source: Self,
1425        clock: &Clock,
1426    ) -> Result<Option<StakeState>, InstructionError> {
1427        Self::metas_can_merge(invoke_context, self.meta(), source.meta(), clock)?;
1428        self.active_stake()
1429            .zip(source.active_stake())
1430            .map(|(stake, source)| {
1431                if invoke_context
1432                    .feature_set
1433                    .is_active(&stake_merge_with_unmatched_credits_observed::id())
1434                {
1435                    Self::active_delegations_can_merge(
1436                        invoke_context,
1437                        &stake.delegation,
1438                        &source.delegation,
1439                    )
1440                } else {
1441                    Self::active_stakes_can_merge(invoke_context, stake, source)
1442                }
1443            })
1444            .unwrap_or(Ok(()))?;
1445        let merged_state = match (self, source) {
1446            (Self::Inactive(_, _), Self::Inactive(_, _)) => None,
1447            (Self::Inactive(_, _), Self::ActivationEpoch(_, _)) => None,
1448            (Self::ActivationEpoch(meta, mut stake), Self::Inactive(_, source_lamports)) => {
1449                stake.delegation.stake = checked_add(stake.delegation.stake, source_lamports)?;
1450                Some(StakeState::Stake(meta, stake))
1451            }
1452            (
1453                Self::ActivationEpoch(meta, mut stake),
1454                Self::ActivationEpoch(source_meta, source_stake),
1455            ) => {
1456                let source_lamports = checked_add(
1457                    source_meta.rent_exempt_reserve,
1458                    source_stake.delegation.stake,
1459                )?;
1460                merge_delegation_stake_and_credits_observed(
1461                    invoke_context,
1462                    &mut stake,
1463                    source_lamports,
1464                    source_stake.credits_observed,
1465                )?;
1466                Some(StakeState::Stake(meta, stake))
1467            }
1468            (Self::FullyActive(meta, mut stake), Self::FullyActive(_, source_stake)) => {
1469                // Don't stake the source account's `rent_exempt_reserve` to
1470                // protect against the magic activation loophole. It will
1471                // instead be moved into the destination account as extra,
1472                // withdrawable `lamports`
1473                merge_delegation_stake_and_credits_observed(
1474                    invoke_context,
1475                    &mut stake,
1476                    source_stake.delegation.stake,
1477                    source_stake.credits_observed,
1478                )?;
1479                Some(StakeState::Stake(meta, stake))
1480            }
1481            _ => return Err(StakeError::MergeMismatch.into()),
1482        };
1483        Ok(merged_state)
1484    }
1485}
1486
1487fn merge_delegation_stake_and_credits_observed(
1488    invoke_context: &InvokeContext,
1489    stake: &mut Stake,
1490    absorbed_lamports: u64,
1491    absorbed_credits_observed: u64,
1492) -> Result<(), InstructionError> {
1493    if invoke_context
1494        .feature_set
1495        .is_active(&stake_merge_with_unmatched_credits_observed::id())
1496    {
1497        stake.credits_observed =
1498            stake_weighted_credits_observed(stake, absorbed_lamports, absorbed_credits_observed)
1499                .ok_or(InstructionError::ArithmeticOverflow)?;
1500    }
1501    stake.delegation.stake = checked_add(stake.delegation.stake, absorbed_lamports)?;
1502    Ok(())
1503}
1504
1505/// Calculate the effective credits observed for two stakes when merging
1506///
1507/// When merging two `ActivationEpoch` or `FullyActive` stakes, the credits
1508/// observed of the merged stake is the weighted average of the two stakes'
1509/// credits observed.
1510///
1511/// This is because we can derive the effective credits_observed by reversing the staking
1512/// rewards equation, _while keeping the rewards unchanged after merge (i.e. strong
1513/// requirement)_, like below:
1514///
1515/// a(N) => account, r => rewards, s => stake, c => credits:
1516/// assume:
1517///   a3 = merge(a1, a2)
1518/// then:
1519///   a3.s = a1.s + a2.s
1520///
1521/// Next, given:
1522///   aN.r = aN.c * aN.s (for every N)
1523/// finally:
1524///        a3.r = a1.r + a2.r
1525/// a3.c * a3.s = a1.c * a1.s + a2.c * a2.s
1526///        a3.c = (a1.c * a1.s + a2.c * a2.s) / (a1.s + a2.s)     // QED
1527///
1528/// (For this discussion, we omitted irrelevant variables, including distance
1529///  calculation against vote_account and point indirection.)
1530fn stake_weighted_credits_observed(
1531    stake: &Stake,
1532    absorbed_lamports: u64,
1533    absorbed_credits_observed: u64,
1534) -> Option<u64> {
1535    if stake.credits_observed == absorbed_credits_observed {
1536        Some(stake.credits_observed)
1537    } else {
1538        let total_stake = u128::from(stake.delegation.stake.checked_add(absorbed_lamports)?);
1539        let stake_weighted_credits =
1540            u128::from(stake.credits_observed).checked_mul(u128::from(stake.delegation.stake))?;
1541        let absorbed_weighted_credits =
1542            u128::from(absorbed_credits_observed).checked_mul(u128::from(absorbed_lamports))?;
1543        // Discard fractional credits as a merge side-effect friction by taking
1544        // the ceiling, done by adding `denominator - 1` to the numerator.
1545        let total_weighted_credits = stake_weighted_credits
1546            .checked_add(absorbed_weighted_credits)?
1547            .checked_add(total_stake)?
1548            .checked_sub(1)?;
1549        u64::try_from(total_weighted_credits.checked_div(total_stake)?).ok()
1550    }
1551}
1552
1553// utility function, used by runtime
1554// returns a tuple of (stakers_reward,voters_reward)
1555#[doc(hidden)]
1556pub fn redeem_rewards(
1557    rewarded_epoch: Epoch,
1558    stake_state: StakeState,
1559    stake_account: &mut AccountSharedData,
1560    vote_state: &VoteState,
1561    point_value: &PointValue,
1562    stake_history: Option<&StakeHistory>,
1563    inflation_point_calc_tracer: Option<impl Fn(&InflationPointCalculationEvent)>,
1564    credits_auto_rewind: bool,
1565) -> Result<(u64, u64), InstructionError> {
1566    if let StakeState::Stake(meta, mut stake) = stake_state {
1567        if let Some(inflation_point_calc_tracer) = inflation_point_calc_tracer.as_ref() {
1568            inflation_point_calc_tracer(
1569                &InflationPointCalculationEvent::EffectiveStakeAtRewardedEpoch(
1570                    stake.stake(rewarded_epoch, stake_history),
1571                ),
1572            );
1573            inflation_point_calc_tracer(&InflationPointCalculationEvent::RentExemptReserve(
1574                meta.rent_exempt_reserve,
1575            ));
1576            inflation_point_calc_tracer(&InflationPointCalculationEvent::Commission(
1577                vote_state.commission,
1578            ));
1579        }
1580
1581        if let Some((stakers_reward, voters_reward)) = redeem_stake_rewards(
1582            rewarded_epoch,
1583            &mut stake,
1584            point_value,
1585            vote_state,
1586            stake_history,
1587            inflation_point_calc_tracer,
1588            credits_auto_rewind,
1589        ) {
1590            stake_account.checked_add_lamports(stakers_reward)?;
1591            stake_account.set_state(&StakeState::Stake(meta, stake))?;
1592
1593            Ok((stakers_reward, voters_reward))
1594        } else {
1595            Err(StakeError::NoCreditsToRedeem.into())
1596        }
1597    } else {
1598        Err(InstructionError::InvalidAccountData)
1599    }
1600}
1601
1602// utility function, used by runtime
1603#[doc(hidden)]
1604pub fn calculate_points(
1605    stake_state: &StakeState,
1606    vote_state: &VoteState,
1607    stake_history: Option<&StakeHistory>,
1608) -> Result<u128, InstructionError> {
1609    if let StakeState::Stake(_meta, stake) = stake_state {
1610        Ok(calculate_stake_points(
1611            stake,
1612            vote_state,
1613            stake_history,
1614            null_tracer(),
1615        ))
1616    } else {
1617        Err(InstructionError::InvalidAccountData)
1618    }
1619}
1620
1621// utility function, used by Split
1622//This emulates current Rent math in order to preserve backward compatibility. In the future, and
1623//to support variable rent, the Split instruction should pass in the Rent sysvar instead.
1624fn calculate_split_rent_exempt_reserve(
1625    source_rent_exempt_reserve: u64,
1626    source_data_len: u64,
1627    split_data_len: u64,
1628) -> u64 {
1629    let lamports_per_byte_year =
1630        source_rent_exempt_reserve / (source_data_len + ACCOUNT_STORAGE_OVERHEAD);
1631    lamports_per_byte_year * (split_data_len + ACCOUNT_STORAGE_OVERHEAD)
1632}
1633
1634pub type RewriteStakeStatus = (&'static str, (u64, u64), (u64, u64));
1635
1636// utility function, used by runtime::Stakes, tests
1637pub fn new_stake_history_entry<'a, I>(
1638    epoch: Epoch,
1639    stakes: I,
1640    history: Option<&StakeHistory>,
1641) -> StakeHistoryEntry
1642where
1643    I: Iterator<Item = &'a Delegation>,
1644{
1645    stakes.fold(StakeHistoryEntry::default(), |sum, stake| {
1646        sum + stake.stake_activating_and_deactivating(epoch, history)
1647    })
1648}
1649
1650// utility function, used by tests
1651pub fn create_stake_history_from_delegations(
1652    bootstrap: Option<u64>,
1653    epochs: std::ops::Range<Epoch>,
1654    delegations: &[Delegation],
1655) -> StakeHistory {
1656    let mut stake_history = StakeHistory::default();
1657
1658    let bootstrap_delegation = if let Some(bootstrap) = bootstrap {
1659        vec![Delegation {
1660            activation_epoch: std::u64::MAX,
1661            stake: bootstrap,
1662            ..Delegation::default()
1663        }]
1664    } else {
1665        vec![]
1666    };
1667
1668    for epoch in epochs {
1669        let entry = new_stake_history_entry(
1670            epoch,
1671            delegations.iter().chain(bootstrap_delegation.iter()),
1672            Some(&stake_history),
1673        );
1674        stake_history.add(epoch, entry);
1675    }
1676
1677    stake_history
1678}
1679
1680// genesis investor accounts
1681pub fn create_lockup_stake_account(
1682    authorized: &Authorized,
1683    lockup: &Lockup,
1684    rent: &Rent,
1685    lamports: u64,
1686) -> AccountSharedData {
1687    let mut stake_account = AccountSharedData::new(lamports, StakeState::size_of(), &id());
1688
1689    let rent_exempt_reserve = rent.minimum_balance(stake_account.data().len());
1690    assert!(
1691        lamports >= rent_exempt_reserve,
1692        "lamports: {lamports} is less than rent_exempt_reserve {rent_exempt_reserve}"
1693    );
1694
1695    stake_account
1696        .set_state(&StakeState::Initialized(Meta {
1697            authorized: *authorized,
1698            lockup: *lockup,
1699            rent_exempt_reserve,
1700        }))
1701        .expect("set_state");
1702
1703    stake_account
1704}
1705
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 = vote_state::from(vote_account).expect("vote_state");
1754
1755    let rent_exempt_reserve = rent.minimum_balance(stake_account.data().len());
1756
1757    stake_account
1758        .set_state(&StakeState::Stake(
1759            Meta {
1760                authorized: Authorized::auto(authorized),
1761                rent_exempt_reserve,
1762                ..Meta::default()
1763            },
1764            new_stake(
1765                lamports - rent_exempt_reserve, // 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 SOL 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, {} SOL",
2993            minimum_balance,
2994            minimum_balance as f64 / solana_sdk::native_token::LAMPORTS_PER_SOL as f64
2995        );
2996    }
2997
2998    #[test]
2999    fn test_calculate_lamports_per_byte_year() {
3000        let rent = Rent::default();
3001        let data_len = 200u64;
3002        let rent_exempt_reserve = rent.minimum_balance(data_len as usize);
3003        assert_eq!(
3004            calculate_split_rent_exempt_reserve(rent_exempt_reserve, data_len, data_len),
3005            rent_exempt_reserve
3006        );
3007
3008        let larger_data = 4008u64;
3009        let larger_rent_exempt_reserve = rent.minimum_balance(larger_data as usize);
3010        assert_eq!(
3011            calculate_split_rent_exempt_reserve(rent_exempt_reserve, data_len, larger_data),
3012            larger_rent_exempt_reserve
3013        );
3014        assert_eq!(
3015            calculate_split_rent_exempt_reserve(larger_rent_exempt_reserve, larger_data, data_len),
3016            rent_exempt_reserve
3017        );
3018
3019        let even_larger_data = solana_sdk::system_instruction::MAX_PERMITTED_DATA_LENGTH;
3020        let even_larger_rent_exempt_reserve = rent.minimum_balance(even_larger_data as usize);
3021        assert_eq!(
3022            calculate_split_rent_exempt_reserve(rent_exempt_reserve, data_len, even_larger_data),
3023            even_larger_rent_exempt_reserve
3024        );
3025        assert_eq!(
3026            calculate_split_rent_exempt_reserve(
3027                even_larger_rent_exempt_reserve,
3028                even_larger_data,
3029                data_len
3030            ),
3031            rent_exempt_reserve
3032        );
3033    }
3034
3035    #[test]
3036    fn test_things_can_merge() {
3037        let mut transaction_context =
3038            TransactionContext::new(Vec::new(), Some(Rent::default()), 1, 1);
3039        let invoke_context = InvokeContext::new_mock(&mut transaction_context, &[]);
3040        let good_stake = Stake {
3041            credits_observed: 4242,
3042            delegation: Delegation {
3043                voter_pubkey: Pubkey::new_unique(),
3044                stake: 424242424242,
3045                activation_epoch: 42,
3046                ..Delegation::default()
3047            },
3048        };
3049
3050        let identical = good_stake;
3051        assert!(
3052            MergeKind::active_stakes_can_merge(&invoke_context, &good_stake, &identical).is_ok()
3053        );
3054
3055        let bad_credits_observed = Stake {
3056            credits_observed: good_stake.credits_observed + 1,
3057            ..good_stake
3058        };
3059        assert!(MergeKind::active_stakes_can_merge(
3060            &invoke_context,
3061            &good_stake,
3062            &bad_credits_observed
3063        )
3064        .is_err());
3065
3066        let good_delegation = good_stake.delegation;
3067        let different_stake_ok = Delegation {
3068            stake: good_delegation.stake + 1,
3069            ..good_delegation
3070        };
3071        assert!(MergeKind::active_delegations_can_merge(
3072            &invoke_context,
3073            &good_delegation,
3074            &different_stake_ok
3075        )
3076        .is_ok());
3077
3078        let different_activation_epoch_ok = Delegation {
3079            activation_epoch: good_delegation.activation_epoch + 1,
3080            ..good_delegation
3081        };
3082        assert!(MergeKind::active_delegations_can_merge(
3083            &invoke_context,
3084            &good_delegation,
3085            &different_activation_epoch_ok
3086        )
3087        .is_ok());
3088
3089        let bad_voter = Delegation {
3090            voter_pubkey: Pubkey::new_unique(),
3091            ..good_delegation
3092        };
3093        assert!(MergeKind::active_delegations_can_merge(
3094            &invoke_context,
3095            &good_delegation,
3096            &bad_voter
3097        )
3098        .is_err());
3099
3100        let bad_warmup_cooldown_rate = Delegation {
3101            warmup_cooldown_rate: good_delegation.warmup_cooldown_rate + f64::EPSILON,
3102            ..good_delegation
3103        };
3104        assert!(MergeKind::active_delegations_can_merge(
3105            &invoke_context,
3106            &good_delegation,
3107            &bad_warmup_cooldown_rate
3108        )
3109        .is_err());
3110        assert!(MergeKind::active_delegations_can_merge(
3111            &invoke_context,
3112            &bad_warmup_cooldown_rate,
3113            &good_delegation
3114        )
3115        .is_err());
3116
3117        let bad_deactivation_epoch = Delegation {
3118            deactivation_epoch: 43,
3119            ..good_delegation
3120        };
3121        assert!(MergeKind::active_delegations_can_merge(
3122            &invoke_context,
3123            &good_delegation,
3124            &bad_deactivation_epoch
3125        )
3126        .is_err());
3127        assert!(MergeKind::active_delegations_can_merge(
3128            &invoke_context,
3129            &bad_deactivation_epoch,
3130            &good_delegation
3131        )
3132        .is_err());
3133    }
3134
3135    #[test]
3136    fn test_metas_can_merge() {
3137        let mut transaction_context =
3138            TransactionContext::new(Vec::new(), Some(Rent::default()), 1, 1);
3139        let invoke_context = InvokeContext::new_mock(&mut transaction_context, &[]);
3140        // 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}