diff --git a/programs/stake/src/stake_instruction.rs b/programs/stake/src/stake_instruction.rs index 4e1f9d4063..26eca03eea 100644 --- a/programs/stake/src/stake_instruction.rs +++ b/programs/stake/src/stake_instruction.rs @@ -595,12 +595,15 @@ pub fn process_instruction( } StakeInstruction::Merge => { let source_stake = &keyed_account_at_index(keyed_accounts, 1)?; + let can_merge_expired_lockups = + invoke_context.is_feature_active(&feature_set::stake_program_v4::id()); me.merge( invoke_context, source_stake, &from_keyed_account::(keyed_account_at_index(keyed_accounts, 2)?)?, &from_keyed_account::(keyed_account_at_index(keyed_accounts, 3)?)?, &signers, + can_merge_expired_lockups, ) } diff --git a/programs/stake/src/stake_state.rs b/programs/stake/src/stake_state.rs index 4718f6017c..db35753ba0 100644 --- a/programs/stake/src/stake_state.rs +++ b/programs/stake/src/stake_state.rs @@ -888,6 +888,7 @@ pub trait StakeAccount { clock: &Clock, stake_history: &StakeHistory, signers: &HashSet, + can_merge_expired_lockups: bool, ) -> Result<(), InstructionError>; fn withdraw( &self, @@ -1191,6 +1192,7 @@ impl<'a> StakeAccount for KeyedAccount<'a> { clock: &Clock, stake_history: &StakeHistory, signers: &HashSet, + can_merge_expired_lockups: bool, ) -> Result<(), InstructionError> { // Ensure source isn't spoofed if source_account.owner()? != id() { @@ -1213,8 +1215,16 @@ impl<'a> StakeAccount for KeyedAccount<'a> { let source_merge_kind = MergeKind::get_if_mergeable(invoke_context, source_account, clock, stake_history)?; + let clock = if can_merge_expired_lockups { + Some(clock) + } else { + None + }; + ic_msg!(invoke_context, "Merging stake accounts"); - if let Some(merged_state) = stake_merge_kind.merge(invoke_context, source_merge_kind)? { + if let Some(merged_state) = + stake_merge_kind.merge(invoke_context, source_merge_kind, clock)? + { self.set_state(&merged_state)?; } @@ -1380,6 +1390,7 @@ impl MergeKind { invoke_context: &dyn InvokeContext, stake: &Meta, source: &Meta, + _clock: Option<&Clock>, ) -> Result<(), InstructionError> { // `rent_exempt_reserve` has no bearing on the mergeability of accounts, // as the source account will be culled by runtime once the operation @@ -1439,8 +1450,9 @@ impl MergeKind { self, invoke_context: &dyn InvokeContext, source: Self, + clock: Option<&Clock>, ) -> Result, InstructionError> { - Self::metas_can_merge(invoke_context, self.meta(), source.meta())?; + Self::metas_can_merge(invoke_context, self.meta(), source.meta(), clock)?; self.active_stake() .zip(source.active_stake()) .map(|(stake, source)| Self::active_stakes_can_merge(invoke_context, stake, source)) @@ -5380,7 +5392,8 @@ mod tests { &source_stake_keyed_account, &Clock::default(), &StakeHistory::default(), - &HashSet::new() + &HashSet::new(), + false, ), Err(InstructionError::MissingRequiredSignature) ); @@ -5391,7 +5404,8 @@ mod tests { &source_stake_keyed_account, &Clock::default(), &StakeHistory::default(), - &signers + &signers, + false, ), Ok(()) ); @@ -5483,6 +5497,7 @@ mod tests { &Clock::default(), &StakeHistory::default(), &signers, + false, ), Err(InstructionError::InvalidArgument), ); @@ -5540,6 +5555,7 @@ mod tests { &Clock::default(), &StakeHistory::default(), &wrong_signers, + false, ), Err(InstructionError::MissingRequiredSignature) ); @@ -5551,6 +5567,7 @@ mod tests { &Clock::default(), &StakeHistory::default(), &signers, + false, ), Err(StakeError::MergeMismatch.into()) ); @@ -5603,6 +5620,7 @@ mod tests { &Clock::default(), &StakeHistory::default(), &signers, + false, ), Err(InstructionError::InvalidAccountData) ); @@ -5651,7 +5669,8 @@ mod tests { &source_stake_keyed_account, &Clock::default(), &StakeHistory::default(), - &signers + &signers, + false, ), Err(InstructionError::IncorrectProgramId) ); @@ -5748,6 +5767,7 @@ mod tests { clock, stake_history, signers, + false, ); if result.is_ok() { assert_eq!(test_source_keyed.state(), Ok(StakeState::Uninitialized),); @@ -6468,11 +6488,19 @@ mod tests { &good_delegation ) .is_err()); + } + #[test] + fn test_metas_can_merge_pre_v4() { + let invoke_context = MockInvokeContext::new(vec![]); // Identical Metas can merge - assert!( - MergeKind::metas_can_merge(&invoke_context, &Meta::default(), &Meta::default()).is_ok() - ); + assert!(MergeKind::metas_can_merge( + &invoke_context, + &Meta::default(), + &Meta::default(), + None, + ) + .is_ok()); let mismatched_rent_exempt_reserve_ok = Meta { rent_exempt_reserve: 42, @@ -6485,13 +6513,15 @@ mod tests { assert!(MergeKind::metas_can_merge( &invoke_context, &Meta::default(), - &mismatched_rent_exempt_reserve_ok + &mismatched_rent_exempt_reserve_ok, + None, ) .is_ok()); assert!(MergeKind::metas_can_merge( &invoke_context, &mismatched_rent_exempt_reserve_ok, - &Meta::default() + &Meta::default(), + None, ) .is_ok()); @@ -6509,13 +6539,15 @@ mod tests { assert!(MergeKind::metas_can_merge( &invoke_context, &Meta::default(), - &mismatched_authorized_fails + &mismatched_authorized_fails, + None, ) .is_err()); assert!(MergeKind::metas_can_merge( &invoke_context, &mismatched_authorized_fails, - &Meta::default() + &Meta::default(), + None, ) .is_err()); @@ -6531,13 +6563,15 @@ mod tests { assert!(MergeKind::metas_can_merge( &invoke_context, &Meta::default(), - &mismatched_lockup_fails + &mismatched_lockup_fails, + None, ) .is_err()); assert!(MergeKind::metas_can_merge( &invoke_context, &mismatched_lockup_fails, - &Meta::default() + &Meta::default(), + None, ) .is_err()); } @@ -6796,37 +6830,37 @@ mod tests { assert_eq!( inactive .clone() - .merge(&invoke_context, inactive.clone()) + .merge(&invoke_context, inactive.clone(), None) .unwrap(), None ); assert_eq!( inactive .clone() - .merge(&invoke_context, activation_epoch.clone()) + .merge(&invoke_context, activation_epoch.clone(), None) .unwrap(), None ); assert!(inactive .clone() - .merge(&invoke_context, fully_active.clone()) + .merge(&invoke_context, fully_active.clone(), None) .is_err()); assert!(activation_epoch .clone() - .merge(&invoke_context, fully_active.clone()) + .merge(&invoke_context, fully_active.clone(), None) .is_err()); assert!(fully_active .clone() - .merge(&invoke_context, inactive.clone()) + .merge(&invoke_context, inactive.clone(), None) .is_err()); assert!(fully_active .clone() - .merge(&invoke_context, activation_epoch.clone()) + .merge(&invoke_context, activation_epoch.clone(), None) .is_err()); let new_state = activation_epoch .clone() - .merge(&invoke_context, inactive) + .merge(&invoke_context, inactive, None) .unwrap() .unwrap(); let delegation = new_state.delegation().unwrap(); @@ -6834,7 +6868,7 @@ mod tests { let new_state = activation_epoch .clone() - .merge(&invoke_context, activation_epoch) + .merge(&invoke_context, activation_epoch, None) .unwrap() .unwrap(); let delegation = new_state.delegation().unwrap(); @@ -6845,7 +6879,7 @@ mod tests { let new_state = fully_active .clone() - .merge(&invoke_context, fully_active) + .merge(&invoke_context, fully_active, None) .unwrap() .unwrap(); let delegation = new_state.delegation().unwrap();