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_stake_interface::state` instead"
9)]
10pub use solana_stake_interface::state::*;
11use {
12    solana_account::{state_traits::StateMut, AccountSharedData, ReadableAccount},
13    solana_clock::{Clock, Epoch},
14    solana_feature_set::FeatureSet,
15    solana_instruction::error::InstructionError,
16    solana_log_collector::ic_msg,
17    solana_program_runtime::invoke_context::InvokeContext,
18    solana_pubkey::Pubkey,
19    solana_rent::Rent,
20    solana_sdk_ids::stake::id,
21    solana_stake_interface::{
22        error::StakeError,
23        instruction::LockupArgs,
24        stake_flags::StakeFlags,
25        tools::{acceptable_reference_epoch_credits, eligible_for_deactivate_delinquent},
26    },
27    solana_sysvar::stake_history::{StakeHistory, StakeHistoryEntry},
28    solana_transaction_context::{
29        BorrowedAccount, IndexOfAccount, InstructionContext, TransactionContext,
30    },
31    solana_vote_interface::state::{VoteState, VoteStateVersions},
32    std::{collections::HashSet, convert::TryFrom},
33};
34
35// utility function, used by Stakes, tests
36pub fn from<T: ReadableAccount + StateMut<StakeStateV2>>(account: &T) -> Option<StakeStateV2> {
37    account.state().ok()
38}
39
40pub fn stake_from<T: ReadableAccount + StateMut<StakeStateV2>>(account: &T) -> Option<Stake> {
41    from(account).and_then(|state: StakeStateV2| state.stake())
42}
43
44pub fn delegation_from(account: &AccountSharedData) -> Option<Delegation> {
45    from(account).and_then(|state: StakeStateV2| state.delegation())
46}
47
48pub fn authorized_from(account: &AccountSharedData) -> Option<Authorized> {
49    from(account).and_then(|state: StakeStateV2| state.authorized())
50}
51
52pub fn lockup_from<T: ReadableAccount + StateMut<StakeStateV2>>(account: &T) -> Option<Lockup> {
53    from(account).and_then(|state: StakeStateV2| state.lockup())
54}
55
56pub fn meta_from(account: &AccountSharedData) -> Option<Meta> {
57    from(account).and_then(|state: StakeStateV2| state.meta())
58}
59
60pub(crate) fn new_warmup_cooldown_rate_epoch(invoke_context: &InvokeContext) -> Option<Epoch> {
61    let epoch_schedule = invoke_context
62        .get_sysvar_cache()
63        .get_epoch_schedule()
64        .unwrap();
65    invoke_context
66        .get_feature_set()
67        .new_warmup_cooldown_rate_epoch(epoch_schedule.as_ref())
68}
69
70fn checked_add(a: u64, b: u64) -> Result<u64, InstructionError> {
71    a.checked_add(b).ok_or(InstructionError::InsufficientFunds)
72}
73
74fn get_stake_status(
75    invoke_context: &InvokeContext,
76    stake: &Stake,
77    clock: &Clock,
78) -> Result<StakeActivationStatus, InstructionError> {
79    let stake_history = invoke_context.get_sysvar_cache().get_stake_history()?;
80    Ok(stake.delegation.stake_activating_and_deactivating(
81        clock.epoch,
82        stake_history.as_ref(),
83        new_warmup_cooldown_rate_epoch(invoke_context),
84    ))
85}
86
87fn redelegate_stake(
88    invoke_context: &InvokeContext,
89    stake: &mut Stake,
90    stake_lamports: u64,
91    voter_pubkey: &Pubkey,
92    vote_state: &VoteState,
93    clock: &Clock,
94    stake_history: &StakeHistory,
95) -> Result<(), StakeError> {
96    let new_rate_activation_epoch = new_warmup_cooldown_rate_epoch(invoke_context);
97    // If stake is currently active:
98    if stake.stake(clock.epoch, stake_history, new_rate_activation_epoch) != 0 {
99        // If pubkey of new voter is the same as current,
100        // and we are scheduled to start deactivating this epoch,
101        // we rescind deactivation
102        if stake.delegation.voter_pubkey == *voter_pubkey
103            && clock.epoch == stake.delegation.deactivation_epoch
104        {
105            stake.delegation.deactivation_epoch = u64::MAX;
106            return Ok(());
107        } else {
108            // can't redelegate to another pubkey if stake is active.
109            return Err(StakeError::TooSoonToRedelegate);
110        }
111    }
112    // Either the stake is freshly activated, is active but has been
113    // deactivated this epoch, or has fully de-activated.
114    // Redelegation implies either re-activation or un-deactivation
115
116    stake.delegation.stake = stake_lamports;
117    stake.delegation.activation_epoch = clock.epoch;
118    stake.delegation.deactivation_epoch = u64::MAX;
119    stake.delegation.voter_pubkey = *voter_pubkey;
120    stake.credits_observed = vote_state.credits();
121    Ok(())
122}
123
124fn move_stake_or_lamports_shared_checks(
125    invoke_context: &InvokeContext,
126    transaction_context: &TransactionContext,
127    instruction_context: &InstructionContext,
128    source_account: &BorrowedAccount,
129    lamports: u64,
130    destination_account: &BorrowedAccount,
131    stake_authority_index: IndexOfAccount,
132) -> Result<(MergeKind, MergeKind), InstructionError> {
133    // authority must sign
134    let stake_authority_pubkey = transaction_context.get_key_of_account_at_index(
135        instruction_context
136            .get_index_of_instruction_account_in_transaction(stake_authority_index)?,
137    )?;
138    if !instruction_context.is_instruction_account_signer(stake_authority_index)? {
139        return Err(InstructionError::MissingRequiredSignature);
140    }
141
142    let mut signers = HashSet::new();
143    signers.insert(*stake_authority_pubkey);
144
145    // check owners
146    if *source_account.get_owner() != id() || *destination_account.get_owner() != id() {
147        return Err(InstructionError::IncorrectProgramId);
148    }
149
150    // confirm not the same account
151    if *source_account.get_key() == *destination_account.get_key() {
152        return Err(InstructionError::InvalidInstructionData);
153    }
154
155    // source and destination must be writable
156    if !source_account.is_writable() || !destination_account.is_writable() {
157        return Err(InstructionError::InvalidInstructionData);
158    }
159
160    // must move something
161    if lamports == 0 {
162        return Err(InstructionError::InvalidArgument);
163    }
164
165    let clock = invoke_context.get_sysvar_cache().get_clock()?;
166    let stake_history = invoke_context.get_sysvar_cache().get_stake_history()?;
167
168    // get_if_mergeable ensures accounts are not partly activated or in any form of deactivating
169    // we still need to exclude activating state ourselves
170    let source_merge_kind = MergeKind::get_if_mergeable(
171        invoke_context,
172        &source_account.get_state()?,
173        source_account.get_lamports(),
174        &clock,
175        &stake_history,
176    )?;
177
178    // Authorized staker is allowed to move stake
179    source_merge_kind
180        .meta()
181        .authorized
182        .check(&signers, StakeAuthorize::Staker)?;
183
184    // same transient assurance as with source
185    let destination_merge_kind = MergeKind::get_if_mergeable(
186        invoke_context,
187        &destination_account.get_state()?,
188        destination_account.get_lamports(),
189        &clock,
190        &stake_history,
191    )?;
192
193    // ensure all authorities match and lockups match if lockup is in force
194    MergeKind::metas_can_merge(
195        invoke_context,
196        source_merge_kind.meta(),
197        destination_merge_kind.meta(),
198        &clock,
199    )?;
200
201    Ok((source_merge_kind, destination_merge_kind))
202}
203
204pub(crate) fn new_stake(
205    stake: u64,
206    voter_pubkey: &Pubkey,
207    vote_state: &VoteState,
208    activation_epoch: Epoch,
209) -> Stake {
210    Stake {
211        delegation: Delegation::new(voter_pubkey, stake, activation_epoch),
212        credits_observed: vote_state.credits(),
213    }
214}
215
216pub fn initialize(
217    stake_account: &mut BorrowedAccount,
218    authorized: &Authorized,
219    lockup: &Lockup,
220    rent: &Rent,
221) -> Result<(), InstructionError> {
222    if stake_account.get_data().len() != StakeStateV2::size_of() {
223        return Err(InstructionError::InvalidAccountData);
224    }
225
226    if let StakeStateV2::Uninitialized = stake_account.get_state()? {
227        let rent_exempt_reserve = rent.minimum_balance(stake_account.get_data().len());
228        if stake_account.get_lamports() >= rent_exempt_reserve {
229            stake_account.set_state(&StakeStateV2::Initialized(Meta {
230                rent_exempt_reserve,
231                authorized: *authorized,
232                lockup: *lockup,
233            }))
234        } else {
235            Err(InstructionError::InsufficientFunds)
236        }
237    } else {
238        Err(InstructionError::InvalidAccountData)
239    }
240}
241
242/// Authorize the given pubkey to manage stake (deactivate, withdraw). This may be called
243/// multiple times, but will implicitly withdraw authorization from the previously authorized
244/// staker. The default staker is the owner of the stake account's pubkey.
245pub fn authorize(
246    stake_account: &mut BorrowedAccount,
247    signers: &HashSet<Pubkey>,
248    new_authority: &Pubkey,
249    stake_authorize: StakeAuthorize,
250    clock: &Clock,
251    custodian: Option<&Pubkey>,
252) -> Result<(), InstructionError> {
253    match stake_account.get_state()? {
254        StakeStateV2::Stake(mut meta, stake, stake_flags) => {
255            meta.authorized.authorize(
256                signers,
257                new_authority,
258                stake_authorize,
259                Some((&meta.lockup, clock, custodian)),
260            )?;
261            stake_account.set_state(&StakeStateV2::Stake(meta, stake, stake_flags))
262        }
263        StakeStateV2::Initialized(mut meta) => {
264            meta.authorized.authorize(
265                signers,
266                new_authority,
267                stake_authorize,
268                Some((&meta.lockup, clock, custodian)),
269            )?;
270            stake_account.set_state(&StakeStateV2::Initialized(meta))
271        }
272        _ => Err(InstructionError::InvalidAccountData),
273    }
274}
275
276#[allow(clippy::too_many_arguments)]
277pub fn authorize_with_seed(
278    transaction_context: &TransactionContext,
279    instruction_context: &InstructionContext,
280    stake_account: &mut BorrowedAccount,
281    authority_base_index: IndexOfAccount,
282    authority_seed: &str,
283    authority_owner: &Pubkey,
284    new_authority: &Pubkey,
285    stake_authorize: StakeAuthorize,
286    clock: &Clock,
287    custodian: Option<&Pubkey>,
288) -> Result<(), InstructionError> {
289    let mut signers = HashSet::default();
290    if instruction_context.is_instruction_account_signer(authority_base_index)? {
291        let base_pubkey = transaction_context.get_key_of_account_at_index(
292            instruction_context
293                .get_index_of_instruction_account_in_transaction(authority_base_index)?,
294        )?;
295        signers.insert(Pubkey::create_with_seed(
296            base_pubkey,
297            authority_seed,
298            authority_owner,
299        )?);
300    }
301    authorize(
302        stake_account,
303        &signers,
304        new_authority,
305        stake_authorize,
306        clock,
307        custodian,
308    )
309}
310
311#[allow(clippy::too_many_arguments)]
312pub fn delegate(
313    invoke_context: &InvokeContext,
314    transaction_context: &TransactionContext,
315    instruction_context: &InstructionContext,
316    stake_account_index: IndexOfAccount,
317    vote_account_index: IndexOfAccount,
318    clock: &Clock,
319    stake_history: &StakeHistory,
320    signers: &HashSet<Pubkey>,
321    feature_set: &FeatureSet,
322) -> Result<(), InstructionError> {
323    let vote_account = instruction_context
324        .try_borrow_instruction_account(transaction_context, vote_account_index)?;
325    if *vote_account.get_owner() != solana_sdk_ids::vote::id() {
326        return Err(InstructionError::IncorrectProgramId);
327    }
328    let vote_pubkey = *vote_account.get_key();
329    let vote_state = vote_account.get_state::<VoteStateVersions>();
330    drop(vote_account);
331
332    let mut stake_account = instruction_context
333        .try_borrow_instruction_account(transaction_context, stake_account_index)?;
334    match stake_account.get_state()? {
335        StakeStateV2::Initialized(meta) => {
336            meta.authorized.check(signers, StakeAuthorize::Staker)?;
337            let ValidatedDelegatedInfo { stake_amount } =
338                validate_delegated_amount(&stake_account, &meta, feature_set)?;
339            let stake = new_stake(
340                stake_amount,
341                &vote_pubkey,
342                &vote_state?.convert_to_current(),
343                clock.epoch,
344            );
345            stake_account.set_state(&StakeStateV2::Stake(meta, stake, StakeFlags::empty()))
346        }
347        StakeStateV2::Stake(meta, mut stake, stake_flags) => {
348            meta.authorized.check(signers, StakeAuthorize::Staker)?;
349            let ValidatedDelegatedInfo { stake_amount } =
350                validate_delegated_amount(&stake_account, &meta, feature_set)?;
351            redelegate_stake(
352                invoke_context,
353                &mut stake,
354                stake_amount,
355                &vote_pubkey,
356                &vote_state?.convert_to_current(),
357                clock,
358                stake_history,
359            )?;
360            stake_account.set_state(&StakeStateV2::Stake(meta, stake, stake_flags))
361        }
362        _ => Err(InstructionError::InvalidAccountData),
363    }
364}
365
366pub fn deactivate(
367    _invoke_context: &InvokeContext,
368    stake_account: &mut BorrowedAccount,
369    clock: &Clock,
370    signers: &HashSet<Pubkey>,
371) -> Result<(), InstructionError> {
372    if let StakeStateV2::Stake(meta, mut stake, stake_flags) = stake_account.get_state()? {
373        meta.authorized.check(signers, StakeAuthorize::Staker)?;
374        stake.deactivate(clock.epoch)?;
375        stake_account.set_state(&StakeStateV2::Stake(meta, stake, stake_flags))
376    } else {
377        Err(InstructionError::InvalidAccountData)
378    }
379}
380
381pub fn set_lockup(
382    stake_account: &mut BorrowedAccount,
383    lockup: &LockupArgs,
384    signers: &HashSet<Pubkey>,
385    clock: &Clock,
386) -> Result<(), InstructionError> {
387    match stake_account.get_state()? {
388        StakeStateV2::Initialized(mut meta) => {
389            meta.set_lockup(lockup, signers, clock)?;
390            stake_account.set_state(&StakeStateV2::Initialized(meta))
391        }
392        StakeStateV2::Stake(mut meta, stake, stake_flags) => {
393            meta.set_lockup(lockup, signers, clock)?;
394            stake_account.set_state(&StakeStateV2::Stake(meta, stake, stake_flags))
395        }
396        _ => Err(InstructionError::InvalidAccountData),
397    }
398}
399
400pub fn split(
401    invoke_context: &InvokeContext,
402    transaction_context: &TransactionContext,
403    instruction_context: &InstructionContext,
404    stake_account_index: IndexOfAccount,
405    lamports: u64,
406    split_index: IndexOfAccount,
407    signers: &HashSet<Pubkey>,
408) -> Result<(), InstructionError> {
409    let split =
410        instruction_context.try_borrow_instruction_account(transaction_context, split_index)?;
411    if *split.get_owner() != id() {
412        return Err(InstructionError::IncorrectProgramId);
413    }
414    if split.get_data().len() != StakeStateV2::size_of() {
415        return Err(InstructionError::InvalidAccountData);
416    }
417    if !matches!(split.get_state()?, StakeStateV2::Uninitialized) {
418        return Err(InstructionError::InvalidAccountData);
419    }
420    let split_lamport_balance = split.get_lamports();
421    drop(split);
422    let stake_account = instruction_context
423        .try_borrow_instruction_account(transaction_context, stake_account_index)?;
424    if lamports > stake_account.get_lamports() {
425        return Err(InstructionError::InsufficientFunds);
426    }
427    let stake_state = stake_account.get_state()?;
428    drop(stake_account);
429
430    match stake_state {
431        StakeStateV2::Stake(meta, mut stake, stake_flags) => {
432            meta.authorized.check(signers, StakeAuthorize::Staker)?;
433            let minimum_delegation =
434                crate::get_minimum_delegation(invoke_context.get_feature_set());
435            let is_active = {
436                let clock = invoke_context.get_sysvar_cache().get_clock()?;
437                let status = get_stake_status(invoke_context, &stake, &clock)?;
438                status.effective > 0
439            };
440            let validated_split_info = validate_split_amount(
441                invoke_context,
442                transaction_context,
443                instruction_context,
444                stake_account_index,
445                split_index,
446                lamports,
447                &meta,
448                minimum_delegation,
449                is_active,
450            )?;
451
452            // split the stake, subtract rent_exempt_balance unless
453            // the destination account already has those lamports
454            // in place.
455            // this means that the new stake account will have a stake equivalent to
456            // lamports minus rent_exempt_reserve if it starts out with a zero balance
457            let (remaining_stake_delta, split_stake_amount) =
458                if validated_split_info.source_remaining_balance == 0 {
459                    // If split amount equals the full source stake (as implied by 0
460                    // source_remaining_balance), the new split stake must equal the same
461                    // amount, regardless of any current lamport balance in the split account.
462                    // Since split accounts retain the state of their source account, this
463                    // prevents any magic activation of stake by prefunding the split account.
464                    //
465                    // The new split stake also needs to ignore any positive delta between the
466                    // original rent_exempt_reserve and the split_rent_exempt_reserve, in order
467                    // to prevent magic activation of stake by splitting between accounts of
468                    // different sizes.
469                    let remaining_stake_delta = lamports.saturating_sub(meta.rent_exempt_reserve);
470                    (remaining_stake_delta, remaining_stake_delta)
471                } else {
472                    // Otherwise, the new split stake should reflect the entire split
473                    // requested, less any lamports needed to cover the split_rent_exempt_reserve.
474
475                    if stake.delegation.stake.saturating_sub(lamports) < minimum_delegation {
476                        return Err(StakeError::InsufficientDelegation.into());
477                    }
478
479                    (
480                        lamports,
481                        lamports.saturating_sub(
482                            validated_split_info
483                                .destination_rent_exempt_reserve
484                                .saturating_sub(split_lamport_balance),
485                        ),
486                    )
487                };
488
489            if split_stake_amount < minimum_delegation {
490                return Err(StakeError::InsufficientDelegation.into());
491            }
492
493            let split_stake = stake.split(remaining_stake_delta, split_stake_amount)?;
494            let mut split_meta = meta;
495            split_meta.rent_exempt_reserve = validated_split_info.destination_rent_exempt_reserve;
496
497            let mut stake_account = instruction_context
498                .try_borrow_instruction_account(transaction_context, stake_account_index)?;
499            stake_account.set_state(&StakeStateV2::Stake(meta, stake, stake_flags))?;
500            drop(stake_account);
501            let mut split = instruction_context
502                .try_borrow_instruction_account(transaction_context, split_index)?;
503            split.set_state(&StakeStateV2::Stake(split_meta, split_stake, stake_flags))?;
504        }
505        StakeStateV2::Initialized(meta) => {
506            meta.authorized.check(signers, StakeAuthorize::Staker)?;
507            let validated_split_info = validate_split_amount(
508                invoke_context,
509                transaction_context,
510                instruction_context,
511                stake_account_index,
512                split_index,
513                lamports,
514                &meta,
515                0, // additional_required_lamports
516                false,
517            )?;
518            let mut split_meta = meta;
519            split_meta.rent_exempt_reserve = validated_split_info.destination_rent_exempt_reserve;
520            let mut split = instruction_context
521                .try_borrow_instruction_account(transaction_context, split_index)?;
522            split.set_state(&StakeStateV2::Initialized(split_meta))?;
523        }
524        StakeStateV2::Uninitialized => {
525            let stake_pubkey = transaction_context.get_key_of_account_at_index(
526                instruction_context
527                    .get_index_of_instruction_account_in_transaction(stake_account_index)?,
528            )?;
529            if !signers.contains(stake_pubkey) {
530                return Err(InstructionError::MissingRequiredSignature);
531            }
532        }
533        _ => return Err(InstructionError::InvalidAccountData),
534    }
535
536    // Deinitialize state upon zero balance
537    let mut stake_account = instruction_context
538        .try_borrow_instruction_account(transaction_context, stake_account_index)?;
539    if lamports == stake_account.get_lamports() {
540        stake_account.set_state(&StakeStateV2::Uninitialized)?;
541    }
542    drop(stake_account);
543
544    let mut split =
545        instruction_context.try_borrow_instruction_account(transaction_context, split_index)?;
546    split.checked_add_lamports(lamports)?;
547    drop(split);
548    let mut stake_account = instruction_context
549        .try_borrow_instruction_account(transaction_context, stake_account_index)?;
550    stake_account.checked_sub_lamports(lamports)?;
551    Ok(())
552}
553
554pub fn merge(
555    invoke_context: &InvokeContext,
556    transaction_context: &TransactionContext,
557    instruction_context: &InstructionContext,
558    stake_account_index: IndexOfAccount,
559    source_account_index: IndexOfAccount,
560    clock: &Clock,
561    stake_history: &StakeHistory,
562    signers: &HashSet<Pubkey>,
563) -> Result<(), InstructionError> {
564    let mut source_account = instruction_context
565        .try_borrow_instruction_account(transaction_context, source_account_index)?;
566    // Ensure source isn't spoofed
567    if *source_account.get_owner() != id() {
568        return Err(InstructionError::IncorrectProgramId);
569    }
570    // Close the stake_account-reference loophole
571    if instruction_context.get_index_of_instruction_account_in_transaction(stake_account_index)?
572        == instruction_context
573            .get_index_of_instruction_account_in_transaction(source_account_index)?
574    {
575        return Err(InstructionError::InvalidArgument);
576    }
577    let mut stake_account = instruction_context
578        .try_borrow_instruction_account(transaction_context, stake_account_index)?;
579
580    ic_msg!(invoke_context, "Checking if destination stake is mergeable");
581    let stake_merge_kind = MergeKind::get_if_mergeable(
582        invoke_context,
583        &stake_account.get_state()?,
584        stake_account.get_lamports(),
585        clock,
586        stake_history,
587    )?;
588
589    // Authorized staker is allowed to split/merge accounts
590    stake_merge_kind
591        .meta()
592        .authorized
593        .check(signers, StakeAuthorize::Staker)?;
594
595    ic_msg!(invoke_context, "Checking if source stake is mergeable");
596    let source_merge_kind = MergeKind::get_if_mergeable(
597        invoke_context,
598        &source_account.get_state()?,
599        source_account.get_lamports(),
600        clock,
601        stake_history,
602    )?;
603
604    ic_msg!(invoke_context, "Merging stake accounts");
605    if let Some(merged_state) = stake_merge_kind.merge(invoke_context, source_merge_kind, clock)? {
606        stake_account.set_state(&merged_state)?;
607    }
608
609    // Source is about to be drained, deinitialize its state
610    source_account.set_state(&StakeStateV2::Uninitialized)?;
611
612    // Drain the source stake account
613    let lamports = source_account.get_lamports();
614    source_account.checked_sub_lamports(lamports)?;
615    stake_account.checked_add_lamports(lamports)?;
616    Ok(())
617}
618
619pub fn move_stake(
620    invoke_context: &InvokeContext,
621    transaction_context: &TransactionContext,
622    instruction_context: &InstructionContext,
623    source_account_index: IndexOfAccount,
624    lamports: u64,
625    destination_account_index: IndexOfAccount,
626    stake_authority_index: IndexOfAccount,
627) -> Result<(), InstructionError> {
628    let mut source_account = instruction_context
629        .try_borrow_instruction_account(transaction_context, source_account_index)?;
630
631    let mut destination_account = instruction_context
632        .try_borrow_instruction_account(transaction_context, destination_account_index)?;
633
634    let (source_merge_kind, destination_merge_kind) = move_stake_or_lamports_shared_checks(
635        invoke_context,
636        transaction_context,
637        instruction_context,
638        &source_account,
639        lamports,
640        &destination_account,
641        stake_authority_index,
642    )?;
643
644    // ensure source and destination are the right size for the current version of StakeState
645    // this a safeguard in case there is a new version of the struct that cannot fit into an old account
646    if source_account.get_data().len() != StakeStateV2::size_of()
647        || destination_account.get_data().len() != StakeStateV2::size_of()
648    {
649        return Err(InstructionError::InvalidAccountData);
650    }
651
652    // source must be fully active
653    let MergeKind::FullyActive(source_meta, mut source_stake) = source_merge_kind else {
654        return Err(InstructionError::InvalidAccountData);
655    };
656
657    let minimum_delegation = crate::get_minimum_delegation(invoke_context.get_feature_set());
658    let source_effective_stake = source_stake.delegation.stake;
659
660    // source cannot move more stake than it has, regardless of how many lamports it has
661    let source_final_stake = source_effective_stake
662        .checked_sub(lamports)
663        .ok_or(InstructionError::InvalidArgument)?;
664
665    // unless all stake is being moved, source must retain at least the minimum delegation
666    if source_final_stake != 0 && source_final_stake < minimum_delegation {
667        return Err(InstructionError::InvalidArgument);
668    }
669
670    // destination must be fully active or fully inactive
671    let destination_meta = match destination_merge_kind {
672        MergeKind::FullyActive(destination_meta, mut destination_stake) => {
673            // if active, destination must be delegated to the same vote account as source
674            if source_stake.delegation.voter_pubkey != destination_stake.delegation.voter_pubkey {
675                return Err(StakeError::VoteAddressMismatch.into());
676            }
677
678            let destination_effective_stake = destination_stake.delegation.stake;
679            let destination_final_stake = destination_effective_stake
680                .checked_add(lamports)
681                .ok_or(InstructionError::ArithmeticOverflow)?;
682
683            // ensure destination meets miniumum delegation
684            // since it is already active, this only really applies if the minimum is raised
685            if destination_final_stake < minimum_delegation {
686                return Err(InstructionError::InvalidArgument);
687            }
688
689            merge_delegation_stake_and_credits_observed(
690                &mut destination_stake,
691                lamports,
692                source_stake.credits_observed,
693            )?;
694
695            destination_account.set_state(&StakeStateV2::Stake(
696                destination_meta,
697                destination_stake,
698                StakeFlags::empty(),
699            ))?;
700
701            destination_meta
702        }
703        MergeKind::Inactive(destination_meta, _, _) => {
704            // if destination is inactive, it must be given at least the minimum delegation
705            if lamports < minimum_delegation {
706                return Err(InstructionError::InvalidArgument);
707            }
708
709            let mut destination_stake = source_stake;
710            destination_stake.delegation.stake = lamports;
711
712            destination_account.set_state(&StakeStateV2::Stake(
713                destination_meta,
714                destination_stake,
715                StakeFlags::empty(),
716            ))?;
717
718            destination_meta
719        }
720        _ => return Err(InstructionError::InvalidAccountData),
721    };
722
723    if source_final_stake == 0 {
724        source_account.set_state(&StakeStateV2::Initialized(source_meta))?;
725    } else {
726        source_stake.delegation.stake = source_final_stake;
727
728        source_account.set_state(&StakeStateV2::Stake(
729            source_meta,
730            source_stake,
731            StakeFlags::empty(),
732        ))?;
733    }
734
735    source_account.checked_sub_lamports(lamports)?;
736    destination_account.checked_add_lamports(lamports)?;
737
738    // this should be impossible, but because we do all our math with delegations, best to guard it
739    if source_account.get_lamports() < source_meta.rent_exempt_reserve
740        || destination_account.get_lamports() < destination_meta.rent_exempt_reserve
741    {
742        ic_msg!(
743            invoke_context,
744            "Delegation calculations violated lamport balance assumptions"
745        );
746        return Err(InstructionError::InvalidArgument);
747    }
748
749    Ok(())
750}
751
752pub fn move_lamports(
753    invoke_context: &InvokeContext,
754    transaction_context: &TransactionContext,
755    instruction_context: &InstructionContext,
756    source_account_index: IndexOfAccount,
757    lamports: u64,
758    destination_account_index: IndexOfAccount,
759    stake_authority_index: IndexOfAccount,
760) -> Result<(), InstructionError> {
761    let mut source_account = instruction_context
762        .try_borrow_instruction_account(transaction_context, source_account_index)?;
763
764    let mut destination_account = instruction_context
765        .try_borrow_instruction_account(transaction_context, destination_account_index)?;
766
767    let (source_merge_kind, _) = move_stake_or_lamports_shared_checks(
768        invoke_context,
769        transaction_context,
770        instruction_context,
771        &source_account,
772        lamports,
773        &destination_account,
774        stake_authority_index,
775    )?;
776
777    let source_free_lamports = match source_merge_kind {
778        MergeKind::FullyActive(source_meta, source_stake) => source_account
779            .get_lamports()
780            .saturating_sub(source_stake.delegation.stake)
781            .saturating_sub(source_meta.rent_exempt_reserve),
782        MergeKind::Inactive(source_meta, source_lamports, _) => {
783            source_lamports.saturating_sub(source_meta.rent_exempt_reserve)
784        }
785        _ => return Err(InstructionError::InvalidAccountData),
786    };
787
788    if lamports > source_free_lamports {
789        return Err(InstructionError::InvalidArgument);
790    }
791
792    source_account.checked_sub_lamports(lamports)?;
793    destination_account.checked_add_lamports(lamports)?;
794
795    Ok(())
796}
797
798#[allow(clippy::too_many_arguments)]
799pub fn withdraw(
800    transaction_context: &TransactionContext,
801    instruction_context: &InstructionContext,
802    stake_account_index: IndexOfAccount,
803    lamports: u64,
804    to_index: IndexOfAccount,
805    clock: &Clock,
806    stake_history: &StakeHistory,
807    withdraw_authority_index: IndexOfAccount,
808    custodian_index: Option<IndexOfAccount>,
809    new_rate_activation_epoch: Option<Epoch>,
810) -> Result<(), InstructionError> {
811    let withdraw_authority_pubkey = transaction_context.get_key_of_account_at_index(
812        instruction_context
813            .get_index_of_instruction_account_in_transaction(withdraw_authority_index)?,
814    )?;
815    if !instruction_context.is_instruction_account_signer(withdraw_authority_index)? {
816        return Err(InstructionError::MissingRequiredSignature);
817    }
818    let mut signers = HashSet::new();
819    signers.insert(*withdraw_authority_pubkey);
820
821    let mut stake_account = instruction_context
822        .try_borrow_instruction_account(transaction_context, stake_account_index)?;
823    let (lockup, reserve, is_staked) = match stake_account.get_state()? {
824        StakeStateV2::Stake(meta, stake, _stake_flag) => {
825            meta.authorized
826                .check(&signers, StakeAuthorize::Withdrawer)?;
827            // if we have a deactivation epoch and we're in cooldown
828            let staked = if clock.epoch >= stake.delegation.deactivation_epoch {
829                stake
830                    .delegation
831                    .stake(clock.epoch, stake_history, new_rate_activation_epoch)
832            } else {
833                // Assume full stake if the stake account hasn't been
834                //  de-activated, because in the future the exposed stake
835                //  might be higher than stake.stake() due to warmup
836                stake.delegation.stake
837            };
838
839            let staked_and_reserve = checked_add(staked, meta.rent_exempt_reserve)?;
840            (meta.lockup, staked_and_reserve, staked != 0)
841        }
842        StakeStateV2::Initialized(meta) => {
843            meta.authorized
844                .check(&signers, StakeAuthorize::Withdrawer)?;
845            // stake accounts must have a balance >= rent_exempt_reserve
846            (meta.lockup, meta.rent_exempt_reserve, false)
847        }
848        StakeStateV2::Uninitialized => {
849            if !signers.contains(stake_account.get_key()) {
850                return Err(InstructionError::MissingRequiredSignature);
851            }
852            (Lockup::default(), 0, false) // no lockup, no restrictions
853        }
854        _ => return Err(InstructionError::InvalidAccountData),
855    };
856
857    // verify that lockup has expired or that the withdrawal is signed by
858    //   the custodian, both epoch and unix_timestamp must have passed
859    let custodian_pubkey = if let Some(custodian_index) = custodian_index {
860        if instruction_context.is_instruction_account_signer(custodian_index)? {
861            Some(
862                transaction_context.get_key_of_account_at_index(
863                    instruction_context
864                        .get_index_of_instruction_account_in_transaction(custodian_index)?,
865                )?,
866            )
867        } else {
868            None
869        }
870    } else {
871        None
872    };
873    if lockup.is_in_force(clock, custodian_pubkey) {
874        return Err(StakeError::LockupInForce.into());
875    }
876
877    let lamports_and_reserve = checked_add(lamports, reserve)?;
878    // if the stake is active, we mustn't allow the account to go away
879    if is_staked // line coverage for branch coverage
880            && lamports_and_reserve > stake_account.get_lamports()
881    {
882        return Err(InstructionError::InsufficientFunds);
883    }
884
885    if lamports != stake_account.get_lamports() // not a full withdrawal
886            && lamports_and_reserve > stake_account.get_lamports()
887    {
888        assert!(!is_staked);
889        return Err(InstructionError::InsufficientFunds);
890    }
891
892    // Deinitialize state upon zero balance
893    if lamports == stake_account.get_lamports() {
894        stake_account.set_state(&StakeStateV2::Uninitialized)?;
895    }
896
897    stake_account.checked_sub_lamports(lamports)?;
898    drop(stake_account);
899    let mut to =
900        instruction_context.try_borrow_instruction_account(transaction_context, to_index)?;
901    to.checked_add_lamports(lamports)?;
902    Ok(())
903}
904
905pub(crate) fn deactivate_delinquent(
906    transaction_context: &TransactionContext,
907    instruction_context: &InstructionContext,
908    stake_account: &mut BorrowedAccount,
909    delinquent_vote_account_index: IndexOfAccount,
910    reference_vote_account_index: IndexOfAccount,
911    current_epoch: Epoch,
912) -> Result<(), InstructionError> {
913    let delinquent_vote_account_pubkey = transaction_context.get_key_of_account_at_index(
914        instruction_context
915            .get_index_of_instruction_account_in_transaction(delinquent_vote_account_index)?,
916    )?;
917    let delinquent_vote_account = instruction_context
918        .try_borrow_instruction_account(transaction_context, delinquent_vote_account_index)?;
919    if *delinquent_vote_account.get_owner() != solana_sdk_ids::vote::id() {
920        return Err(InstructionError::IncorrectProgramId);
921    }
922    let delinquent_vote_state = delinquent_vote_account
923        .get_state::<VoteStateVersions>()?
924        .convert_to_current();
925
926    let reference_vote_account = instruction_context
927        .try_borrow_instruction_account(transaction_context, reference_vote_account_index)?;
928    if *reference_vote_account.get_owner() != solana_sdk_ids::vote::id() {
929        return Err(InstructionError::IncorrectProgramId);
930    }
931    let reference_vote_state = reference_vote_account
932        .get_state::<VoteStateVersions>()?
933        .convert_to_current();
934
935    if !acceptable_reference_epoch_credits(&reference_vote_state.epoch_credits, current_epoch) {
936        return Err(StakeError::InsufficientReferenceVotes.into());
937    }
938
939    if let StakeStateV2::Stake(meta, mut stake, stake_flags) = stake_account.get_state()? {
940        if stake.delegation.voter_pubkey != *delinquent_vote_account_pubkey {
941            return Err(StakeError::VoteAddressMismatch.into());
942        }
943
944        // Deactivate the stake account if its delegated vote account has never voted or has not
945        // voted in the last `MINIMUM_DELINQUENT_EPOCHS_FOR_DEACTIVATION`
946        if eligible_for_deactivate_delinquent(&delinquent_vote_state.epoch_credits, current_epoch) {
947            stake.deactivate(current_epoch)?;
948            stake_account.set_state(&StakeStateV2::Stake(meta, stake, stake_flags))
949        } else {
950            Err(StakeError::MinimumDelinquentEpochsForDeactivationNotMet.into())
951        }
952    } else {
953        Err(InstructionError::InvalidAccountData)
954    }
955}
956
957/// After calling `validate_delegated_amount()`, this struct contains calculated values that are used
958/// by the caller.
959struct ValidatedDelegatedInfo {
960    stake_amount: u64,
961}
962
963/// Ensure the stake delegation amount is valid.  This checks that the account meets the minimum
964/// balance requirements of delegated stake.  If not, return an error.
965fn validate_delegated_amount(
966    account: &BorrowedAccount,
967    meta: &Meta,
968    feature_set: &FeatureSet,
969) -> Result<ValidatedDelegatedInfo, InstructionError> {
970    let stake_amount = account
971        .get_lamports()
972        .saturating_sub(meta.rent_exempt_reserve); // can't stake the rent
973
974    // Stake accounts may be initialized with a stake amount below the minimum delegation so check
975    // that the minimum is met before delegation.
976    if stake_amount < crate::get_minimum_delegation(feature_set) {
977        return Err(StakeError::InsufficientDelegation.into());
978    }
979    Ok(ValidatedDelegatedInfo { stake_amount })
980}
981
982/// After calling `validate_split_amount()`, this struct contains calculated values that are used
983/// by the caller.
984#[derive(Copy, Clone, Debug, Default)]
985struct ValidatedSplitInfo {
986    source_remaining_balance: u64,
987    destination_rent_exempt_reserve: u64,
988}
989
990/// Ensure the split amount is valid.  This checks the source and destination accounts meet the
991/// minimum balance requirements, which is the rent exempt reserve plus the minimum stake
992/// delegation, and that the source account has enough lamports for the request split amount.  If
993/// not, return an error.
994fn validate_split_amount(
995    invoke_context: &InvokeContext,
996    transaction_context: &TransactionContext,
997    instruction_context: &InstructionContext,
998    source_account_index: IndexOfAccount,
999    destination_account_index: IndexOfAccount,
1000    lamports: u64,
1001    source_meta: &Meta,
1002    additional_required_lamports: u64,
1003    source_is_active: bool,
1004) -> Result<ValidatedSplitInfo, InstructionError> {
1005    let source_account = instruction_context
1006        .try_borrow_instruction_account(transaction_context, source_account_index)?;
1007    let source_lamports = source_account.get_lamports();
1008    drop(source_account);
1009    let destination_account = instruction_context
1010        .try_borrow_instruction_account(transaction_context, destination_account_index)?;
1011    let destination_lamports = destination_account.get_lamports();
1012    let destination_data_len = destination_account.get_data().len();
1013    drop(destination_account);
1014
1015    // Split amount has to be something
1016    if lamports == 0 {
1017        return Err(InstructionError::InsufficientFunds);
1018    }
1019
1020    // Obviously cannot split more than what the source account has
1021    if lamports > source_lamports {
1022        return Err(InstructionError::InsufficientFunds);
1023    }
1024
1025    // Verify that the source account still has enough lamports left after splitting:
1026    // EITHER at least the minimum balance, OR zero (in this case the source
1027    // account is transferring all lamports to new destination account, and the source
1028    // account will be closed)
1029    let source_minimum_balance = source_meta
1030        .rent_exempt_reserve
1031        .saturating_add(additional_required_lamports);
1032    let source_remaining_balance = source_lamports.saturating_sub(lamports);
1033    if source_remaining_balance == 0 {
1034        // full amount is a withdrawal
1035        // nothing to do here
1036    } else if source_remaining_balance < source_minimum_balance {
1037        // the remaining balance is too low to do the split
1038        return Err(InstructionError::InsufficientFunds);
1039    } else {
1040        // all clear!
1041        // nothing to do here
1042    }
1043
1044    let rent = invoke_context.get_sysvar_cache().get_rent()?;
1045    let destination_rent_exempt_reserve = rent.minimum_balance(destination_data_len);
1046
1047    // If the source is active stake, one of these criteria must be met:
1048    // 1. the destination account must be prefunded with at least the rent-exempt reserve, or
1049    // 2. the split must consume 100% of the source
1050    if source_is_active
1051        && source_remaining_balance != 0
1052        && destination_lamports < destination_rent_exempt_reserve
1053    {
1054        return Err(InstructionError::InsufficientFunds);
1055    }
1056
1057    // Verify the destination account meets the minimum balance requirements
1058    // This must handle:
1059    // 1. The destination account having a different rent exempt reserve due to data size changes
1060    // 2. The destination account being prefunded, which would lower the minimum split amount
1061    let destination_minimum_balance =
1062        destination_rent_exempt_reserve.saturating_add(additional_required_lamports);
1063    let destination_balance_deficit =
1064        destination_minimum_balance.saturating_sub(destination_lamports);
1065    if lamports < destination_balance_deficit {
1066        return Err(InstructionError::InsufficientFunds);
1067    }
1068
1069    Ok(ValidatedSplitInfo {
1070        source_remaining_balance,
1071        destination_rent_exempt_reserve,
1072    })
1073}
1074
1075#[derive(Clone, Debug, PartialEq)]
1076enum MergeKind {
1077    Inactive(Meta, u64, StakeFlags),
1078    ActivationEpoch(Meta, Stake, StakeFlags),
1079    FullyActive(Meta, Stake),
1080}
1081
1082impl MergeKind {
1083    fn meta(&self) -> &Meta {
1084        match self {
1085            Self::Inactive(meta, _, _) => meta,
1086            Self::ActivationEpoch(meta, _, _) => meta,
1087            Self::FullyActive(meta, _) => meta,
1088        }
1089    }
1090
1091    fn active_stake(&self) -> Option<&Stake> {
1092        match self {
1093            Self::Inactive(_, _, _) => None,
1094            Self::ActivationEpoch(_, stake, _) => Some(stake),
1095            Self::FullyActive(_, stake) => Some(stake),
1096        }
1097    }
1098
1099    fn get_if_mergeable(
1100        invoke_context: &InvokeContext,
1101        stake_state: &StakeStateV2,
1102        stake_lamports: u64,
1103        clock: &Clock,
1104        stake_history: &StakeHistory,
1105    ) -> Result<Self, InstructionError> {
1106        match stake_state {
1107            StakeStateV2::Stake(meta, stake, stake_flags) => {
1108                // stake must not be in a transient state. Transient here meaning
1109                // activating or deactivating with non-zero effective stake.
1110                let status = stake.delegation.stake_activating_and_deactivating(
1111                    clock.epoch,
1112                    stake_history,
1113                    new_warmup_cooldown_rate_epoch(invoke_context),
1114                );
1115
1116                match (status.effective, status.activating, status.deactivating) {
1117                    (0, 0, 0) => Ok(Self::Inactive(*meta, stake_lamports, *stake_flags)),
1118                    (0, _, _) => Ok(Self::ActivationEpoch(*meta, *stake, *stake_flags)),
1119                    (_, 0, 0) => Ok(Self::FullyActive(*meta, *stake)),
1120                    _ => {
1121                        let err = StakeError::MergeTransientStake;
1122                        ic_msg!(invoke_context, "{}", err);
1123                        Err(err.into())
1124                    }
1125                }
1126            }
1127            StakeStateV2::Initialized(meta) => {
1128                Ok(Self::Inactive(*meta, stake_lamports, StakeFlags::empty()))
1129            }
1130            _ => Err(InstructionError::InvalidAccountData),
1131        }
1132    }
1133
1134    fn metas_can_merge(
1135        invoke_context: &InvokeContext,
1136        stake: &Meta,
1137        source: &Meta,
1138        clock: &Clock,
1139    ) -> Result<(), InstructionError> {
1140        // lockups may mismatch so long as both have expired
1141        let can_merge_lockups = stake.lockup == source.lockup
1142            || (!stake.lockup.is_in_force(clock, None) && !source.lockup.is_in_force(clock, None));
1143        // `rent_exempt_reserve` has no bearing on the mergeability of accounts,
1144        // as the source account will be culled by runtime once the operation
1145        // succeeds. Considering it here would needlessly prevent merging stake
1146        // accounts with differing data lengths, which already exist in the wild
1147        // due to an SDK bug
1148        if stake.authorized == source.authorized && can_merge_lockups {
1149            Ok(())
1150        } else {
1151            ic_msg!(invoke_context, "Unable to merge due to metadata mismatch");
1152            Err(StakeError::MergeMismatch.into())
1153        }
1154    }
1155
1156    fn active_delegations_can_merge(
1157        invoke_context: &InvokeContext,
1158        stake: &Delegation,
1159        source: &Delegation,
1160    ) -> Result<(), InstructionError> {
1161        if stake.voter_pubkey != source.voter_pubkey {
1162            ic_msg!(invoke_context, "Unable to merge due to voter mismatch");
1163            Err(StakeError::MergeMismatch.into())
1164        } else if stake.deactivation_epoch == Epoch::MAX && source.deactivation_epoch == Epoch::MAX
1165        {
1166            Ok(())
1167        } else {
1168            ic_msg!(invoke_context, "Unable to merge due to stake deactivation");
1169            Err(StakeError::MergeMismatch.into())
1170        }
1171    }
1172
1173    fn merge(
1174        self,
1175        invoke_context: &InvokeContext,
1176        source: Self,
1177        clock: &Clock,
1178    ) -> Result<Option<StakeStateV2>, InstructionError> {
1179        Self::metas_can_merge(invoke_context, self.meta(), source.meta(), clock)?;
1180        self.active_stake()
1181            .zip(source.active_stake())
1182            .map(|(stake, source)| {
1183                Self::active_delegations_can_merge(
1184                    invoke_context,
1185                    &stake.delegation,
1186                    &source.delegation,
1187                )
1188            })
1189            .unwrap_or(Ok(()))?;
1190        let merged_state = match (self, source) {
1191            (Self::Inactive(_, _, _), Self::Inactive(_, _, _)) => None,
1192            (Self::Inactive(_, _, _), Self::ActivationEpoch(_, _, _)) => None,
1193            (
1194                Self::ActivationEpoch(meta, mut stake, stake_flags),
1195                Self::Inactive(_, source_lamports, source_stake_flags),
1196            ) => {
1197                stake.delegation.stake = checked_add(stake.delegation.stake, source_lamports)?;
1198                Some(StakeStateV2::Stake(
1199                    meta,
1200                    stake,
1201                    stake_flags.union(source_stake_flags),
1202                ))
1203            }
1204            (
1205                Self::ActivationEpoch(meta, mut stake, stake_flags),
1206                Self::ActivationEpoch(source_meta, source_stake, source_stake_flags),
1207            ) => {
1208                let source_lamports = checked_add(
1209                    source_meta.rent_exempt_reserve,
1210                    source_stake.delegation.stake,
1211                )?;
1212                merge_delegation_stake_and_credits_observed(
1213                    &mut stake,
1214                    source_lamports,
1215                    source_stake.credits_observed,
1216                )?;
1217                Some(StakeStateV2::Stake(
1218                    meta,
1219                    stake,
1220                    stake_flags.union(source_stake_flags),
1221                ))
1222            }
1223            (Self::FullyActive(meta, mut stake), Self::FullyActive(_, source_stake)) => {
1224                // Don't stake the source account's `rent_exempt_reserve` to
1225                // protect against the magic activation loophole. It will
1226                // instead be moved into the destination account as extra,
1227                // withdrawable `lamports`
1228                merge_delegation_stake_and_credits_observed(
1229                    &mut stake,
1230                    source_stake.delegation.stake,
1231                    source_stake.credits_observed,
1232                )?;
1233                Some(StakeStateV2::Stake(meta, stake, StakeFlags::empty()))
1234            }
1235            _ => return Err(StakeError::MergeMismatch.into()),
1236        };
1237        Ok(merged_state)
1238    }
1239}
1240
1241fn merge_delegation_stake_and_credits_observed(
1242    stake: &mut Stake,
1243    absorbed_lamports: u64,
1244    absorbed_credits_observed: u64,
1245) -> Result<(), InstructionError> {
1246    stake.credits_observed =
1247        stake_weighted_credits_observed(stake, absorbed_lamports, absorbed_credits_observed)
1248            .ok_or(InstructionError::ArithmeticOverflow)?;
1249    stake.delegation.stake = checked_add(stake.delegation.stake, absorbed_lamports)?;
1250    Ok(())
1251}
1252
1253/// Calculate the effective credits observed for two stakes when merging
1254///
1255/// When merging two `ActivationEpoch` or `FullyActive` stakes, the credits
1256/// observed of the merged stake is the weighted average of the two stakes'
1257/// credits observed.
1258///
1259/// This is because we can derive the effective credits_observed by reversing the staking
1260/// rewards equation, _while keeping the rewards unchanged after merge (i.e. strong
1261/// requirement)_, like below:
1262///
1263/// a(N) => account, r => rewards, s => stake, c => credits:
1264/// assume:
1265///   a3 = merge(a1, a2)
1266/// then:
1267///   a3.s = a1.s + a2.s
1268///
1269/// Next, given:
1270///   aN.r = aN.c * aN.s (for every N)
1271/// finally:
1272///        a3.r = a1.r + a2.r
1273/// a3.c * a3.s = a1.c * a1.s + a2.c * a2.s
1274///        a3.c = (a1.c * a1.s + a2.c * a2.s) / (a1.s + a2.s)     // QED
1275///
1276/// (For this discussion, we omitted irrelevant variables, including distance
1277///  calculation against vote_account and point indirection.)
1278fn stake_weighted_credits_observed(
1279    stake: &Stake,
1280    absorbed_lamports: u64,
1281    absorbed_credits_observed: u64,
1282) -> Option<u64> {
1283    if stake.credits_observed == absorbed_credits_observed {
1284        Some(stake.credits_observed)
1285    } else {
1286        let total_stake = u128::from(stake.delegation.stake.checked_add(absorbed_lamports)?);
1287        let stake_weighted_credits =
1288            u128::from(stake.credits_observed).checked_mul(u128::from(stake.delegation.stake))?;
1289        let absorbed_weighted_credits =
1290            u128::from(absorbed_credits_observed).checked_mul(u128::from(absorbed_lamports))?;
1291        // Discard fractional credits as a merge side-effect friction by taking
1292        // the ceiling, done by adding `denominator - 1` to the numerator.
1293        let total_weighted_credits = stake_weighted_credits
1294            .checked_add(absorbed_weighted_credits)?
1295            .checked_add(total_stake)?
1296            .checked_sub(1)?;
1297        u64::try_from(total_weighted_credits.checked_div(total_stake)?).ok()
1298    }
1299}
1300
1301pub type RewriteStakeStatus = (&'static str, (u64, u64), (u64, u64));
1302
1303// utility function, used by runtime::Stakes, tests
1304pub fn new_stake_history_entry<'a, I>(
1305    epoch: Epoch,
1306    stakes: I,
1307    history: &StakeHistory,
1308    new_rate_activation_epoch: Option<Epoch>,
1309) -> StakeHistoryEntry
1310where
1311    I: Iterator<Item = &'a Delegation>,
1312{
1313    stakes.fold(StakeHistoryEntry::default(), |sum, stake| {
1314        sum + stake.stake_activating_and_deactivating(epoch, history, new_rate_activation_epoch)
1315    })
1316}
1317
1318// utility function, used by tests
1319pub fn create_stake_history_from_delegations(
1320    bootstrap: Option<u64>,
1321    epochs: std::ops::Range<Epoch>,
1322    delegations: &[Delegation],
1323    new_rate_activation_epoch: Option<Epoch>,
1324) -> StakeHistory {
1325    let mut stake_history = StakeHistory::default();
1326
1327    let bootstrap_delegation = if let Some(bootstrap) = bootstrap {
1328        vec![Delegation {
1329            activation_epoch: u64::MAX,
1330            stake: bootstrap,
1331            ..Delegation::default()
1332        }]
1333    } else {
1334        vec![]
1335    };
1336
1337    for epoch in epochs {
1338        let entry = new_stake_history_entry(
1339            epoch,
1340            delegations.iter().chain(bootstrap_delegation.iter()),
1341            &stake_history,
1342            new_rate_activation_epoch,
1343        );
1344        stake_history.add(epoch, entry);
1345    }
1346
1347    stake_history
1348}
1349
1350// genesis investor accounts
1351pub fn create_lockup_stake_account(
1352    authorized: &Authorized,
1353    lockup: &Lockup,
1354    rent: &Rent,
1355    lamports: u64,
1356) -> AccountSharedData {
1357    let mut stake_account = AccountSharedData::new(lamports, StakeStateV2::size_of(), &id());
1358
1359    let rent_exempt_reserve = rent.minimum_balance(stake_account.data().len());
1360    assert!(
1361        lamports >= rent_exempt_reserve,
1362        "lamports: {lamports} is less than rent_exempt_reserve {rent_exempt_reserve}"
1363    );
1364
1365    stake_account
1366        .set_state(&StakeStateV2::Initialized(Meta {
1367            authorized: *authorized,
1368            lockup: *lockup,
1369            rent_exempt_reserve,
1370        }))
1371        .expect("set_state");
1372
1373    stake_account
1374}
1375
1376// utility function, used by Bank, tests, genesis for bootstrap
1377pub fn create_account(
1378    authorized: &Pubkey,
1379    voter_pubkey: &Pubkey,
1380    vote_account: &AccountSharedData,
1381    rent: &Rent,
1382    lamports: u64,
1383) -> AccountSharedData {
1384    do_create_account(
1385        authorized,
1386        voter_pubkey,
1387        vote_account,
1388        rent,
1389        lamports,
1390        Epoch::MAX,
1391    )
1392}
1393
1394// utility function, used by tests
1395pub fn create_account_with_activation_epoch(
1396    authorized: &Pubkey,
1397    voter_pubkey: &Pubkey,
1398    vote_account: &AccountSharedData,
1399    rent: &Rent,
1400    lamports: u64,
1401    activation_epoch: Epoch,
1402) -> AccountSharedData {
1403    do_create_account(
1404        authorized,
1405        voter_pubkey,
1406        vote_account,
1407        rent,
1408        lamports,
1409        activation_epoch,
1410    )
1411}
1412
1413fn do_create_account(
1414    authorized: &Pubkey,
1415    voter_pubkey: &Pubkey,
1416    vote_account: &AccountSharedData,
1417    rent: &Rent,
1418    lamports: u64,
1419    activation_epoch: Epoch,
1420) -> AccountSharedData {
1421    let mut stake_account = AccountSharedData::new(lamports, StakeStateV2::size_of(), &id());
1422
1423    let vote_state = VoteState::deserialize(vote_account.data()).expect("vote_state");
1424
1425    let rent_exempt_reserve = rent.minimum_balance(stake_account.data().len());
1426
1427    stake_account
1428        .set_state(&StakeStateV2::Stake(
1429            Meta {
1430                authorized: Authorized::auto(authorized),
1431                rent_exempt_reserve,
1432                ..Meta::default()
1433            },
1434            new_stake(
1435                lamports - rent_exempt_reserve, // underflow is an error, is basically: assert!(lamports > rent_exempt_reserve);
1436                voter_pubkey,
1437                &vote_state,
1438                activation_epoch,
1439            ),
1440            StakeFlags::empty(),
1441        ))
1442        .expect("set_state");
1443
1444    stake_account
1445}
1446
1447#[cfg(test)]
1448mod tests {
1449    use {
1450        super::*,
1451        proptest::prelude::*,
1452        solana_account::{create_account_shared_data_for_test, AccountSharedData},
1453        solana_epoch_schedule::EpochSchedule,
1454        solana_program_runtime::with_mock_invoke_context,
1455        solana_pubkey::Pubkey,
1456        solana_sdk_ids::sysvar::epoch_schedule,
1457        solana_stake_interface::state::warmup_cooldown_rate,
1458        solana_sysvar_id::SysvarId,
1459        test_case::test_case,
1460    };
1461
1462    #[test]
1463    fn test_authorized_authorize() {
1464        let staker = solana_pubkey::new_rand();
1465        let mut authorized = Authorized::auto(&staker);
1466        let mut signers = HashSet::new();
1467        assert_eq!(
1468            authorized.authorize(&signers, &staker, StakeAuthorize::Staker, None),
1469            Err(InstructionError::MissingRequiredSignature)
1470        );
1471        signers.insert(staker);
1472        assert_eq!(
1473            authorized.authorize(&signers, &staker, StakeAuthorize::Staker, None),
1474            Ok(())
1475        );
1476    }
1477
1478    #[test]
1479    fn test_authorized_authorize_with_custodian() {
1480        let staker = solana_pubkey::new_rand();
1481        let custodian = solana_pubkey::new_rand();
1482        let invalid_custodian = solana_pubkey::new_rand();
1483        let mut authorized = Authorized::auto(&staker);
1484        let mut signers = HashSet::new();
1485        signers.insert(staker);
1486
1487        let lockup = Lockup {
1488            epoch: 1,
1489            unix_timestamp: 1,
1490            custodian,
1491        };
1492        let clock = Clock {
1493            epoch: 0,
1494            unix_timestamp: 0,
1495            ..Clock::default()
1496        };
1497
1498        // No lockup, no custodian
1499        assert_eq!(
1500            authorized.authorize(
1501                &signers,
1502                &staker,
1503                StakeAuthorize::Withdrawer,
1504                Some((&Lockup::default(), &clock, None))
1505            ),
1506            Ok(())
1507        );
1508
1509        // No lockup, invalid custodian not a signer
1510        assert_eq!(
1511            authorized.authorize(
1512                &signers,
1513                &staker,
1514                StakeAuthorize::Withdrawer,
1515                Some((&Lockup::default(), &clock, Some(&invalid_custodian)))
1516            ),
1517            Ok(()) // <== invalid custodian doesn't matter, there's no lockup
1518        );
1519
1520        // Lockup active, invalid custodian not a signer
1521        assert_eq!(
1522            authorized.authorize(
1523                &signers,
1524                &staker,
1525                StakeAuthorize::Withdrawer,
1526                Some((&lockup, &clock, Some(&invalid_custodian)))
1527            ),
1528            Err(StakeError::CustodianSignatureMissing.into()),
1529        );
1530
1531        signers.insert(invalid_custodian);
1532
1533        // No lockup, invalid custodian is a signer
1534        assert_eq!(
1535            authorized.authorize(
1536                &signers,
1537                &staker,
1538                StakeAuthorize::Withdrawer,
1539                Some((&Lockup::default(), &clock, Some(&invalid_custodian)))
1540            ),
1541            Ok(()) // <== invalid custodian doesn't matter, there's no lockup
1542        );
1543
1544        // Lockup active, invalid custodian is a signer
1545        signers.insert(invalid_custodian);
1546        assert_eq!(
1547            authorized.authorize(
1548                &signers,
1549                &staker,
1550                StakeAuthorize::Withdrawer,
1551                Some((&lockup, &clock, Some(&invalid_custodian)))
1552            ),
1553            Err(StakeError::LockupInForce.into()), // <== invalid custodian rejected
1554        );
1555
1556        signers.remove(&invalid_custodian);
1557
1558        // Lockup active, no custodian
1559        assert_eq!(
1560            authorized.authorize(
1561                &signers,
1562                &staker,
1563                StakeAuthorize::Withdrawer,
1564                Some((&lockup, &clock, None))
1565            ),
1566            Err(StakeError::CustodianMissing.into()),
1567        );
1568
1569        // Lockup active, custodian not a signer
1570        assert_eq!(
1571            authorized.authorize(
1572                &signers,
1573                &staker,
1574                StakeAuthorize::Withdrawer,
1575                Some((&lockup, &clock, Some(&custodian)))
1576            ),
1577            Err(StakeError::CustodianSignatureMissing.into()),
1578        );
1579
1580        // Lockup active, custodian is a signer
1581        signers.insert(custodian);
1582        assert_eq!(
1583            authorized.authorize(
1584                &signers,
1585                &staker,
1586                StakeAuthorize::Withdrawer,
1587                Some((&lockup, &clock, Some(&custodian)))
1588            ),
1589            Ok(())
1590        );
1591    }
1592
1593    #[test]
1594    fn test_stake_state_stake_from_fail() {
1595        let mut stake_account = AccountSharedData::new(0, StakeStateV2::size_of(), &id());
1596
1597        stake_account
1598            .set_state(&StakeStateV2::default())
1599            .expect("set_state");
1600
1601        assert_eq!(stake_from(&stake_account), None);
1602    }
1603
1604    #[test]
1605    fn test_stake_is_bootstrap() {
1606        assert!(Delegation {
1607            activation_epoch: u64::MAX,
1608            ..Delegation::default()
1609        }
1610        .is_bootstrap());
1611        assert!(!Delegation {
1612            activation_epoch: 0,
1613            ..Delegation::default()
1614        }
1615        .is_bootstrap());
1616    }
1617
1618    #[test]
1619    fn test_stake_activating_and_deactivating() {
1620        let stake = Delegation {
1621            stake: 1_000,
1622            activation_epoch: 0, // activating at zero
1623            deactivation_epoch: 5,
1624            ..Delegation::default()
1625        };
1626
1627        // save this off so stake.config.warmup_rate changes don't break this test
1628        let increment = (1_000_f64 * warmup_cooldown_rate(0, None)) as u64;
1629
1630        let mut stake_history = StakeHistory::default();
1631        // assert that this stake follows step function if there's no history
1632        assert_eq!(
1633            stake.stake_activating_and_deactivating(stake.activation_epoch, &stake_history, None),
1634            StakeActivationStatus::with_effective_and_activating(0, stake.stake),
1635        );
1636        for epoch in stake.activation_epoch + 1..stake.deactivation_epoch {
1637            assert_eq!(
1638                stake.stake_activating_and_deactivating(epoch, &stake_history, None),
1639                StakeActivationStatus::with_effective(stake.stake),
1640            );
1641        }
1642        // assert that this stake is full deactivating
1643        assert_eq!(
1644            stake.stake_activating_and_deactivating(stake.deactivation_epoch, &stake_history, None),
1645            StakeActivationStatus::with_deactivating(stake.stake),
1646        );
1647        // assert that this stake is fully deactivated if there's no history
1648        assert_eq!(
1649            stake.stake_activating_and_deactivating(
1650                stake.deactivation_epoch + 1,
1651                &stake_history,
1652                None
1653            ),
1654            StakeActivationStatus::default(),
1655        );
1656
1657        stake_history.add(
1658            0u64, // entry for zero doesn't have my activating amount
1659            StakeHistoryEntry {
1660                effective: 1_000,
1661                ..StakeHistoryEntry::default()
1662            },
1663        );
1664        // assert that this stake is broken, because above setup is broken
1665        assert_eq!(
1666            stake.stake_activating_and_deactivating(1, &stake_history, None),
1667            StakeActivationStatus::with_effective_and_activating(0, stake.stake),
1668        );
1669
1670        stake_history.add(
1671            0u64, // entry for zero has my activating amount
1672            StakeHistoryEntry {
1673                effective: 1_000,
1674                activating: 1_000,
1675                ..StakeHistoryEntry::default()
1676            },
1677            // no entry for 1, so this stake gets shorted
1678        );
1679        // assert that this stake is broken, because above setup is broken
1680        assert_eq!(
1681            stake.stake_activating_and_deactivating(2, &stake_history, None),
1682            StakeActivationStatus::with_effective_and_activating(
1683                increment,
1684                stake.stake - increment
1685            ),
1686        );
1687
1688        // start over, test deactivation edge cases
1689        let mut stake_history = StakeHistory::default();
1690
1691        stake_history.add(
1692            stake.deactivation_epoch, // entry for zero doesn't have my de-activating amount
1693            StakeHistoryEntry {
1694                effective: 1_000,
1695                ..StakeHistoryEntry::default()
1696            },
1697        );
1698        // assert that this stake is broken, because above setup is broken
1699        assert_eq!(
1700            stake.stake_activating_and_deactivating(
1701                stake.deactivation_epoch + 1,
1702                &stake_history,
1703                None,
1704            ),
1705            StakeActivationStatus::with_deactivating(stake.stake),
1706        );
1707
1708        // put in my initial deactivating amount, but don't put in an entry for next
1709        stake_history.add(
1710            stake.deactivation_epoch, // entry for zero has my de-activating amount
1711            StakeHistoryEntry {
1712                effective: 1_000,
1713                deactivating: 1_000,
1714                ..StakeHistoryEntry::default()
1715            },
1716        );
1717        // assert that this stake is broken, because above setup is broken
1718        assert_eq!(
1719            stake.stake_activating_and_deactivating(
1720                stake.deactivation_epoch + 2,
1721                &stake_history,
1722                None,
1723            ),
1724            // hung, should be lower
1725            StakeActivationStatus::with_deactivating(stake.stake - increment),
1726        );
1727    }
1728
1729    mod same_epoch_activation_then_deactivation {
1730        use super::*;
1731
1732        enum OldDeactivationBehavior {
1733            Stuck,
1734            Slow,
1735        }
1736
1737        fn do_test(
1738            old_behavior: OldDeactivationBehavior,
1739            expected_stakes: &[StakeActivationStatus],
1740        ) {
1741            let cluster_stake = 1_000;
1742            let activating_stake = 10_000;
1743            let some_stake = 700;
1744            let some_epoch = 0;
1745
1746            let stake = Delegation {
1747                stake: some_stake,
1748                activation_epoch: some_epoch,
1749                deactivation_epoch: some_epoch,
1750                ..Delegation::default()
1751            };
1752
1753            let mut stake_history = StakeHistory::default();
1754            let cluster_deactivation_at_stake_modified_epoch = match old_behavior {
1755                OldDeactivationBehavior::Stuck => 0,
1756                OldDeactivationBehavior::Slow => 1000,
1757            };
1758
1759            let stake_history_entries = vec![
1760                (
1761                    cluster_stake,
1762                    activating_stake,
1763                    cluster_deactivation_at_stake_modified_epoch,
1764                ),
1765                (cluster_stake, activating_stake, 1000),
1766                (cluster_stake, activating_stake, 1000),
1767                (cluster_stake, activating_stake, 100),
1768                (cluster_stake, activating_stake, 100),
1769                (cluster_stake, activating_stake, 100),
1770                (cluster_stake, activating_stake, 100),
1771            ];
1772
1773            for (epoch, (effective, activating, deactivating)) in
1774                stake_history_entries.into_iter().enumerate()
1775            {
1776                stake_history.add(
1777                    epoch as Epoch,
1778                    StakeHistoryEntry {
1779                        effective,
1780                        activating,
1781                        deactivating,
1782                    },
1783                );
1784            }
1785
1786            assert_eq!(
1787                expected_stakes,
1788                (0..expected_stakes.len())
1789                    .map(|epoch| stake.stake_activating_and_deactivating(
1790                        epoch as u64,
1791                        &stake_history,
1792                        None,
1793                    ))
1794                    .collect::<Vec<_>>()
1795            );
1796        }
1797
1798        #[test]
1799        fn test_new_behavior_previously_slow() {
1800            // any stake accounts activated and deactivated at the same epoch
1801            // shouldn't been activated (then deactivated) at all!
1802
1803            do_test(
1804                OldDeactivationBehavior::Slow,
1805                &[
1806                    StakeActivationStatus::default(),
1807                    StakeActivationStatus::default(),
1808                    StakeActivationStatus::default(),
1809                    StakeActivationStatus::default(),
1810                    StakeActivationStatus::default(),
1811                    StakeActivationStatus::default(),
1812                    StakeActivationStatus::default(),
1813                ],
1814            );
1815        }
1816
1817        #[test]
1818        fn test_new_behavior_previously_stuck() {
1819            // any stake accounts activated and deactivated at the same epoch
1820            // shouldn't been activated (then deactivated) at all!
1821
1822            do_test(
1823                OldDeactivationBehavior::Stuck,
1824                &[
1825                    StakeActivationStatus::default(),
1826                    StakeActivationStatus::default(),
1827                    StakeActivationStatus::default(),
1828                    StakeActivationStatus::default(),
1829                    StakeActivationStatus::default(),
1830                    StakeActivationStatus::default(),
1831                    StakeActivationStatus::default(),
1832                ],
1833            );
1834        }
1835    }
1836
1837    #[test]
1838    fn test_inflation_and_slashing_with_activating_and_deactivating_stake() {
1839        // some really boring delegation and stake_history setup
1840        let (delegated_stake, mut stake, stake_history) = {
1841            let cluster_stake = 1_000;
1842            let delegated_stake = 700;
1843
1844            let stake = Delegation {
1845                stake: delegated_stake,
1846                activation_epoch: 0,
1847                deactivation_epoch: 4,
1848                ..Delegation::default()
1849            };
1850
1851            let mut stake_history = StakeHistory::default();
1852            stake_history.add(
1853                0,
1854                StakeHistoryEntry {
1855                    effective: cluster_stake,
1856                    activating: delegated_stake,
1857                    ..StakeHistoryEntry::default()
1858                },
1859            );
1860            let newly_effective_at_epoch1 = (cluster_stake as f64 * 0.25) as u64;
1861            assert_eq!(newly_effective_at_epoch1, 250);
1862            stake_history.add(
1863                1,
1864                StakeHistoryEntry {
1865                    effective: cluster_stake + newly_effective_at_epoch1,
1866                    activating: delegated_stake - newly_effective_at_epoch1,
1867                    ..StakeHistoryEntry::default()
1868                },
1869            );
1870            let newly_effective_at_epoch2 =
1871                ((cluster_stake + newly_effective_at_epoch1) as f64 * 0.25) as u64;
1872            assert_eq!(newly_effective_at_epoch2, 312);
1873            stake_history.add(
1874                2,
1875                StakeHistoryEntry {
1876                    effective: cluster_stake
1877                        + newly_effective_at_epoch1
1878                        + newly_effective_at_epoch2,
1879                    activating: delegated_stake
1880                        - newly_effective_at_epoch1
1881                        - newly_effective_at_epoch2,
1882                    ..StakeHistoryEntry::default()
1883                },
1884            );
1885            stake_history.add(
1886                3,
1887                StakeHistoryEntry {
1888                    effective: cluster_stake + delegated_stake,
1889                    ..StakeHistoryEntry::default()
1890                },
1891            );
1892            stake_history.add(
1893                4,
1894                StakeHistoryEntry {
1895                    effective: cluster_stake + delegated_stake,
1896                    deactivating: delegated_stake,
1897                    ..StakeHistoryEntry::default()
1898                },
1899            );
1900            let newly_not_effective_stake_at_epoch5 =
1901                ((cluster_stake + delegated_stake) as f64 * 0.25) as u64;
1902            assert_eq!(newly_not_effective_stake_at_epoch5, 425);
1903            stake_history.add(
1904                5,
1905                StakeHistoryEntry {
1906                    effective: cluster_stake + delegated_stake
1907                        - newly_not_effective_stake_at_epoch5,
1908                    deactivating: delegated_stake - newly_not_effective_stake_at_epoch5,
1909                    ..StakeHistoryEntry::default()
1910                },
1911            );
1912
1913            (delegated_stake, stake, stake_history)
1914        };
1915
1916        // helper closures
1917        let calculate_each_staking_status = |stake: &Delegation, epoch_count: usize| -> Vec<_> {
1918            (0..epoch_count)
1919                .map(|epoch| {
1920                    stake.stake_activating_and_deactivating(epoch as u64, &stake_history, None)
1921                })
1922                .collect::<Vec<_>>()
1923        };
1924        let adjust_staking_status = |rate: f64, status: &[StakeActivationStatus]| {
1925            status
1926                .iter()
1927                .map(|entry| StakeActivationStatus {
1928                    effective: (entry.effective as f64 * rate) as u64,
1929                    activating: (entry.activating as f64 * rate) as u64,
1930                    deactivating: (entry.deactivating as f64 * rate) as u64,
1931                })
1932                .collect::<Vec<_>>()
1933        };
1934
1935        let expected_staking_status_transition = vec![
1936            StakeActivationStatus::with_effective_and_activating(0, 700),
1937            StakeActivationStatus::with_effective_and_activating(250, 450),
1938            StakeActivationStatus::with_effective_and_activating(562, 138),
1939            StakeActivationStatus::with_effective(700),
1940            StakeActivationStatus::with_deactivating(700),
1941            StakeActivationStatus::with_deactivating(275),
1942            StakeActivationStatus::default(),
1943        ];
1944        let expected_staking_status_transition_base = vec![
1945            StakeActivationStatus::with_effective_and_activating(0, 700),
1946            StakeActivationStatus::with_effective_and_activating(250, 450),
1947            StakeActivationStatus::with_effective_and_activating(562, 138 + 1), // +1 is needed for rounding
1948            StakeActivationStatus::with_effective(700),
1949            StakeActivationStatus::with_deactivating(700),
1950            StakeActivationStatus::with_deactivating(275 + 1), // +1 is needed for rounding
1951            StakeActivationStatus::default(),
1952        ];
1953
1954        // normal stake activating and deactivating transition test, just in case
1955        assert_eq!(
1956            expected_staking_status_transition,
1957            calculate_each_staking_status(&stake, expected_staking_status_transition.len())
1958        );
1959
1960        // 10% inflation rewards assuming some sizable epochs passed!
1961        let rate = 1.10;
1962        stake.stake = (delegated_stake as f64 * rate) as u64;
1963        let expected_staking_status_transition =
1964            adjust_staking_status(rate, &expected_staking_status_transition_base);
1965
1966        assert_eq!(
1967            expected_staking_status_transition,
1968            calculate_each_staking_status(&stake, expected_staking_status_transition_base.len()),
1969        );
1970
1971        // 50% slashing!!!
1972        let rate = 0.5;
1973        stake.stake = (delegated_stake as f64 * rate) as u64;
1974        let expected_staking_status_transition =
1975            adjust_staking_status(rate, &expected_staking_status_transition_base);
1976
1977        assert_eq!(
1978            expected_staking_status_transition,
1979            calculate_each_staking_status(&stake, expected_staking_status_transition_base.len()),
1980        );
1981    }
1982
1983    #[test]
1984    fn test_stop_activating_after_deactivation() {
1985        let stake = Delegation {
1986            stake: 1_000,
1987            activation_epoch: 0,
1988            deactivation_epoch: 3,
1989            ..Delegation::default()
1990        };
1991
1992        let base_stake = 1_000;
1993        let mut stake_history = StakeHistory::default();
1994        let mut effective = base_stake;
1995        let other_activation = 100;
1996        let mut other_activations = vec![0];
1997
1998        // Build a stake history where the test staker always consumes all of the available warm
1999        // up and cool down stake. However, simulate other stakers beginning to activate during
2000        // the test staker's deactivation.
2001        for epoch in 0..=stake.deactivation_epoch + 1 {
2002            let (activating, deactivating) = if epoch < stake.deactivation_epoch {
2003                (stake.stake + base_stake - effective, 0)
2004            } else {
2005                let other_activation_sum: u64 = other_activations.iter().sum();
2006                let deactivating = effective - base_stake - other_activation_sum;
2007                (other_activation, deactivating)
2008            };
2009
2010            stake_history.add(
2011                epoch,
2012                StakeHistoryEntry {
2013                    effective,
2014                    activating,
2015                    deactivating,
2016                },
2017            );
2018
2019            let effective_rate_limited = (effective as f64 * warmup_cooldown_rate(0, None)) as u64;
2020            if epoch < stake.deactivation_epoch {
2021                effective += effective_rate_limited.min(activating);
2022                other_activations.push(0);
2023            } else {
2024                effective -= effective_rate_limited.min(deactivating);
2025                effective += other_activation;
2026                other_activations.push(other_activation);
2027            }
2028        }
2029
2030        for epoch in 0..=stake.deactivation_epoch + 1 {
2031            let history = stake_history.get(epoch).unwrap();
2032            let other_activations: u64 = other_activations[..=epoch as usize].iter().sum();
2033            let expected_stake = history.effective - base_stake - other_activations;
2034            let (expected_activating, expected_deactivating) = if epoch < stake.deactivation_epoch {
2035                (history.activating, 0)
2036            } else {
2037                (0, history.deactivating)
2038            };
2039            assert_eq!(
2040                stake.stake_activating_and_deactivating(epoch, &stake_history, None),
2041                StakeActivationStatus {
2042                    effective: expected_stake,
2043                    activating: expected_activating,
2044                    deactivating: expected_deactivating,
2045                },
2046            );
2047        }
2048    }
2049
2050    #[test]
2051    fn test_stake_warmup_cooldown_sub_integer_moves() {
2052        let delegations = [Delegation {
2053            stake: 2,
2054            activation_epoch: 0, // activating at zero
2055            deactivation_epoch: 5,
2056            ..Delegation::default()
2057        }];
2058        // give 2 epochs of cooldown
2059        let epochs = 7;
2060        // make bootstrap stake smaller than warmup so warmup/cooldownn
2061        //  increment is always smaller than 1
2062        let bootstrap = (warmup_cooldown_rate(0, None) * 100.0 / 2.0) as u64;
2063        let stake_history =
2064            create_stake_history_from_delegations(Some(bootstrap), 0..epochs, &delegations, None);
2065        let mut max_stake = 0;
2066        let mut min_stake = 2;
2067
2068        for epoch in 0..epochs {
2069            let stake = delegations
2070                .iter()
2071                .map(|delegation| delegation.stake(epoch, &stake_history, None))
2072                .sum::<u64>();
2073            max_stake = max_stake.max(stake);
2074            min_stake = min_stake.min(stake);
2075        }
2076        assert_eq!(max_stake, 2);
2077        assert_eq!(min_stake, 0);
2078    }
2079
2080    #[test_case(None ; "old rate")]
2081    #[test_case(Some(1) ; "new rate activated in epoch 1")]
2082    #[test_case(Some(10) ; "new rate activated in epoch 10")]
2083    #[test_case(Some(30) ; "new rate activated in epoch 30")]
2084    #[test_case(Some(50) ; "new rate activated in epoch 50")]
2085    #[test_case(Some(60) ; "new rate activated in epoch 60")]
2086    fn test_stake_warmup_cooldown(new_rate_activation_epoch: Option<Epoch>) {
2087        let delegations = [
2088            Delegation {
2089                // never deactivates
2090                stake: 1_000,
2091                activation_epoch: u64::MAX,
2092                ..Delegation::default()
2093            },
2094            Delegation {
2095                stake: 1_000,
2096                activation_epoch: 0,
2097                deactivation_epoch: 9,
2098                ..Delegation::default()
2099            },
2100            Delegation {
2101                stake: 1_000,
2102                activation_epoch: 1,
2103                deactivation_epoch: 6,
2104                ..Delegation::default()
2105            },
2106            Delegation {
2107                stake: 1_000,
2108                activation_epoch: 2,
2109                deactivation_epoch: 5,
2110                ..Delegation::default()
2111            },
2112            Delegation {
2113                stake: 1_000,
2114                activation_epoch: 2,
2115                deactivation_epoch: 4,
2116                ..Delegation::default()
2117            },
2118            Delegation {
2119                stake: 1_000,
2120                activation_epoch: 4,
2121                deactivation_epoch: 4,
2122                ..Delegation::default()
2123            },
2124        ];
2125        // chosen to ensure that the last activated stake (at 4) finishes
2126        //  warming up and cooling down
2127        //  a stake takes 2.0f64.log(1.0 + STAKE_WARMUP_RATE) epochs to warm up or cool down
2128        //  when all alone, but the above overlap a lot
2129        let epochs = 60;
2130
2131        let stake_history = create_stake_history_from_delegations(
2132            None,
2133            0..epochs,
2134            &delegations,
2135            new_rate_activation_epoch,
2136        );
2137
2138        let mut prev_total_effective_stake = delegations
2139            .iter()
2140            .map(|delegation| delegation.stake(0, &stake_history, new_rate_activation_epoch))
2141            .sum::<u64>();
2142
2143        // uncomment and add ! for fun with graphing
2144        // eprintln("\n{:8} {:8} {:8}", "   epoch", "   total", "   delta");
2145        for epoch in 1..epochs {
2146            let total_effective_stake = delegations
2147                .iter()
2148                .map(|delegation| {
2149                    delegation.stake(epoch, &stake_history, new_rate_activation_epoch)
2150                })
2151                .sum::<u64>();
2152
2153            let delta = if total_effective_stake > prev_total_effective_stake {
2154                total_effective_stake - prev_total_effective_stake
2155            } else {
2156                prev_total_effective_stake - total_effective_stake
2157            };
2158
2159            // uncomment and add ! for fun with graphing
2160            // eprint("{:8} {:8} {:8} ", epoch, total_effective_stake, delta);
2161            // (0..(total_effective_stake as usize / (delegations.len() * 5))).for_each(|_| eprint("#"));
2162            // eprintln();
2163
2164            assert!(
2165                delta
2166                    <= ((prev_total_effective_stake as f64
2167                        * warmup_cooldown_rate(epoch, new_rate_activation_epoch))
2168                        as u64)
2169                        .max(1)
2170            );
2171
2172            prev_total_effective_stake = total_effective_stake;
2173        }
2174    }
2175
2176    #[test]
2177    fn test_lockup_is_expired() {
2178        let custodian = solana_pubkey::new_rand();
2179        let lockup = Lockup {
2180            epoch: 1,
2181            unix_timestamp: 1,
2182            custodian,
2183        };
2184        // neither time
2185        assert!(lockup.is_in_force(
2186            &Clock {
2187                epoch: 0,
2188                unix_timestamp: 0,
2189                ..Clock::default()
2190            },
2191            None
2192        ));
2193        // not timestamp
2194        assert!(lockup.is_in_force(
2195            &Clock {
2196                epoch: 2,
2197                unix_timestamp: 0,
2198                ..Clock::default()
2199            },
2200            None
2201        ));
2202        // not epoch
2203        assert!(lockup.is_in_force(
2204            &Clock {
2205                epoch: 0,
2206                unix_timestamp: 2,
2207                ..Clock::default()
2208            },
2209            None
2210        ));
2211        // both, no custodian
2212        assert!(!lockup.is_in_force(
2213            &Clock {
2214                epoch: 1,
2215                unix_timestamp: 1,
2216                ..Clock::default()
2217            },
2218            None
2219        ));
2220        // neither, but custodian
2221        assert!(!lockup.is_in_force(
2222            &Clock {
2223                epoch: 0,
2224                unix_timestamp: 0,
2225                ..Clock::default()
2226            },
2227            Some(&custodian),
2228        ));
2229    }
2230
2231    #[test]
2232    #[ignore]
2233    #[should_panic]
2234    fn test_dbg_stake_minimum_balance() {
2235        let minimum_balance = Rent::default().minimum_balance(StakeStateV2::size_of());
2236        panic!(
2237            "stake minimum_balance: {} lamports, {} SOL",
2238            minimum_balance,
2239            minimum_balance as f64 / solana_native_token::LAMPORTS_PER_SOL as f64
2240        );
2241    }
2242
2243    #[test]
2244    fn test_things_can_merge() {
2245        with_mock_invoke_context!(invoke_context, transaction_context, Vec::new());
2246        let good_stake = Stake {
2247            credits_observed: 4242,
2248            delegation: Delegation {
2249                voter_pubkey: Pubkey::new_unique(),
2250                stake: 424242424242,
2251                activation_epoch: 42,
2252                ..Delegation::default()
2253            },
2254        };
2255
2256        let identical = good_stake;
2257        assert!(MergeKind::active_delegations_can_merge(
2258            &invoke_context,
2259            &good_stake.delegation,
2260            &identical.delegation
2261        )
2262        .is_ok());
2263
2264        let good_delegation = good_stake.delegation;
2265        let different_stake_ok = Delegation {
2266            stake: good_delegation.stake + 1,
2267            ..good_delegation
2268        };
2269        assert!(MergeKind::active_delegations_can_merge(
2270            &invoke_context,
2271            &good_delegation,
2272            &different_stake_ok
2273        )
2274        .is_ok());
2275
2276        let different_activation_epoch_ok = Delegation {
2277            activation_epoch: good_delegation.activation_epoch + 1,
2278            ..good_delegation
2279        };
2280        assert!(MergeKind::active_delegations_can_merge(
2281            &invoke_context,
2282            &good_delegation,
2283            &different_activation_epoch_ok
2284        )
2285        .is_ok());
2286
2287        let bad_voter = Delegation {
2288            voter_pubkey: Pubkey::new_unique(),
2289            ..good_delegation
2290        };
2291        assert!(MergeKind::active_delegations_can_merge(
2292            &invoke_context,
2293            &good_delegation,
2294            &bad_voter
2295        )
2296        .is_err());
2297
2298        let bad_deactivation_epoch = Delegation {
2299            deactivation_epoch: 43,
2300            ..good_delegation
2301        };
2302        assert!(MergeKind::active_delegations_can_merge(
2303            &invoke_context,
2304            &good_delegation,
2305            &bad_deactivation_epoch
2306        )
2307        .is_err());
2308        assert!(MergeKind::active_delegations_can_merge(
2309            &invoke_context,
2310            &bad_deactivation_epoch,
2311            &good_delegation
2312        )
2313        .is_err());
2314    }
2315
2316    #[test]
2317    fn test_metas_can_merge() {
2318        with_mock_invoke_context!(invoke_context, transaction_context, Vec::new());
2319        // Identical Metas can merge
2320        assert!(MergeKind::metas_can_merge(
2321            &invoke_context,
2322            &Meta::default(),
2323            &Meta::default(),
2324            &Clock::default()
2325        )
2326        .is_ok());
2327
2328        let mismatched_rent_exempt_reserve_ok = Meta {
2329            rent_exempt_reserve: 42,
2330            ..Meta::default()
2331        };
2332        assert_ne!(
2333            mismatched_rent_exempt_reserve_ok.rent_exempt_reserve,
2334            Meta::default().rent_exempt_reserve,
2335        );
2336        assert!(MergeKind::metas_can_merge(
2337            &invoke_context,
2338            &Meta::default(),
2339            &mismatched_rent_exempt_reserve_ok,
2340            &Clock::default()
2341        )
2342        .is_ok());
2343        assert!(MergeKind::metas_can_merge(
2344            &invoke_context,
2345            &mismatched_rent_exempt_reserve_ok,
2346            &Meta::default(),
2347            &Clock::default()
2348        )
2349        .is_ok());
2350
2351        let mismatched_authorized_fails = Meta {
2352            authorized: Authorized {
2353                staker: Pubkey::new_unique(),
2354                withdrawer: Pubkey::new_unique(),
2355            },
2356            ..Meta::default()
2357        };
2358        assert_ne!(
2359            mismatched_authorized_fails.authorized,
2360            Meta::default().authorized,
2361        );
2362        assert!(MergeKind::metas_can_merge(
2363            &invoke_context,
2364            &Meta::default(),
2365            &mismatched_authorized_fails,
2366            &Clock::default()
2367        )
2368        .is_err());
2369        assert!(MergeKind::metas_can_merge(
2370            &invoke_context,
2371            &mismatched_authorized_fails,
2372            &Meta::default(),
2373            &Clock::default()
2374        )
2375        .is_err());
2376
2377        let lockup1_timestamp = 42;
2378        let lockup2_timestamp = 4242;
2379        let lockup1_epoch = 4;
2380        let lockup2_epoch = 42;
2381        let metas_with_lockup1 = Meta {
2382            lockup: Lockup {
2383                unix_timestamp: lockup1_timestamp,
2384                epoch: lockup1_epoch,
2385                custodian: Pubkey::new_unique(),
2386            },
2387            ..Meta::default()
2388        };
2389        let metas_with_lockup2 = Meta {
2390            lockup: Lockup {
2391                unix_timestamp: lockup2_timestamp,
2392                epoch: lockup2_epoch,
2393                custodian: Pubkey::new_unique(),
2394            },
2395            ..Meta::default()
2396        };
2397
2398        // Mismatched lockups fail when both in force
2399        assert_ne!(metas_with_lockup1.lockup, Meta::default().lockup);
2400        assert!(MergeKind::metas_can_merge(
2401            &invoke_context,
2402            &metas_with_lockup1,
2403            &metas_with_lockup2,
2404            &Clock::default()
2405        )
2406        .is_err());
2407        assert!(MergeKind::metas_can_merge(
2408            &invoke_context,
2409            &metas_with_lockup2,
2410            &metas_with_lockup1,
2411            &Clock::default()
2412        )
2413        .is_err());
2414
2415        let clock = Clock {
2416            epoch: lockup1_epoch + 1,
2417            unix_timestamp: lockup1_timestamp + 1,
2418            ..Clock::default()
2419        };
2420
2421        // Mismatched lockups fail when either in force
2422        assert_ne!(metas_with_lockup1.lockup, Meta::default().lockup);
2423        assert!(MergeKind::metas_can_merge(
2424            &invoke_context,
2425            &metas_with_lockup1,
2426            &metas_with_lockup2,
2427            &clock
2428        )
2429        .is_err());
2430        assert!(MergeKind::metas_can_merge(
2431            &invoke_context,
2432            &metas_with_lockup2,
2433            &metas_with_lockup1,
2434            &clock
2435        )
2436        .is_err());
2437
2438        let clock = Clock {
2439            epoch: lockup2_epoch + 1,
2440            unix_timestamp: lockup2_timestamp + 1,
2441            ..Clock::default()
2442        };
2443
2444        // Mismatched lockups succeed when both expired
2445        assert_ne!(metas_with_lockup1.lockup, Meta::default().lockup);
2446        assert!(MergeKind::metas_can_merge(
2447            &invoke_context,
2448            &metas_with_lockup1,
2449            &metas_with_lockup2,
2450            &clock
2451        )
2452        .is_ok());
2453        assert!(MergeKind::metas_can_merge(
2454            &invoke_context,
2455            &metas_with_lockup2,
2456            &metas_with_lockup1,
2457            &clock
2458        )
2459        .is_ok());
2460    }
2461
2462    #[test]
2463    fn test_merge_kind_get_if_mergeable() {
2464        let transaction_accounts = vec![(
2465            epoch_schedule::id(),
2466            create_account_shared_data_for_test(&EpochSchedule::default()),
2467        )];
2468        with_mock_invoke_context!(invoke_context, transaction_context, transaction_accounts);
2469        let authority_pubkey = Pubkey::new_unique();
2470        let initial_lamports = 4242424242;
2471        let rent = Rent::default();
2472        let rent_exempt_reserve = rent.minimum_balance(StakeStateV2::size_of());
2473        let stake_lamports = rent_exempt_reserve + initial_lamports;
2474        let new_rate_activation_epoch = Some(0);
2475
2476        let meta = Meta {
2477            rent_exempt_reserve,
2478            ..Meta::auto(&authority_pubkey)
2479        };
2480        let mut stake_account = AccountSharedData::new_data_with_space(
2481            stake_lamports,
2482            &StakeStateV2::Uninitialized,
2483            StakeStateV2::size_of(),
2484            &id(),
2485        )
2486        .expect("stake_account");
2487        let mut clock = Clock::default();
2488        let mut stake_history = StakeHistory::default();
2489
2490        // Uninitialized state fails
2491        assert_eq!(
2492            MergeKind::get_if_mergeable(
2493                &invoke_context,
2494                &stake_account.state().unwrap(),
2495                stake_account.lamports(),
2496                &clock,
2497                &stake_history
2498            )
2499            .unwrap_err(),
2500            InstructionError::InvalidAccountData
2501        );
2502
2503        // RewardsPool state fails
2504        stake_account.set_state(&StakeStateV2::RewardsPool).unwrap();
2505        assert_eq!(
2506            MergeKind::get_if_mergeable(
2507                &invoke_context,
2508                &stake_account.state().unwrap(),
2509                stake_account.lamports(),
2510                &clock,
2511                &stake_history
2512            )
2513            .unwrap_err(),
2514            InstructionError::InvalidAccountData
2515        );
2516
2517        // Initialized state succeeds
2518        stake_account
2519            .set_state(&StakeStateV2::Initialized(meta))
2520            .unwrap();
2521        assert_eq!(
2522            MergeKind::get_if_mergeable(
2523                &invoke_context,
2524                &stake_account.state().unwrap(),
2525                stake_account.lamports(),
2526                &clock,
2527                &stake_history
2528            )
2529            .unwrap(),
2530            MergeKind::Inactive(meta, stake_lamports, StakeFlags::empty())
2531        );
2532
2533        clock.epoch = 0;
2534        let mut effective = 2 * initial_lamports;
2535        let mut activating = 0;
2536        let mut deactivating = 0;
2537        stake_history.add(
2538            clock.epoch,
2539            StakeHistoryEntry {
2540                effective,
2541                activating,
2542                deactivating,
2543            },
2544        );
2545
2546        clock.epoch += 1;
2547        activating = initial_lamports;
2548        stake_history.add(
2549            clock.epoch,
2550            StakeHistoryEntry {
2551                effective,
2552                activating,
2553                deactivating,
2554            },
2555        );
2556
2557        let stake = Stake {
2558            delegation: Delegation {
2559                stake: initial_lamports,
2560                activation_epoch: 1,
2561                deactivation_epoch: 9,
2562                ..Delegation::default()
2563            },
2564            ..Stake::default()
2565        };
2566        stake_account
2567            .set_state(&StakeStateV2::Stake(meta, stake, StakeFlags::empty()))
2568            .unwrap();
2569        // activation_epoch succeeds
2570        assert_eq!(
2571            MergeKind::get_if_mergeable(
2572                &invoke_context,
2573                &stake_account.state().unwrap(),
2574                stake_account.lamports(),
2575                &clock,
2576                &stake_history
2577            )
2578            .unwrap(),
2579            MergeKind::ActivationEpoch(meta, stake, StakeFlags::empty()),
2580        );
2581
2582        // all paritially activated, transient epochs fail
2583        loop {
2584            clock.epoch += 1;
2585            let delta = activating.min(
2586                (effective as f64 * warmup_cooldown_rate(clock.epoch, new_rate_activation_epoch))
2587                    as u64,
2588            );
2589            effective += delta;
2590            activating -= delta;
2591            stake_history.add(
2592                clock.epoch,
2593                StakeHistoryEntry {
2594                    effective,
2595                    activating,
2596                    deactivating,
2597                },
2598            );
2599            if activating == 0 {
2600                break;
2601            }
2602            assert_eq!(
2603                MergeKind::get_if_mergeable(
2604                    &invoke_context,
2605                    &stake_account.state().unwrap(),
2606                    stake_account.lamports(),
2607                    &clock,
2608                    &stake_history
2609                )
2610                .unwrap_err(),
2611                InstructionError::from(StakeError::MergeTransientStake),
2612            );
2613        }
2614
2615        // all epochs for which we're fully active succeed
2616        while clock.epoch < stake.delegation.deactivation_epoch - 1 {
2617            clock.epoch += 1;
2618            stake_history.add(
2619                clock.epoch,
2620                StakeHistoryEntry {
2621                    effective,
2622                    activating,
2623                    deactivating,
2624                },
2625            );
2626            assert_eq!(
2627                MergeKind::get_if_mergeable(
2628                    &invoke_context,
2629                    &stake_account.state().unwrap(),
2630                    stake_account.lamports(),
2631                    &clock,
2632                    &stake_history
2633                )
2634                .unwrap(),
2635                MergeKind::FullyActive(meta, stake),
2636            );
2637        }
2638
2639        clock.epoch += 1;
2640        deactivating = stake.delegation.stake;
2641        stake_history.add(
2642            clock.epoch,
2643            StakeHistoryEntry {
2644                effective,
2645                activating,
2646                deactivating,
2647            },
2648        );
2649        // deactivation epoch fails, fully transient/deactivating
2650        assert_eq!(
2651            MergeKind::get_if_mergeable(
2652                &invoke_context,
2653                &stake_account.state().unwrap(),
2654                stake_account.lamports(),
2655                &clock,
2656                &stake_history
2657            )
2658            .unwrap_err(),
2659            InstructionError::from(StakeError::MergeTransientStake),
2660        );
2661
2662        // all transient, deactivating epochs fail
2663        loop {
2664            clock.epoch += 1;
2665            let delta = deactivating.min(
2666                (effective as f64 * warmup_cooldown_rate(clock.epoch, new_rate_activation_epoch))
2667                    as u64,
2668            );
2669            effective -= delta;
2670            deactivating -= delta;
2671            stake_history.add(
2672                clock.epoch,
2673                StakeHistoryEntry {
2674                    effective,
2675                    activating,
2676                    deactivating,
2677                },
2678            );
2679            if deactivating == 0 {
2680                break;
2681            }
2682            assert_eq!(
2683                MergeKind::get_if_mergeable(
2684                    &invoke_context,
2685                    &stake_account.state().unwrap(),
2686                    stake_account.lamports(),
2687                    &clock,
2688                    &stake_history
2689                )
2690                .unwrap_err(),
2691                InstructionError::from(StakeError::MergeTransientStake),
2692            );
2693        }
2694
2695        // first fully-deactivated epoch succeeds
2696        assert_eq!(
2697            MergeKind::get_if_mergeable(
2698                &invoke_context,
2699                &stake_account.state().unwrap(),
2700                stake_account.lamports(),
2701                &clock,
2702                &stake_history
2703            )
2704            .unwrap(),
2705            MergeKind::Inactive(meta, stake_lamports, StakeFlags::empty()),
2706        );
2707    }
2708
2709    #[test]
2710    fn test_merge_kind_merge() {
2711        with_mock_invoke_context!(invoke_context, transaction_context, Vec::new());
2712        let clock = Clock::default();
2713        let lamports = 424242;
2714        let meta = Meta {
2715            rent_exempt_reserve: 42,
2716            ..Meta::default()
2717        };
2718        let stake = Stake {
2719            delegation: Delegation {
2720                stake: 4242,
2721                ..Delegation::default()
2722            },
2723            ..Stake::default()
2724        };
2725        let inactive = MergeKind::Inactive(Meta::default(), lamports, StakeFlags::empty());
2726        let activation_epoch = MergeKind::ActivationEpoch(meta, stake, StakeFlags::empty());
2727        let fully_active = MergeKind::FullyActive(meta, stake);
2728
2729        assert_eq!(
2730            inactive
2731                .clone()
2732                .merge(&invoke_context, inactive.clone(), &clock)
2733                .unwrap(),
2734            None
2735        );
2736        assert_eq!(
2737            inactive
2738                .clone()
2739                .merge(&invoke_context, activation_epoch.clone(), &clock)
2740                .unwrap(),
2741            None
2742        );
2743        assert!(inactive
2744            .clone()
2745            .merge(&invoke_context, fully_active.clone(), &clock)
2746            .is_err());
2747        assert!(activation_epoch
2748            .clone()
2749            .merge(&invoke_context, fully_active.clone(), &clock)
2750            .is_err());
2751        assert!(fully_active
2752            .clone()
2753            .merge(&invoke_context, inactive.clone(), &clock)
2754            .is_err());
2755        assert!(fully_active
2756            .clone()
2757            .merge(&invoke_context, activation_epoch.clone(), &clock)
2758            .is_err());
2759
2760        let new_state = activation_epoch
2761            .clone()
2762            .merge(&invoke_context, inactive, &clock)
2763            .unwrap()
2764            .unwrap();
2765        let delegation = new_state.delegation().unwrap();
2766        assert_eq!(delegation.stake, stake.delegation.stake + lamports);
2767
2768        let new_state = activation_epoch
2769            .clone()
2770            .merge(&invoke_context, activation_epoch, &clock)
2771            .unwrap()
2772            .unwrap();
2773        let delegation = new_state.delegation().unwrap();
2774        assert_eq!(
2775            delegation.stake,
2776            2 * stake.delegation.stake + meta.rent_exempt_reserve
2777        );
2778
2779        let new_state = fully_active
2780            .clone()
2781            .merge(&invoke_context, fully_active, &clock)
2782            .unwrap()
2783            .unwrap();
2784        let delegation = new_state.delegation().unwrap();
2785        assert_eq!(delegation.stake, 2 * stake.delegation.stake);
2786    }
2787
2788    #[test]
2789    fn test_active_stake_merge() {
2790        let transaction_accounts = vec![(
2791            Rent::id(),
2792            create_account_shared_data_for_test(&Rent::default()),
2793        )];
2794        with_mock_invoke_context!(invoke_context, transaction_context, transaction_accounts);
2795        let clock = Clock::default();
2796        let delegation_a = 4_242_424_242u64;
2797        let delegation_b = 6_200_000_000u64;
2798        let credits_a = 124_521_000u64;
2799        let rent_exempt_reserve = 227_000_000u64;
2800        let meta = Meta {
2801            rent_exempt_reserve,
2802            ..Meta::default()
2803        };
2804        let stake_a = Stake {
2805            delegation: Delegation {
2806                stake: delegation_a,
2807                ..Delegation::default()
2808            },
2809            credits_observed: credits_a,
2810        };
2811        let stake_b = Stake {
2812            delegation: Delegation {
2813                stake: delegation_b,
2814                ..Delegation::default()
2815            },
2816            credits_observed: credits_a,
2817        };
2818
2819        // activating stake merge, match credits observed
2820        let activation_epoch_a = MergeKind::ActivationEpoch(meta, stake_a, StakeFlags::empty());
2821        let activation_epoch_b = MergeKind::ActivationEpoch(meta, stake_b, StakeFlags::empty());
2822        let new_stake = activation_epoch_a
2823            .merge(&invoke_context, activation_epoch_b, &clock)
2824            .unwrap()
2825            .unwrap()
2826            .stake()
2827            .unwrap();
2828        assert_eq!(new_stake.credits_observed, credits_a);
2829        assert_eq!(
2830            new_stake.delegation.stake,
2831            delegation_a + delegation_b + rent_exempt_reserve
2832        );
2833
2834        // active stake merge, match credits observed
2835        let fully_active_a = MergeKind::FullyActive(meta, stake_a);
2836        let fully_active_b = MergeKind::FullyActive(meta, stake_b);
2837        let new_stake = fully_active_a
2838            .merge(&invoke_context, fully_active_b, &clock)
2839            .unwrap()
2840            .unwrap()
2841            .stake()
2842            .unwrap();
2843        assert_eq!(new_stake.credits_observed, credits_a);
2844        assert_eq!(new_stake.delegation.stake, delegation_a + delegation_b);
2845
2846        // activating stake merge, unmatched credits observed
2847        let credits_b = 125_124_521u64;
2848        let stake_b = Stake {
2849            delegation: Delegation {
2850                stake: delegation_b,
2851                ..Delegation::default()
2852            },
2853            credits_observed: credits_b,
2854        };
2855        let activation_epoch_a = MergeKind::ActivationEpoch(meta, stake_a, StakeFlags::empty());
2856        let activation_epoch_b = MergeKind::ActivationEpoch(meta, stake_b, StakeFlags::empty());
2857        let new_stake = activation_epoch_a
2858            .merge(&invoke_context, activation_epoch_b, &clock)
2859            .unwrap()
2860            .unwrap()
2861            .stake()
2862            .unwrap();
2863        assert_eq!(
2864            new_stake.credits_observed,
2865            (credits_a * delegation_a + credits_b * (delegation_b + rent_exempt_reserve))
2866                / (delegation_a + delegation_b + rent_exempt_reserve)
2867                + 1
2868        );
2869        assert_eq!(
2870            new_stake.delegation.stake,
2871            delegation_a + delegation_b + rent_exempt_reserve
2872        );
2873
2874        // active stake merge, unmatched credits observed
2875        let fully_active_a = MergeKind::FullyActive(meta, stake_a);
2876        let fully_active_b = MergeKind::FullyActive(meta, stake_b);
2877        let new_stake = fully_active_a
2878            .merge(&invoke_context, fully_active_b, &clock)
2879            .unwrap()
2880            .unwrap()
2881            .stake()
2882            .unwrap();
2883        assert_eq!(
2884            new_stake.credits_observed,
2885            (credits_a * delegation_a + credits_b * delegation_b) / (delegation_a + delegation_b)
2886                + 1
2887        );
2888        assert_eq!(new_stake.delegation.stake, delegation_a + delegation_b);
2889
2890        // active stake merge, unmatched credits observed, no need to ceiling the calculation
2891        let delegation = 1_000_000u64;
2892        let credits_a = 200_000_000u64;
2893        let credits_b = 100_000_000u64;
2894        let rent_exempt_reserve = 227_000_000u64;
2895        let meta = Meta {
2896            rent_exempt_reserve,
2897            ..Meta::default()
2898        };
2899        let stake_a = Stake {
2900            delegation: Delegation {
2901                stake: delegation,
2902                ..Delegation::default()
2903            },
2904            credits_observed: credits_a,
2905        };
2906        let stake_b = Stake {
2907            delegation: Delegation {
2908                stake: delegation,
2909                ..Delegation::default()
2910            },
2911            credits_observed: credits_b,
2912        };
2913        let fully_active_a = MergeKind::FullyActive(meta, stake_a);
2914        let fully_active_b = MergeKind::FullyActive(meta, stake_b);
2915        let new_stake = fully_active_a
2916            .merge(&invoke_context, fully_active_b, &clock)
2917            .unwrap()
2918            .unwrap()
2919            .stake()
2920            .unwrap();
2921        assert_eq!(
2922            new_stake.credits_observed,
2923            (credits_a * delegation + credits_b * delegation) / (delegation + delegation)
2924        );
2925        assert_eq!(new_stake.delegation.stake, delegation * 2);
2926    }
2927
2928    prop_compose! {
2929        pub fn sum_within(max: u64)(total in 1..max)
2930            (intermediate in 1..total, total in Just(total))
2931            -> (u64, u64) {
2932                (intermediate, total - intermediate)
2933        }
2934    }
2935
2936    proptest! {
2937        #[test]
2938        fn test_stake_weighted_credits_observed(
2939            (credits_a, credits_b) in sum_within(u64::MAX),
2940            (delegation_a, delegation_b) in sum_within(u64::MAX),
2941        ) {
2942            let stake = Stake {
2943                delegation: Delegation {
2944                    stake: delegation_a,
2945                    ..Delegation::default()
2946                },
2947                credits_observed: credits_a
2948            };
2949            let credits_observed = stake_weighted_credits_observed(
2950                &stake,
2951                delegation_b,
2952                credits_b,
2953            ).unwrap();
2954
2955            // calculated credits observed should always be between the credits of a and b
2956            if credits_a < credits_b {
2957                assert!(credits_a < credits_observed);
2958                assert!(credits_observed <= credits_b);
2959            } else {
2960                assert!(credits_b <= credits_observed);
2961                assert!(credits_observed <= credits_a);
2962            }
2963
2964            // the difference of the combined weighted credits and the separate weighted credits
2965            // should be 1 or 0
2966            let weighted_credits_total = credits_observed as u128 * (delegation_a + delegation_b) as u128;
2967            let weighted_credits_a = credits_a as u128 * delegation_a as u128;
2968            let weighted_credits_b = credits_b as u128 * delegation_b as u128;
2969            let raw_diff = weighted_credits_total - (weighted_credits_a + weighted_credits_b);
2970            let credits_observed_diff = raw_diff / (delegation_a + delegation_b) as u128;
2971            assert!(credits_observed_diff <= 1);
2972        }
2973    }
2974}