From 05c2981d4fed72a99fa25a5aca889077154253e6 Mon Sep 17 00:00:00 2001 From: Brooks Prumo Date: Tue, 19 Apr 2022 14:38:28 -0500 Subject: [PATCH] Refactor tests to run for both old and new behavior --- programs/stake/src/stake_instruction.rs | 1078 ++++++++++++++++++----- 1 file changed, 874 insertions(+), 204 deletions(-) diff --git a/programs/stake/src/stake_instruction.rs b/programs/stake/src/stake_instruction.rs index 8702c1a2b8c628..a4f308093ec366 100644 --- a/programs/stake/src/stake_instruction.rs +++ b/programs/stake/src/stake_instruction.rs @@ -511,25 +511,28 @@ mod tests { } fn process_instruction( + feature_set: &FeatureSet, instruction_data: &[u8], transaction_accounts: Vec<(Pubkey, AccountSharedData)>, instruction_accounts: Vec, expected_result: Result<(), InstructionError>, ) -> Vec { - process_instruction_with_sysvar_cache( + process_instruction_with_overrides( instruction_data, transaction_accounts, instruction_accounts, None, + Some(Arc::new(feature_set.clone())), expected_result, ) } - fn process_instruction_with_sysvar_cache( + fn process_instruction_with_overrides( instruction_data: &[u8], transaction_accounts: Vec<(Pubkey, AccountSharedData)>, instruction_accounts: Vec, sysvar_cache_override: Option<&SysvarCache>, + feature_set_override: Option>, expected_result: Result<(), InstructionError>, ) -> Vec { mock_process_instruction( @@ -539,13 +542,14 @@ mod tests { transaction_accounts, instruction_accounts, sysvar_cache_override, - None, + feature_set_override, expected_result, super::process_instruction, ) } fn process_instruction_as_one_arg( + feature_set: &FeatureSet, instruction: &Instruction, expected_result: Result<(), InstructionError>, ) -> Vec { @@ -587,6 +591,7 @@ mod tests { }) .collect(); process_instruction( + feature_set, &instruction.data, transaction_accounts, instruction.accounts.clone(), @@ -607,9 +612,9 @@ mod tests { ) } - #[test] - fn test_stake_process_instruction() { + fn do_test_stake_process_instruction(feature_set: FeatureSet) { process_instruction_as_one_arg( + &feature_set, &instruction::initialize( &Pubkey::new_unique(), &Authorized::default(), @@ -618,6 +623,7 @@ mod tests { Err(InstructionError::InvalidAccountData), ); process_instruction_as_one_arg( + &feature_set, &instruction::authorize( &Pubkey::new_unique(), &Pubkey::new_unique(), @@ -628,6 +634,7 @@ mod tests { Err(InstructionError::InvalidAccountData), ); process_instruction_as_one_arg( + &feature_set, &instruction::split( &Pubkey::new_unique(), &Pubkey::new_unique(), @@ -637,6 +644,7 @@ mod tests { Err(InstructionError::InvalidAccountData), ); process_instruction_as_one_arg( + &feature_set, &instruction::merge( &Pubkey::new_unique(), &invalid_stake_state_pubkey(), @@ -645,6 +653,7 @@ mod tests { Err(InstructionError::InvalidAccountData), ); process_instruction_as_one_arg( + &feature_set, &instruction::split_with_seed( &Pubkey::new_unique(), &Pubkey::new_unique(), @@ -656,6 +665,7 @@ mod tests { Err(InstructionError::InvalidAccountData), ); process_instruction_as_one_arg( + &feature_set, &instruction::delegate_stake( &Pubkey::new_unique(), &Pubkey::new_unique(), @@ -664,6 +674,7 @@ mod tests { Err(InstructionError::InvalidAccountData), ); process_instruction_as_one_arg( + &feature_set, &instruction::withdraw( &Pubkey::new_unique(), &Pubkey::new_unique(), @@ -674,10 +685,12 @@ mod tests { Err(InstructionError::InvalidAccountData), ); process_instruction_as_one_arg( + &feature_set, &instruction::deactivate_stake(&Pubkey::new_unique(), &Pubkey::new_unique()), Err(InstructionError::InvalidAccountData), ); process_instruction_as_one_arg( + &feature_set, &instruction::set_lockup( &Pubkey::new_unique(), &LockupArgs::default(), @@ -686,6 +699,7 @@ mod tests { Err(InstructionError::InvalidAccountData), ); process_instruction_as_one_arg( + &feature_set, &instruction::deactivate_delinquent_stake( &Pubkey::new_unique(), &Pubkey::new_unique(), @@ -694,6 +708,7 @@ mod tests { Err(InstructionError::IncorrectProgramId), ); process_instruction_as_one_arg( + &feature_set, &instruction::deactivate_delinquent_stake( &Pubkey::new_unique(), &invalid_vote_state_pubkey(), @@ -702,6 +717,7 @@ mod tests { Err(InstructionError::InvalidAccountData), ); process_instruction_as_one_arg( + &feature_set, &instruction::deactivate_delinquent_stake( &Pubkey::new_unique(), &invalid_vote_state_pubkey(), @@ -711,9 +727,9 @@ mod tests { ); } - #[test] - fn test_spoofed_stake_accounts() { + fn do_test_spoofed_stake_accounts(feature_set: FeatureSet) { process_instruction_as_one_arg( + &feature_set, &instruction::initialize( &spoofed_stake_state_pubkey(), &Authorized::default(), @@ -722,6 +738,7 @@ mod tests { Err(InstructionError::InvalidAccountOwner), ); process_instruction_as_one_arg( + &feature_set, &instruction::authorize( &spoofed_stake_state_pubkey(), &Pubkey::new_unique(), @@ -732,6 +749,7 @@ mod tests { Err(InstructionError::InvalidAccountOwner), ); process_instruction_as_one_arg( + &feature_set, &instruction::split( &spoofed_stake_state_pubkey(), &Pubkey::new_unique(), @@ -741,6 +759,7 @@ mod tests { Err(InstructionError::InvalidAccountOwner), ); process_instruction_as_one_arg( + &feature_set, &instruction::split( &Pubkey::new_unique(), &Pubkey::new_unique(), @@ -750,6 +769,7 @@ mod tests { Err(InstructionError::IncorrectProgramId), ); process_instruction_as_one_arg( + &feature_set, &instruction::merge( &spoofed_stake_state_pubkey(), &Pubkey::new_unique(), @@ -758,6 +778,7 @@ mod tests { Err(InstructionError::InvalidAccountOwner), ); process_instruction_as_one_arg( + &feature_set, &instruction::merge( &Pubkey::new_unique(), &spoofed_stake_state_pubkey(), @@ -766,6 +787,7 @@ mod tests { Err(InstructionError::IncorrectProgramId), ); process_instruction_as_one_arg( + &feature_set, &instruction::split_with_seed( &spoofed_stake_state_pubkey(), &Pubkey::new_unique(), @@ -777,6 +799,7 @@ mod tests { Err(InstructionError::InvalidAccountOwner), ); process_instruction_as_one_arg( + &feature_set, &instruction::delegate_stake( &spoofed_stake_state_pubkey(), &Pubkey::new_unique(), @@ -785,6 +808,7 @@ mod tests { Err(InstructionError::InvalidAccountOwner), ); process_instruction_as_one_arg( + &feature_set, &instruction::withdraw( &spoofed_stake_state_pubkey(), &Pubkey::new_unique(), @@ -795,10 +819,12 @@ mod tests { Err(InstructionError::InvalidAccountOwner), ); process_instruction_as_one_arg( + &feature_set, &instruction::deactivate_stake(&spoofed_stake_state_pubkey(), &Pubkey::new_unique()), Err(InstructionError::InvalidAccountOwner), ); process_instruction_as_one_arg( + &feature_set, &instruction::set_lockup( &spoofed_stake_state_pubkey(), &LockupArgs::default(), @@ -807,6 +833,7 @@ mod tests { Err(InstructionError::InvalidAccountOwner), ); process_instruction_as_one_arg( + &feature_set, &instruction::deactivate_delinquent_stake( &spoofed_stake_state_pubkey(), &Pubkey::new_unique(), @@ -816,8 +843,7 @@ mod tests { ); } - #[test] - fn test_stake_process_instruction_decode_bail() { + fn do_test_stake_process_instruction_decode_bail(feature_set: FeatureSet) { // these will not call stake_state, have bogus contents let stake_address = Pubkey::new_unique(); let stake_account = create_default_stake_account(); @@ -838,11 +864,12 @@ mod tests { let config_address = stake_config::id(); let config_account = config::create_account(0, &stake_config::Config::default()); let rent_exempt_reserve = rent.minimum_balance(StakeState::size_of()); - let minimum_delegation = crate::get_minimum_delegation(&FeatureSet::all_enabled()); + let minimum_delegation = crate::get_minimum_delegation(&feature_set); let withdrawal_amount = rent_exempt_reserve + minimum_delegation; // gets the "is_empty()" check process_instruction( + &feature_set, &serialize(&StakeInstruction::Initialize( Authorized::default(), Lockup::default(), @@ -855,6 +882,7 @@ mod tests { // no account for rent process_instruction( + &feature_set, &serialize(&StakeInstruction::Initialize( Authorized::default(), Lockup::default(), @@ -871,6 +899,7 @@ mod tests { // fails to deserialize stake state process_instruction( + &feature_set, &serialize(&StakeInstruction::Initialize( Authorized::default(), Lockup::default(), @@ -897,6 +926,7 @@ mod tests { // gets the first check in delegate, wrong number of accounts process_instruction( + &feature_set, &serialize(&StakeInstruction::DelegateStake).unwrap(), vec![(stake_address, stake_account.clone())], vec![AccountMeta { @@ -909,6 +939,7 @@ mod tests { // gets the sub-check for number of args process_instruction( + &feature_set, &serialize(&StakeInstruction::DelegateStake).unwrap(), vec![(stake_address, stake_account.clone())], vec![AccountMeta { @@ -921,6 +952,7 @@ mod tests { // gets the check non-deserialize-able account in delegate_stake process_instruction( + &feature_set, &serialize(&StakeInstruction::DelegateStake).unwrap(), vec![ (stake_address, stake_account.clone()), @@ -961,6 +993,7 @@ mod tests { // Tests 3rd keyed account is of correct type (Clock instead of rewards) in withdraw process_instruction( + &feature_set, &serialize(&StakeInstruction::Withdraw(withdrawal_amount)).unwrap(), vec![ (stake_address, stake_account.clone()), @@ -995,6 +1028,7 @@ mod tests { // Tests correct number of accounts are provided in withdraw process_instruction( + &feature_set, &serialize(&StakeInstruction::Withdraw(withdrawal_amount)).unwrap(), vec![(stake_address, stake_account.clone())], vec![AccountMeta { @@ -1007,6 +1041,7 @@ mod tests { // Tests 2nd keyed account is of correct type (Clock instead of rewards) in deactivate process_instruction( + &feature_set, &serialize(&StakeInstruction::Deactivate).unwrap(), vec![ (stake_address, stake_account.clone()), @@ -1029,6 +1064,7 @@ mod tests { // Tests correct number of accounts are provided in deactivate process_instruction( + &feature_set, &serialize(&StakeInstruction::Deactivate).unwrap(), Vec::new(), Vec::new(), @@ -1037,12 +1073,14 @@ mod tests { // Tests correct number of accounts are provided in deactivate_delinquent process_instruction( + &feature_set, &serialize(&StakeInstruction::DeactivateDelinquent).unwrap(), Vec::new(), Vec::new(), Err(InstructionError::NotEnoughAccountKeys), ); process_instruction( + &feature_set, &serialize(&StakeInstruction::DeactivateDelinquent).unwrap(), vec![(stake_address, stake_account.clone())], vec![AccountMeta { @@ -1053,6 +1091,7 @@ mod tests { Err(InstructionError::NotEnoughAccountKeys), ); process_instruction( + &feature_set, &serialize(&StakeInstruction::DeactivateDelinquent).unwrap(), vec![(stake_address, stake_account), (vote_address, vote_account)], vec![ @@ -1071,8 +1110,7 @@ mod tests { ); } - #[test] - fn test_stake_checked_instructions() { + fn do_test_stake_checked_instructions(feature_set: FeatureSet) { let stake_address = Pubkey::new_unique(); let staker = Pubkey::new_unique(); let staker_account = create_default_account(); @@ -1089,13 +1127,14 @@ mod tests { let rent_address = sysvar::rent::id(); let rent_account = account::create_account_shared_data_for_test(&rent); let rent_exempt_reserve = rent.minimum_balance(StakeState::size_of()); - let minimum_delegation = crate::get_minimum_delegation(&FeatureSet::all_enabled()); + let minimum_delegation = crate::get_minimum_delegation(&feature_set); // Test InitializeChecked with non-signing withdrawer let mut instruction = initialize_checked(&stake_address, &Authorized { staker, withdrawer }); instruction.accounts[3] = AccountMeta::new_readonly(withdrawer, false); process_instruction_as_one_arg( + &feature_set, &instruction, Err(InstructionError::MissingRequiredSignature), ); @@ -1103,10 +1142,11 @@ mod tests { // Test InitializeChecked with withdrawer signer let stake_account = AccountSharedData::new( rent_exempt_reserve + minimum_delegation, - std::mem::size_of::(), + StakeState::size_of(), &id(), ); process_instruction( + &feature_set, &serialize(&StakeInstruction::InitializeChecked).unwrap(), vec![ (stake_address, stake_account), @@ -1149,6 +1189,7 @@ mod tests { ); instruction.accounts[3] = AccountMeta::new_readonly(staker, false); process_instruction_as_one_arg( + &feature_set, &instruction, Err(InstructionError::MissingRequiredSignature), ); @@ -1162,6 +1203,7 @@ mod tests { ); instruction.accounts[3] = AccountMeta::new_readonly(withdrawer, false); process_instruction_as_one_arg( + &feature_set, &instruction, Err(InstructionError::MissingRequiredSignature), ); @@ -1175,6 +1217,7 @@ mod tests { ) .unwrap(); process_instruction( + &feature_set, &serialize(&StakeInstruction::AuthorizeChecked(StakeAuthorize::Staker)).unwrap(), vec![ (stake_address, stake_account.clone()), @@ -1208,6 +1251,7 @@ mod tests { ); process_instruction( + &feature_set, &serialize(&StakeInstruction::AuthorizeChecked( StakeAuthorize::Withdrawer, )) @@ -1259,6 +1303,7 @@ mod tests { ); instruction.accounts[3] = AccountMeta::new_readonly(staker, false); process_instruction_as_one_arg( + &feature_set, &instruction, Err(InstructionError::MissingRequiredSignature), ); @@ -1274,6 +1319,7 @@ mod tests { ); instruction.accounts[3] = AccountMeta::new_readonly(staker, false); process_instruction_as_one_arg( + &feature_set, &instruction, Err(InstructionError::MissingRequiredSignature), ); @@ -1287,6 +1333,7 @@ mod tests { ) .unwrap(); process_instruction( + &feature_set, &serialize(&StakeInstruction::AuthorizeCheckedWithSeed( AuthorizeCheckedWithSeedArgs { stake_authorize: StakeAuthorize::Staker, @@ -1327,6 +1374,7 @@ mod tests { ); process_instruction( + &feature_set, &serialize(&StakeInstruction::AuthorizeCheckedWithSeed( AuthorizeCheckedWithSeedArgs { stake_authorize: StakeAuthorize::Withdrawer, @@ -1378,6 +1426,7 @@ mod tests { ); instruction.accounts[2] = AccountMeta::new_readonly(custodian, false); process_instruction_as_one_arg( + &feature_set, &instruction, Err(InstructionError::MissingRequiredSignature), ); @@ -1392,6 +1441,7 @@ mod tests { .unwrap(); process_instruction( + &feature_set, &instruction.data, vec![ (clock_address, clock_account), @@ -1420,11 +1470,10 @@ mod tests { ); } - #[test] - fn test_stake_initialize() { + fn do_test_stake_initialize(feature_set: FeatureSet) { let rent = Rent::default(); let rent_exempt_reserve = rent.minimum_balance(StakeState::size_of()); - let minimum_delegation = crate::get_minimum_delegation(&FeatureSet::all_enabled()); + let minimum_delegation = crate::get_minimum_delegation(&feature_set); let stake_lamports = rent_exempt_reserve + minimum_delegation; let stake_address = solana_sdk::pubkey::new_rand(); let stake_account = AccountSharedData::new(stake_lamports, StakeState::size_of(), &id()); @@ -1461,6 +1510,7 @@ mod tests { // should pass let accounts = process_instruction( + &feature_set, &instruction_data, transaction_accounts.clone(), instruction_accounts.clone(), @@ -1479,6 +1529,7 @@ mod tests { // 2nd time fails, can't move it from anything other than uninit->init transaction_accounts[0] = (stake_address, accounts[0].clone()); process_instruction( + &feature_set, &instruction_data, transaction_accounts.clone(), instruction_accounts.clone(), @@ -1495,6 +1546,7 @@ mod tests { }), ); process_instruction( + &feature_set, &instruction_data, transaction_accounts.clone(), instruction_accounts.clone(), @@ -1506,6 +1558,7 @@ mod tests { AccountSharedData::new(stake_lamports, StakeState::size_of() + 1, &id()); transaction_accounts[0] = (stake_address, stake_account); process_instruction( + &feature_set, &instruction_data, transaction_accounts.clone(), instruction_accounts.clone(), @@ -1516,6 +1569,7 @@ mod tests { AccountSharedData::new(stake_lamports, StakeState::size_of() - 1, &id()); transaction_accounts[0] = (stake_address, stake_account); process_instruction( + &feature_set, &instruction_data, transaction_accounts, instruction_accounts, @@ -1523,8 +1577,7 @@ mod tests { ); } - #[test] - fn test_authorize() { + fn do_test_authorize(feature_set: FeatureSet) { let authority_address = solana_sdk::pubkey::new_rand(); let authority_address_2 = solana_sdk::pubkey::new_rand(); let stake_address = solana_sdk::pubkey::new_rand(); @@ -1571,6 +1624,7 @@ mod tests { // should fail, uninit process_instruction( + &feature_set, &serialize(&StakeInstruction::Authorize( authority_address, StakeAuthorize::Staker, @@ -1591,6 +1645,7 @@ mod tests { .unwrap(); transaction_accounts[0] = (stake_address, stake_account); let accounts = process_instruction( + &feature_set, &serialize(&StakeInstruction::Authorize( authority_address, StakeAuthorize::Staker, @@ -1602,6 +1657,7 @@ mod tests { ); transaction_accounts[0] = (stake_address, accounts[0].clone()); let accounts = process_instruction( + &feature_set, &serialize(&StakeInstruction::Authorize( authority_address, StakeAuthorize::Withdrawer, @@ -1621,6 +1677,7 @@ mod tests { // A second authorization signed by the stake account should fail process_instruction( + &feature_set, &serialize(&StakeInstruction::Authorize( authority_address_2, StakeAuthorize::Staker, @@ -1635,6 +1692,7 @@ mod tests { instruction_accounts[0].is_signer = false; instruction_accounts[2].is_signer = true; let accounts = process_instruction( + &feature_set, &serialize(&StakeInstruction::Authorize( authority_address_2, StakeAuthorize::Staker, @@ -1679,6 +1737,7 @@ mod tests { }, ]; let accounts = process_instruction( + &feature_set, &serialize(&StakeInstruction::Withdraw(stake_lamports)).unwrap(), transaction_accounts.clone(), instruction_accounts.clone(), @@ -1689,6 +1748,7 @@ mod tests { // Test that withdrawal to account fails without authorized withdrawer instruction_accounts[4].is_signer = false; process_instruction( + &feature_set, &serialize(&StakeInstruction::Withdraw(stake_lamports)).unwrap(), transaction_accounts, instruction_accounts, @@ -1696,8 +1756,7 @@ mod tests { ); } - #[test] - fn test_authorize_override() { + fn do_test_authorize_override(feature_set: FeatureSet) { let authority_address = solana_sdk::pubkey::new_rand(); let mallory_address = solana_sdk::pubkey::new_rand(); let stake_address = solana_sdk::pubkey::new_rand(); @@ -1737,6 +1796,7 @@ mod tests { // Authorize a staker pubkey and move the withdrawer key into cold storage. let accounts = process_instruction( + &feature_set, &serialize(&StakeInstruction::Authorize( authority_address, StakeAuthorize::Staker, @@ -1752,6 +1812,7 @@ mod tests { instruction_accounts[0].is_signer = false; instruction_accounts[2].is_signer = true; let accounts = process_instruction( + &feature_set, &serialize(&StakeInstruction::Authorize( mallory_address, StakeAuthorize::Staker, @@ -1765,6 +1826,7 @@ mod tests { // Verify the original staker no longer has access. process_instruction( + &feature_set, &serialize(&StakeInstruction::Authorize( authority_address, StakeAuthorize::Staker, @@ -1779,6 +1841,7 @@ mod tests { instruction_accounts[0].is_signer = true; instruction_accounts[2].is_signer = false; let accounts = process_instruction( + &feature_set, &serialize(&StakeInstruction::Authorize( authority_address, StakeAuthorize::Withdrawer, @@ -1798,6 +1861,7 @@ mod tests { is_writable: false, }; process_instruction( + &feature_set, &serialize(&StakeInstruction::Authorize( authority_address, StakeAuthorize::Withdrawer, @@ -1809,8 +1873,7 @@ mod tests { ); } - #[test] - fn test_authorize_with_seed() { + fn do_test_authorize_with_seed(feature_set: FeatureSet) { let authority_base_address = solana_sdk::pubkey::new_rand(); let authority_address = solana_sdk::pubkey::new_rand(); let seed = "42"; @@ -1851,6 +1914,7 @@ mod tests { // Wrong seed process_instruction( + &feature_set, &serialize(&StakeInstruction::AuthorizeWithSeed( AuthorizeWithSeedArgs { new_authorized_pubkey: authority_address, @@ -1877,6 +1941,7 @@ mod tests { )) .unwrap(); process_instruction( + &feature_set, &instruction_data, transaction_accounts.clone(), instruction_accounts.clone(), @@ -1886,6 +1951,7 @@ mod tests { // Set stake authority let accounts = process_instruction( + &feature_set, &instruction_data, transaction_accounts.clone(), instruction_accounts.clone(), @@ -1904,6 +1970,7 @@ mod tests { )) .unwrap(); let accounts = process_instruction( + &feature_set, &instruction_data, transaction_accounts.clone(), instruction_accounts.clone(), @@ -1913,6 +1980,7 @@ mod tests { // No longer withdraw authority process_instruction( + &feature_set, &instruction_data, transaction_accounts, instruction_accounts, @@ -1920,11 +1988,10 @@ mod tests { ); } - #[test] - fn test_authorize_delegated_stake() { + fn do_test_authorize_delegated_stake(feature_set: FeatureSet) { let authority_address = solana_sdk::pubkey::new_rand(); let stake_address = solana_sdk::pubkey::new_rand(); - let minimum_delegation = crate::get_minimum_delegation(&FeatureSet::all_enabled()); + let minimum_delegation = crate::get_minimum_delegation(&feature_set); let stake_lamports = minimum_delegation; let stake_account = AccountSharedData::new_data_with_space( stake_lamports, @@ -1991,6 +2058,7 @@ mod tests { // delegate stake let accounts = process_instruction( + &feature_set, &serialize(&StakeInstruction::DelegateStake).unwrap(), transaction_accounts.clone(), instruction_accounts.clone(), @@ -2000,6 +2068,7 @@ mod tests { // deactivate, so we can re-delegate let accounts = process_instruction( + &feature_set, &serialize(&StakeInstruction::Deactivate).unwrap(), transaction_accounts.clone(), vec![ @@ -2020,6 +2089,7 @@ mod tests { // authorize let accounts = process_instruction( + &feature_set, &serialize(&StakeInstruction::Authorize( authority_address, StakeAuthorize::Staker, @@ -2055,6 +2125,7 @@ mod tests { instruction_accounts[0].is_signer = false; instruction_accounts[1].pubkey = vote_address_2; process_instruction( + &feature_set, &serialize(&StakeInstruction::DelegateStake).unwrap(), transaction_accounts.clone(), instruction_accounts.clone(), @@ -2068,6 +2139,7 @@ mod tests { is_writable: false, }); let accounts = process_instruction( + &feature_set, &serialize(&StakeInstruction::DelegateStake).unwrap(), transaction_accounts.clone(), instruction_accounts, @@ -2081,6 +2153,7 @@ mod tests { // Test another staking action process_instruction( + &feature_set, &serialize(&StakeInstruction::Deactivate).unwrap(), transaction_accounts, vec![ @@ -2104,8 +2177,7 @@ mod tests { ); } - #[test] - fn test_stake_delegate() { + fn do_test_stake_delegate(feature_set: FeatureSet) { let mut vote_state = VoteState::default(); for i in 0..1000 { vote_state.process_slot_vote_unchecked(i); @@ -2123,7 +2195,7 @@ mod tests { vote_account_2 .set_state(&VoteStateVersions::new_current(vote_state)) .unwrap(); - let minimum_delegation = crate::get_minimum_delegation(&FeatureSet::all_enabled()); + let minimum_delegation = crate::get_minimum_delegation(&feature_set); let stake_lamports = minimum_delegation; let stake_address = solana_sdk::pubkey::new_rand(); let mut stake_account = AccountSharedData::new_data_with_space( @@ -2191,6 +2263,7 @@ mod tests { // should fail, unsigned stake account instruction_accounts[0].is_signer = false; process_instruction( + &feature_set, &serialize(&StakeInstruction::DelegateStake).unwrap(), transaction_accounts.clone(), instruction_accounts.clone(), @@ -2200,6 +2273,7 @@ mod tests { // should pass let accounts = process_instruction( + &feature_set, &serialize(&StakeInstruction::DelegateStake).unwrap(), transaction_accounts.clone(), instruction_accounts.clone(), @@ -2228,6 +2302,7 @@ mod tests { account::create_account_shared_data_for_test(&clock), ); process_instruction( + &feature_set, &serialize(&StakeInstruction::DelegateStake).unwrap(), transaction_accounts.clone(), instruction_accounts.clone(), @@ -2236,6 +2311,7 @@ mod tests { // deactivate let accounts = process_instruction( + &feature_set, &serialize(&StakeInstruction::Deactivate).unwrap(), transaction_accounts.clone(), vec![ @@ -2258,6 +2334,7 @@ mod tests { transaction_accounts[0] = (stake_address, accounts[0].clone()); instruction_accounts[1].pubkey = vote_address_2; process_instruction( + &feature_set, &serialize(&StakeInstruction::DelegateStake).unwrap(), transaction_accounts.clone(), instruction_accounts.clone(), @@ -2268,6 +2345,7 @@ mod tests { // verify that delegate succeeds to same vote account // when stake is deactivating let accounts_2 = process_instruction( + &feature_set, &serialize(&StakeInstruction::DelegateStake).unwrap(), transaction_accounts.clone(), instruction_accounts.clone(), @@ -2282,6 +2360,7 @@ mod tests { transaction_accounts[0] = (stake_address, accounts_2[0].clone()); instruction_accounts[1].pubkey = vote_address_2; process_instruction( + &feature_set, &serialize(&StakeInstruction::DelegateStake).unwrap(), transaction_accounts.clone(), instruction_accounts.clone(), @@ -2298,6 +2377,7 @@ mod tests { // verify that delegate can be called to new vote account, 2nd is redelegate transaction_accounts[0] = (stake_address, accounts[0].clone()); let accounts = process_instruction( + &feature_set, &serialize(&StakeInstruction::DelegateStake).unwrap(), transaction_accounts.clone(), instruction_accounts.clone(), @@ -2325,6 +2405,7 @@ mod tests { .1 .set_owner(solana_sdk::pubkey::new_rand()); process_instruction( + &feature_set, &serialize(&StakeInstruction::DelegateStake).unwrap(), transaction_accounts.clone(), instruction_accounts.clone(), @@ -2336,6 +2417,7 @@ mod tests { stake_account.set_state(&stake_state).unwrap(); transaction_accounts[0] = (stake_address, stake_account); process_instruction( + &feature_set, &serialize(&StakeInstruction::DelegateStake).unwrap(), transaction_accounts, instruction_accounts, @@ -2343,8 +2425,7 @@ mod tests { ); } - #[test] - fn test_redelegate_consider_balance_changes() { + fn do_test_redelegate_consider_balance_changes(feature_set: FeatureSet) { let mut clock = Clock::default(); let rent = Rent::default(); let rent_exempt_reserve = rent.minimum_balance(StakeState::size_of()); @@ -2438,6 +2519,7 @@ mod tests { ]; let accounts = process_instruction( + &feature_set, &serialize(&StakeInstruction::DelegateStake).unwrap(), transaction_accounts.clone(), delegate_instruction_accounts.clone(), @@ -2451,6 +2533,7 @@ mod tests { account::create_account_shared_data_for_test(&clock), ); let accounts = process_instruction( + &feature_set, &serialize(&StakeInstruction::Deactivate).unwrap(), transaction_accounts.clone(), deactivate_instruction_accounts.clone(), @@ -2466,6 +2549,7 @@ mod tests { ); let withdraw_lamports = initial_lamports / 2; let accounts = process_instruction( + &feature_set, &serialize(&StakeInstruction::Withdraw(withdraw_lamports)).unwrap(), transaction_accounts.clone(), vec![ @@ -2507,6 +2591,7 @@ mod tests { account::create_account_shared_data_for_test(&clock), ); let accounts = process_instruction( + &feature_set, &serialize(&StakeInstruction::DelegateStake).unwrap(), transaction_accounts.clone(), delegate_instruction_accounts.clone(), @@ -2524,6 +2609,7 @@ mod tests { account::create_account_shared_data_for_test(&clock), ); let accounts = process_instruction( + &feature_set, &serialize(&StakeInstruction::Deactivate).unwrap(), transaction_accounts.clone(), deactivate_instruction_accounts, @@ -2543,6 +2629,7 @@ mod tests { account::create_account_shared_data_for_test(&clock), ); let accounts = process_instruction( + &feature_set, &serialize(&StakeInstruction::DelegateStake).unwrap(), transaction_accounts, delegate_instruction_accounts, @@ -2554,10 +2641,9 @@ mod tests { ); } - #[test] - fn test_split() { + fn do_test_split(feature_set: FeatureSet) { let stake_address = solana_sdk::pubkey::new_rand(); - let minimum_delegation = crate::get_minimum_delegation(&FeatureSet::all_enabled()); + let minimum_delegation = crate::get_minimum_delegation(&feature_set); let stake_lamports = minimum_delegation * 2; let split_to_address = solana_sdk::pubkey::new_rand(); let split_to_account = AccountSharedData::new_data_with_space( @@ -2591,6 +2677,7 @@ mod tests { lamports_per_byte_year: 0, ..Rent::default() }); + let feature_set = Arc::new(feature_set); for state in [ StakeState::Initialized(Meta::auto(&stake_address)), @@ -2606,20 +2693,22 @@ mod tests { transaction_accounts[0] = (stake_address, stake_account); // should fail, split more than available - process_instruction_with_sysvar_cache( + process_instruction_with_overrides( &serialize(&StakeInstruction::Split(stake_lamports + 1)).unwrap(), transaction_accounts.clone(), instruction_accounts.clone(), Some(&sysvar_cache_override), + Some(Arc::clone(&feature_set)), Err(InstructionError::InsufficientFunds), ); // should pass - let accounts = process_instruction_with_sysvar_cache( + let accounts = process_instruction_with_overrides( &serialize(&StakeInstruction::Split(stake_lamports / 2)).unwrap(), transaction_accounts.clone(), instruction_accounts.clone(), Some(&sysvar_cache_override), + Some(Arc::clone(&feature_set)), Ok(()), ); // no lamport leakage @@ -2650,21 +2739,22 @@ mod tests { ) .unwrap(); transaction_accounts[1] = (split_to_address, split_to_account); - process_instruction( + process_instruction_with_overrides( &serialize(&StakeInstruction::Split(stake_lamports / 2)).unwrap(), transaction_accounts, instruction_accounts, + Some(&sysvar_cache_override), + Some(Arc::clone(&feature_set)), Err(InstructionError::IncorrectProgramId), ); } - #[test] - fn test_withdraw_stake() { + fn do_test_withdraw_stake(feature_set: FeatureSet) { let recipient_address = solana_sdk::pubkey::new_rand(); let authority_address = solana_sdk::pubkey::new_rand(); let custodian_address = solana_sdk::pubkey::new_rand(); let stake_address = solana_sdk::pubkey::new_rand(); - let minimum_delegation = crate::get_minimum_delegation(&FeatureSet::all_enabled()); + let minimum_delegation = crate::get_minimum_delegation(&feature_set); let stake_lamports = minimum_delegation; let stake_account = AccountSharedData::new_data_with_space( stake_lamports, @@ -2736,6 +2826,7 @@ mod tests { // should fail, no signer instruction_accounts[4].is_signer = false; process_instruction( + &feature_set, &serialize(&StakeInstruction::Withdraw(stake_lamports)).unwrap(), transaction_accounts.clone(), instruction_accounts.clone(), @@ -2745,6 +2836,7 @@ mod tests { // should pass, signed keyed account and uninitialized let accounts = process_instruction( + &feature_set, &serialize(&StakeInstruction::Withdraw(stake_lamports)).unwrap(), transaction_accounts.clone(), instruction_accounts.clone(), @@ -2760,6 +2852,7 @@ mod tests { custodian: custodian_address, }; let accounts = process_instruction( + &feature_set, &serialize(&StakeInstruction::Initialize( Authorized::auto(&stake_address), lockup, @@ -2784,6 +2877,7 @@ mod tests { // should fail, signed keyed account and locked up, more than available process_instruction( + &feature_set, &serialize(&StakeInstruction::Withdraw(stake_lamports + 1)).unwrap(), transaction_accounts.clone(), instruction_accounts.clone(), @@ -2792,6 +2886,7 @@ mod tests { // Stake some lamports (available lamports for withdrawals will reduce to zero) let accounts = process_instruction( + &feature_set, &serialize(&StakeInstruction::DelegateStake).unwrap(), transaction_accounts.clone(), vec![ @@ -2830,6 +2925,7 @@ mod tests { // withdrawal before deactivate works for rewards amount process_instruction( + &feature_set, &serialize(&StakeInstruction::Withdraw(10)).unwrap(), transaction_accounts.clone(), instruction_accounts.clone(), @@ -2838,6 +2934,7 @@ mod tests { // withdrawal of rewards fails if not in excess of stake process_instruction( + &feature_set, &serialize(&StakeInstruction::Withdraw(11)).unwrap(), transaction_accounts.clone(), instruction_accounts.clone(), @@ -2846,6 +2943,7 @@ mod tests { // deactivate the stake before withdrawal let accounts = process_instruction( + &feature_set, &serialize(&StakeInstruction::Deactivate).unwrap(), transaction_accounts.clone(), vec![ @@ -2876,6 +2974,7 @@ mod tests { // Try to withdraw more than what's available process_instruction( + &feature_set, &serialize(&StakeInstruction::Withdraw(stake_lamports + 11)).unwrap(), transaction_accounts.clone(), instruction_accounts.clone(), @@ -2884,6 +2983,7 @@ mod tests { // Try to withdraw all lamports let accounts = process_instruction( + &feature_set, &serialize(&StakeInstruction::Withdraw(stake_lamports + 10)).unwrap(), transaction_accounts.clone(), instruction_accounts.clone(), @@ -2913,6 +3013,7 @@ mod tests { transaction_accounts[2] = (recipient_address, stake_account); instruction_accounts[4].pubkey = authority_address; process_instruction( + &feature_set, &serialize(&StakeInstruction::Withdraw(u64::MAX - 10)).unwrap(), transaction_accounts.clone(), instruction_accounts.clone(), @@ -2929,6 +3030,7 @@ mod tests { .unwrap(); transaction_accounts[0] = (stake_address, stake_account); process_instruction( + &feature_set, &serialize(&StakeInstruction::Withdraw(stake_lamports)).unwrap(), transaction_accounts, instruction_accounts, @@ -2936,11 +3038,10 @@ mod tests { ); } - #[test] - fn test_withdraw_stake_before_warmup() { + fn do_test_withdraw_stake_before_warmup(feature_set: FeatureSet) { let recipient_address = solana_sdk::pubkey::new_rand(); let stake_address = solana_sdk::pubkey::new_rand(); - let minimum_delegation = crate::get_minimum_delegation(&FeatureSet::all_enabled()); + let minimum_delegation = crate::get_minimum_delegation(&feature_set); let stake_lamports = minimum_delegation; let total_lamports = stake_lamports + 33; let stake_account = AccountSharedData::new_data_with_space( @@ -3007,6 +3108,7 @@ mod tests { // Stake some lamports (available lamports for withdrawals will reduce to zero) let accounts = process_instruction( + &feature_set, &serialize(&StakeInstruction::DelegateStake).unwrap(), transaction_accounts.clone(), vec![ @@ -3056,6 +3158,7 @@ mod tests { account::create_account_shared_data_for_test(&clock), ); process_instruction( + &feature_set, &serialize(&StakeInstruction::Withdraw( total_lamports - stake_lamports + 1, )) @@ -3066,8 +3169,7 @@ mod tests { ); } - #[test] - fn test_withdraw_lockup() { + fn do_test_withdraw_lockup(feature_set: FeatureSet) { let recipient_address = solana_sdk::pubkey::new_rand(); let custodian_address = solana_sdk::pubkey::new_rand(); let stake_address = solana_sdk::pubkey::new_rand(); @@ -3131,6 +3233,7 @@ mod tests { // should fail, lockup is still in force process_instruction( + &feature_set, &serialize(&StakeInstruction::Withdraw(total_lamports)).unwrap(), transaction_accounts.clone(), instruction_accounts.clone(), @@ -3144,6 +3247,7 @@ mod tests { is_writable: false, }); let accounts = process_instruction( + &feature_set, &serialize(&StakeInstruction::Withdraw(total_lamports)).unwrap(), transaction_accounts.clone(), instruction_accounts.clone(), @@ -3163,6 +3267,7 @@ mod tests { .unwrap(); transaction_accounts[0] = (stake_address, stake_account_self_as_custodian); let accounts = process_instruction( + &feature_set, &serialize(&StakeInstruction::Withdraw(total_lamports)).unwrap(), transaction_accounts.clone(), instruction_accounts.clone(), @@ -3179,6 +3284,7 @@ mod tests { account::create_account_shared_data_for_test(&clock), ); let accounts = process_instruction( + &feature_set, &serialize(&StakeInstruction::Withdraw(total_lamports)).unwrap(), transaction_accounts, instruction_accounts, @@ -3187,14 +3293,13 @@ mod tests { assert_eq!(from(&accounts[0]).unwrap(), StakeState::Uninitialized); } - #[test] - fn test_withdraw_rent_exempt() { + fn do_test_withdraw_rent_exempt(feature_set: FeatureSet) { let recipient_address = solana_sdk::pubkey::new_rand(); let custodian_address = solana_sdk::pubkey::new_rand(); let stake_address = solana_sdk::pubkey::new_rand(); let rent = Rent::default(); let rent_exempt_reserve = rent.minimum_balance(StakeState::size_of()); - let minimum_delegation = crate::get_minimum_delegation(&FeatureSet::all_enabled()); + let minimum_delegation = crate::get_minimum_delegation(&feature_set); let stake_lamports = 7 * minimum_delegation; let stake_account = AccountSharedData::new_data_with_space( stake_lamports + rent_exempt_reserve, @@ -3249,6 +3354,7 @@ mod tests { // should pass, withdrawing account down to minimum balance process_instruction( + &feature_set, &serialize(&StakeInstruction::Withdraw( stake_lamports - minimum_delegation, )) @@ -3260,6 +3366,7 @@ mod tests { // should fail, withdrawing account down to only rent-exempt reserve process_instruction( + &feature_set, &serialize(&StakeInstruction::Withdraw(stake_lamports)).unwrap(), transaction_accounts.clone(), instruction_accounts.clone(), @@ -3268,6 +3375,7 @@ mod tests { // should fail, withdrawal that would leave less than rent-exempt reserve process_instruction( + &feature_set, &serialize(&StakeInstruction::Withdraw( stake_lamports + minimum_delegation, )) @@ -3279,6 +3387,7 @@ mod tests { // should pass, withdrawal of complete account process_instruction( + &feature_set, &serialize(&StakeInstruction::Withdraw( stake_lamports + rent_exempt_reserve, )) @@ -3289,10 +3398,9 @@ mod tests { ); } - #[test] - fn test_deactivate() { + fn do_test_deactivate(feature_set: FeatureSet) { let stake_address = solana_sdk::pubkey::new_rand(); - let minimum_delegation = crate::get_minimum_delegation(&FeatureSet::all_enabled()); + let minimum_delegation = crate::get_minimum_delegation(&feature_set); let stake_lamports = minimum_delegation; let stake_account = AccountSharedData::new_data_with_space( stake_lamports, @@ -3339,6 +3447,7 @@ mod tests { // should fail, not signed instruction_accounts[0].is_signer = false; process_instruction( + &feature_set, &serialize(&StakeInstruction::Deactivate).unwrap(), transaction_accounts.clone(), instruction_accounts.clone(), @@ -3348,6 +3457,7 @@ mod tests { // should fail, not staked yet process_instruction( + &feature_set, &serialize(&StakeInstruction::Deactivate).unwrap(), transaction_accounts.clone(), instruction_accounts.clone(), @@ -3356,6 +3466,7 @@ mod tests { // Staking let accounts = process_instruction( + &feature_set, &serialize(&StakeInstruction::DelegateStake).unwrap(), transaction_accounts.clone(), vec![ @@ -3391,6 +3502,7 @@ mod tests { // should pass let accounts = process_instruction( + &feature_set, &serialize(&StakeInstruction::Deactivate).unwrap(), transaction_accounts.clone(), instruction_accounts.clone(), @@ -3400,6 +3512,7 @@ mod tests { // should fail, only works once process_instruction( + &feature_set, &serialize(&StakeInstruction::Deactivate).unwrap(), transaction_accounts, instruction_accounts, @@ -3407,12 +3520,11 @@ mod tests { ); } - #[test] - fn test_set_lockup() { + fn do_test_set_lockup(feature_set: FeatureSet) { let custodian_address = solana_sdk::pubkey::new_rand(); let authorized_address = solana_sdk::pubkey::new_rand(); let stake_address = solana_sdk::pubkey::new_rand(); - let minimum_delegation = crate::get_minimum_delegation(&FeatureSet::all_enabled()); + let minimum_delegation = crate::get_minimum_delegation(&feature_set); let stake_lamports = minimum_delegation; let stake_account = AccountSharedData::new_data_with_space( stake_lamports, @@ -3475,6 +3587,7 @@ mod tests { // should fail, wrong state process_instruction( + &feature_set, &instruction_data, transaction_accounts.clone(), instruction_accounts.clone(), @@ -3488,6 +3601,7 @@ mod tests { custodian: custodian_address, }; let accounts = process_instruction( + &feature_set, &serialize(&StakeInstruction::Initialize( Authorized::auto(&stake_address), lockup, @@ -3513,6 +3627,7 @@ mod tests { // should fail, not signed instruction_accounts[2].is_signer = false; process_instruction( + &feature_set, &instruction_data, transaction_accounts.clone(), instruction_accounts.clone(), @@ -3522,6 +3637,7 @@ mod tests { // should pass process_instruction( + &feature_set, &instruction_data, transaction_accounts.clone(), instruction_accounts.clone(), @@ -3530,6 +3646,7 @@ mod tests { // Staking let accounts = process_instruction( + &feature_set, &serialize(&StakeInstruction::DelegateStake).unwrap(), transaction_accounts.clone(), vec![ @@ -3566,6 +3683,7 @@ mod tests { // should fail, not signed instruction_accounts[2].is_signer = false; process_instruction( + &feature_set, &instruction_data, transaction_accounts.clone(), instruction_accounts.clone(), @@ -3575,6 +3693,7 @@ mod tests { // should pass process_instruction( + &feature_set, &instruction_data, transaction_accounts.clone(), instruction_accounts.clone(), @@ -3593,6 +3712,7 @@ mod tests { instruction_accounts[0].is_signer = true; instruction_accounts[2].is_signer = false; process_instruction( + &feature_set, &instruction_data, transaction_accounts.clone(), instruction_accounts.clone(), @@ -3603,6 +3723,7 @@ mod tests { // should pass, custodian can change it process_instruction( + &feature_set, &instruction_data, transaction_accounts.clone(), instruction_accounts.clone(), @@ -3622,6 +3743,7 @@ mod tests { // should fail, custodian cannot change it process_instruction( + &feature_set, &instruction_data, transaction_accounts.clone(), instruction_accounts.clone(), @@ -3632,6 +3754,7 @@ mod tests { instruction_accounts[0].is_signer = true; instruction_accounts[2].is_signer = false; process_instruction( + &feature_set, &instruction_data, transaction_accounts.clone(), instruction_accounts.clone(), @@ -3640,6 +3763,7 @@ mod tests { // Change authorized withdrawer let accounts = process_instruction( + &feature_set, &serialize(&StakeInstruction::Authorize( authorized_address, StakeAuthorize::Withdrawer, @@ -3669,6 +3793,7 @@ mod tests { // should fail, previous authorized withdrawer cannot change the lockup anymore process_instruction( + &feature_set, &instruction_data, transaction_accounts, instruction_accounts, @@ -3679,9 +3804,7 @@ mod tests { /// Ensure that `initialize()` respects the minimum delegation requirements /// - Assert 1: accounts with a balance equal-to the minimum initialize OK /// - Assert 2: accounts with a balance less-than the minimum do not initialize - #[test] - fn test_initialize_minimum_stake_delegation() { - let feature_set = FeatureSet::all_enabled(); + fn do_test_initialize_minimum_stake_delegation(feature_set: FeatureSet) { let minimum_delegation = crate::get_minimum_delegation(&feature_set); let rent = Rent::default(); let rent_exempt_reserve = rent.minimum_balance(StakeState::size_of()); @@ -3716,6 +3839,7 @@ mod tests { &id(), ); process_instruction( + &feature_set, &instruction_data, vec![ (stake_address, stake_account), @@ -3731,19 +3855,22 @@ mod tests { } /// Ensure that `delegate()` respects the minimum delegation requirements - /// - Assert 1: delegating an amount equal-to the minimum delegates OK - /// - Assert 2: delegating an amount less-than the minimum delegates OK + /// - Assert 1: delegating an amount equal-to the minimum delegates is OK + /// - Assert 2 (old): delegating an amount less-than the minimum delegates *is* OK + /// - Assert 2 (new): delegating an amount less-than the minimum delegates is *not* OK + /// /// Also test both asserts above over both StakeState::{Initialized and Stake}, since the logic /// is slightly different for the variants. /// /// NOTE: Even though new stake accounts must have a minimum balance that is at least - /// the minimum delegation (plus rent exempt reserve), the current behavior allows + /// the minimum delegation (plus rent exempt reserve), the old behavior allowed /// withdrawing below the minimum delegation, then re-delegating successfully (see /// `test_behavior_withdrawal_then_redelegate_with_less_than_minimum_stake_delegation()` for /// more information.) - #[test] - fn test_delegate_minimum_stake_delegation() { - let feature_set = FeatureSet::all_enabled(); + fn do_test_delegate_minimum_stake_delegation( + feature_set: FeatureSet, + expected_result: Result<(), InstructionError>, + ) { let minimum_delegation = crate::get_minimum_delegation(&feature_set); let rent = Rent::default(); let rent_exempt_reserve = rent.minimum_balance(StakeState::size_of()); @@ -3782,13 +3909,13 @@ mod tests { is_writable: false, }, ]; - for (stake_delegation, expected_result) in [ + for (stake_delegation, expected_result) in &[ (minimum_delegation, Ok(())), - (minimum_delegation - 1, Ok(())), + (minimum_delegation - 1, expected_result), ] { for stake_state in &[ StakeState::Initialized(meta), - just_stake(meta, stake_delegation), + just_stake(meta, *stake_delegation), ] { let stake_account = AccountSharedData::new_data_with_space( stake_delegation + rent_exempt_reserve, @@ -3798,6 +3925,7 @@ mod tests { ) .unwrap(); process_instruction( + &feature_set, &serialize(&StakeInstruction::DelegateStake).unwrap(), vec![ (stake_address, stake_account), @@ -3832,9 +3960,7 @@ mod tests { /// EQ | LT | Err /// LT | EQ | Err /// LT | LT | Err - #[test] - fn test_split_minimum_stake_delegation() { - let feature_set = FeatureSet::all_enabled(); + fn do_test_split_minimum_stake_delegation(feature_set: FeatureSet) { let minimum_delegation = crate::get_minimum_delegation(&feature_set); let rent = Rent::default(); let rent_exempt_reserve = rent.minimum_balance(StakeState::size_of()); @@ -3897,6 +4023,7 @@ mod tests { ) .unwrap(); process_instruction( + &feature_set, &serialize(&StakeInstruction::Split( dest_stake_delegation + rent_exempt_reserve, )) @@ -3923,9 +4050,7 @@ mod tests { /// delegation is OK /// - Assert 2: splitting the full amount from an account that has less than the minimum /// delegation is not OK - #[test] - fn test_split_full_amount_minimum_stake_delegation() { - let feature_set = FeatureSet::all_enabled(); + fn do_test_split_full_amount_minimum_stake_delegation(feature_set: FeatureSet) { let minimum_delegation = crate::get_minimum_delegation(&feature_set); let rent = Rent::default(); let rent_exempt_reserve = rent.minimum_balance(StakeState::size_of()); @@ -3973,6 +4098,7 @@ mod tests { ) .unwrap(); process_instruction( + &feature_set, &serialize(&StakeInstruction::Split(source_account.lamports())).unwrap(), vec![ (source_address, source_account), @@ -3990,10 +4116,9 @@ mod tests { } /// Ensure that `split()` correctly handles prefunded destination accounts. When a destination - /// account already has funds, ensure the minimum split amount reduces accordingly. - #[test] - fn test_split_destination_minimum_stake_delegation() { - let feature_set = FeatureSet::all_enabled(); + /// account already has funds, ensure the minimum split amount reduces accordingly. This test + /// is only for when the source account's state is Initialized. + fn do_test_split_destination_minimum_stake_delegation_initialized(feature_set: FeatureSet) { let minimum_delegation = crate::get_minimum_delegation(&feature_set); let rent = Rent::default(); let rent_exempt_reserve = rent.minimum_balance(StakeState::size_of()); @@ -4002,7 +4127,18 @@ mod tests { rent_exempt_reserve, ..Meta::auto(&source_address) }; - let dest_address = Pubkey::new_unique(); + let source_stake_state = StakeState::Initialized(source_meta); + // Set the source's starting balance to something large to ensure its post-split + // balance meets all the requirements + let source_balance = u64::MAX; + let source_account = AccountSharedData::new_data_with_space( + source_balance, + &source_stake_state, + StakeState::size_of(), + &id(), + ) + .unwrap(); + let destination_address = Pubkey::new_unique(); let instruction_accounts = vec![ AccountMeta { pubkey: source_address, @@ -4010,7 +4146,7 @@ mod tests { is_writable: false, }, AccountMeta { - pubkey: dest_address, + pubkey: destination_address, is_signer: false, is_writable: false, }, @@ -4069,65 +4205,181 @@ mod tests { Err(InstructionError::InsufficientFunds), ), ] { - // Set the source's starting balance and stake delegation amount to something large - // to ensure its post-split balance meets all the requirements - let source_balance = u64::MAX; - let source_stake_delegation = source_balance - rent_exempt_reserve; - for source_stake_state in &[ - StakeState::Initialized(source_meta), - just_stake(source_meta, source_stake_delegation), - ] { - let source_account = AccountSharedData::new_data_with_space( - source_balance, - &source_stake_state, - StakeState::size_of(), - &id(), - ) - .unwrap(); - let dest_account = AccountSharedData::new_data_with_space( - destination_starting_balance, - &StakeState::Uninitialized, - StakeState::size_of(), - &id(), - ) - .unwrap(); - let accounts = process_instruction( - &serialize(&StakeInstruction::Split(split_amount)).unwrap(), - vec![ - (source_address, source_account), - (dest_address, dest_account), - ( - sysvar::rent::id(), - account::create_account_shared_data_for_test(&rent), - ), - ], - instruction_accounts.clone(), - expected_result.clone(), - ); - // For the expected OK cases, when the source's StakeState is Stake, then the - // destination's StakeState *must* also end up as Stake as well. Additionally, - // check to ensure the destination's delegation amount is correct. If the - // destination is already rent exempt, then the destination's stake delegation - // *must* equal the split amount. Otherwise, the split amount must first be used to - // make the destination rent exempt, and then the leftover lamports are delegated. - if expected_result.is_ok() { - if let StakeState::Stake(_, _) = accounts[0].state().unwrap() { - if let StakeState::Stake(_, destination_stake) = - accounts[1].state().unwrap() - { - let destination_initial_rent_deficit = - rent_exempt_reserve.saturating_sub(destination_starting_balance); - let expected_destination_stake_delegation = - split_amount - destination_initial_rent_deficit; - assert_eq!( - expected_destination_stake_delegation, - destination_stake.delegation.stake - ); - assert!(destination_stake.delegation.stake >= minimum_delegation,); - } else { - panic!("destination state must be StakeStake::Stake after successful split when source is also StakeState::Stake!"); - } + let destination_account = AccountSharedData::new_data_with_space( + destination_starting_balance, + &StakeState::Uninitialized, + StakeState::size_of(), + &id(), + ) + .unwrap(); + process_instruction( + &feature_set, + &serialize(&StakeInstruction::Split(split_amount)).unwrap(), + vec![ + (source_address, source_account.clone()), + (destination_address, destination_account), + ( + sysvar::rent::id(), + account::create_account_shared_data_for_test(&rent), + ), + ], + instruction_accounts.clone(), + expected_result.clone(), + ); + } + } + + /// Ensure that `split()` correctly handles prefunded destination accounts. When a destination + /// account already has funds, ensure the minimum split amount reduces accordingly. This test + /// is only for when the source account's state is Staked. + fn do_test_split_destination_minimum_stake_delegation_staked( + feature_set: FeatureSet, + expected_results: &[Result<(), InstructionError>], + ) { + solana_logger::setup(); + let minimum_delegation = crate::get_minimum_delegation(&feature_set); + let rent = Rent::default(); + let rent_exempt_reserve = rent.minimum_balance(StakeState::size_of()); + let source_address = Pubkey::new_unique(); + let source_meta = Meta { + rent_exempt_reserve, + ..Meta::auto(&source_address) + }; + // Set the source's starting balance and stake delegation amount to something large + // to ensure its post-split balance meets all the requirements + let source_balance = u64::MAX; + let source_stake_delegation = source_balance - rent_exempt_reserve; + let source_stake_state = just_stake(source_meta, source_stake_delegation); + let destination_address = Pubkey::new_unique(); + let instruction_accounts = vec![ + AccountMeta { + pubkey: source_address, + is_signer: true, + is_writable: false, + }, + AccountMeta { + pubkey: destination_address, + is_signer: false, + is_writable: false, + }, + ]; + for (destination_starting_balance, split_amount, expected_result) in [ + // split amount must be non zero + ( + rent_exempt_reserve + minimum_delegation, + 0, + Err(InstructionError::InsufficientFunds), + ), + // destination is fully funded: + // - old behavior: any split amount is OK + // - new behavior: split amount must be at least the minimum delegation + ( + rent_exempt_reserve + minimum_delegation, + 1, + expected_results[0].clone(), + ), + // if destination is only short by 1 lamport, then... + // - old behavior: split amount can be 1 lamport + // - new behavior: split amount must be at least the minimum delegation + ( + rent_exempt_reserve + minimum_delegation - 1, + 1, + expected_results[1].clone(), + ), + // destination short by 2 lamports, so 1 isn't enough (non-zero split amount) + ( + rent_exempt_reserve + minimum_delegation - 2, + 1, + Err(InstructionError::InsufficientFunds), + ), + // destination is rent exempt, so split enough for minimum delegation + (rent_exempt_reserve, minimum_delegation, Ok(())), + // destination is rent exempt, but split amount less than minimum delegation + ( + rent_exempt_reserve, + minimum_delegation - 1, + Err(InstructionError::InsufficientFunds), + ), + // destination is not rent exempt, so split enough for rent and minimum delegation + (rent_exempt_reserve - 1, minimum_delegation + 1, Ok(())), + // destination is not rent exempt, but split amount only for minimum delegation + ( + rent_exempt_reserve - 1, + minimum_delegation, + Err(InstructionError::InsufficientFunds), + ), + // destination has smallest non-zero balance, so can split the minimum balance + // requirements minus what destination already has + (1, rent_exempt_reserve + minimum_delegation - 1, Ok(())), + // destination has smallest non-zero balance, but cannot split less than the minimum + // balance requirements minus what destination already has + ( + 1, + rent_exempt_reserve + minimum_delegation - 2, + Err(InstructionError::InsufficientFunds), + ), + // destination has zero lamports, so split must be at least rent exempt reserve plus + // minimum delegation + (0, rent_exempt_reserve + minimum_delegation, Ok(())), + // destination has zero lamports, but split amount is less than rent exempt reserve + // plus minimum delegation + ( + 0, + rent_exempt_reserve + minimum_delegation - 1, + Err(InstructionError::InsufficientFunds), + ), + ] { + let source_account = AccountSharedData::new_data_with_space( + source_balance, + &source_stake_state, + StakeState::size_of(), + &id(), + ) + .unwrap(); + let destination_account = AccountSharedData::new_data_with_space( + destination_starting_balance, + &StakeState::Uninitialized, + StakeState::size_of(), + &id(), + ) + .unwrap(); + let accounts = process_instruction( + &feature_set, + &serialize(&StakeInstruction::Split(split_amount)).unwrap(), + vec![ + (source_address, source_account.clone()), + (destination_address, destination_account), + ( + sysvar::rent::id(), + account::create_account_shared_data_for_test(&rent), + ), + ], + instruction_accounts.clone(), + expected_result.clone(), + ); + // For the expected OK cases, when the source's StakeState is Stake, then the + // destination's StakeState *must* also end up as Stake as well. Additionally, + // check to ensure the destination's delegation amount is correct. If the + // destination is already rent exempt, then the destination's stake delegation + // *must* equal the split amount. Otherwise, the split amount must first be used to + // make the destination rent exempt, and then the leftover lamports are delegated. + if expected_result.is_ok() { + if let StakeState::Stake(_, _) = accounts[0].state().unwrap() { + if let StakeState::Stake(_, destination_stake) = accounts[1].state().unwrap() { + let destination_initial_rent_deficit = + rent_exempt_reserve.saturating_sub(destination_starting_balance); + let expected_destination_stake_delegation = + split_amount - destination_initial_rent_deficit; + assert_eq!( + expected_destination_stake_delegation, + destination_stake.delegation.stake + ); + assert!(destination_stake.delegation.stake >= minimum_delegation,); + } else { + panic!("destination state must be StakeStake::Stake after successful split when source is also StakeState::Stake!"); } + } else { + unreachable!("source state is StakeState::Stake"); } } } @@ -4136,9 +4388,7 @@ mod tests { /// Ensure that `withdraw()` respects the minimum delegation requirements /// - Assert 1: withdrawing so remaining stake is equal-to the minimum is OK /// - Assert 2: withdrawing so remaining stake is less-than the minimum is not OK - #[test] - fn test_withdraw_minimum_stake_delegation() { - let feature_set = FeatureSet::all_enabled(); + fn do_test_withdraw_minimum_stake_delegation(feature_set: FeatureSet) { let minimum_delegation = crate::get_minimum_delegation(&feature_set); let rent = Rent::default(); let rent_exempt_reserve = rent.minimum_balance(StakeState::size_of()); @@ -4198,6 +4448,7 @@ mod tests { let withdraw_amount = (starting_stake_delegation + rewards_balance) - ending_stake_delegation; process_instruction( + &feature_set, &serialize(&StakeInstruction::Withdraw(withdraw_amount)).unwrap(), vec![ (stake_address, stake_account), @@ -4229,9 +4480,10 @@ mod tests { } } - /// The stake program currently allows delegations below the minimum stake delegation (see also - /// `test_delegate_minimum_stake_delegation()`). This is not the ultimate desired behavior, - /// but this test ensures the existing behavior is not changed inadvertently. + /// The stake program's old behavior allowed delegations below the minimum stake delegation + /// (see also `test_delegate_minimum_stake_delegation()`). This was not the desired behavior, + /// and has been fixed in the new behavior. This test ensures the behavior is not changed + /// inadvertently. /// /// This test: /// 1. Initialises a stake account (with sufficient balance for both rent and minimum delegation) @@ -4239,9 +4491,11 @@ mod tests { /// 3. Deactives the delegation /// 4. Withdraws from the account such that the ending balance is *below* rent + minimum delegation /// 5. Re-delegates, now with less than the minimum delegation, but it still succeeds - #[test] - fn test_behavior_withdrawal_then_redelegate_with_less_than_minimum_stake_delegation() { - let feature_set = FeatureSet::all_enabled(); + fn do_test_behavior_withdrawal_then_redelegate_with_less_than_minimum_stake_delegation( + feature_set: FeatureSet, + expected_result: Result<(), InstructionError>, + ) { + solana_logger::setup(); let minimum_delegation = crate::get_minimum_delegation(&feature_set); let rent = Rent::default(); let rent_exempt_reserve = rent.minimum_balance(StakeState::size_of()); @@ -4309,6 +4563,7 @@ mod tests { ]; let accounts = process_instruction( + &feature_set, &serialize(&StakeInstruction::Initialize( Authorized::auto(&stake_address), Lockup::default(), @@ -4332,6 +4587,7 @@ mod tests { transaction_accounts[0] = (stake_address, accounts[0].clone()); let accounts = process_instruction( + &feature_set, &serialize(&StakeInstruction::DelegateStake).unwrap(), transaction_accounts.clone(), instruction_accounts.clone(), @@ -4346,6 +4602,7 @@ mod tests { account::create_account_shared_data_for_test(&clock), ); let accounts = process_instruction( + &feature_set, &serialize(&StakeInstruction::Deactivate).unwrap(), transaction_accounts.clone(), vec![ @@ -4371,7 +4628,8 @@ mod tests { ); let withdraw_amount = accounts[0].lamports() - (rent_exempt_reserve + minimum_delegation - 1); - process_instruction( + let accounts = process_instruction( + &feature_set, &serialize(&StakeInstruction::Withdraw(withdraw_amount)).unwrap(), transaction_accounts.clone(), vec![ @@ -4406,18 +4664,18 @@ mod tests { transaction_accounts[0] = (stake_address, accounts[0].clone()); process_instruction( + &feature_set, &serialize(&StakeInstruction::DelegateStake).unwrap(), transaction_accounts, instruction_accounts, - Ok(()), + expected_result, ); } - #[test] - fn test_split_source_uninitialized() { + fn do_test_split_source_uninitialized(feature_set: FeatureSet) { let rent = Rent::default(); let rent_exempt_reserve = rent.minimum_balance(StakeState::size_of()); - let minimum_delegation = crate::get_minimum_delegation(&FeatureSet::all_enabled()); + let minimum_delegation = crate::get_minimum_delegation(&feature_set); let stake_lamports = (rent_exempt_reserve + minimum_delegation) * 2; let stake_address = solana_sdk::pubkey::new_rand(); let stake_account = AccountSharedData::new_data_with_space( @@ -4461,24 +4719,28 @@ mod tests { // // and splitting should fail when the split amount is greater than the balance process_instruction( + &feature_set, &serialize(&StakeInstruction::Split(stake_lamports)).unwrap(), transaction_accounts.clone(), instruction_accounts.clone(), Ok(()), ); process_instruction( + &feature_set, &serialize(&StakeInstruction::Split(0)).unwrap(), transaction_accounts.clone(), instruction_accounts.clone(), Ok(()), ); process_instruction( + &feature_set, &serialize(&StakeInstruction::Split(stake_lamports / 2)).unwrap(), transaction_accounts.clone(), instruction_accounts.clone(), Ok(()), ); process_instruction( + &feature_set, &serialize(&StakeInstruction::Split(stake_lamports + 1)).unwrap(), transaction_accounts.clone(), instruction_accounts.clone(), @@ -4489,6 +4751,7 @@ mod tests { // this should work instruction_accounts[1].pubkey = split_to_address; let accounts = process_instruction( + &feature_set, &serialize(&StakeInstruction::Split(stake_lamports / 2)).unwrap(), transaction_accounts.clone(), instruction_accounts.clone(), @@ -4499,6 +4762,7 @@ mod tests { // no signers should fail instruction_accounts[0].is_signer = false; process_instruction( + &feature_set, &serialize(&StakeInstruction::Split(stake_lamports / 2)).unwrap(), transaction_accounts, instruction_accounts, @@ -4506,8 +4770,7 @@ mod tests { ); } - #[test] - fn test_split_split_not_uninitialized() { + fn do_test_split_split_not_uninitialized(feature_set: FeatureSet) { let stake_lamports = 42; let stake_address = solana_sdk::pubkey::new_rand(); let stake_account = AccountSharedData::new_data_with_space( @@ -4544,6 +4807,7 @@ mod tests { ) .unwrap(); process_instruction( + &feature_set, &serialize(&StakeInstruction::Split(stake_lamports / 2)).unwrap(), vec![ (stake_address, stake_account.clone()), @@ -4555,11 +4819,10 @@ mod tests { } } - #[test] - fn test_split_more_than_staked() { + fn do_test_split_more_than_staked(feature_set: FeatureSet) { let rent = Rent::default(); let rent_exempt_reserve = rent.minimum_balance(StakeState::size_of()); - let minimum_delegation = crate::get_minimum_delegation(&FeatureSet::all_enabled()); + let minimum_delegation = crate::get_minimum_delegation(&feature_set); let stake_lamports = (rent_exempt_reserve + minimum_delegation) * 2; let stake_address = solana_sdk::pubkey::new_rand(); let stake_account = AccountSharedData::new_data_with_space( @@ -4605,6 +4868,7 @@ mod tests { ]; process_instruction( + &feature_set, &serialize(&StakeInstruction::Split(stake_lamports / 2)).unwrap(), transaction_accounts, instruction_accounts, @@ -4612,11 +4876,10 @@ mod tests { ); } - #[test] - fn test_split_with_rent() { + fn do_test_split_with_rent(feature_set: FeatureSet) { let rent = Rent::default(); let rent_exempt_reserve = rent.minimum_balance(StakeState::size_of()); - let minimum_delegation = crate::get_minimum_delegation(&FeatureSet::all_enabled()); + let minimum_delegation = crate::get_minimum_delegation(&feature_set); let minimum_balance = rent_exempt_reserve + minimum_delegation; let stake_lamports = minimum_balance * 2; let stake_address = solana_sdk::pubkey::new_rand(); @@ -4669,6 +4932,7 @@ mod tests { // not enough to make a non-zero stake account process_instruction( + &feature_set, &serialize(&StakeInstruction::Split(rent_exempt_reserve)).unwrap(), transaction_accounts.clone(), instruction_accounts.clone(), @@ -4677,6 +4941,7 @@ mod tests { // doesn't leave enough for initial stake to be non-zero process_instruction( + &feature_set, &serialize(&StakeInstruction::Split( stake_lamports - rent_exempt_reserve, )) @@ -4689,6 +4954,7 @@ mod tests { // split account already has way enough lamports transaction_accounts[1].1.set_lamports(minimum_balance); let accounts = process_instruction( + &feature_set, &serialize(&StakeInstruction::Split(stake_lamports - minimum_balance)).unwrap(), transaction_accounts, instruction_accounts.clone(), @@ -4716,11 +4982,10 @@ mod tests { } } - #[test] - fn test_split_to_account_with_rent_exempt_reserve() { + fn do_test_split_to_account_with_rent_exempt_reserve(feature_set: FeatureSet) { let rent = Rent::default(); let rent_exempt_reserve = rent.minimum_balance(StakeState::size_of()); - let minimum_delegation = crate::get_minimum_delegation(&FeatureSet::all_enabled()); + let minimum_delegation = crate::get_minimum_delegation(&feature_set); let stake_lamports = (rent_exempt_reserve + minimum_delegation) * 2; let stake_address = solana_sdk::pubkey::new_rand(); let meta = Meta { @@ -4779,6 +5044,7 @@ mod tests { // split more than available fails process_instruction( + &feature_set, &serialize(&StakeInstruction::Split(stake_lamports + 1)).unwrap(), transaction_accounts.clone(), instruction_accounts.clone(), @@ -4787,6 +5053,7 @@ mod tests { // should work let accounts = process_instruction( + &feature_set, &serialize(&StakeInstruction::Split(stake_lamports / 2)).unwrap(), transaction_accounts, instruction_accounts.clone(), @@ -4838,12 +5105,11 @@ mod tests { } } - #[test] - fn test_split_from_larger_sized_account() { + fn do_test_split_from_larger_sized_account(feature_set: FeatureSet) { let rent = Rent::default(); let source_larger_rent_exempt_reserve = rent.minimum_balance(StakeState::size_of() + 100); let split_rent_exempt_reserve = rent.minimum_balance(StakeState::size_of()); - let minimum_delegation = crate::get_minimum_delegation(&FeatureSet::all_enabled()); + let minimum_delegation = crate::get_minimum_delegation(&feature_set); let stake_lamports = (source_larger_rent_exempt_reserve + minimum_delegation) * 2; let stake_address = solana_sdk::pubkey::new_rand(); let meta = Meta { @@ -4902,6 +5168,7 @@ mod tests { // split more than available fails process_instruction( + &feature_set, &serialize(&StakeInstruction::Split(stake_lamports + 1)).unwrap(), transaction_accounts.clone(), instruction_accounts.clone(), @@ -4910,6 +5177,7 @@ mod tests { // should work let accounts = process_instruction( + &feature_set, &serialize(&StakeInstruction::Split(stake_lamports / 2)).unwrap(), transaction_accounts.clone(), instruction_accounts.clone(), @@ -4966,8 +5234,7 @@ mod tests { } } - #[test] - fn test_split_from_smaller_sized_account() { + fn do_test_split_from_smaller_sized_account(feature_set: FeatureSet) { let rent = Rent::default(); let source_smaller_rent_exempt_reserve = rent.minimum_balance(StakeState::size_of()); let split_rent_exempt_reserve = rent.minimum_balance(StakeState::size_of() + 100); @@ -5026,6 +5293,7 @@ mod tests { // should always return error when splitting to larger account process_instruction( + &feature_set, &serialize(&StakeInstruction::Split(split_amount)).unwrap(), transaction_accounts.clone(), instruction_accounts.clone(), @@ -5034,6 +5302,7 @@ mod tests { // Splitting 100% of source should not make a difference process_instruction( + &feature_set, &serialize(&StakeInstruction::Split(stake_lamports)).unwrap(), transaction_accounts, instruction_accounts.clone(), @@ -5042,11 +5311,10 @@ mod tests { } } - #[test] - fn test_split_100_percent_of_source() { + fn do_test_split_100_percent_of_source(feature_set: FeatureSet) { let rent = Rent::default(); let rent_exempt_reserve = rent.minimum_balance(StakeState::size_of()); - let minimum_delegation = crate::get_minimum_delegation(&FeatureSet::all_enabled()); + let minimum_delegation = crate::get_minimum_delegation(&feature_set); let stake_lamports = rent_exempt_reserve + minimum_delegation; let stake_address = solana_sdk::pubkey::new_rand(); let meta = Meta { @@ -5098,6 +5366,7 @@ mod tests { // split 100% over to dest let accounts = process_instruction( + &feature_set, &serialize(&StakeInstruction::Split(stake_lamports)).unwrap(), transaction_accounts, instruction_accounts.clone(), @@ -5136,11 +5405,10 @@ mod tests { } } - #[test] - fn test_split_100_percent_of_source_to_account_with_lamports() { + fn do_test_split_100_percent_of_source_to_account_with_lamports(feature_set: FeatureSet) { let rent = Rent::default(); let rent_exempt_reserve = rent.minimum_balance(StakeState::size_of()); - let minimum_delegation = crate::get_minimum_delegation(&FeatureSet::all_enabled()); + let minimum_delegation = crate::get_minimum_delegation(&feature_set); let stake_lamports = rent_exempt_reserve + minimum_delegation; let stake_address = solana_sdk::pubkey::new_rand(); let meta = Meta { @@ -5199,6 +5467,7 @@ mod tests { // split 100% over to dest let accounts = process_instruction( + &feature_set, &serialize(&StakeInstruction::Split(stake_lamports)).unwrap(), transaction_accounts, instruction_accounts.clone(), @@ -5230,12 +5499,11 @@ mod tests { } } - #[test] - fn test_split_rent_exemptness() { + fn do_test_split_rent_exemptness(feature_set: FeatureSet) { let rent = Rent::default(); let source_rent_exempt_reserve = rent.minimum_balance(StakeState::size_of() + 100); let split_rent_exempt_reserve = rent.minimum_balance(StakeState::size_of()); - let minimum_delegation = crate::get_minimum_delegation(&FeatureSet::all_enabled()); + let minimum_delegation = crate::get_minimum_delegation(&feature_set); let stake_lamports = source_rent_exempt_reserve + minimum_delegation; let stake_address = solana_sdk::pubkey::new_rand(); let meta = Meta { @@ -5285,6 +5553,7 @@ mod tests { ), ]; process_instruction( + &feature_set, &serialize(&StakeInstruction::Split(stake_lamports)).unwrap(), transaction_accounts, instruction_accounts.clone(), @@ -5316,6 +5585,7 @@ mod tests { ), ]; let accounts = process_instruction( + &feature_set, &serialize(&StakeInstruction::Split(stake_lamports)).unwrap(), transaction_accounts, instruction_accounts.clone(), @@ -5365,8 +5635,7 @@ mod tests { } } - #[test] - fn test_merge() { + fn do_test_merge(feature_set: FeatureSet) { 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(); @@ -5439,6 +5708,7 @@ mod tests { // Authorized staker signature required... instruction_accounts[4].is_signer = false; process_instruction( + &feature_set, &serialize(&StakeInstruction::Merge).unwrap(), transaction_accounts.clone(), instruction_accounts.clone(), @@ -5447,6 +5717,7 @@ mod tests { instruction_accounts[4].is_signer = true; let accounts = process_instruction( + &feature_set, &serialize(&StakeInstruction::Merge).unwrap(), transaction_accounts, instruction_accounts.clone(), @@ -5492,8 +5763,7 @@ mod tests { } } - #[test] - fn test_merge_self_fails() { + fn do_test_merge_self_fails(feature_set: FeatureSet) { let stake_address = solana_sdk::pubkey::new_rand(); let authorized_address = solana_sdk::pubkey::new_rand(); let rent = Rent::default(); @@ -5560,6 +5830,7 @@ mod tests { ]; process_instruction( + &feature_set, &serialize(&StakeInstruction::Merge).unwrap(), transaction_accounts, instruction_accounts, @@ -5567,8 +5838,7 @@ mod tests { ); } - #[test] - fn test_merge_incorrect_authorized_staker() { + fn do_test_merge_incorrect_authorized_staker(feature_set: FeatureSet) { 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(); @@ -5641,6 +5911,7 @@ mod tests { instruction_accounts[4].pubkey = wrong_authorized_address; process_instruction( + &feature_set, &serialize(&StakeInstruction::Merge).unwrap(), transaction_accounts.clone(), instruction_accounts.clone(), @@ -5649,6 +5920,7 @@ mod tests { instruction_accounts[4].pubkey = authorized_address; process_instruction( + &feature_set, &serialize(&StakeInstruction::Merge).unwrap(), transaction_accounts, instruction_accounts.clone(), @@ -5658,8 +5930,7 @@ mod tests { } } - #[test] - fn test_merge_invalid_account_data() { + fn do_test_merge_invalid_account_data(feature_set: FeatureSet) { 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(); @@ -5728,6 +5999,7 @@ mod tests { ]; process_instruction( + &feature_set, &serialize(&StakeInstruction::Merge).unwrap(), transaction_accounts, instruction_accounts.clone(), @@ -5737,8 +6009,7 @@ mod tests { } } - #[test] - fn test_merge_fake_stake_source() { + fn do_test_merge_fake_stake_source(feature_set: FeatureSet) { 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(); @@ -5799,6 +6070,7 @@ mod tests { ]; process_instruction( + &feature_set, &serialize(&StakeInstruction::Merge).unwrap(), transaction_accounts, instruction_accounts, @@ -5806,8 +6078,7 @@ mod tests { ); } - #[test] - fn test_merge_active_stake() { + fn do_test_merge_active_stake(feature_set: FeatureSet) { 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(); @@ -5908,6 +6179,7 @@ mod tests { ]; fn try_merge( + feature_set: &FeatureSet, transaction_accounts: Vec<(Pubkey, AccountSharedData)>, mut instruction_accounts: Vec, expected_result: Result<(), InstructionError>, @@ -5916,11 +6188,11 @@ mod tests { if iteration == 1 { instruction_accounts.swap(0, 1); } - let accounts = process_instruction_with_sysvar_cache( + let accounts = process_instruction( + feature_set, &serialize(&StakeInstruction::Merge).unwrap(), transaction_accounts.clone(), instruction_accounts.clone(), - None, expected_result.clone(), ); if expected_result.is_ok() { @@ -5934,6 +6206,7 @@ mod tests { // stake activation epoch, source initialized succeeds try_merge( + &feature_set, transaction_accounts.clone(), instruction_accounts.clone(), Ok(()), @@ -5971,6 +6244,7 @@ mod tests { break; } try_merge( + &feature_set, transaction_accounts.clone(), instruction_accounts.clone(), Err(InstructionError::from(StakeError::MergeTransientStake)), @@ -5979,6 +6253,7 @@ mod tests { // Both fully activated works try_merge( + &feature_set, transaction_accounts.clone(), instruction_accounts.clone(), Ok(()), @@ -6045,6 +6320,7 @@ mod tests { break; } try_merge( + &feature_set, transaction_accounts.clone(), instruction_accounts.clone(), Err(InstructionError::from(StakeError::MergeTransientStake)), @@ -6052,11 +6328,15 @@ mod tests { } // Both fully deactivated works - try_merge(transaction_accounts, instruction_accounts, Ok(())); + try_merge( + &feature_set, + transaction_accounts, + instruction_accounts, + Ok(()), + ); } - #[test] - fn test_stake_get_minimum_delegation() { + fn do_test_stake_get_minimum_delegation(feature_set: FeatureSet) { let stake_address = Pubkey::new_unique(); let stake_account = create_default_stake_account(); let instruction_data = serialize(&StakeInstruction::GetMinimumDelegation).unwrap(); @@ -6074,7 +6354,7 @@ mod tests { transaction_accounts, instruction_accounts, None, - None, + Some(Arc::new(feature_set)), Ok(()), |first_instruction_account, invoke_context| { super::process_instruction(first_instruction_account, invoke_context)?; @@ -6109,18 +6389,13 @@ mod tests { // disabled | bad | some || Err InvalidInstructionData // disabled | good | none || Err NotEnoughAccountKeys // disabled | bad | none || Err NotEnoughAccountKeys - #[test] - fn test_stake_process_instruction_error_ordering() { + fn do_test_stake_process_instruction_error_ordering(feature_set: FeatureSet) { let rent = Rent::default(); let rent_address = sysvar::rent::id(); let rent_account = account::create_account_shared_data_for_test(&rent); let good_stake_address = Pubkey::new_unique(); - let good_stake_account = AccountSharedData::new( - u64::MAX, - std::mem::size_of::(), - &id(), - ); + let good_stake_account = AccountSharedData::new(u64::MAX, StakeState::size_of(), &id()); let good_instruction = instruction::initialize( &good_stake_address, &Authorized::auto(&good_stake_address), @@ -6197,7 +6472,7 @@ mod tests { Err(InstructionError::NotEnoughAccountKeys), ), ] { - let mut feature_set = FeatureSet::all_enabled(); + let mut feature_set = feature_set.clone(); if !is_feature_enabled { feature_set.deactivate( &feature_set::add_get_minimum_delegation_instruction_to_stake_program::id(), @@ -6218,8 +6493,8 @@ mod tests { } } - #[test] - fn test_deactivate_delinquent() { + fn do_test_deactivate_delinquent(feature_set: FeatureSet) { + let feature_set = Arc::new(feature_set); let mut sysvar_cache_override = SysvarCache::default(); let reference_vote_address = Pubkey::new_unique(); @@ -6274,7 +6549,7 @@ mod tests { vote_account: &AccountSharedData, reference_vote_account: &AccountSharedData, expected_result| { - process_instruction_with_sysvar_cache( + process_instruction_with_overrides( &serialize(&StakeInstruction::DeactivateDelinquent).unwrap(), vec![ (*stake_address, stake_account.clone()), @@ -6299,6 +6574,7 @@ mod tests { }, ], Some(&sysvar_cache_override), + Some(Arc::clone(&feature_set)), expected_result, ) }; @@ -6481,4 +6757,398 @@ mod tests { Err(StakeError::MinimumDelinquentEpochsForDeactivationNotMet.into()), ); } + + mod old_behavior { + use super::*; + + fn new_feature_set() -> FeatureSet { + let mut feature_set = FeatureSet::all_enabled(); + feature_set.deactivate(&feature_set::raise_minimum_stake_delegation_to_1_sol::id()); + feature_set + } + + #[test] + fn test_stake_process_instruction() { + do_test_stake_process_instruction(new_feature_set()); + } + #[test] + fn test_stake_process_instruction_decode_bail() { + do_test_stake_process_instruction_decode_bail(new_feature_set()); + } + #[test] + fn test_stake_checked_instructions() { + do_test_stake_checked_instructions(new_feature_set()); + } + #[test] + fn test_stake_initialize() { + do_test_stake_initialize(new_feature_set()); + } + #[test] + fn test_authorize() { + do_test_authorize(new_feature_set()); + } + #[test] + fn test_authorize_override() { + do_test_authorize_override(new_feature_set()); + } + #[test] + fn test_authorize_with_seed() { + do_test_authorize_with_seed(new_feature_set()); + } + #[test] + fn test_authorize_delegated_stake() { + do_test_authorize_delegated_stake(new_feature_set()); + } + #[test] + fn test_stake_delegate() { + do_test_stake_delegate(new_feature_set()); + } + #[test] + fn test_redelegate_consider_balance_changes() { + do_test_redelegate_consider_balance_changes(new_feature_set()); + } + #[test] + fn test_split() { + do_test_split(new_feature_set()); + } + #[test] + fn test_withdraw_stake() { + do_test_withdraw_stake(new_feature_set()); + } + #[test] + fn test_withdraw_stake_before_warmup() { + do_test_withdraw_stake_before_warmup(new_feature_set()); + } + #[test] + fn test_withdraw_lockup() { + do_test_withdraw_lockup(new_feature_set()); + } + #[test] + fn test_withdraw_rent_exempt() { + do_test_withdraw_rent_exempt(new_feature_set()); + } + #[test] + fn test_deactivate() { + do_test_deactivate(new_feature_set()); + } + #[test] + fn test_set_lockup() { + do_test_set_lockup(new_feature_set()); + } + #[test] + fn test_initialize_minimum_stake_delegation() { + do_test_initialize_minimum_stake_delegation(new_feature_set()); + } + #[test] + fn test_delegate_minimum_stake_delegation() { + do_test_delegate_minimum_stake_delegation(new_feature_set(), Ok(())); + } + #[test] + fn test_split_minimum_stake_delegation() { + do_test_split_minimum_stake_delegation(new_feature_set()); + } + #[test] + fn test_split_full_amount_minimum_stake_delegation() { + do_test_split_full_amount_minimum_stake_delegation(new_feature_set()); + } + #[test] + fn test_split_destination_minimum_stake_delegation_initialized() { + do_test_split_destination_minimum_stake_delegation_initialized(new_feature_set()); + } + #[test] + fn test_split_destination_minimum_stake_delegation_staked() { + do_test_split_destination_minimum_stake_delegation_staked( + new_feature_set(), + &[Ok(()), Ok(())], + ); + } + #[test] + fn test_withdraw_minimum_stake_delegation() { + do_test_withdraw_minimum_stake_delegation(new_feature_set()); + } + #[test] + fn test_behavior_withdrawal_then_redelegate_with_less_than_minimum_stake_delegation() { + do_test_behavior_withdrawal_then_redelegate_with_less_than_minimum_stake_delegation( + new_feature_set(), + Ok(()), + ); + } + #[test] + fn test_split_source_uninitialized() { + do_test_split_source_uninitialized(new_feature_set()); + } + #[test] + fn test_split_split_not_uninitialized() { + do_test_split_split_not_uninitialized(new_feature_set()); + } + #[test] + fn test_split_more_than_staked() { + do_test_split_more_than_staked(new_feature_set()); + } + #[test] + fn test_split_with_rent() { + do_test_split_with_rent(new_feature_set()); + } + #[test] + fn test_split_to_account_with_rent_exempt_reserve() { + do_test_split_to_account_with_rent_exempt_reserve(new_feature_set()); + } + #[test] + fn test_split_from_larger_sized_account() { + do_test_split_from_larger_sized_account(new_feature_set()); + } + #[test] + fn test_split_from_smaller_sized_account() { + do_test_split_from_smaller_sized_account(new_feature_set()); + } + #[test] + fn test_split_100_percent_of_source() { + do_test_split_100_percent_of_source(new_feature_set()); + } + #[test] + fn test_split_100_percent_of_source_to_account_with_lamports() { + do_test_split_100_percent_of_source_to_account_with_lamports(new_feature_set()); + } + #[test] + fn test_split_rent_exemptness() { + do_test_split_rent_exemptness(new_feature_set()); + } + #[test] + fn test_merge() { + do_test_merge(new_feature_set()); + } + #[test] + fn test_merge_self_fails() { + do_test_merge_self_fails(new_feature_set()); + } + #[test] + fn test_merge_incorrect_authorized_staker() { + do_test_merge_incorrect_authorized_staker(new_feature_set()); + } + #[test] + fn test_merge_invalid_account_data() { + do_test_merge_invalid_account_data(new_feature_set()); + } + #[test] + fn test_merge_fake_stake_source() { + do_test_merge_fake_stake_source(new_feature_set()); + } + #[test] + fn test_merge_active_stake() { + do_test_merge_active_stake(new_feature_set()); + } + #[test] + fn test_stake_get_minimum_delegation() { + do_test_stake_get_minimum_delegation(new_feature_set()); + } + #[test] + fn test_stake_process_instruction_error_ordering() { + do_test_stake_process_instruction_error_ordering(new_feature_set()); + } + #[test] + fn test_deactivate_delinquent() { + do_test_deactivate_delinquent(new_feature_set()); + } + } + + mod new_behavior { + use super::*; + + fn new_feature_set() -> FeatureSet { + FeatureSet::all_enabled() + } + + #[test] + fn test_stake_process_instruction() { + do_test_stake_process_instruction(new_feature_set()); + } + #[test] + fn test_spoofed_stake_accounts() { + do_test_spoofed_stake_accounts(new_feature_set()); + } + #[test] + fn test_stake_process_instruction_decode_bail() { + do_test_stake_process_instruction_decode_bail(new_feature_set()); + } + #[test] + fn test_stake_checked_instructions() { + do_test_stake_checked_instructions(new_feature_set()); + } + #[test] + fn test_stake_initialize() { + do_test_stake_initialize(new_feature_set()); + } + #[test] + fn test_authorize() { + do_test_authorize(new_feature_set()); + } + #[test] + fn test_authorize_override() { + do_test_authorize_override(new_feature_set()); + } + #[test] + fn test_authorize_with_seed() { + do_test_authorize_with_seed(new_feature_set()); + } + #[test] + fn test_authorize_delegated_stake() { + do_test_authorize_delegated_stake(new_feature_set()); + } + #[test] + fn test_stake_delegate() { + do_test_stake_delegate(new_feature_set()); + } + #[test] + fn test_redelegate_consider_balance_changes() { + do_test_redelegate_consider_balance_changes(new_feature_set()); + } + #[test] + fn test_split() { + do_test_split(new_feature_set()); + } + #[test] + fn test_withdraw_stake() { + do_test_withdraw_stake(new_feature_set()); + } + #[test] + fn test_withdraw_stake_before_warmup() { + do_test_withdraw_stake_before_warmup(new_feature_set()); + } + #[test] + fn test_withdraw_lockup() { + do_test_withdraw_lockup(new_feature_set()); + } + #[test] + fn test_withdraw_rent_exempt() { + do_test_withdraw_rent_exempt(new_feature_set()); + } + #[test] + fn test_deactivate() { + do_test_deactivate(new_feature_set()); + } + #[test] + fn test_set_lockup() { + do_test_set_lockup(new_feature_set()); + } + #[test] + fn test_initialize_minimum_stake_delegation() { + do_test_initialize_minimum_stake_delegation(new_feature_set()); + } + #[test] + fn test_delegate_minimum_stake_delegation() { + do_test_delegate_minimum_stake_delegation( + new_feature_set(), + Err(InstructionError::InsufficientStakeDelegation), + ); + } + #[test] + fn test_split_minimum_stake_delegation() { + do_test_split_minimum_stake_delegation(new_feature_set()); + } + #[test] + fn test_split_full_amount_minimum_stake_delegation() { + do_test_split_full_amount_minimum_stake_delegation(new_feature_set()); + } + #[test] + fn test_split_destination_minimum_stake_delegation_initialized() { + do_test_split_destination_minimum_stake_delegation_initialized(new_feature_set()); + } + #[test] + fn test_split_destination_minimum_stake_delegation_staked() { + do_test_split_destination_minimum_stake_delegation_staked( + new_feature_set(), + &[ + Err(InstructionError::InsufficientFunds), + Err(InstructionError::InsufficientFunds), + ], + ); + } + #[test] + fn test_withdraw_minimum_stake_delegation() { + do_test_withdraw_minimum_stake_delegation(new_feature_set()); + } + #[test] + fn test_behavior_withdrawal_then_redelegate_with_less_than_minimum_stake_delegation() { + do_test_behavior_withdrawal_then_redelegate_with_less_than_minimum_stake_delegation( + new_feature_set(), + Err(InstructionError::InsufficientStakeDelegation), + ); + } + #[test] + fn test_split_source_uninitialized() { + do_test_split_source_uninitialized(new_feature_set()); + } + #[test] + fn test_split_split_not_uninitialized() { + do_test_split_split_not_uninitialized(new_feature_set()); + } + #[test] + fn test_split_more_than_staked() { + do_test_split_more_than_staked(new_feature_set()); + } + #[test] + fn test_split_with_rent() { + do_test_split_with_rent(new_feature_set()); + } + #[test] + fn test_split_to_account_with_rent_exempt_reserve() { + do_test_split_to_account_with_rent_exempt_reserve(new_feature_set()); + } + #[test] + fn test_split_from_larger_sized_account() { + do_test_split_from_larger_sized_account(new_feature_set()); + } + #[test] + fn test_split_from_smaller_sized_account() { + do_test_split_from_smaller_sized_account(new_feature_set()); + } + #[test] + fn test_split_100_percent_of_source() { + do_test_split_100_percent_of_source(new_feature_set()); + } + #[test] + fn test_split_100_percent_of_source_to_account_with_lamports() { + do_test_split_100_percent_of_source_to_account_with_lamports(new_feature_set()); + } + #[test] + fn test_split_rent_exemptness() { + do_test_split_rent_exemptness(new_feature_set()); + } + #[test] + fn test_merge() { + do_test_merge(new_feature_set()); + } + #[test] + fn test_merge_self_fails() { + do_test_merge_self_fails(new_feature_set()); + } + #[test] + fn test_merge_incorrect_authorized_staker() { + do_test_merge_incorrect_authorized_staker(new_feature_set()); + } + #[test] + fn test_merge_invalid_account_data() { + do_test_merge_invalid_account_data(new_feature_set()); + } + #[test] + fn test_merge_fake_stake_source() { + do_test_merge_fake_stake_source(new_feature_set()); + } + #[test] + fn test_merge_active_stake() { + do_test_merge_active_stake(new_feature_set()); + } + #[test] + fn test_stake_get_minimum_delegation() { + do_test_stake_get_minimum_delegation(new_feature_set()); + } + #[test] + fn test_stake_process_instruction_error_ordering() { + do_test_stake_process_instruction_error_ordering(new_feature_set()); + } + #[test] + fn test_deactivate_delinquent() { + do_test_deactivate_delinquent(new_feature_set()); + } + } }