solana_stake_program/
stake_state.rs

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