From cb5e67d3277653c48a9c63bc86ebb44afcc9dc08 Mon Sep 17 00:00:00 2001 From: Justin Starry Date: Thu, 31 Mar 2022 16:46:35 +0800 Subject: [PATCH] Use Rent sysvar directly for stake split instruction (#24008) * Use Rent sysvar directly for stake split ix * Add feature to gate rent sysvar change * fix tests * cargo clippy --- program-runtime/src/invoke_context.rs | 4 + programs/bpf_loader/src/lib.rs | 3 + programs/config/src/config_processor.rs | 1 + programs/stake/src/stake_instruction.rs | 37 ++- programs/stake/src/stake_state.rs | 290 +++++++++++++++----- programs/vote/src/vote_processor.rs | 2 + runtime/src/system_instruction_processor.rs | 1 + sdk/src/feature_set.rs | 7 +- 8 files changed, 264 insertions(+), 81 deletions(-) diff --git a/program-runtime/src/invoke_context.rs b/program-runtime/src/invoke_context.rs index 3b90fa1fb6..0346fc0677 100644 --- a/program-runtime/src/invoke_context.rs +++ b/program-runtime/src/invoke_context.rs @@ -1135,6 +1135,7 @@ pub fn mock_process_instruction( instruction_data: &[u8], transaction_accounts: Vec, instruction_accounts: Vec, + sysvar_cache_override: Option<&SysvarCache>, expected_result: Result<(), InstructionError>, process_instruction: ProcessInstructionWithContext, ) -> Vec { @@ -1151,6 +1152,9 @@ pub fn mock_process_instruction( 1, ); let mut invoke_context = InvokeContext::new_mock(&mut transaction_context, &[]); + if let Some(sysvar_cache) = sysvar_cache_override { + invoke_context.sysvar_cache = Cow::Borrowed(sysvar_cache); + } let result = invoke_context .push( &preparation.instruction_accounts, diff --git a/programs/bpf_loader/src/lib.rs b/programs/bpf_loader/src/lib.rs index 60ea7083c0..73b957644e 100644 --- a/programs/bpf_loader/src/lib.rs +++ b/programs/bpf_loader/src/lib.rs @@ -1333,6 +1333,7 @@ mod tests { instruction_data, transaction_accounts, instruction_accounts, + None, expected_result, super::process_instruction, ) @@ -1585,6 +1586,7 @@ mod tests { &[], vec![(program_id, program_account.clone())], Vec::new(), + None, Err(InstructionError::ProgramFailedToComplete), |first_instruction_account: usize, instruction_data: &[u8], @@ -2855,6 +2857,7 @@ mod tests { &instruction_data, transaction_accounts, instruction_accounts, + None, expected_result, super::process_instruction, ) diff --git a/programs/config/src/config_processor.rs b/programs/config/src/config_processor.rs index 05accbf99c..43a6c0d93d 100644 --- a/programs/config/src/config_processor.rs +++ b/programs/config/src/config_processor.rs @@ -166,6 +166,7 @@ mod tests { instruction_data, transaction_accounts, instruction_accounts, + None, expected_result, super::process_instruction, ) diff --git a/programs/stake/src/stake_instruction.rs b/programs/stake/src/stake_instruction.rs index 49ac51a5cd..ad6ac8b68e 100644 --- a/programs/stake/src/stake_instruction.rs +++ b/programs/stake/src/stake_instruction.rs @@ -144,7 +144,7 @@ pub fn process_instruction( instruction_context.check_number_of_instruction_accounts(2)?; let split_stake = &keyed_account_at_index(keyed_accounts, first_instruction_account + 1)?; - me.split(lamports, split_stake, &signers) + me.split(invoke_context, lamports, split_stake, &signers) } StakeInstruction::Merge => { instruction_context.check_number_of_instruction_accounts(2)?; @@ -323,7 +323,9 @@ mod tests { Meta, Stake, StakeState, }, bincode::serialize, - solana_program_runtime::invoke_context::mock_process_instruction, + solana_program_runtime::{ + invoke_context::mock_process_instruction, sysvar_cache::SysvarCache, + }, solana_sdk::{ account::{self, AccountSharedData, ReadableAccount, WritableAccount}, account_utils::StateMut, @@ -373,6 +375,22 @@ mod tests { transaction_accounts: Vec<(Pubkey, AccountSharedData)>, instruction_accounts: Vec, expected_result: Result<(), InstructionError>, + ) -> Vec { + process_instruction_with_sysvar_cache( + instruction_data, + transaction_accounts, + instruction_accounts, + None, + expected_result, + ) + } + + fn process_instruction_with_sysvar_cache( + instruction_data: &[u8], + transaction_accounts: Vec<(Pubkey, AccountSharedData)>, + instruction_accounts: Vec, + sysvar_cache_override: Option<&SysvarCache>, + expected_result: Result<(), InstructionError>, ) -> Vec { mock_process_instruction( &id(), @@ -380,6 +398,7 @@ mod tests { instruction_data, transaction_accounts, instruction_accounts, + sysvar_cache_override, expected_result, super::process_instruction, ) @@ -2352,6 +2371,14 @@ mod tests { }, ]; + // Define rent here so that it's used consistently for setting the rent exempt reserve + // and in the sysvar cache used for mock instruction processing. + let mut sysvar_cache_override = SysvarCache::default(); + sysvar_cache_override.set_rent(Rent { + lamports_per_byte_year: 0, + ..Rent::default() + }); + for state in [ StakeState::Initialized(Meta::auto(&stake_address)), just_stake(Meta::auto(&stake_address), stake_lamports), @@ -2366,18 +2393,20 @@ mod tests { transaction_accounts[0] = (stake_address, stake_account); // should fail, split more than available - process_instruction( + process_instruction_with_sysvar_cache( &serialize(&StakeInstruction::Split(stake_lamports + 1)).unwrap(), transaction_accounts.clone(), instruction_accounts.clone(), + Some(&sysvar_cache_override), Err(InstructionError::InsufficientFunds), ); // should pass - let accounts = process_instruction( + let accounts = process_instruction_with_sysvar_cache( &serialize(&StakeInstruction::Split(stake_lamports / 2)).unwrap(), transaction_accounts.clone(), instruction_accounts.clone(), + Some(&sysvar_cache_override), Ok(()), ); // no lamport leakage diff --git a/programs/stake/src/stake_state.rs b/programs/stake/src/stake_state.rs index 71f6fd31ee..bd09f15c7e 100644 --- a/programs/stake/src/stake_state.rs +++ b/programs/stake/src/stake_state.rs @@ -14,7 +14,7 @@ use { account::{AccountSharedData, ReadableAccount, WritableAccount}, account_utils::{State, StateMut}, clock::{Clock, Epoch}, - feature_set::stake_merge_with_unmatched_credits_observed, + feature_set::{stake_merge_with_unmatched_credits_observed, stake_split_uses_rent_sysvar}, instruction::{checked_add, InstructionError}, keyed_account::KeyedAccount, pubkey::Pubkey, @@ -408,6 +408,7 @@ pub trait StakeAccount { ) -> Result<(), InstructionError>; fn split( &self, + invoke_context: &InvokeContext, lamports: u64, split_stake: &KeyedAccount, signers: &HashSet, @@ -604,6 +605,7 @@ impl<'a> StakeAccount for KeyedAccount<'a> { fn split( &self, + invoke_context: &InvokeContext, lamports: u64, split: &KeyedAccount, signers: &HashSet, @@ -624,8 +626,14 @@ impl<'a> StakeAccount for KeyedAccount<'a> { match self.state()? { StakeState::Stake(meta, mut stake) => { meta.authorized.check(signers, StakeAuthorize::Staker)?; - let validated_split_info = - validate_split_amount(self, split, lamports, &meta, Some(&stake))?; + let validated_split_info = validate_split_amount( + invoke_context, + self, + split, + lamports, + &meta, + Some(&stake), + )?; // split the stake, subtract rent_exempt_balance unless // the destination account already has those lamports @@ -670,7 +678,7 @@ impl<'a> StakeAccount for KeyedAccount<'a> { StakeState::Initialized(meta) => { meta.authorized.check(signers, StakeAuthorize::Staker)?; let validated_split_info = - validate_split_amount(self, split, lamports, &meta, None)?; + validate_split_amount(invoke_context, self, split, lamports, &meta, None)?; let mut split_meta = meta; split_meta.rent_exempt_reserve = validated_split_info.destination_rent_exempt_reserve; @@ -855,6 +863,7 @@ struct ValidatedSplitInfo { /// delegation, and that the source account has enough lamports for the request split amount. If /// not, return an error. fn validate_split_amount( + invoke_context: &InvokeContext, source_account: &KeyedAccount, destination_account: &KeyedAccount, lamports: u64, @@ -897,11 +906,19 @@ fn validate_split_amount( // This must handle: // 1. The destination account having a different rent exempt reserve due to data size changes // 2. The destination account being prefunded, which would lower the minimum split amount - let destination_rent_exempt_reserve = calculate_split_rent_exempt_reserve( - source_meta.rent_exempt_reserve, - source_account.data_len()? as u64, - destination_account.data_len()? as u64, - ); + let destination_rent_exempt_reserve = if invoke_context + .feature_set + .is_active(&stake_split_uses_rent_sysvar::ID) + { + let rent = invoke_context.get_sysvar_cache().get_rent()?; + rent.minimum_balance(destination_account.data_len()?) + } else { + calculate_split_rent_exempt_reserve( + source_meta.rent_exempt_reserve, + source_account.data_len()? as u64, + destination_account.data_len()? as u64, + ) + }; let destination_minimum_balance = destination_rent_exempt_reserve.saturating_add(MINIMUM_STAKE_DELEGATION); let destination_balance_deficit = @@ -1417,10 +1434,11 @@ mod tests { proptest::prelude::*, solana_program_runtime::invoke_context::InvokeContext, solana_sdk::{ - account::{AccountSharedData, WritableAccount}, + account::{create_account_shared_data_for_test, AccountSharedData, WritableAccount}, native_token, pubkey::Pubkey, system_program, + sysvar::SysvarId, transaction_context::TransactionContext, }, solana_vote_program::vote_state, @@ -2492,8 +2510,21 @@ mod tests { ); } + fn create_mock_tx_context() -> TransactionContext { + TransactionContext::new( + vec![( + Rent::id(), + create_account_shared_data_for_test(&Rent::default()), + )], + 1, + 1, + ) + } + #[test] fn test_split_source_uninitialized() { + let mut transaction_context = create_mock_tx_context(); + let invoke_context = InvokeContext::new_mock(&mut transaction_context, &[]); let rent = Rent::default(); let rent_exempt_reserve = rent.minimum_balance(std::mem::size_of::()); let stake_pubkey = solana_sdk::pubkey::new_rand(); @@ -2522,6 +2553,7 @@ mod tests { // no signers should fail assert_eq!( stake_keyed_account.split( + &invoke_context, stake_lamports / 2, &split_stake_keyed_account, &HashSet::default() // no signers @@ -2540,26 +2572,46 @@ mod tests { // // and splitting should fail when the split amount is greater than the balance assert_eq!( - stake_keyed_account.split(stake_lamports, &stake_keyed_account, &signers), + stake_keyed_account.split( + &invoke_context, + stake_lamports, + &stake_keyed_account, + &signers + ), Ok(()), ); assert_eq!( - stake_keyed_account.split(0, &stake_keyed_account, &signers), + stake_keyed_account.split(&invoke_context, 0, &stake_keyed_account, &signers), Ok(()), ); assert_eq!( - stake_keyed_account.split(stake_lamports / 2, &stake_keyed_account, &signers), + stake_keyed_account.split( + &invoke_context, + stake_lamports / 2, + &stake_keyed_account, + &signers + ), Ok(()), ); assert_eq!( - stake_keyed_account.split(stake_lamports + 1, &stake_keyed_account, &signers), + stake_keyed_account.split( + &invoke_context, + stake_lamports + 1, + &stake_keyed_account, + &signers + ), Err(InstructionError::InsufficientFunds), ); } // this should work assert_eq!( - stake_keyed_account.split(stake_lamports / 2, &split_stake_keyed_account, &signers), + stake_keyed_account.split( + &invoke_context, + stake_lamports / 2, + &split_stake_keyed_account, + &signers + ), Ok(()) ); assert_eq!( @@ -2570,6 +2622,8 @@ mod tests { #[test] fn test_split_split_not_uninitialized() { + let mut transaction_context = create_mock_tx_context(); + let invoke_context = InvokeContext::new_mock(&mut transaction_context, &[]); let stake_pubkey = solana_sdk::pubkey::new_rand(); let stake_lamports = 42; let stake_account = AccountSharedData::new_ref_data_with_space( @@ -2599,7 +2653,12 @@ mod tests { let split_stake_keyed_account = KeyedAccount::new(&split_stake_pubkey, true, &split_stake_account); assert_eq!( - stake_keyed_account.split(stake_lamports / 2, &split_stake_keyed_account, &signers), + stake_keyed_account.split( + &invoke_context, + stake_lamports / 2, + &split_stake_keyed_account, + &signers + ), Err(InstructionError::InvalidAccountData) ); } @@ -2616,6 +2675,8 @@ mod tests { #[test] fn test_split_more_than_staked() { + let mut transaction_context = create_mock_tx_context(); + let invoke_context = InvokeContext::new_mock(&mut transaction_context, &[]); let rent = Rent::default(); let rent_exempt_reserve = rent.minimum_balance(std::mem::size_of::()); let stake_pubkey = solana_sdk::pubkey::new_rand(); @@ -2648,13 +2709,20 @@ mod tests { let split_stake_keyed_account = KeyedAccount::new(&split_stake_pubkey, true, &split_stake_account); assert_eq!( - stake_keyed_account.split(stake_lamports / 2, &split_stake_keyed_account, &signers), + stake_keyed_account.split( + &invoke_context, + stake_lamports / 2, + &split_stake_keyed_account, + &signers + ), Err(StakeError::InsufficientStake.into()) ); } #[test] fn test_split_with_rent() { + let mut transaction_context = create_mock_tx_context(); + let invoke_context = InvokeContext::new_mock(&mut transaction_context, &[]); let rent = Rent::default(); let rent_exempt_reserve = rent.minimum_balance(std::mem::size_of::()); let minimum_balance = rent_exempt_reserve + MINIMUM_STAKE_DELEGATION; @@ -2698,6 +2766,7 @@ mod tests { // not enough to make a non-zero stake account assert_eq!( stake_keyed_account.split( + &invoke_context, rent_exempt_reserve, &split_stake_keyed_account, &signers @@ -2708,6 +2777,7 @@ mod tests { // doesn't leave enough for initial stake to be non-zero assert_eq!( stake_keyed_account.split( + &invoke_context, stake_lamports - rent_exempt_reserve, &split_stake_keyed_account, &signers @@ -2722,6 +2792,7 @@ mod tests { .set_lamports(minimum_balance); assert_eq!( stake_keyed_account.split( + &invoke_context, stake_lamports - minimum_balance, &split_stake_keyed_account, &signers @@ -2758,6 +2829,8 @@ mod tests { #[test] fn test_split_to_account_with_rent_exempt_reserve() { + let mut transaction_context = create_mock_tx_context(); + let invoke_context = InvokeContext::new_mock(&mut transaction_context, &[]); let stake_pubkey = solana_sdk::pubkey::new_rand(); let rent = Rent::default(); let rent_exempt_reserve = rent.minimum_balance(std::mem::size_of::()); @@ -2806,13 +2879,23 @@ mod tests { // split more than available fails assert_eq!( - stake_keyed_account.split(stake_lamports + 1, &split_stake_keyed_account, &signers), + stake_keyed_account.split( + &invoke_context, + stake_lamports + 1, + &split_stake_keyed_account, + &signers + ), Err(InstructionError::InsufficientFunds) ); // should work assert_eq!( - stake_keyed_account.split(stake_lamports / 2, &split_stake_keyed_account, &signers), + stake_keyed_account.split( + &invoke_context, + stake_lamports / 2, + &split_stake_keyed_account, + &signers + ), Ok(()) ); // no lamport leakage @@ -2863,27 +2946,28 @@ mod tests { } #[test] - fn test_split_to_smaller_account_with_rent_exempt_reserve() { + fn test_split_from_larger_sized_account() { + let mut transaction_context = create_mock_tx_context(); + let invoke_context = InvokeContext::new_mock(&mut transaction_context, &[]); let stake_pubkey = solana_sdk::pubkey::new_rand(); let rent = Rent::default(); - let rent_exempt_reserve = rent.minimum_balance(std::mem::size_of::()); - let stake_lamports = (rent_exempt_reserve + MINIMUM_STAKE_DELEGATION) * 2; + let source_larger_rent_exempt_reserve = + rent.minimum_balance(std::mem::size_of::() + 100); + let split_rent_exempt_reserve = rent.minimum_balance(std::mem::size_of::()); + let stake_lamports = (source_larger_rent_exempt_reserve + MINIMUM_STAKE_DELEGATION) * 2; let split_stake_pubkey = solana_sdk::pubkey::new_rand(); let signers = vec![stake_pubkey].into_iter().collect(); let meta = Meta { authorized: Authorized::auto(&stake_pubkey), - rent_exempt_reserve, + rent_exempt_reserve: source_larger_rent_exempt_reserve, ..Meta::default() }; - let state = StakeState::Stake(meta, just_stake(stake_lamports - rent_exempt_reserve)); - - let expected_rent_exempt_reserve = calculate_split_rent_exempt_reserve( - meta.rent_exempt_reserve, - std::mem::size_of::() as u64 + 100, - std::mem::size_of::() as u64, + let state = StakeState::Stake( + meta, + just_stake(stake_lamports - source_larger_rent_exempt_reserve), ); // Test various account prefunding, including empty, less than rent_exempt_reserve, exactly @@ -2891,10 +2975,10 @@ mod tests { // test_split, since that test uses a Meta with rent_exempt_reserve = 0 let split_lamport_balances = vec![ 0, - expected_rent_exempt_reserve - 1, - expected_rent_exempt_reserve, - expected_rent_exempt_reserve + MINIMUM_STAKE_DELEGATION - 1, - expected_rent_exempt_reserve + MINIMUM_STAKE_DELEGATION, + split_rent_exempt_reserve - 1, + split_rent_exempt_reserve, + split_rent_exempt_reserve + MINIMUM_STAKE_DELEGATION - 1, + split_rent_exempt_reserve + MINIMUM_STAKE_DELEGATION, ]; for initial_balance in split_lamport_balances { let split_stake_account = AccountSharedData::new_ref_data_with_space( @@ -2919,13 +3003,23 @@ mod tests { // split more than available fails assert_eq!( - stake_keyed_account.split(stake_lamports + 1, &split_stake_keyed_account, &signers), + stake_keyed_account.split( + &invoke_context, + stake_lamports + 1, + &split_stake_keyed_account, + &signers + ), Err(InstructionError::InsufficientFunds) ); // should work assert_eq!( - stake_keyed_account.split(stake_lamports / 2, &split_stake_keyed_account, &signers), + stake_keyed_account.split( + &invoke_context, + stake_lamports / 2, + &split_stake_keyed_account, + &signers + ), Ok(()) ); // no lamport leakage @@ -2938,11 +3032,11 @@ mod tests { if let StakeState::Stake(meta, stake) = state { let expected_split_meta = Meta { authorized: Authorized::auto(&stake_pubkey), - rent_exempt_reserve: expected_rent_exempt_reserve, + rent_exempt_reserve: split_rent_exempt_reserve, ..Meta::default() }; let expected_stake = stake_lamports / 2 - - (expected_rent_exempt_reserve.saturating_sub(initial_balance)); + - (split_rent_exempt_reserve.saturating_sub(initial_balance)); assert_eq!( Ok(StakeState::Stake( @@ -2960,15 +3054,15 @@ mod tests { assert_eq!( split_stake_keyed_account.account.borrow().lamports(), expected_stake - + expected_rent_exempt_reserve - + initial_balance.saturating_sub(expected_rent_exempt_reserve) + + split_rent_exempt_reserve + + initial_balance.saturating_sub(split_rent_exempt_reserve) ); assert_eq!( Ok(StakeState::Stake( meta, Stake { delegation: Delegation { - stake: stake_lamports / 2 - rent_exempt_reserve, + stake: stake_lamports / 2 - source_larger_rent_exempt_reserve, ..stake.delegation }, ..stake @@ -2981,35 +3075,38 @@ mod tests { } #[test] - fn test_split_to_larger_account() { + fn test_split_from_smaller_sized_account() { + let mut transaction_context = create_mock_tx_context(); + let invoke_context = InvokeContext::new_mock(&mut transaction_context, &[]); let stake_pubkey = solana_sdk::pubkey::new_rand(); let rent = Rent::default(); - let rent_exempt_reserve = rent.minimum_balance(std::mem::size_of::()); + let source_smaller_rent_exempt_reserve = + rent.minimum_balance(std::mem::size_of::()); + let split_rent_exempt_reserve = + rent.minimum_balance(std::mem::size_of::() + 100); let split_stake_pubkey = solana_sdk::pubkey::new_rand(); let signers = vec![stake_pubkey].into_iter().collect(); let meta = Meta { authorized: Authorized::auto(&stake_pubkey), - rent_exempt_reserve, + rent_exempt_reserve: source_smaller_rent_exempt_reserve, ..Meta::default() }; - let expected_rent_exempt_reserve = calculate_split_rent_exempt_reserve( - meta.rent_exempt_reserve, - std::mem::size_of::() as u64, - std::mem::size_of::() as u64 + 100, - ); - let stake_lamports = expected_rent_exempt_reserve + 1; - let split_amount = stake_lamports - (rent_exempt_reserve + 1); // Enough so that split stake is > 0 + let stake_lamports = split_rent_exempt_reserve + 1; + let split_amount = stake_lamports - (source_smaller_rent_exempt_reserve + 1); // Enough so that split stake is > 0 - let state = StakeState::Stake(meta, just_stake(stake_lamports - rent_exempt_reserve)); + let state = StakeState::Stake( + meta, + just_stake(stake_lamports - source_smaller_rent_exempt_reserve), + ); let split_lamport_balances = vec![ 0, 1, - expected_rent_exempt_reserve, - expected_rent_exempt_reserve + 1, + split_rent_exempt_reserve, + split_rent_exempt_reserve + 1, ]; for initial_balance in split_lamport_balances { let split_stake_account = AccountSharedData::new_ref_data_with_space( @@ -3033,19 +3130,29 @@ mod tests { let stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &stake_account); // should always return error when splitting to larger account - let split_result = - stake_keyed_account.split(split_amount, &split_stake_keyed_account, &signers); + let split_result = stake_keyed_account.split( + &invoke_context, + split_amount, + &split_stake_keyed_account, + &signers, + ); assert_eq!(split_result, Err(InstructionError::InvalidAccountData)); // Splitting 100% of source should not make a difference - let split_result = - stake_keyed_account.split(stake_lamports, &split_stake_keyed_account, &signers); + let split_result = stake_keyed_account.split( + &invoke_context, + stake_lamports, + &split_stake_keyed_account, + &signers, + ); assert_eq!(split_result, Err(InstructionError::InvalidAccountData)); } } #[test] fn test_split_100_percent_of_source() { + let mut transaction_context = create_mock_tx_context(); + let invoke_context = InvokeContext::new_mock(&mut transaction_context, &[]); let stake_pubkey = solana_sdk::pubkey::new_rand(); let rent = Rent::default(); let rent_exempt_reserve = rent.minimum_balance(std::mem::size_of::()); @@ -3087,7 +3194,12 @@ mod tests { // split 100% over to dest assert_eq!( - stake_keyed_account.split(stake_lamports, &split_stake_keyed_account, &signers), + stake_keyed_account.split( + &invoke_context, + stake_lamports, + &split_stake_keyed_account, + &signers + ), Ok(()) ); @@ -3132,6 +3244,8 @@ mod tests { #[test] fn test_split_100_percent_of_source_to_account_with_lamports() { + let mut transaction_context = create_mock_tx_context(); + let invoke_context = InvokeContext::new_mock(&mut transaction_context, &[]); let stake_pubkey = solana_sdk::pubkey::new_rand(); let rent = Rent::default(); let rent_exempt_reserve = rent.minimum_balance(std::mem::size_of::()); @@ -3180,7 +3294,12 @@ mod tests { // split 100% over to dest assert_eq!( - stake_keyed_account.split(stake_lamports, &split_stake_keyed_account, &signers), + stake_keyed_account.split( + &invoke_context, + stake_lamports, + &split_stake_keyed_account, + &signers + ), Ok(()) ); @@ -3212,23 +3331,30 @@ mod tests { #[test] fn test_split_rent_exemptness() { + let mut transaction_context = create_mock_tx_context(); + let invoke_context = InvokeContext::new_mock(&mut transaction_context, &[]); let stake_pubkey = solana_sdk::pubkey::new_rand(); let rent = Rent::default(); - let rent_exempt_reserve = rent.minimum_balance(std::mem::size_of::()); - let stake_lamports = rent_exempt_reserve + MINIMUM_STAKE_DELEGATION; + let source_rent_exempt_reserve = + rent.minimum_balance(std::mem::size_of::() + 100); + let split_rent_exempt_reserve = rent.minimum_balance(std::mem::size_of::()); + let stake_lamports = source_rent_exempt_reserve + MINIMUM_STAKE_DELEGATION; let split_stake_pubkey = solana_sdk::pubkey::new_rand(); let signers = vec![stake_pubkey].into_iter().collect(); let meta = Meta { authorized: Authorized::auto(&stake_pubkey), - rent_exempt_reserve, + rent_exempt_reserve: source_rent_exempt_reserve, ..Meta::default() }; for state in &[ StakeState::Initialized(meta), - StakeState::Stake(meta, just_stake(stake_lamports - rent_exempt_reserve)), + StakeState::Stake( + meta, + just_stake(stake_lamports - source_rent_exempt_reserve), + ), ] { // Test that splitting to a larger account fails let split_stake_account = AccountSharedData::new_ref_data_with_space( @@ -3251,7 +3377,12 @@ mod tests { let stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &stake_account); assert_eq!( - stake_keyed_account.split(stake_lamports, &split_stake_keyed_account, &signers), + stake_keyed_account.split( + &invoke_context, + stake_lamports, + &split_stake_keyed_account, + &signers + ), Err(InstructionError::InvalidAccountData) ); @@ -3277,7 +3408,12 @@ mod tests { let stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &stake_account); assert_eq!( - stake_keyed_account.split(stake_lamports, &split_stake_keyed_account, &signers), + stake_keyed_account.split( + &invoke_context, + stake_lamports, + &split_stake_keyed_account, + &signers + ), Ok(()) ); @@ -3286,14 +3422,9 @@ mod tests { stake_lamports ); - let expected_rent_exempt_reserve = calculate_split_rent_exempt_reserve( - meta.rent_exempt_reserve, - std::mem::size_of::() as u64 + 100, - std::mem::size_of::() as u64, - ); let expected_split_meta = Meta { authorized: Authorized::auto(&stake_pubkey), - rent_exempt_reserve: expected_rent_exempt_reserve, + rent_exempt_reserve: split_rent_exempt_reserve, ..Meta::default() }; @@ -3308,7 +3439,7 @@ mod tests { StakeState::Stake(_meta, stake) => { // Expected stake should reflect original stake amount so that extra lamports // from the rent_exempt_reserve inequality do not magically activate - let expected_stake = stake_lamports - rent_exempt_reserve; + let expected_stake = stake_lamports - source_rent_exempt_reserve; assert_eq!( Ok(StakeState::Stake( @@ -3325,9 +3456,7 @@ mod tests { ); assert_eq!( split_stake_keyed_account.account.borrow().lamports(), - expected_stake - + expected_rent_exempt_reserve - + (rent_exempt_reserve - expected_rent_exempt_reserve) + expected_stake + source_rent_exempt_reserve, ); assert_eq!(Ok(StakeState::Uninitialized), stake_keyed_account.state()); } @@ -4581,7 +4710,7 @@ mod tests { #[test] fn test_active_stake_merge() { - let mut transaction_context = TransactionContext::new(Vec::new(), 1, 1); + let mut transaction_context = create_mock_tx_context(); let invoke_context = InvokeContext::new_mock(&mut transaction_context, &[]); let clock = Clock::default(); let delegation_a = 4_242_424_242u64; @@ -4823,6 +4952,8 @@ mod tests { /// LT | LT | Err #[test] fn test_split_minimum_stake_delegation() { + let mut transaction_context = create_mock_tx_context(); + let invoke_context = InvokeContext::new_mock(&mut transaction_context, &[]); for (source_stake_delegation, dest_stake_delegation, expected_result) in [ (MINIMUM_STAKE_DELEGATION, MINIMUM_STAKE_DELEGATION, Ok(())), ( @@ -4882,6 +5013,7 @@ mod tests { assert_eq!( expected_result, source_keyed_account.split( + &invoke_context, dest_stake_delegation + rent_exempt_reserve, &dest_keyed_account, &HashSet::from([source_pubkey]), @@ -4900,6 +5032,8 @@ mod tests { /// delegation is not OK #[test] fn test_split_full_amount_minimum_stake_delegation() { + let mut transaction_context = create_mock_tx_context(); + let invoke_context = InvokeContext::new_mock(&mut transaction_context, &[]); for (stake_delegation, expected_result) in [ (MINIMUM_STAKE_DELEGATION, Ok(())), ( @@ -4941,6 +5075,7 @@ mod tests { assert_eq!( expected_result, source_keyed_account.split( + &invoke_context, source_keyed_account.lamports().unwrap(), &dest_keyed_account, &HashSet::from([source_pubkey]), @@ -4954,6 +5089,8 @@ mod tests { /// account already has funds, ensure the minimum split amount reduces accordingly. #[test] fn test_split_destination_minimum_stake_delegation() { + let mut transaction_context = create_mock_tx_context(); + let invoke_context = InvokeContext::new_mock(&mut transaction_context, &[]); let rent = Rent::default(); let rent_exempt_reserve = rent.minimum_balance(std::mem::size_of::()); @@ -5061,6 +5198,7 @@ mod tests { assert_eq!( expected_result, source_keyed_account.split( + &invoke_context, split_amount, &destination_keyed_account, &HashSet::from([source_pubkey]), diff --git a/programs/vote/src/vote_processor.rs b/programs/vote/src/vote_processor.rs index 4662d49f3e..c57f4e0161 100644 --- a/programs/vote/src/vote_processor.rs +++ b/programs/vote/src/vote_processor.rs @@ -216,6 +216,7 @@ mod tests { instruction_data, transaction_accounts, instruction_accounts, + None, expected_result, super::process_instruction, ) @@ -233,6 +234,7 @@ mod tests { instruction_data, transaction_accounts, instruction_accounts, + None, expected_result, |first_instruction_account: usize, instruction_data: &[u8], diff --git a/runtime/src/system_instruction_processor.rs b/runtime/src/system_instruction_processor.rs index cdede8ed2f..4927c39ee9 100644 --- a/runtime/src/system_instruction_processor.rs +++ b/runtime/src/system_instruction_processor.rs @@ -530,6 +530,7 @@ mod tests { instruction_data, transaction_accounts, instruction_accounts, + None, expected_result, process_instruction, ) diff --git a/sdk/src/feature_set.rs b/sdk/src/feature_set.rs index 3cbaa56df3..bd0c32c71c 100644 --- a/sdk/src/feature_set.rs +++ b/sdk/src/feature_set.rs @@ -335,6 +335,10 @@ pub mod check_slice_translation_size { solana_sdk::declare_id!("GmC19j9qLn2RFk5NduX6QXaDhVpGncVVBzyM8e9WMz2F"); } +pub mod stake_split_uses_rent_sysvar { + solana_sdk::declare_id!("FQnc7U4koHqWgRvFaBJjZnV8VPg6L6wWK33yJeDp4yvV"); +} + lazy_static! { /// Map of feature identifiers to user-visible description pub static ref FEATURE_NAMES: HashMap = [ @@ -412,7 +416,8 @@ lazy_static! { (check_physical_overlapping::id(), "check physical overlapping regions"), (limit_secp256k1_recovery_id::id(), "limit secp256k1 recovery id"), (disable_deprecated_loader::id(), "disable the deprecated BPF loader"), - (check_slice_translation_size::id(), "check size when translating slices",) + (check_slice_translation_size::id(), "check size when translating slices"), + (stake_split_uses_rent_sysvar::id(), "stake split instruction uses rent sysvar"), /*************** ADD NEW FEATURES HERE ***************/ ] .iter()