1#[deprecated(
7 since = "1.8.0",
8 note = "Please use `solana_sdk::stake::state` or `solana_program::stake::state` instead"
9)]
10pub use solana_sdk::stake::state::*;
11use {
12 solana_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
37pub 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.stake(clock.epoch, stake_history, new_rate_activation_epoch) != 0 {
97 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 return Err(StakeError::TooSoonToRedelegate);
108 }
109 }
110 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 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 if *source_account.get_owner() != id() || *destination_account.get_owner() != id() {
145 return Err(InstructionError::IncorrectProgramId);
146 }
147
148 if *source_account.get_key() == *destination_account.get_key() {
150 return Err(InstructionError::InvalidInstructionData);
151 }
152
153 if !source_account.is_writable() || !destination_account.is_writable() {
155 return Err(InstructionError::InvalidInstructionData);
156 }
157
158 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 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 source_merge_kind
178 .meta()
179 .authorized
180 .check(&signers, StakeAuthorize::Staker)?;
181
182 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 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
240pub 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 let (remaining_stake_delta, split_stake_amount) =
456 if validated_split_info.source_remaining_balance == 0 {
457 let remaining_stake_delta = lamports.saturating_sub(meta.rent_exempt_reserve);
468 (remaining_stake_delta, remaining_stake_delta)
469 } else {
470 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, 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 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 if *source_account.get_owner() != id() {
566 return Err(InstructionError::IncorrectProgramId);
567 }
568 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 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_account.set_state(&StakeStateV2::Uninitialized)?;
609
610 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 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 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 let source_final_stake = source_effective_stake
660 .checked_sub(lamports)
661 .ok_or(InstructionError::InvalidArgument)?;
662
663 if source_final_stake != 0 && source_final_stake < minimum_delegation {
665 return Err(InstructionError::InvalidArgument);
666 }
667
668 let destination_meta = match destination_merge_kind {
670 MergeKind::FullyActive(destination_meta, mut destination_stake) => {
671 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 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 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 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 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 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 (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) }
852 _ => return Err(InstructionError::InvalidAccountData),
853 };
854
855 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 is_staked && lamports_and_reserve > stake_account.get_lamports()
879 {
880 return Err(InstructionError::InsufficientFunds);
881 }
882
883 if lamports != stake_account.get_lamports() && lamports_and_reserve > stake_account.get_lamports()
885 {
886 assert!(!is_staked);
887 return Err(InstructionError::InsufficientFunds);
888 }
889
890 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 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
955struct ValidatedDelegatedInfo {
958 stake_amount: u64,
959}
960
961fn 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); if stake_amount < crate::get_minimum_delegation(feature_set) {
975 return Err(StakeError::InsufficientDelegation.into());
976 }
977 Ok(ValidatedDelegatedInfo { stake_amount })
978}
979
980#[derive(Copy, Clone, Debug, Default)]
983struct ValidatedSplitInfo {
984 source_remaining_balance: u64,
985 destination_rent_exempt_reserve: u64,
986}
987
988fn 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 if lamports == 0 {
1015 return Err(InstructionError::InsufficientFunds);
1016 }
1017
1018 if lamports > source_lamports {
1020 return Err(InstructionError::InsufficientFunds);
1021 }
1022
1023 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 } else if source_remaining_balance < source_minimum_balance {
1035 return Err(InstructionError::InsufficientFunds);
1037 } else {
1038 }
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 source_is_active
1049 && source_remaining_balance != 0
1050 && destination_lamports < destination_rent_exempt_reserve
1051 {
1052 return Err(InstructionError::InsufficientFunds);
1053 }
1054
1055 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 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 let can_merge_lockups = stake.lockup == source.lockup
1140 || (!stake.lockup.is_in_force(clock, None) && !source.lockup.is_in_force(clock, None));
1141 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 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
1251fn 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 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
1301pub 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
1316pub 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
1348pub 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
1374pub 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
1392pub 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, 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 assert_eq!(
1499 authorized.authorize(
1500 &signers,
1501 &staker,
1502 StakeAuthorize::Withdrawer,
1503 Some((&Lockup::default(), &clock, None))
1504 ),
1505 Ok(())
1506 );
1507
1508 assert_eq!(
1510 authorized.authorize(
1511 &signers,
1512 &staker,
1513 StakeAuthorize::Withdrawer,
1514 Some((&Lockup::default(), &clock, Some(&invalid_custodian)))
1515 ),
1516 Ok(()) );
1518
1519 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 assert_eq!(
1534 authorized.authorize(
1535 &signers,
1536 &staker,
1537 StakeAuthorize::Withdrawer,
1538 Some((&Lockup::default(), &clock, Some(&invalid_custodian)))
1539 ),
1540 Ok(()) );
1542
1543 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()), );
1554
1555 signers.remove(&invalid_custodian);
1556
1557 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 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 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, deactivation_epoch: 5,
1623 ..Delegation::default()
1624 };
1625
1626 let increment = (1_000_f64 * warmup_cooldown_rate(0, None)) as u64;
1628
1629 let mut stake_history = StakeHistory::default();
1630 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_eq!(
1643 stake.stake_activating_and_deactivating(stake.deactivation_epoch, &stake_history, None),
1644 StakeActivationStatus::with_deactivating(stake.stake),
1645 );
1646 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, StakeHistoryEntry {
1659 effective: 1_000,
1660 ..StakeHistoryEntry::default()
1661 },
1662 );
1663 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, StakeHistoryEntry {
1672 effective: 1_000,
1673 activating: 1_000,
1674 ..StakeHistoryEntry::default()
1675 },
1676 );
1678 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 let mut stake_history = StakeHistory::default();
1689
1690 stake_history.add(
1691 stake.deactivation_epoch, StakeHistoryEntry {
1693 effective: 1_000,
1694 ..StakeHistoryEntry::default()
1695 },
1696 );
1697 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 stake_history.add(
1709 stake.deactivation_epoch, StakeHistoryEntry {
1711 effective: 1_000,
1712 deactivating: 1_000,
1713 ..StakeHistoryEntry::default()
1714 },
1715 );
1716 assert_eq!(
1718 stake.stake_activating_and_deactivating(
1719 stake.deactivation_epoch + 2,
1720 &stake_history,
1721 None,
1722 ),
1723 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 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 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 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 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), StakeActivationStatus::with_effective(700),
1948 StakeActivationStatus::with_deactivating(700),
1949 StakeActivationStatus::with_deactivating(275 + 1), StakeActivationStatus::default(),
1951 ];
1952
1953 assert_eq!(
1955 expected_staking_status_transition,
1956 calculate_each_staking_status(&stake, expected_staking_status_transition.len())
1957 );
1958
1959 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 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 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, deactivation_epoch: 5,
2055 ..Delegation::default()
2056 }];
2057 let epochs = 7;
2059 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 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 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 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 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 assert!(lockup.is_in_force(
2185 &Clock {
2186 epoch: 0,
2187 unix_timestamp: 0,
2188 ..Clock::default()
2189 },
2190 None
2191 ));
2192 assert!(lockup.is_in_force(
2194 &Clock {
2195 epoch: 2,
2196 unix_timestamp: 0,
2197 ..Clock::default()
2198 },
2199 None
2200 ));
2201 assert!(lockup.is_in_force(
2203 &Clock {
2204 epoch: 0,
2205 unix_timestamp: 2,
2206 ..Clock::default()
2207 },
2208 None
2209 ));
2210 assert!(!lockup.is_in_force(
2212 &Clock {
2213 epoch: 1,
2214 unix_timestamp: 1,
2215 ..Clock::default()
2216 },
2217 None
2218 ));
2219 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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}