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],
transaction_accounts: Vec<TransactionAccount>,
instruction_accounts: Vec<AccountMeta>,
sysvar_cache_override: Option<&SysvarCache>,
expected_result: Result<(), InstructionError>,
process_instruction: ProcessInstructionWithContext,
) -> Vec<AccountSharedData> {
@ -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,

View File

@ -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,
)

View File

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

View File

@ -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<AccountMeta>,
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> {
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

View File

@ -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<Pubkey>,
@ -604,6 +605,7 @@ impl<'a> StakeAccount for KeyedAccount<'a> {
fn split(
&self,
invoke_context: &InvokeContext,
lamports: u64,
split: &KeyedAccount,
signers: &HashSet<Pubkey>,
@ -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::<StakeState>());
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::<StakeState>());
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::<StakeState>());
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::<StakeState>());
@ -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::<StakeState>());
let stake_lamports = (rent_exempt_reserve + MINIMUM_STAKE_DELEGATION) * 2;
let source_larger_rent_exempt_reserve =
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 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::<StakeState>() as u64 + 100,
std::mem::size_of::<StakeState>() 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::<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 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::<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 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::<StakeState>());
@ -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::<StakeState>());
@ -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::<StakeState>());
let stake_lamports = rent_exempt_reserve + MINIMUM_STAKE_DELEGATION;
let source_rent_exempt_reserve =
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 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::<StakeState>() as u64 + 100,
std::mem::size_of::<StakeState>() 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::<StakeState>());
@ -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]),

View File

@ -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],

View File

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

View File

@ -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<Pubkey, &'static str> = [
@ -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()