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