From 07f4a9040a24fc9637c2ef4a431c6cd583d6f382 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Mei=C3=9Fner?= Date: Wed, 6 Apr 2022 12:04:35 +0200 Subject: [PATCH] Removes KeyedAccount from tests in stake instruction. (Part 4) (#24124) * Moves tests from stake state to stake instruction. * Migrates test_merge. * Migrates test_merge_self_fails. * Migrates test_merge_incorrect_authorized_staker. * Migrates test_merge_invalid_account_data. * Migrates test_merge_fake_stake_source. * Migrates test_merge_active_stake. --- programs/stake/src/stake_instruction.rs | 694 +++++++++++++++++++++++- programs/stake/src/stake_state.rs | 591 -------------------- 2 files changed, 692 insertions(+), 593 deletions(-) diff --git a/programs/stake/src/stake_instruction.rs b/programs/stake/src/stake_instruction.rs index 6d177e984d..d9499eab1d 100644 --- a/programs/stake/src/stake_instruction.rs +++ b/programs/stake/src/stake_instruction.rs @@ -359,8 +359,8 @@ mod tests { instruction::{self, LockupArgs}, state::{Authorized, Lockup, StakeAuthorize}, }, - system_program, - sysvar::{self, stake_history::StakeHistory}, + stake_history::{StakeHistory, StakeHistoryEntry}, + system_program, sysvar, }, solana_vote_program::vote_state::{self, VoteState, VoteStateVersions}, std::{collections::HashSet, str::FromStr}, @@ -5176,4 +5176,694 @@ mod tests { } } } + + #[test] + fn test_merge() { + let stake_address = solana_sdk::pubkey::new_rand(); + let merge_from_address = solana_sdk::pubkey::new_rand(); + let authorized_address = solana_sdk::pubkey::new_rand(); + let meta = Meta::auto(&authorized_address); + let stake_lamports = 42; + let mut instruction_accounts = vec![ + AccountMeta { + pubkey: stake_address, + is_signer: false, + is_writable: false, + }, + AccountMeta { + pubkey: merge_from_address, + is_signer: false, + is_writable: false, + }, + AccountMeta { + pubkey: sysvar::clock::id(), + is_signer: false, + is_writable: false, + }, + AccountMeta { + pubkey: sysvar::stake_history::id(), + is_signer: false, + is_writable: false, + }, + AccountMeta { + pubkey: authorized_address, + is_signer: true, + is_writable: false, + }, + ]; + + for state in &[ + StakeState::Initialized(meta), + just_stake(meta, stake_lamports), + ] { + let stake_account = AccountSharedData::new_data_with_space( + stake_lamports, + state, + std::mem::size_of::(), + &id(), + ) + .unwrap(); + for merge_from_state in &[ + StakeState::Initialized(meta), + just_stake(meta, stake_lamports), + ] { + let merge_from_account = AccountSharedData::new_data_with_space( + stake_lamports, + merge_from_state, + std::mem::size_of::(), + &id(), + ) + .unwrap(); + let transaction_accounts = vec![ + (stake_address, stake_account.clone()), + (merge_from_address, merge_from_account), + (authorized_address, AccountSharedData::default()), + ( + sysvar::clock::id(), + account::create_account_shared_data_for_test(&Clock::default()), + ), + ( + sysvar::stake_history::id(), + account::create_account_shared_data_for_test(&StakeHistory::default()), + ), + ]; + + // Authorized staker signature required... + instruction_accounts[4].is_signer = false; + process_instruction( + &serialize(&StakeInstruction::Merge).unwrap(), + transaction_accounts.clone(), + instruction_accounts.clone(), + Err(InstructionError::MissingRequiredSignature), + ); + instruction_accounts[4].is_signer = true; + + let accounts = process_instruction( + &serialize(&StakeInstruction::Merge).unwrap(), + transaction_accounts, + instruction_accounts.clone(), + Ok(()), + ); + + // check lamports + assert_eq!(accounts[0].lamports(), stake_lamports * 2); + assert_eq!(accounts[1].lamports(), 0); + + // check state + match state { + StakeState::Initialized(meta) => { + assert_eq!(accounts[0].state(), Ok(StakeState::Initialized(*meta)),); + } + StakeState::Stake(meta, stake) => { + let expected_stake = stake.delegation.stake + + merge_from_state + .stake() + .map(|stake| stake.delegation.stake) + .unwrap_or_else(|| { + stake_lamports + - merge_from_state.meta().unwrap().rent_exempt_reserve + }); + assert_eq!( + accounts[0].state(), + Ok(StakeState::Stake( + *meta, + Stake { + delegation: Delegation { + stake: expected_stake, + ..stake.delegation + }, + ..*stake + } + )), + ); + } + _ => unreachable!(), + } + assert_eq!(accounts[1].state(), Ok(StakeState::Uninitialized)); + } + } + } + + #[test] + fn test_merge_self_fails() { + let stake_address = solana_sdk::pubkey::new_rand(); + let authorized_address = solana_sdk::pubkey::new_rand(); + let rent = Rent::default(); + let rent_exempt_reserve = rent.minimum_balance(std::mem::size_of::()); + let stake_amount = 4242424242; + let stake_lamports = rent_exempt_reserve + stake_amount; + let meta = Meta { + rent_exempt_reserve, + ..Meta::auto(&authorized_address) + }; + let stake = Stake { + delegation: Delegation { + stake: stake_amount, + activation_epoch: 0, + ..Delegation::default() + }, + ..Stake::default() + }; + let stake_account = AccountSharedData::new_data_with_space( + stake_lamports, + &StakeState::Stake(meta, stake), + std::mem::size_of::(), + &id(), + ) + .unwrap(); + let transaction_accounts = vec![ + (stake_address, stake_account), + (authorized_address, AccountSharedData::default()), + ( + sysvar::clock::id(), + account::create_account_shared_data_for_test(&Clock::default()), + ), + ( + sysvar::stake_history::id(), + account::create_account_shared_data_for_test(&StakeHistory::default()), + ), + ]; + let instruction_accounts = vec![ + AccountMeta { + pubkey: stake_address, + is_signer: false, + is_writable: false, + }, + AccountMeta { + pubkey: stake_address, + is_signer: false, + is_writable: false, + }, + AccountMeta { + pubkey: sysvar::clock::id(), + is_signer: false, + is_writable: false, + }, + AccountMeta { + pubkey: sysvar::stake_history::id(), + is_signer: false, + is_writable: false, + }, + AccountMeta { + pubkey: authorized_address, + is_signer: true, + is_writable: false, + }, + ]; + + process_instruction( + &serialize(&StakeInstruction::Merge).unwrap(), + transaction_accounts, + instruction_accounts, + Err(InstructionError::InvalidArgument), + ); + } + + #[test] + fn test_merge_incorrect_authorized_staker() { + let stake_address = solana_sdk::pubkey::new_rand(); + let merge_from_address = solana_sdk::pubkey::new_rand(); + let authorized_address = solana_sdk::pubkey::new_rand(); + let wrong_authorized_address = solana_sdk::pubkey::new_rand(); + let stake_lamports = 42; + let mut instruction_accounts = vec![ + AccountMeta { + pubkey: stake_address, + is_signer: false, + is_writable: false, + }, + AccountMeta { + pubkey: merge_from_address, + is_signer: false, + is_writable: false, + }, + AccountMeta { + pubkey: sysvar::clock::id(), + is_signer: false, + is_writable: false, + }, + AccountMeta { + pubkey: sysvar::stake_history::id(), + is_signer: false, + is_writable: false, + }, + AccountMeta { + pubkey: authorized_address, + is_signer: true, + is_writable: false, + }, + ]; + + for state in &[ + StakeState::Initialized(Meta::auto(&authorized_address)), + just_stake(Meta::auto(&authorized_address), stake_lamports), + ] { + let stake_account = AccountSharedData::new_data_with_space( + stake_lamports, + state, + std::mem::size_of::(), + &id(), + ) + .unwrap(); + for merge_from_state in &[ + StakeState::Initialized(Meta::auto(&wrong_authorized_address)), + just_stake(Meta::auto(&wrong_authorized_address), stake_lamports), + ] { + let merge_from_account = AccountSharedData::new_data_with_space( + stake_lamports, + merge_from_state, + std::mem::size_of::(), + &id(), + ) + .unwrap(); + let transaction_accounts = vec![ + (stake_address, stake_account.clone()), + (merge_from_address, merge_from_account), + (authorized_address, AccountSharedData::default()), + (wrong_authorized_address, AccountSharedData::default()), + ( + sysvar::clock::id(), + account::create_account_shared_data_for_test(&Clock::default()), + ), + ( + sysvar::stake_history::id(), + account::create_account_shared_data_for_test(&StakeHistory::default()), + ), + ]; + + instruction_accounts[4].pubkey = wrong_authorized_address; + process_instruction( + &serialize(&StakeInstruction::Merge).unwrap(), + transaction_accounts.clone(), + instruction_accounts.clone(), + Err(InstructionError::MissingRequiredSignature), + ); + instruction_accounts[4].pubkey = authorized_address; + + process_instruction( + &serialize(&StakeInstruction::Merge).unwrap(), + transaction_accounts, + instruction_accounts.clone(), + Err(StakeError::MergeMismatch.into()), + ); + } + } + } + + #[test] + fn test_merge_invalid_account_data() { + let stake_address = solana_sdk::pubkey::new_rand(); + let merge_from_address = solana_sdk::pubkey::new_rand(); + let authorized_address = solana_sdk::pubkey::new_rand(); + let stake_lamports = 42; + let instruction_accounts = vec![ + AccountMeta { + pubkey: stake_address, + is_signer: false, + is_writable: false, + }, + AccountMeta { + pubkey: merge_from_address, + is_signer: false, + is_writable: false, + }, + AccountMeta { + pubkey: sysvar::clock::id(), + is_signer: false, + is_writable: false, + }, + AccountMeta { + pubkey: sysvar::stake_history::id(), + is_signer: false, + is_writable: false, + }, + AccountMeta { + pubkey: authorized_address, + is_signer: true, + is_writable: false, + }, + ]; + + for state in &[ + StakeState::Uninitialized, + StakeState::RewardsPool, + StakeState::Initialized(Meta::auto(&authorized_address)), + just_stake(Meta::auto(&authorized_address), stake_lamports), + ] { + let stake_account = AccountSharedData::new_data_with_space( + stake_lamports, + state, + std::mem::size_of::(), + &id(), + ) + .unwrap(); + for merge_from_state in &[StakeState::Uninitialized, StakeState::RewardsPool] { + let merge_from_account = AccountSharedData::new_data_with_space( + stake_lamports, + merge_from_state, + std::mem::size_of::(), + &id(), + ) + .unwrap(); + let transaction_accounts = vec![ + (stake_address, stake_account.clone()), + (merge_from_address, merge_from_account), + (authorized_address, AccountSharedData::default()), + ( + sysvar::clock::id(), + account::create_account_shared_data_for_test(&Clock::default()), + ), + ( + sysvar::stake_history::id(), + account::create_account_shared_data_for_test(&StakeHistory::default()), + ), + ]; + + process_instruction( + &serialize(&StakeInstruction::Merge).unwrap(), + transaction_accounts, + instruction_accounts.clone(), + Err(InstructionError::InvalidAccountData), + ); + } + } + } + + #[test] + fn test_merge_fake_stake_source() { + let stake_address = solana_sdk::pubkey::new_rand(); + let merge_from_address = solana_sdk::pubkey::new_rand(); + let authorized_address = solana_sdk::pubkey::new_rand(); + let stake_lamports = 42; + let stake_account = AccountSharedData::new_data_with_space( + stake_lamports, + &just_stake(Meta::auto(&authorized_address), stake_lamports), + std::mem::size_of::(), + &id(), + ) + .unwrap(); + let merge_from_account = AccountSharedData::new_data_with_space( + stake_lamports, + &just_stake(Meta::auto(&authorized_address), stake_lamports), + std::mem::size_of::(), + &solana_sdk::pubkey::new_rand(), + ) + .unwrap(); + let transaction_accounts = vec![ + (stake_address, stake_account), + (merge_from_address, merge_from_account), + (authorized_address, AccountSharedData::default()), + ( + sysvar::clock::id(), + account::create_account_shared_data_for_test(&Clock::default()), + ), + ( + sysvar::stake_history::id(), + account::create_account_shared_data_for_test(&StakeHistory::default()), + ), + ]; + let instruction_accounts = vec![ + AccountMeta { + pubkey: stake_address, + is_signer: false, + is_writable: false, + }, + AccountMeta { + pubkey: merge_from_address, + is_signer: false, + is_writable: false, + }, + AccountMeta { + pubkey: sysvar::clock::id(), + is_signer: false, + is_writable: false, + }, + AccountMeta { + pubkey: sysvar::stake_history::id(), + is_signer: false, + is_writable: false, + }, + AccountMeta { + pubkey: authorized_address, + is_signer: true, + is_writable: false, + }, + ]; + + process_instruction( + &serialize(&StakeInstruction::Merge).unwrap(), + transaction_accounts, + instruction_accounts, + Err(InstructionError::IncorrectProgramId), + ); + } + + #[test] + fn test_merge_active_stake() { + let stake_address = solana_sdk::pubkey::new_rand(); + let merge_from_address = solana_sdk::pubkey::new_rand(); + let authorized_address = solana_sdk::pubkey::new_rand(); + let base_lamports = 4242424242; + let rent = Rent::default(); + let rent_exempt_reserve = rent.minimum_balance(std::mem::size_of::()); + let stake_amount = base_lamports; + let stake_lamports = rent_exempt_reserve + stake_amount; + let merge_from_amount = base_lamports; + let merge_from_lamports = rent_exempt_reserve + merge_from_amount; + let meta = Meta { + rent_exempt_reserve, + ..Meta::auto(&authorized_address) + }; + let mut stake = Stake { + delegation: Delegation { + stake: stake_amount, + activation_epoch: 0, + ..Delegation::default() + }, + ..Stake::default() + }; + let stake_account = AccountSharedData::new_data_with_space( + stake_lamports, + &StakeState::Stake(meta, stake), + std::mem::size_of::(), + &id(), + ) + .unwrap(); + let merge_from_activation_epoch = 2; + let mut merge_from_stake = Stake { + delegation: Delegation { + stake: merge_from_amount, + activation_epoch: merge_from_activation_epoch, + ..stake.delegation + }, + ..stake + }; + let merge_from_account = AccountSharedData::new_data_with_space( + merge_from_lamports, + &StakeState::Stake(meta, merge_from_stake), + std::mem::size_of::(), + &id(), + ) + .unwrap(); + let mut clock = Clock::default(); + let mut stake_history = StakeHistory::default(); + let mut effective = base_lamports; + let mut activating = stake_amount; + let mut deactivating = 0; + stake_history.add( + clock.epoch, + StakeHistoryEntry { + effective, + activating, + deactivating, + }, + ); + let mut transaction_accounts = vec![ + (stake_address, stake_account), + (merge_from_address, merge_from_account), + (authorized_address, AccountSharedData::default()), + ( + sysvar::clock::id(), + account::create_account_shared_data_for_test(&clock), + ), + ( + sysvar::stake_history::id(), + account::create_account_shared_data_for_test(&stake_history), + ), + ]; + let instruction_accounts = vec![ + AccountMeta { + pubkey: stake_address, + is_signer: false, + is_writable: false, + }, + AccountMeta { + pubkey: merge_from_address, + is_signer: false, + is_writable: false, + }, + AccountMeta { + pubkey: sysvar::clock::id(), + is_signer: false, + is_writable: false, + }, + AccountMeta { + pubkey: sysvar::stake_history::id(), + is_signer: false, + is_writable: false, + }, + AccountMeta { + pubkey: authorized_address, + is_signer: true, + is_writable: false, + }, + ]; + + fn try_merge( + transaction_accounts: Vec<(Pubkey, AccountSharedData)>, + mut instruction_accounts: Vec, + expected_result: Result<(), InstructionError>, + ) { + for iteration in 0..2 { + if iteration == 1 { + instruction_accounts.swap(0, 1); + } + let accounts = process_instruction_with_sysvar_cache( + &serialize(&StakeInstruction::Merge).unwrap(), + transaction_accounts.clone(), + instruction_accounts.clone(), + None, + expected_result.clone(), + ); + if expected_result.is_ok() { + assert_eq!( + accounts[1 - iteration].state(), + Ok(StakeState::Uninitialized) + ); + } + } + } + + // stake activation epoch, source initialized succeeds + try_merge( + transaction_accounts.clone(), + instruction_accounts.clone(), + Ok(()), + ); + + // both activating fails + loop { + clock.epoch += 1; + if clock.epoch == merge_from_activation_epoch { + activating += merge_from_amount; + } + let delta = + activating.min((effective as f64 * stake.delegation.warmup_cooldown_rate) as u64); + effective += delta; + activating -= delta; + stake_history.add( + clock.epoch, + StakeHistoryEntry { + effective, + activating, + deactivating, + }, + ); + transaction_accounts[3] = ( + sysvar::clock::id(), + account::create_account_shared_data_for_test(&clock), + ); + transaction_accounts[4] = ( + sysvar::stake_history::id(), + account::create_account_shared_data_for_test(&stake_history), + ); + if stake_amount == stake.stake(clock.epoch, Some(&stake_history)) + && merge_from_amount == merge_from_stake.stake(clock.epoch, Some(&stake_history)) + { + break; + } + try_merge( + transaction_accounts.clone(), + instruction_accounts.clone(), + Err(InstructionError::from(StakeError::MergeTransientStake)), + ); + } + + // Both fully activated works + try_merge( + transaction_accounts.clone(), + instruction_accounts.clone(), + Ok(()), + ); + + // deactivate setup for deactivation + let merge_from_deactivation_epoch = clock.epoch + 1; + let stake_deactivation_epoch = clock.epoch + 2; + + // active/deactivating and deactivating/inactive mismatches fail + loop { + clock.epoch += 1; + let delta = + deactivating.min((effective as f64 * stake.delegation.warmup_cooldown_rate) as u64); + effective -= delta; + deactivating -= delta; + if clock.epoch == stake_deactivation_epoch { + deactivating += stake_amount; + stake = Stake { + delegation: Delegation { + deactivation_epoch: stake_deactivation_epoch, + ..stake.delegation + }, + ..stake + }; + transaction_accounts[0] + .1 + .set_state(&StakeState::Stake(meta, stake)) + .unwrap(); + } + if clock.epoch == merge_from_deactivation_epoch { + deactivating += merge_from_amount; + merge_from_stake = Stake { + delegation: Delegation { + deactivation_epoch: merge_from_deactivation_epoch, + ..merge_from_stake.delegation + }, + ..merge_from_stake + }; + transaction_accounts[1] + .1 + .set_state(&StakeState::Stake(meta, merge_from_stake)) + .unwrap(); + } + stake_history.add( + clock.epoch, + StakeHistoryEntry { + effective, + activating, + deactivating, + }, + ); + transaction_accounts[3] = ( + sysvar::clock::id(), + account::create_account_shared_data_for_test(&clock), + ); + transaction_accounts[4] = ( + sysvar::stake_history::id(), + account::create_account_shared_data_for_test(&stake_history), + ); + if 0 == stake.stake(clock.epoch, Some(&stake_history)) + && 0 == merge_from_stake.stake(clock.epoch, Some(&stake_history)) + { + break; + } + try_merge( + transaction_accounts.clone(), + instruction_accounts.clone(), + Err(InstructionError::from(StakeError::MergeTransientStake)), + ); + } + + // Both fully deactivated works + try_merge(transaction_accounts, instruction_accounts, Ok(())); + } } diff --git a/programs/stake/src/stake_state.rs b/programs/stake/src/stake_state.rs index f1cbb31429..b8f868a17a 100644 --- a/programs/stake/src/stake_state.rs +++ b/programs/stake/src/stake_state.rs @@ -1450,7 +1450,6 @@ mod tests { sysvar::SysvarId, transaction_context::TransactionContext, }, - std::iter::FromIterator, }; #[test] @@ -2529,596 +2528,6 @@ mod tests { ) } - fn just_stake(stake: u64) -> Stake { - Stake { - delegation: Delegation { - stake, - ..Delegation::default() - }, - ..Stake::default() - } - } - - #[test] - fn test_merge() { - let mut transaction_context = TransactionContext::new(Vec::new(), 1, 1); - let invoke_context = InvokeContext::new_mock(&mut transaction_context, &[]); - let stake_pubkey = solana_sdk::pubkey::new_rand(); - let source_stake_pubkey = solana_sdk::pubkey::new_rand(); - let authorized_pubkey = solana_sdk::pubkey::new_rand(); - let stake_lamports = 42; - - let signers = vec![authorized_pubkey].into_iter().collect(); - - for state in &[ - StakeState::Initialized(Meta::auto(&authorized_pubkey)), - StakeState::Stake(Meta::auto(&authorized_pubkey), just_stake(stake_lamports)), - ] { - for source_state in &[ - StakeState::Initialized(Meta::auto(&authorized_pubkey)), - StakeState::Stake(Meta::auto(&authorized_pubkey), just_stake(stake_lamports)), - ] { - let stake_account = AccountSharedData::new_ref_data_with_space( - stake_lamports, - state, - std::mem::size_of::(), - &id(), - ) - .expect("stake_account"); - let stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &stake_account); - - let source_stake_account = AccountSharedData::new_ref_data_with_space( - stake_lamports, - source_state, - std::mem::size_of::(), - &id(), - ) - .expect("source_stake_account"); - let source_stake_keyed_account = - KeyedAccount::new(&source_stake_pubkey, true, &source_stake_account); - - // Authorized staker signature required... - assert_eq!( - stake_keyed_account.merge( - &invoke_context, - &source_stake_keyed_account, - &Clock::default(), - &StakeHistory::default(), - &HashSet::new(), - ), - Err(InstructionError::MissingRequiredSignature) - ); - - assert_eq!( - stake_keyed_account.merge( - &invoke_context, - &source_stake_keyed_account, - &Clock::default(), - &StakeHistory::default(), - &signers, - ), - Ok(()) - ); - - // check lamports - assert_eq!( - stake_keyed_account.account.borrow().lamports(), - stake_lamports * 2 - ); - assert_eq!(source_stake_keyed_account.account.borrow().lamports(), 0); - - // check state - match state { - StakeState::Initialized(meta) => { - assert_eq!( - stake_keyed_account.state(), - Ok(StakeState::Initialized(*meta)), - ); - } - StakeState::Stake(meta, stake) => { - let expected_stake = stake.delegation.stake - + source_state - .stake() - .map(|stake| stake.delegation.stake) - .unwrap_or_else(|| { - stake_lamports - - source_state.meta().unwrap().rent_exempt_reserve - }); - assert_eq!( - stake_keyed_account.state(), - Ok(StakeState::Stake( - *meta, - Stake { - delegation: Delegation { - stake: expected_stake, - ..stake.delegation - }, - ..*stake - } - )), - ); - } - _ => unreachable!(), - } - assert_eq!( - source_stake_keyed_account.state(), - Ok(StakeState::Uninitialized) - ); - } - } - } - - #[test] - fn test_merge_self_fails() { - let mut transaction_context = TransactionContext::new(Vec::new(), 1, 1); - let invoke_context = InvokeContext::new_mock(&mut transaction_context, &[]); - let stake_address = Pubkey::new_unique(); - let authority_pubkey = Pubkey::new_unique(); - let signers = HashSet::from_iter(vec![authority_pubkey]); - let rent = Rent::default(); - let rent_exempt_reserve = rent.minimum_balance(std::mem::size_of::()); - let stake_amount = 4242424242; - let stake_lamports = rent_exempt_reserve + stake_amount; - - let meta = Meta { - rent_exempt_reserve, - ..Meta::auto(&authority_pubkey) - }; - let stake = Stake { - delegation: Delegation { - stake: stake_amount, - activation_epoch: 0, - ..Delegation::default() - }, - ..Stake::default() - }; - let stake_account = AccountSharedData::new_ref_data_with_space( - stake_lamports, - &StakeState::Stake(meta, stake), - std::mem::size_of::(), - &id(), - ) - .expect("stake_account"); - let stake_keyed_account = KeyedAccount::new(&stake_address, true, &stake_account); - - assert_eq!( - stake_keyed_account.merge( - &invoke_context, - &stake_keyed_account, - &Clock::default(), - &StakeHistory::default(), - &signers, - ), - Err(InstructionError::InvalidArgument), - ); - } - - #[test] - fn test_merge_incorrect_authorized_staker() { - let mut transaction_context = TransactionContext::new(Vec::new(), 1, 1); - let invoke_context = InvokeContext::new_mock(&mut transaction_context, &[]); - let stake_pubkey = solana_sdk::pubkey::new_rand(); - let source_stake_pubkey = solana_sdk::pubkey::new_rand(); - let authorized_pubkey = solana_sdk::pubkey::new_rand(); - let wrong_authorized_pubkey = solana_sdk::pubkey::new_rand(); - let stake_lamports = 42; - - let signers = vec![authorized_pubkey].into_iter().collect(); - let wrong_signers = vec![wrong_authorized_pubkey].into_iter().collect(); - - for state in &[ - StakeState::Initialized(Meta::auto(&authorized_pubkey)), - StakeState::Stake(Meta::auto(&authorized_pubkey), just_stake(stake_lamports)), - ] { - for source_state in &[ - StakeState::Initialized(Meta::auto(&wrong_authorized_pubkey)), - StakeState::Stake( - Meta::auto(&wrong_authorized_pubkey), - just_stake(stake_lamports), - ), - ] { - let stake_account = AccountSharedData::new_ref_data_with_space( - stake_lamports, - state, - std::mem::size_of::(), - &id(), - ) - .expect("stake_account"); - let stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &stake_account); - - let source_stake_account = AccountSharedData::new_ref_data_with_space( - stake_lamports, - source_state, - std::mem::size_of::(), - &id(), - ) - .expect("source_stake_account"); - let source_stake_keyed_account = - KeyedAccount::new(&source_stake_pubkey, true, &source_stake_account); - - assert_eq!( - stake_keyed_account.merge( - &invoke_context, - &source_stake_keyed_account, - &Clock::default(), - &StakeHistory::default(), - &wrong_signers, - ), - Err(InstructionError::MissingRequiredSignature) - ); - - assert_eq!( - stake_keyed_account.merge( - &invoke_context, - &source_stake_keyed_account, - &Clock::default(), - &StakeHistory::default(), - &signers, - ), - Err(StakeError::MergeMismatch.into()) - ); - } - } - } - - #[test] - fn test_merge_invalid_account_data() { - let mut transaction_context = TransactionContext::new(Vec::new(), 1, 1); - let invoke_context = InvokeContext::new_mock(&mut transaction_context, &[]); - let stake_pubkey = solana_sdk::pubkey::new_rand(); - let source_stake_pubkey = solana_sdk::pubkey::new_rand(); - let authorized_pubkey = solana_sdk::pubkey::new_rand(); - let stake_lamports = 42; - let signers = vec![authorized_pubkey].into_iter().collect(); - - for state in &[ - StakeState::Uninitialized, - StakeState::RewardsPool, - StakeState::Initialized(Meta::auto(&authorized_pubkey)), - StakeState::Stake(Meta::auto(&authorized_pubkey), just_stake(stake_lamports)), - ] { - for source_state in &[StakeState::Uninitialized, StakeState::RewardsPool] { - let stake_account = AccountSharedData::new_ref_data_with_space( - stake_lamports, - state, - std::mem::size_of::(), - &id(), - ) - .expect("stake_account"); - let stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &stake_account); - - let source_stake_account = AccountSharedData::new_ref_data_with_space( - stake_lamports, - source_state, - std::mem::size_of::(), - &id(), - ) - .expect("source_stake_account"); - let source_stake_keyed_account = - KeyedAccount::new(&source_stake_pubkey, true, &source_stake_account); - - assert_eq!( - stake_keyed_account.merge( - &invoke_context, - &source_stake_keyed_account, - &Clock::default(), - &StakeHistory::default(), - &signers, - ), - Err(InstructionError::InvalidAccountData) - ); - } - } - } - - #[test] - fn test_merge_fake_stake_source() { - let mut transaction_context = TransactionContext::new(Vec::new(), 1, 1); - let invoke_context = InvokeContext::new_mock(&mut transaction_context, &[]); - let stake_pubkey = solana_sdk::pubkey::new_rand(); - let source_stake_pubkey = solana_sdk::pubkey::new_rand(); - let authorized_pubkey = solana_sdk::pubkey::new_rand(); - let stake_lamports = 42; - - let signers = vec![authorized_pubkey].into_iter().collect(); - - let stake_account = AccountSharedData::new_ref_data_with_space( - stake_lamports, - &StakeState::Stake(Meta::auto(&authorized_pubkey), just_stake(stake_lamports)), - std::mem::size_of::(), - &id(), - ) - .expect("stake_account"); - let stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &stake_account); - - let source_stake_account = AccountSharedData::new_ref_data_with_space( - stake_lamports, - &StakeState::Stake(Meta::auto(&authorized_pubkey), just_stake(stake_lamports)), - std::mem::size_of::(), - &solana_sdk::pubkey::new_rand(), - ) - .expect("source_stake_account"); - let source_stake_keyed_account = - KeyedAccount::new(&source_stake_pubkey, true, &source_stake_account); - - assert_eq!( - stake_keyed_account.merge( - &invoke_context, - &source_stake_keyed_account, - &Clock::default(), - &StakeHistory::default(), - &signers, - ), - Err(InstructionError::IncorrectProgramId) - ); - } - - #[test] - fn test_merge_active_stake() { - let mut transaction_context = TransactionContext::new(Vec::new(), 1, 1); - let invoke_context = InvokeContext::new_mock(&mut transaction_context, &[]); - let base_lamports = 4242424242; - let stake_address = Pubkey::new_unique(); - let source_address = Pubkey::new_unique(); - let authority_pubkey = Pubkey::new_unique(); - let signers = HashSet::from_iter(vec![authority_pubkey]); - let rent = Rent::default(); - let rent_exempt_reserve = rent.minimum_balance(std::mem::size_of::()); - let stake_amount = base_lamports; - let stake_lamports = rent_exempt_reserve + stake_amount; - let source_amount = base_lamports; - let source_lamports = rent_exempt_reserve + source_amount; - - let meta = Meta { - rent_exempt_reserve, - ..Meta::auto(&authority_pubkey) - }; - let mut stake = Stake { - delegation: Delegation { - stake: stake_amount, - activation_epoch: 0, - ..Delegation::default() - }, - ..Stake::default() - }; - let stake_account = AccountSharedData::new_ref_data_with_space( - stake_lamports, - &StakeState::Stake(meta, stake), - std::mem::size_of::(), - &id(), - ) - .expect("stake_account"); - let stake_keyed_account = KeyedAccount::new(&stake_address, true, &stake_account); - - let source_activation_epoch = 2; - let mut source_stake = Stake { - delegation: Delegation { - stake: source_amount, - activation_epoch: source_activation_epoch, - ..stake.delegation - }, - ..stake - }; - let source_account = AccountSharedData::new_ref_data_with_space( - source_lamports, - &StakeState::Stake(meta, source_stake), - std::mem::size_of::(), - &id(), - ) - .expect("source_account"); - let source_keyed_account = KeyedAccount::new(&source_address, true, &source_account); - - let mut clock = Clock::default(); - let mut stake_history = StakeHistory::default(); - - clock.epoch = 0; - let mut effective = base_lamports; - let mut activating = stake_amount; - let mut deactivating = 0; - stake_history.add( - clock.epoch, - StakeHistoryEntry { - effective, - activating, - deactivating, - }, - ); - - fn try_merge( - invoke_context: &InvokeContext, - stake_account: &KeyedAccount, - source_account: &KeyedAccount, - clock: &Clock, - stake_history: &StakeHistory, - signers: &HashSet, - ) -> Result<(), InstructionError> { - let test_stake_account = stake_account.account.clone(); - let test_stake_keyed = - KeyedAccount::new(stake_account.unsigned_key(), true, &test_stake_account); - let test_source_account = source_account.account.clone(); - let test_source_keyed = - KeyedAccount::new(source_account.unsigned_key(), true, &test_source_account); - - let result = test_stake_keyed.merge( - invoke_context, - &test_source_keyed, - clock, - stake_history, - signers, - ); - if result.is_ok() { - assert_eq!(test_source_keyed.state(), Ok(StakeState::Uninitialized),); - } - result - } - - // stake activation epoch, source initialized succeeds - assert!(try_merge( - &invoke_context, - &stake_keyed_account, - &source_keyed_account, - &clock, - &stake_history, - &signers - ) - .is_ok(),); - assert!(try_merge( - &invoke_context, - &source_keyed_account, - &stake_keyed_account, - &clock, - &stake_history, - &signers - ) - .is_ok(),); - - // both activating fails - loop { - clock.epoch += 1; - if clock.epoch == source_activation_epoch { - activating += source_amount; - } - let delta = - activating.min((effective as f64 * stake.delegation.warmup_cooldown_rate) as u64); - effective += delta; - activating -= delta; - stake_history.add( - clock.epoch, - StakeHistoryEntry { - effective, - activating, - deactivating, - }, - ); - if stake_amount == stake.stake(clock.epoch, Some(&stake_history)) - && source_amount == source_stake.stake(clock.epoch, Some(&stake_history)) - { - break; - } - assert_eq!( - try_merge( - &invoke_context, - &stake_keyed_account, - &source_keyed_account, - &clock, - &stake_history, - &signers - ) - .unwrap_err(), - InstructionError::from(StakeError::MergeTransientStake), - ); - assert_eq!( - try_merge( - &invoke_context, - &source_keyed_account, - &stake_keyed_account, - &clock, - &stake_history, - &signers - ) - .unwrap_err(), - InstructionError::from(StakeError::MergeTransientStake), - ); - } - // Both fully activated works - assert!(try_merge( - &invoke_context, - &stake_keyed_account, - &source_keyed_account, - &clock, - &stake_history, - &signers - ) - .is_ok(),); - - // deactivate setup for deactivation - let source_deactivation_epoch = clock.epoch + 1; - let stake_deactivation_epoch = clock.epoch + 2; - - // active/deactivating and deactivating/inactive mismatches fail - loop { - clock.epoch += 1; - let delta = - deactivating.min((effective as f64 * stake.delegation.warmup_cooldown_rate) as u64); - effective -= delta; - deactivating -= delta; - if clock.epoch == stake_deactivation_epoch { - deactivating += stake_amount; - stake = Stake { - delegation: Delegation { - deactivation_epoch: stake_deactivation_epoch, - ..stake.delegation - }, - ..stake - }; - stake_keyed_account - .set_state(&StakeState::Stake(meta, stake)) - .unwrap(); - } - if clock.epoch == source_deactivation_epoch { - deactivating += source_amount; - source_stake = Stake { - delegation: Delegation { - deactivation_epoch: source_deactivation_epoch, - ..source_stake.delegation - }, - ..source_stake - }; - source_keyed_account - .set_state(&StakeState::Stake(meta, source_stake)) - .unwrap(); - } - stake_history.add( - clock.epoch, - StakeHistoryEntry { - effective, - activating, - deactivating, - }, - ); - if 0 == stake.stake(clock.epoch, Some(&stake_history)) - && 0 == source_stake.stake(clock.epoch, Some(&stake_history)) - { - break; - } - assert_eq!( - try_merge( - &invoke_context, - &stake_keyed_account, - &source_keyed_account, - &clock, - &stake_history, - &signers - ) - .unwrap_err(), - InstructionError::from(StakeError::MergeTransientStake), - ); - assert_eq!( - try_merge( - &invoke_context, - &source_keyed_account, - &stake_keyed_account, - &clock, - &stake_history, - &signers - ) - .unwrap_err(), - InstructionError::from(StakeError::MergeTransientStake), - ); - } - - // Both fully deactivated works - assert!(try_merge( - &invoke_context, - &stake_keyed_account, - &source_keyed_account, - &clock, - &stake_history, - &signers - ) - .is_ok(),); - } - #[test] fn test_lockup_is_expired() { let custodian = solana_sdk::pubkey::new_rand();