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
This commit is contained in:
Justin Starry 2022-03-31 16:46:35 +08:00 committed by GitHub
parent 210d98bc06
commit cb5e67d327
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 264 additions and 81 deletions

View File

@ -1135,6 +1135,7 @@ pub fn mock_process_instruction(
instruction_data: &[u8], instruction_data: &[u8],
transaction_accounts: Vec<TransactionAccount>, transaction_accounts: Vec<TransactionAccount>,
instruction_accounts: Vec<AccountMeta>, instruction_accounts: Vec<AccountMeta>,
sysvar_cache_override: Option<&SysvarCache>,
expected_result: Result<(), InstructionError>, expected_result: Result<(), InstructionError>,
process_instruction: ProcessInstructionWithContext, process_instruction: ProcessInstructionWithContext,
) -> Vec<AccountSharedData> { ) -> Vec<AccountSharedData> {
@ -1151,6 +1152,9 @@ pub fn mock_process_instruction(
1, 1,
); );
let mut invoke_context = InvokeContext::new_mock(&mut transaction_context, &[]); 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 let result = invoke_context
.push( .push(
&preparation.instruction_accounts, &preparation.instruction_accounts,

View File

@ -1333,6 +1333,7 @@ mod tests {
instruction_data, instruction_data,
transaction_accounts, transaction_accounts,
instruction_accounts, instruction_accounts,
None,
expected_result, expected_result,
super::process_instruction, super::process_instruction,
) )
@ -1585,6 +1586,7 @@ mod tests {
&[], &[],
vec![(program_id, program_account.clone())], vec![(program_id, program_account.clone())],
Vec::new(), Vec::new(),
None,
Err(InstructionError::ProgramFailedToComplete), Err(InstructionError::ProgramFailedToComplete),
|first_instruction_account: usize, |first_instruction_account: usize,
instruction_data: &[u8], instruction_data: &[u8],
@ -2855,6 +2857,7 @@ mod tests {
&instruction_data, &instruction_data,
transaction_accounts, transaction_accounts,
instruction_accounts, instruction_accounts,
None,
expected_result, expected_result,
super::process_instruction, super::process_instruction,
) )

View File

@ -166,6 +166,7 @@ mod tests {
instruction_data, instruction_data,
transaction_accounts, transaction_accounts,
instruction_accounts, instruction_accounts,
None,
expected_result, expected_result,
super::process_instruction, super::process_instruction,
) )

View File

