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:
parent
210d98bc06
commit
cb5e67d327
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
)
|
||||
|
|
|
@ -166,6 +166,7 @@ mod tests {
|
|||
instruction_data,
|
||||
transaction_accounts,
|
||||
instruction_accounts,
|
||||
None,
|
||||
expected_result,
|
||||
super::process_instruction,
|
||||
)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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(
|
||||
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]),
|
||||
|
|
|
@ -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],
|
||||
|
|
|
@ -530,6 +530,7 @@ mod tests {
|
|||
instruction_data,
|
||||
transaction_accounts,
|
||||
instruction_accounts,
|
||||
None,
|
||||
expected_result,
|
||||
process_instruction,
|
||||
)
|
||||
|
|
|
@ -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()
|
||||
|
|
Loading…
Reference in New Issue