@ -144,7 +144,7 @@ pub fn process_instruction(
instruction_context.check_number_of_instruction_accounts(2)?; instruction_context.check_number_of_instruction_accounts(2)?;
let split_stake = let split_stake =
&keyed_account_at_index(keyed_accounts, first_instruction_account + 1)?; &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 => { StakeInstruction::Merge => {
instruction_context.check_number_of_instruction_accounts(2)?; instruction_context.check_number_of_instruction_accounts(2)?;
@ -323,7 +323,9 @@ mod tests {
Meta, Stake, StakeState, Meta, Stake, StakeState,
}, },
bincode::serialize, bincode::serialize,
solana_program_runtime::invoke_context::mock_process_instruction, solana_program_runtime::{
invoke_context::mock_process_instruction, sysvar_cache::SysvarCache,
},
solana_sdk::{ solana_sdk::{
account::{self, AccountSharedData, ReadableAccount, WritableAccount}, account::{self, AccountSharedData, ReadableAccount, WritableAccount},
account_utils::StateMut, account_utils::StateMut,
@ -373,6 +375,22 @@ mod tests {
transaction_accounts: Vec<(Pubkey, AccountSharedData)>, transaction_accounts: Vec<(Pubkey, AccountSharedData)>,
instruction_accounts: Vec<AccountMeta>, instruction_accounts: Vec<AccountMeta>,
expected_result: Result<(), InstructionError>, expected_result: Result<(), InstructionError>,
) -> Vec<AccountSharedData> {
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<AccountMeta>,
sysvar_cache_override: Option<&SysvarCache>,
expected_result: Result<(), InstructionError>,
) -> Vec<AccountSharedData> { ) -> Vec<AccountSharedData> {
mock_process_instruction( mock_process_instruction(
&id(), &id(),
@ -380,6 +398,7 @@ mod tests {
instruction_data, instruction_data,
transaction_accounts, transaction_accounts,
instruction_accounts, instruction_accounts,
sysvar_cache_override,
expected_result, expected_result,
super::process_instruction, 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 [ for state in [
StakeState::Initialized(Meta::auto(&stake_address)), StakeState::Initialized(Meta::auto(&stake_address)),
just_stake(Meta::auto(&stake_address), stake_lamports), just_stake(Meta::auto(&stake_address), stake_lamports),
@ -2366,18 +2393,20 @@ mod tests {
transaction_accounts[0] = (stake_address, stake_account); transaction_accounts[0] = (stake_address, stake_account);
// should fail, split more than available // should fail, split more than available
process_instruction( process_instruction_with_sysvar_cache(
&serialize(&StakeInstruction::Split(stake_lamports + 1)).unwrap(), &serialize(&StakeInstruction::Split(stake_lamports + 1)).unwrap(),
transaction_accounts.clone(), transaction_accounts.clone(),
instruction_accounts.clone(), instruction_accounts.clone(),
Some(&sysvar_cache_override),
Err(InstructionError::InsufficientFunds), Err(InstructionError::InsufficientFunds),
); );
// should pass // should pass
let accounts = process_instruction( let accounts = process_instruction_with_sysvar_cache(
&serialize(&StakeInstruction::Split(stake_lamports / 2)).unwrap(), &serialize(&StakeInstruction::Split(stake_lamports / 2)).unwrap(),
transaction_accounts.clone(), transaction_accounts.clone(),
instruction_accounts.clone(), instruction_accounts.clone(),
Some(&sysvar_cache_override),
Ok(()), Ok(()),
); );
// no lamport leakage // no lamport leakage

View File

@ -14,7 +14,7 @@ use {
account::{AccountSharedData, ReadableAccount, WritableAccount}, account::{AccountSharedData, ReadableAccount, WritableAccount},
account_utils::{State, StateMut}, account_utils::{State, StateMut},
clock::{Clock, Epoch}, 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}, instruction::{checked_add, InstructionError},
keyed_account::KeyedAccount, keyed_account::KeyedAccount,
pubkey::Pubkey, pubkey::Pubkey,
@ -408,6 +408,7 @@ pub trait StakeAccount {
) -> Result<(), InstructionError>; ) -> Result<(), InstructionError>;
fn split( fn split(
&self, &self,
invoke_context: &InvokeContext,
lamports: u64, lamports: u64,
split_stake: &KeyedAccount, split_stake: &KeyedAccount,
signers: &HashSet<Pubkey>, signers: &HashSet<Pubkey>,
@ -604,6 +605,7 @@ impl<'a> StakeAccount for KeyedAccount<'a> {
fn split( fn split(
&self, &self,
invoke_context: &InvokeContext,
lamports: u64, lamports: u64,
split: &KeyedAccount, split: &KeyedAccount,
signers: &HashSet<Pubkey>, signers: &HashSet<Pubkey>,
@ -624,8 +626,14 @@ impl<'a> StakeAccount for KeyedAccount<'a> {
match self.state()? { match self.state()? {
StakeState::Stake(meta, mut stake) => { StakeState::Stake(meta, mut stake) => {
meta.authorized.check(signers, StakeAuthorize::Staker)?; meta.authorized.check(signers, StakeAuthorize::Staker)?;
let validated_split_info = let validated_split_info = validate_split_amount(
validate_split_amount(self, split, lamports, &meta, Some(&stake))?; invoke_context,
self,
split,
lamports,
&meta,
Some(&stake),
)?;
// split the stake, subtract rent_exempt_balance unless // split the stake, subtract rent_exempt_balance unless
// the destination account already has those lamports // the destination account already has those lamports
@ -670,7 +678,7 @@ impl<'a> StakeAccount for KeyedAccount<'a> {
StakeState::Initialized(meta) => { StakeState::Initialized(meta) => {
meta.authorized.check(signers, StakeAuthorize::Staker)?; meta.authorized.check(signers, StakeAuthorize::Staker)?;
let validated_split_info = 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; let mut split_meta = meta;
split_meta.rent_exempt_reserve = split_meta.rent_exempt_reserve =
validated_split_info.destination_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 /// delegation, and that the source account has enough lamports for the request split amount. If
/// not, return an error. /// not, return an error.
fn validate_split_amount( fn validate_split_amount(
invoke_context: &InvokeContext,
source_account: &KeyedAccount, source_account: &KeyedAccount,
destination_account: &KeyedAccount, destination_account: &KeyedAccount,
lamports: u64, lamports: u64,
@ -897,11 +906,19 @@ fn validate_split_amount(
// This must handle: // This must handle:
// 1. The destination account having a different rent exempt reserve due to data size changes // 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 // 2. The destination account being prefunded, which would lower the minimum split amount
let destination_rent_exempt_reserve = calculate_split_rent_exempt_reserve( let destination_rent_exempt_reserve = if invoke_context
source_meta.rent_exempt_reserve, .feature_set
source_account.data_len()? as u64, .is_active(&stake_split_uses_rent_sysvar::ID)
destination_account.data_len()? as u64, {
); 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 = let destination_minimum_balance =
destination_rent_exempt_reserve.saturating_add(MINIMUM_STAKE_DELEGATION); destination_rent_exempt_reserve.saturating_add(MINIMUM_STAKE_DELEGATION);
let destination_balance_deficit = let destination_balance_deficit =
@ -1417,10 +1434,11 @@ mod tests {
proptest::prelude::*, proptest::prelude::*,
solana_program_runtime::invoke_context::InvokeContext, solana_program_runtime::invoke_context::InvokeContext,
solana_sdk::{ solana_sdk::{
account::{AccountSharedData, WritableAccount}, account::{create_account_shared_data_for_test, AccountSharedData, WritableAccount},
native_token, native_token,
pubkey::Pubkey, pubkey::Pubkey,
system_program, system_program,
sysvar::SysvarId,
transaction_context::TransactionContext, transaction_context::TransactionContext,
}, },
solana_vote_program::vote_state, 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] #[test]
fn test_split_source_uninitialized() { 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 = Rent::default();
let rent_exempt_reserve = rent.minimum_balance(std::mem::size_of::<StakeState>()); let rent_exempt_reserve = rent.minimum_balance(std::mem::size_of::<StakeState>());
let stake_pubkey = solana_sdk::pubkey::new_rand(); let stake_pubkey = solana_sdk::pubkey::new_rand();
@ -2522,6 +2553,7 @@ mod tests {
// no signers should fail // no signers should fail
assert_eq!( assert_eq!(
stake_keyed_account.split( stake_keyed_account.split(
&invoke_context,
stake_lamports / 2, stake_lamports / 2,
&split_stake_keyed_account, &split_stake_keyed_account,
&HashSet::default() // no signers &HashSet::default() // no signers
@ -2540,26 +2572,46 @@ mod tests {
// //
// and splitting should fail when the split amount is greater than the balance // and splitting should fail when the split amount is greater than the balance
assert_eq!( 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(()), Ok(()),
); );
assert_eq!( assert_eq!(
stake_keyed_account.split(0, &stake_keyed_account, &signers), stake_keyed_account.split(&invoke_context, 0, &stake_keyed_account, &signers),
Ok(()), Ok(()),
); );
assert_eq!( 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(()), Ok(()),
); );
assert_eq!( 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), Err(InstructionError::InsufficientFunds),
); );
} }
// this should work // this should work
assert_eq!( 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(()) Ok(())
); );
assert_eq!( assert_eq!(
@ -2570,6 +2622,8 @@ mod tests {
#[test] #[test]
fn test_split_split_not_uninitialized() { 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_pubkey = solana_sdk::pubkey::new_rand();
let stake_lamports = 42; let stake_lamports = 42;
let stake_account = AccountSharedData::new_ref_data_with_space( let stake_account = AccountSharedData::new_ref_data_with_space(
@ -2599,7 +2653,12 @@ mod tests {
let split_stake_keyed_account = let split_stake_keyed_account =
KeyedAccount::new(&split_stake_pubkey, true, &split_stake_account); KeyedAccount::new(&split_stake_pubkey, true, &split_stake_account);
assert_eq!( 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) Err(InstructionError::InvalidAccountData)
); );
} }
@ -2616,6 +2675,8 @@ mod tests {
#[test] #[test]
fn test_split_more_than_staked() { 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 = Rent::default();
let rent_exempt_reserve = rent.minimum_balance(std::mem::size_of::<StakeState>()); let rent_exempt_reserve = rent.minimum_balance(std::mem::size_of::<StakeState>());
let stake_pubkey = solana_sdk::pubkey::new_rand(); let stake_pubkey = solana_sdk::pubkey::new_rand();
@ -2648,13 +2709,20 @@ mod tests {
let split_stake_keyed_account = let split_stake_keyed_account =
KeyedAccount::new(&split_stake_pubkey, true, &split_stake_account); KeyedAccount::new(&split_stake_pubkey, true, &split_stake_account);
assert_eq!( 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()) Err(StakeError::InsufficientStake.into())
); );
} }
#[test] #[test]
fn test_split_with_rent() { 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 = Rent::default();
let rent_exempt_reserve = rent.minimum_balance(std::mem::size_of::<StakeState>()); let rent_exempt_reserve = rent.minimum_balance(std::mem::size_of::<StakeState>());
let minimum_balance = rent_exempt_reserve + MINIMUM_STAKE_DELEGATION; let minimum_balance = rent_exempt_reserve + MINIMUM_STAKE_DELEGATION;
@ -2698,6 +2766,7 @@ mod tests {
// not enough to make a non-zero stake account // not enough to make a non-zero stake account
assert_eq!( assert_eq!(
stake_keyed_account.split( stake_keyed_account.split(
&invoke_context,
rent_exempt_reserve, rent_exempt_reserve,
&split_stake_keyed_account, &split_stake_keyed_account,
&signers &signers
@ -2708,6 +2777,7 @@ mod tests {
// doesn't leave enough for initial stake to be non-zero // doesn't leave enough for initial stake to be non-zero
assert_eq!( assert_eq!(
stake_keyed_account.split( stake_keyed_account.split(
&invoke_context,
stake_lamports - rent_exempt_reserve, stake_lamports - rent_exempt_reserve,
&split_stake_keyed_account, &split_stake_keyed_account,
&signers &signers
@ -2722,6 +2792,7 @@ mod tests {
.set_lamports(minimum_balance); .set_lamports(minimum_balance);
assert_eq!( assert_eq!(
stake_keyed_account.split( stake_keyed_account.split(
&invoke_context,
stake_lamports - minimum_balance, stake_lamports - minimum_balance,
&split_stake_keyed_account, &split_stake_keyed_account,
&signers &signers
@ -2758,6 +2829,8 @@ mod tests {
#[test] #[test]
fn test_split_to_account_with_rent_exempt_reserve() { 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 stake_pubkey = solana_sdk::pubkey::new_rand();
let rent = Rent::default(); let rent = Rent::default();
let rent_exempt_reserve = rent.minimum_balance(std::mem::size_of::<StakeState>()); let rent_exempt_reserve = rent.minimum_balance(std::mem::size_of::<StakeState>());
@ -2806,13 +2879,23 @@ mod tests {
// split more than available fails // split more than available fails
assert_eq!( 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) Err(InstructionError::InsufficientFunds)
); );
// should work // should work
assert_eq!( 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(()) Ok(())
); );
// no lamport leakage // no lamport leakage
@ -2863,27 +2946,28 @@ mod tests {
} }
#[test] #[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 stake_pubkey = solana_sdk::pubkey::new_rand();
let rent = Rent::default(); let rent = Rent::default();
let rent_exempt_reserve = rent.minimum_balance(std::mem::size_of::<StakeState>()); let source_larger_rent_exempt_reserve =
let stake_lamports = (rent_exempt_reserve + MINIMUM_STAKE_DELEGATION) * 2; rent.minimum_balance(std::mem::size_of::<StakeState>() + 100);
let split_rent_exempt_reserve = rent.minimum_balance(std::mem::size_of::<StakeState>());
let stake_lamports = (source_larger_rent_exempt_reserve + MINIMUM_STAKE_DELEGATION) * 2;
let split_stake_pubkey = solana_sdk::pubkey::new_rand(); let split_stake_pubkey = solana_sdk::pubkey::new_rand();
let signers = vec![stake_pubkey].into_iter().collect(); let signers = vec![stake_pubkey].into_iter().collect();
let meta = Meta { let meta = Meta {
authorized: Authorized::auto(&stake_pubkey), authorized: Authorized::auto(&stake_pubkey),
rent_exempt_reserve, rent_exempt_reserve: source_larger_rent_exempt_reserve,
..Meta::default() ..Meta::default()
}; };
let state = StakeState::Stake(meta, just_stake(stake_lamports - rent_exempt_reserve)); let state = StakeState::Stake(
meta,
let expected_rent_exempt_reserve = calculate_split_rent_exempt_reserve( just_stake(stake_lamports - source_larger_rent_exempt_reserve),
meta.rent_exempt_reserve,
std::mem::size_of::<StakeState>() as u64 + 100,
std::mem::size_of::<StakeState>() as u64,
); );
// Test various account prefunding, including empty, less than rent_exempt_reserve, exactly // 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 // test_split, since that test uses a Meta with rent_exempt_reserve = 0
let split_lamport_balances = vec![ let split_lamport_balances = vec![
0, 0,
expected_rent_exempt_reserve - 1, split_rent_exempt_reserve - 1,
expected_rent_exempt_reserve, split_rent_exempt_reserve,
expected_rent_exempt_reserve + MINIMUM_STAKE_DELEGATION - 1, split_rent_exempt_reserve + MINIMUM_STAKE_DELEGATION - 1,
expected_rent_exempt_reserve + MINIMUM_STAKE_DELEGATION, split_rent_exempt_reserve + MINIMUM_STAKE_DELEGATION,
]; ];
for initial_balance in split_lamport_balances { for initial_balance in split_lamport_balances {
let split_stake_account = AccountSharedData::new_ref_data_with_space( let split_stake_account = AccountSharedData::new_ref_data_with_space(
@ -2919,13 +3003,23 @@ mod tests {
// split more than available fails // split more than available fails
assert_eq!( 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) Err(InstructionError::InsufficientFunds)
); );
// should work // should work
assert_eq!( 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(()) Ok(())
); );
// no lamport leakage // no lamport leakage
@ -2938,11 +3032,11 @@ mod tests {
if let StakeState::Stake(meta, stake) = state { if let StakeState::Stake(meta, stake) = state {
let expected_split_meta = Meta { let expected_split_meta = Meta {
authorized: Authorized::auto(&stake_pubkey), authorized: Authorized::auto(&stake_pubkey),
rent_exempt_reserve: expected_rent_exempt_reserve, rent_exempt_reserve: split_rent_exempt_reserve,
..Meta::default() ..Meta::default()
}; };
let expected_stake = stake_lamports / 2 let expected_stake = stake_lamports / 2
- (expected_rent_exempt_reserve.saturating_sub(initial_balance)); - (split_rent_exempt_reserve.saturating_sub(initial_balance));
assert_eq!( assert_eq!(
Ok(StakeState::Stake( Ok(StakeState::Stake(
@ -2960,15 +3054,15 @@ mod tests {
assert_eq!( assert_eq!(
split_stake_keyed_account.account.borrow().lamports(), split_stake_keyed_account.account.borrow().lamports(),
expected_stake expected_stake
+ expected_rent_exempt_reserve + split_rent_exempt_reserve
+ initial_balance.saturating_sub(expected_rent_exempt_reserve) + initial_balance.saturating_sub(split_rent_exempt_reserve)
); );
assert_eq!( assert_eq!(
Ok(StakeState::Stake( Ok(StakeState::Stake(
meta, meta,
Stake { Stake {
delegation: Delegation { delegation: Delegation {
stake: stake_lamports / 2 - rent_exempt_reserve, stake: stake_lamports / 2 - source_larger_rent_exempt_reserve,
..stake.delegation ..stake.delegation
}, },
..stake ..stake
@ -2981,35 +3075,38 @@ mod tests {
} }
#[test] #[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 stake_pubkey = solana_sdk::pubkey::new_rand();
let rent = Rent::default(); let rent = Rent::default();
let rent_exempt_reserve = rent.minimum_balance(std::mem::size_of::<StakeState>()); let source_smaller_rent_exempt_reserve =
rent.minimum_balance(std::mem::size_of::<StakeState>());
let split_rent_exempt_reserve =
rent.minimum_balance(std::mem::size_of::<StakeState>() + 100);
let split_stake_pubkey = solana_sdk::pubkey::new_rand(); let split_stake_pubkey = solana_sdk::pubkey::new_rand();
let signers = vec![stake_pubkey].into_iter().collect(); let signers = vec![stake_pubkey].into_iter().collect();
let meta = Meta { let meta = Meta {
authorized: Authorized::auto(&stake_pubkey), authorized: Authorized::auto(&stake_pubkey),
rent_exempt_reserve, rent_exempt_reserve: source_smaller_rent_exempt_reserve,
..Meta::default() ..Meta::default()
}; };
let expected_rent_exempt_reserve = calculate_split_rent_exempt_reserve( let stake_lamports = split_rent_exempt_reserve + 1;
meta.rent_exempt_reserve, let split_amount = stake_lamports - (source_smaller_rent_exempt_reserve + 1); // Enough so that split stake is > 0
std::mem::size_of::<StakeState>() as u64,
std::mem::size_of::<StakeState>() 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 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![ let split_lamport_balances = vec![
0, 0,
1, 1,
expected_rent_exempt_reserve, split_rent_exempt_reserve,
expected_rent_exempt_reserve + 1, split_rent_exempt_reserve + 1,
]; ];
for initial_balance in split_lamport_balances { for initial_balance in split_lamport_balances {
let split_stake_account = AccountSharedData::new_ref_data_with_space( 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); let stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &stake_account);
// should always return error when splitting to larger account // should always return error when splitting to larger account
let split_result = let split_result = stake_keyed_account.split(
stake_keyed_account.split(split_amount, &split_stake_keyed_account, &signers); &invoke_context,
split_amount,
&split_stake_keyed_account,
&signers,
);
assert_eq!(split_result, Err(InstructionError::InvalidAccountData)); assert_eq!(split_result, Err(InstructionError::InvalidAccountData));
// Splitting 100% of source should not make a difference // Splitting 100% of source should not make a difference
let split_result = let split_result = stake_keyed_account.split(
stake_keyed_account.split(stake_lamports, &split_stake_keyed_account, &signers); &invoke_context,
stake_lamports,
&split_stake_keyed_account,
&signers,
);
assert_eq!(split_result, Err(InstructionError::InvalidAccountData)); assert_eq!(split_result, Err(InstructionError::InvalidAccountData));
} }
} }
#[test] #[test]
fn test_split_100_percent_of_source() { 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 stake_pubkey = solana_sdk::pubkey::new_rand();
let rent = Rent::default(); let rent = Rent::default();
let rent_exempt_reserve = rent.minimum_balance(std::mem::size_of::<StakeState>()); let rent_exempt_reserve = rent.minimum_balance(std::mem::size_of::<StakeState>());
@ -3087,7 +3194,12 @@ mod tests {
// split 100% over to dest // split 100% over to dest
assert_eq!( 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(()) Ok(())
); );
@ -3132,6 +3244,8 @@ mod tests {
#[test] #[test]
fn test_split_100_percent_of_source_to_account_with_lamports() { 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 stake_pubkey = solana_sdk::pubkey::new_rand();
let rent = Rent::default(); let rent = Rent::default();
let rent_exempt_reserve = rent.minimum_balance(std::mem::size_of::<StakeState>()); let rent_exempt_reserve = rent.minimum_balance(std::mem::size_of::<StakeState>());
@ -3180,7 +3294,12 @@ mod tests {
// split 100% over to dest // split 100% over to dest
assert_eq!( 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(()) Ok(())
); );
@ -3212,23 +3331,30 @@ mod tests {
#[test] #[test]
fn test_split_rent_exemptness() { 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 stake_pubkey = solana_sdk::pubkey::new_rand();
let rent = Rent::default(); let rent = Rent::default();
let rent_exempt_reserve = rent.minimum_balance(std::mem::size_of::<StakeState>()); let source_rent_exempt_reserve =
let stake_lamports = rent_exempt_reserve + MINIMUM_STAKE_DELEGATION; rent.minimum_balance(std::mem::size_of::<StakeState>() + 100);
let split_rent_exempt_reserve = rent.minimum_balance(std::mem::size_of::<StakeState>());
let stake_lamports = source_rent_exempt_reserve + MINIMUM_STAKE_DELEGATION;
let split_stake_pubkey = solana_sdk::pubkey::new_rand(); let split_stake_pubkey = solana_sdk::pubkey::new_rand();
let signers = vec![stake_pubkey].into_iter().collect(); let signers = vec![stake_pubkey].into_iter().collect();
let meta = Meta { let meta = Meta {
authorized: Authorized::auto(&stake_pubkey), authorized: Authorized::auto(&stake_pubkey),
rent_exempt_reserve, rent_exempt_reserve: source_rent_exempt_reserve,
..Meta::default() ..Meta::default()
}; };
for state in &[ for state in &[
StakeState::Initialized(meta), 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 // Test that splitting to a larger account fails
let split_stake_account = AccountSharedData::new_ref_data_with_space( 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); let stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &stake_account);
assert_eq!( 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) Err(InstructionError::InvalidAccountData)
); );
@ -3277,7 +3408,12 @@ mod tests {
let stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &stake_account); let stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &stake_account);
assert_eq!( 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(()) Ok(())
); );
@ -3286,14 +3422,9 @@ mod tests {
stake_lamports stake_lamports
); );
let expected_rent_exempt_reserve = calculate_split_rent_exempt_reserve(
meta.rent_exempt_reserve,
std::mem::size_of::<StakeState>() as u64 + 100,
std::mem::size_of::<StakeState>() as u64,
);
let expected_split_meta = Meta { let expected_split_meta = Meta {
authorized: Authorized::auto(&stake_pubkey), authorized: Authorized::auto(&stake_pubkey),
rent_exempt_reserve: expected_rent_exempt_reserve, rent_exempt_reserve: split_rent_exempt_reserve,
..Meta::default() ..Meta::default()
}; };
@ -3308,7 +3439,7 @@ mod tests {
StakeState::Stake(_meta, stake) => { StakeState::Stake(_meta, stake) => {
// Expected stake should reflect original stake amount so that extra lamports // Expected stake should reflect original stake amount so that extra lamports
// from the rent_exempt_reserve inequality do not magically activate // 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!( assert_eq!(
Ok(StakeState::Stake( Ok(StakeState::Stake(
@ -3325,9 +3456,7 @@ mod tests {
); );
assert_eq!( assert_eq!(
split_stake_keyed_account.account.borrow().lamports(), split_stake_keyed_account.account.borrow().lamports(),
expected_stake expected_stake + source_rent_exempt_reserve,
+ expected_rent_exempt_reserve
+ (rent_exempt_reserve - expected_rent_exempt_reserve)
); );
assert_eq!(Ok(StakeState::Uninitialized), stake_keyed_account.state()); assert_eq!(Ok(StakeState::Uninitialized), stake_keyed_account.state());
} }
@ -4581,7 +4710,7 @@ mod tests {
#[test] #[test]
fn test_active_stake_merge() { 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 invoke_context = InvokeContext::new_mock(&mut transaction_context, &[]);
let clock = Clock::default(); let clock = Clock::default();
let delegation_a = 4_242_424_242u64; let delegation_a = 4_242_424_242u64;
@ -4823,6 +4952,8 @@ mod tests {
/// LT | LT | Err /// LT | LT | Err
#[test] #[test]
fn test_split_minimum_stake_delegation() { 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 [ for (source_stake_delegation, dest_stake_delegation, expected_result) in [
(MINIMUM_STAKE_DELEGATION, MINIMUM_STAKE_DELEGATION, Ok(())), (MINIMUM_STAKE_DELEGATION, MINIMUM_STAKE_DELEGATION, Ok(())),
( (
@ -4882,6 +5013,7 @@ mod tests {
assert_eq!( assert_eq!(
expected_result, expected_result,
source_keyed_account.split( source_keyed_account.split(
&invoke_context,
dest_stake_delegation + rent_exempt_reserve, dest_stake_delegation + rent_exempt_reserve,
&dest_keyed_account, &dest_keyed_account,
&HashSet::from([source_pubkey]), &HashSet::from([source_pubkey]),
@ -4900,6 +5032,8 @@ mod tests {
/// delegation is not OK /// delegation is not OK
#[test] #[test]
fn test_split_full_amount_minimum_stake_delegation() { 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 [ for (stake_delegation, expected_result) in [
(MINIMUM_STAKE_DELEGATION, Ok(())), (MINIMUM_STAKE_DELEGATION, Ok(())),
( (
@ -4941,6 +5075,7 @@ mod tests {
assert_eq!( assert_eq!(
expected_result, expected_result,
source_keyed_account.split( source_keyed_account.split(
&invoke_context,
source_keyed_account.lamports().unwrap(), source_keyed_account.lamports().unwrap(),
&dest_keyed_account, &dest_keyed_account,
&HashSet::from([source_pubkey]), &HashSet::from([source_pubkey]),
@ -4954,6 +5089,8 @@ mod tests {
/// account already has funds, ensure the minimum split amount reduces accordingly. /// account already has funds, ensure the minimum split amount reduces accordingly.
#[test] #[test]
fn test_split_destination_minimum_stake_delegation() { 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 = Rent::default();
let rent_exempt_reserve = rent.minimum_balance(std::mem::size_of::<StakeState>()); let rent_exempt_reserve = rent.minimum_balance(std::mem::size_of::<StakeState>());
@ -5061,6 +5198,7 @@ mod tests {
assert_eq!( assert_eq!(
expected_result, expected_result,
source_keyed_account.split( source_keyed_account.split(
&invoke_context,
split_amount, split_amount,
&destination_keyed_account, &destination_keyed_account,
&HashSet::from([source_pubkey]), &HashSet::from([source_pubkey]),

View File

@ -216,6 +216,7 @@ mod tests {
instruction_data, instruction_data,
transaction_accounts, transaction_accounts,
instruction_accounts, instruction_accounts,
None,
expected_result, expected_result,
super::process_instruction, super::process_instruction,
) )
@ -233,6 +234,7 @@ mod tests {
instruction_data, instruction_data,
transaction_accounts, transaction_accounts,
instruction_accounts, instruction_accounts,
None,
expected_result, expected_result,
|first_instruction_account: usize, |first_instruction_account: usize,
instruction_data: &[u8], instruction_data: &[u8],

View File

@ -530,6 +530,7 @@ mod tests {
instruction_data, instruction_data,
transaction_accounts, transaction_accounts,
instruction_accounts, instruction_accounts,
None,
expected_result, expected_result,
process_instruction, process_instruction,
) )

View File

@ -335,6 +335,10 @@ pub mod check_slice_translation_size {
solana_sdk::declare_id!("GmC19j9qLn2RFk5NduX6QXaDhVpGncVVBzyM8e9WMz2F"); solana_sdk::declare_id!("GmC19j9qLn2RFk5NduX6QXaDhVpGncVVBzyM8e9WMz2F");
} }
pub mod stake_split_uses_rent_sysvar {
solana_sdk::declare_id!("FQnc7U4koHqWgRvFaBJjZnV8VPg6L6wWK33yJeDp4yvV");
}
lazy_static! { lazy_static! {
/// Map of feature identifiers to user-visible description /// Map of feature identifiers to user-visible description
pub static ref FEATURE_NAMES: HashMap<Pubkey, &'static str> = [ pub static ref FEATURE_NAMES: HashMap<Pubkey, &'static str> = [
@ -412,7 +416,8 @@ lazy_static! {
(check_physical_overlapping::id(), "check physical overlapping regions"), (check_physical_overlapping::id(), "check physical overlapping regions"),
(limit_secp256k1_recovery_id::id(), "limit secp256k1 recovery id"), (limit_secp256k1_recovery_id::id(), "limit secp256k1 recovery id"),
(disable_deprecated_loader::id(), "disable the deprecated BPF loader"), (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 ***************/ /*************** ADD NEW FEATURES HERE ***************/
] ]
.iter() .iter()