Removes KeyedAccount from tests in stake instruction. (Part 3) (#24110)

* Moves test from stake state to stake instruction.

* Migrates test_split_source_uninitialized.

* Migrates test_split_split_not_uninitialized.

* Migrates test_split_more_than_staked.

* Migrates test_split_with_rent.

* Migrates test_split_to_account_with_rent_exempt_reserve.

* Migrates test_split_from_larger_sized_account.

* Migrates test_split_from_smaller_sized_account.

* Migrates test_split_100_percent_of_source.

* Migrates test_split_100_percent_of_source_to_account_with_lamports.

* Migrates test_split_rent_exemptness.
This commit is contained in:
Alexander Meißner 2022-04-05 12:36:01 +02:00 committed by GitHub
parent 6fb99891f2
commit e051c7c162
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 957 additions and 943 deletions

View File

@ -4220,4 +4220,960 @@ mod tests {
Ok(()),
);
}
#[test]
fn test_split_source_uninitialized() {
let rent = Rent::default();
let rent_exempt_reserve = rent.minimum_balance(std::mem::size_of::<StakeState>());
let minimum_delegation = crate::get_minimum_delegation(&FeatureSet::all_enabled());
let stake_lamports = (rent_exempt_reserve + minimum_delegation) * 2;
let stake_address = solana_sdk::pubkey::new_rand();
let stake_account = AccountSharedData::new_data_with_space(
stake_lamports,
&StakeState::Uninitialized,
std::mem::size_of::<StakeState>(),
&id(),
)
.unwrap();
let split_to_address = solana_sdk::pubkey::new_rand();
let split_to_account = AccountSharedData::new_data_with_space(
0,
&StakeState::Uninitialized,
std::mem::size_of::<StakeState>(),
&id(),
)
.unwrap();
let transaction_accounts = vec![
(stake_address, stake_account),
(split_to_address, split_to_account),
];
let mut instruction_accounts = vec![
AccountMeta {
pubkey: stake_address,
is_signer: true,
is_writable: false,
},
AccountMeta {
pubkey: stake_address,
is_signer: false,
is_writable: false,
},
];
// splitting an uninitialized account where the destination is the same as the source
{
// splitting should work when...
// - when split amount is the full balance
// - when split amount is zero
// - when split amount is non-zero and less than the full balance
//
// and splitting should fail when the split amount is greater than the balance
process_instruction(
&serialize(&StakeInstruction::Split(stake_lamports)).unwrap(),
transaction_accounts.clone(),
instruction_accounts.clone(),
Ok(()),
);
process_instruction(
&serialize(&StakeInstruction::Split(0)).unwrap(),
transaction_accounts.clone(),
instruction_accounts.clone(),
Ok(()),
);
process_instruction(
&serialize(&StakeInstruction::Split(stake_lamports / 2)).unwrap(),
transaction_accounts.clone(),
instruction_accounts.clone(),
Ok(()),
);
process_instruction(
&serialize(&StakeInstruction::Split(stake_lamports + 1)).unwrap(),
transaction_accounts.clone(),
instruction_accounts.clone(),
Err(InstructionError::InsufficientFunds),
);
}
// this should work
instruction_accounts[1].pubkey = split_to_address;
let accounts = process_instruction(
&serialize(&StakeInstruction::Split(stake_lamports / 2)).unwrap(),
transaction_accounts.clone(),
instruction_accounts.clone(),
Ok(()),
);
assert_eq!(accounts[0].lamports(), accounts[1].lamports());
// no signers should fail
instruction_accounts[0].is_signer = false;
process_instruction(
&serialize(&StakeInstruction::Split(stake_lamports / 2)).unwrap(),
transaction_accounts,
instruction_accounts,
Err(InstructionError::MissingRequiredSignature),
);
}
#[test]
fn test_split_split_not_uninitialized() {
let stake_lamports = 42;
let stake_address = solana_sdk::pubkey::new_rand();
let stake_account = AccountSharedData::new_data_with_space(
stake_lamports,
&just_stake(Meta::auto(&stake_address), stake_lamports),
std::mem::size_of::<StakeState>(),
&id(),
)
.unwrap();
let split_to_address = solana_sdk::pubkey::new_rand();
let instruction_accounts = vec![
AccountMeta {
pubkey: stake_address,
is_signer: true,
is_writable: false,
},
AccountMeta {
pubkey: stake_address,
is_signer: false,
is_writable: false,
},
];
for split_to_state in &[
StakeState::Initialized(Meta::default()),
StakeState::Stake(Meta::default(), Stake::default()),
StakeState::RewardsPool,
] {
let split_to_account = AccountSharedData::new_data_with_space(
0,
split_to_state,
std::mem::size_of::<StakeState>(),
&id(),
)
.unwrap();
process_instruction(
&serialize(&StakeInstruction::Split(stake_lamports / 2)).unwrap(),
vec![
(stake_address, stake_account.clone()),
(split_to_address, split_to_account),
],
instruction_accounts.clone(),
Err(InstructionError::InvalidAccountData),
);
}
}
#[test]
fn test_split_more_than_staked() {
let rent = Rent::default();
let rent_exempt_reserve = rent.minimum_balance(std::mem::size_of::<StakeState>());
let minimum_delegation = crate::get_minimum_delegation(&FeatureSet::all_enabled());
let stake_lamports = (rent_exempt_reserve + minimum_delegation) * 2;
let stake_address = solana_sdk::pubkey::new_rand();
let stake_account = AccountSharedData::new_data_with_space(
stake_lamports,
&just_stake(
Meta {
rent_exempt_reserve,
..Meta::auto(&stake_address)
},
stake_lamports / 2 - 1,
),
std::mem::size_of::<StakeState>(),
&id(),
)
.unwrap();
let split_to_address = solana_sdk::pubkey::new_rand();
let split_to_account = AccountSharedData::new_data_with_space(
0,
&StakeState::Uninitialized,
std::mem::size_of::<StakeState>(),
&id(),
)
.unwrap();
let transaction_accounts = vec![
(stake_address, stake_account),
(split_to_address, split_to_account),
(
sysvar::rent::id(),
account::create_account_shared_data_for_test(&rent),
),
];
let instruction_accounts = vec![
AccountMeta {
pubkey: stake_address,
is_signer: true,
is_writable: false,
},
AccountMeta {
pubkey: split_to_address,
is_signer: false,
is_writable: false,
},
];
process_instruction(
&serialize(&StakeInstruction::Split(stake_lamports / 2)).unwrap(),
transaction_accounts,
instruction_accounts,
Err(StakeError::InsufficientStake.into()),
);
}
#[test]
fn test_split_with_rent() {
let rent = Rent::default();
let rent_exempt_reserve = rent.minimum_balance(std::mem::size_of::<StakeState>());
let minimum_delegation = crate::get_minimum_delegation(&FeatureSet::all_enabled());
let minimum_balance = rent_exempt_reserve + minimum_delegation;
let stake_lamports = minimum_balance * 2;
let stake_address = solana_sdk::pubkey::new_rand();
let split_to_address = solana_sdk::pubkey::new_rand();
let split_to_account = AccountSharedData::new_data_with_space(
0,
&StakeState::Uninitialized,
std::mem::size_of::<StakeState>(),
&id(),
)
.unwrap();
let instruction_accounts = vec![
AccountMeta {
pubkey: stake_address,
is_signer: true,
is_writable: false,
},
AccountMeta {
pubkey: split_to_address,
is_signer: false,
is_writable: false,
},
];
let meta = Meta {
authorized: Authorized::auto(&stake_address),
rent_exempt_reserve,
..Meta::default()
};
// test splitting both an Initialized stake and a Staked stake
for state in &[
StakeState::Initialized(meta),
just_stake(meta, stake_lamports - rent_exempt_reserve),
] {
let stake_account = AccountSharedData::new_data_with_space(
stake_lamports,
state,
std::mem::size_of::<StakeState>(),
&id(),
)
.unwrap();
let mut transaction_accounts = vec![
(stake_address, stake_account),
(split_to_address, split_to_account.clone()),
(
sysvar::rent::id(),
account::create_account_shared_data_for_test(&rent),
),
];
// not enough to make a non-zero stake account
process_instruction(
&serialize(&StakeInstruction::Split(rent_exempt_reserve)).unwrap(),
transaction_accounts.clone(),
instruction_accounts.clone(),
Err(InstructionError::InsufficientFunds),
);
// doesn't leave enough for initial stake to be non-zero
process_instruction(
&serialize(&StakeInstruction::Split(
stake_lamports - rent_exempt_reserve,
))
.unwrap(),
transaction_accounts.clone(),
instruction_accounts.clone(),
Err(InstructionError::InsufficientFunds),
);
// split account already has way enough lamports
transaction_accounts[1].1.set_lamports(minimum_balance);
let accounts = process_instruction(
&serialize(&StakeInstruction::Split(stake_lamports - minimum_balance)).unwrap(),
transaction_accounts,
instruction_accounts.clone(),
Ok(()),
);
// verify no stake leakage in the case of a stake
if let StakeState::Stake(meta, stake) = state {
assert_eq!(
accounts[1].state(),
Ok(StakeState::Stake(
*meta,
Stake {
delegation: Delegation {
stake: stake_lamports - minimum_balance,
..stake.delegation
},
..*stake
}
))
);
assert_eq!(accounts[0].lamports(), minimum_balance,);
assert_eq!(accounts[1].lamports(), stake_lamports,);
}
}
}
#[test]
fn test_split_to_account_with_rent_exempt_reserve() {
let rent = Rent::default();
let rent_exempt_reserve = rent.minimum_balance(std::mem::size_of::<StakeState>());
let minimum_delegation = crate::get_minimum_delegation(&FeatureSet::all_enabled());
let stake_lamports = (rent_exempt_reserve + minimum_delegation) * 2;
let stake_address = solana_sdk::pubkey::new_rand();
let meta = Meta {
authorized: Authorized::auto(&stake_address),
rent_exempt_reserve,
..Meta::default()
};
let state = just_stake(meta, stake_lamports - rent_exempt_reserve);
let stake_account = AccountSharedData::new_data_with_space(
stake_lamports,
&state,
std::mem::size_of::<StakeState>(),
&id(),
)
.unwrap();
let split_to_address = solana_sdk::pubkey::new_rand();
let instruction_accounts = vec![
AccountMeta {
pubkey: stake_address,
is_signer: true,
is_writable: false,
},
AccountMeta {
pubkey: split_to_address,
is_signer: false,
is_writable: false,
},
];
// Test various account prefunding, including empty, less than rent_exempt_reserve, exactly
// rent_exempt_reserve, and more than rent_exempt_reserve. The empty case is not covered in
// test_split, since that test uses a Meta with rent_exempt_reserve = 0
let split_lamport_balances = vec![
0,
rent_exempt_reserve - 1,
rent_exempt_reserve,
rent_exempt_reserve + minimum_delegation - 1,
rent_exempt_reserve + minimum_delegation,
];
for initial_balance in split_lamport_balances {
let split_to_account = AccountSharedData::new_data_with_space(
initial_balance,
&StakeState::Uninitialized,
std::mem::size_of::<StakeState>(),
&id(),
)
.unwrap();
let transaction_accounts = vec![
(stake_address, stake_account.clone()),
(split_to_address, split_to_account),
(
sysvar::rent::id(),
account::create_account_shared_data_for_test(&rent),
),
];
// split more than available fails
process_instruction(
&serialize(&StakeInstruction::Split(stake_lamports + 1)).unwrap(),
transaction_accounts.clone(),
instruction_accounts.clone(),
Err(InstructionError::InsufficientFunds),
);
// should work
let accounts = process_instruction(
&serialize(&StakeInstruction::Split(stake_lamports / 2)).unwrap(),
transaction_accounts,
instruction_accounts.clone(),
Ok(()),
);
// no lamport leakage
assert_eq!(
accounts[0].lamports() + accounts[1].lamports(),
stake_lamports + initial_balance,
);
if let StakeState::Stake(meta, stake) = state {
let expected_stake =
stake_lamports / 2 - (rent_exempt_reserve.saturating_sub(initial_balance));
assert_eq!(
Ok(StakeState::Stake(
meta,
Stake {
delegation: Delegation {
stake: stake_lamports / 2
- (rent_exempt_reserve.saturating_sub(initial_balance)),
..stake.delegation
},
..stake
}
)),
accounts[1].state(),
);
assert_eq!(
accounts[1].lamports(),
expected_stake
+ rent_exempt_reserve
+ initial_balance.saturating_sub(rent_exempt_reserve),
);
assert_eq!(
Ok(StakeState::Stake(
meta,
Stake {
delegation: Delegation {
stake: stake_lamports / 2 - rent_exempt_reserve,
..stake.delegation
},
..stake
}
)),
accounts[0].state(),
);
}
}
}
#[test]
fn test_split_from_larger_sized_account() {
let rent = Rent::default();
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 minimum_delegation = crate::get_minimum_delegation(&FeatureSet::all_enabled());
let stake_lamports = (source_larger_rent_exempt_reserve + minimum_delegation) * 2;
let stake_address = solana_sdk::pubkey::new_rand();
let meta = Meta {
authorized: Authorized::auto(&stake_address),
rent_exempt_reserve: source_larger_rent_exempt_reserve,
..Meta::default()
};
let state = just_stake(meta, stake_lamports - source_larger_rent_exempt_reserve);
let stake_account = AccountSharedData::new_data_with_space(
stake_lamports,
&state,
std::mem::size_of::<StakeState>() + 100,
&id(),
)
.unwrap();
let split_to_address = solana_sdk::pubkey::new_rand();
let instruction_accounts = vec![
AccountMeta {
pubkey: stake_address,
is_signer: true,
is_writable: false,
},
AccountMeta {
pubkey: split_to_address,
is_signer: false,
is_writable: false,
},
];
// Test various account prefunding, including empty, less than rent_exempt_reserve, exactly
// rent_exempt_reserve, and more than rent_exempt_reserve. The empty case is not covered in
// test_split, since that test uses a Meta with rent_exempt_reserve = 0
let split_lamport_balances = vec![
0,
split_rent_exempt_reserve - 1,
split_rent_exempt_reserve,
split_rent_exempt_reserve + minimum_delegation - 1,
split_rent_exempt_reserve + minimum_delegation,
];
for initial_balance in split_lamport_balances {
let split_to_account = AccountSharedData::new_data_with_space(
initial_balance,
&StakeState::Uninitialized,
std::mem::size_of::<StakeState>(),
&id(),
)
.unwrap();
let transaction_accounts = vec![
(stake_address, stake_account.clone()),
(split_to_address, split_to_account),
(
sysvar::rent::id(),
account::create_account_shared_data_for_test(&rent),
),
];
// split more than available fails
process_instruction(
&serialize(&StakeInstruction::Split(stake_lamports + 1)).unwrap(),
transaction_accounts.clone(),
instruction_accounts.clone(),
Err(InstructionError::InsufficientFunds),
);
// should work
let accounts = process_instruction(
&serialize(&StakeInstruction::Split(stake_lamports / 2)).unwrap(),
transaction_accounts.clone(),
instruction_accounts.clone(),
Ok(()),
);
// no lamport leakage
assert_eq!(
accounts[0].lamports() + accounts[1].lamports(),
stake_lamports + initial_balance
);
if let StakeState::Stake(meta, stake) = state {
let expected_split_meta = Meta {
authorized: Authorized::auto(&stake_address),
rent_exempt_reserve: split_rent_exempt_reserve,
..Meta::default()
};
let expected_stake = stake_lamports / 2
- (split_rent_exempt_reserve.saturating_sub(initial_balance));
assert_eq!(
Ok(StakeState::Stake(
expected_split_meta,
Stake {
delegation: Delegation {
stake: expected_stake,
..stake.delegation
},
..stake
}
)),
accounts[1].state()
);
assert_eq!(
accounts[1].lamports(),
expected_stake
+ 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 - source_larger_rent_exempt_reserve,
..stake.delegation
},
..stake
}
)),
accounts[0].state()
);
}
}
}
#[test]
fn test_split_from_smaller_sized_account() {
let rent = Rent::default();
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 stake_lamports = split_rent_exempt_reserve + 1;
let stake_address = solana_sdk::pubkey::new_rand();
let meta = Meta {
authorized: Authorized::auto(&stake_address),
rent_exempt_reserve: source_smaller_rent_exempt_reserve,
..Meta::default()
};
let state = just_stake(meta, stake_lamports - source_smaller_rent_exempt_reserve);
let stake_account = AccountSharedData::new_data_with_space(
stake_lamports,
&state,
std::mem::size_of::<StakeState>(),
&id(),
)
.unwrap();
let split_to_address = solana_sdk::pubkey::new_rand();
let instruction_accounts = vec![
AccountMeta {
pubkey: stake_address,
is_signer: true,
is_writable: false,
},
AccountMeta {
pubkey: split_to_address,
is_signer: false,
is_writable: false,
},
];
let split_amount = stake_lamports - (source_smaller_rent_exempt_reserve + 1); // Enough so that split stake is > 0
let split_lamport_balances = vec![
0,
1,
split_rent_exempt_reserve,
split_rent_exempt_reserve + 1,
];
for initial_balance in split_lamport_balances {
let split_to_account = AccountSharedData::new_data_with_space(
initial_balance,
&StakeState::Uninitialized,
std::mem::size_of::<StakeState>() + 100,
&id(),
)
.unwrap();
let transaction_accounts = vec![
(stake_address, stake_account.clone()),
(split_to_address, split_to_account),
(
sysvar::rent::id(),
account::create_account_shared_data_for_test(&rent),
),
];
// should always return error when splitting to larger account
process_instruction(
&serialize(&StakeInstruction::Split(split_amount)).unwrap(),
transaction_accounts.clone(),
instruction_accounts.clone(),
Err(InstructionError::InvalidAccountData),
);
// Splitting 100% of source should not make a difference
process_instruction(
&serialize(&StakeInstruction::Split(stake_lamports)).unwrap(),
transaction_accounts,
instruction_accounts.clone(),
Err(InstructionError::InvalidAccountData),
);
}
}
#[test]
fn test_split_100_percent_of_source() {
let rent = Rent::default();
let rent_exempt_reserve = rent.minimum_balance(std::mem::size_of::<StakeState>());
let minimum_delegation = crate::get_minimum_delegation(&FeatureSet::all_enabled());
let stake_lamports = rent_exempt_reserve + minimum_delegation;
let stake_address = solana_sdk::pubkey::new_rand();
let meta = Meta {
authorized: Authorized::auto(&stake_address),
rent_exempt_reserve,
..Meta::default()
};
let split_to_address = solana_sdk::pubkey::new_rand();
let split_to_account = AccountSharedData::new_data_with_space(
0,
&StakeState::Uninitialized,
std::mem::size_of::<StakeState>(),
&id(),
)
.unwrap();
let instruction_accounts = vec![
AccountMeta {
pubkey: stake_address,
is_signer: true,
is_writable: false,
},
AccountMeta {
pubkey: split_to_address,
is_signer: false,
is_writable: false,
},
];
// test splitting both an Initialized stake and a Staked stake
for state in &[
StakeState::Initialized(meta),
just_stake(meta, stake_lamports - rent_exempt_reserve),
] {
let stake_account = AccountSharedData::new_data_with_space(
stake_lamports,
&state,
std::mem::size_of::<StakeState>(),
&id(),
)
.unwrap();
let transaction_accounts = vec![
(stake_address, stake_account),
(split_to_address, split_to_account.clone()),
(
sysvar::rent::id(),
account::create_account_shared_data_for_test(&rent),
),
];
// split 100% over to dest
let accounts = process_instruction(
&serialize(&StakeInstruction::Split(stake_lamports)).unwrap(),
transaction_accounts,
instruction_accounts.clone(),
Ok(()),
);
// no lamport leakage
assert_eq!(
accounts[0].lamports() + accounts[1].lamports(),
stake_lamports
);
match state {
StakeState::Initialized(_) => {
assert_eq!(Ok(*state), accounts[1].state());
assert_eq!(Ok(StakeState::Uninitialized), accounts[0].state());
}
StakeState::Stake(meta, stake) => {
assert_eq!(
Ok(StakeState::Stake(
*meta,
Stake {
delegation: Delegation {
stake: stake_lamports - rent_exempt_reserve,
..stake.delegation
},
..*stake
}
)),
accounts[1].state()
);
assert_eq!(Ok(StakeState::Uninitialized), accounts[0].state());
}
_ => unreachable!(),
}
}
}
#[test]
fn test_split_100_percent_of_source_to_account_with_lamports() {
let rent = Rent::default();
let rent_exempt_reserve = rent.minimum_balance(std::mem::size_of::<StakeState>());
let minimum_delegation = crate::get_minimum_delegation(&FeatureSet::all_enabled());
let stake_lamports = rent_exempt_reserve + minimum_delegation;
let stake_address = solana_sdk::pubkey::new_rand();
let meta = Meta {
authorized: Authorized::auto(&stake_address),
rent_exempt_reserve,
..Meta::default()
};
let state = just_stake(meta, stake_lamports - rent_exempt_reserve);
let stake_account = AccountSharedData::new_data_with_space(
stake_lamports,
&state,
std::mem::size_of::<StakeState>(),
&id(),
)
.unwrap();
let split_to_address = solana_sdk::pubkey::new_rand();
let instruction_accounts = vec![
AccountMeta {
pubkey: stake_address,
is_signer: true,
is_writable: false,
},
AccountMeta {
pubkey: split_to_address,
is_signer: false,
is_writable: false,
},
];
// Test various account prefunding, including empty, less than rent_exempt_reserve, exactly
// rent_exempt_reserve, and more than rent_exempt_reserve. Technically, the empty case is
// covered in test_split_100_percent_of_source, but included here as well for readability
let split_lamport_balances = vec![
0,
rent_exempt_reserve - 1,
rent_exempt_reserve,
rent_exempt_reserve + minimum_delegation - 1,
rent_exempt_reserve + minimum_delegation,
];
for initial_balance in split_lamport_balances {
let split_to_account = AccountSharedData::new_data_with_space(
initial_balance,
&StakeState::Uninitialized,
std::mem::size_of::<StakeState>(),
&id(),
)
.unwrap();
let transaction_accounts = vec![
(stake_address, stake_account.clone()),
(split_to_address, split_to_account),
(
sysvar::rent::id(),
account::create_account_shared_data_for_test(&rent),
),
];
// split 100% over to dest
let accounts = process_instruction(
&serialize(&StakeInstruction::Split(stake_lamports)).unwrap(),
transaction_accounts,
instruction_accounts.clone(),
Ok(()),
);
// no lamport leakage
assert_eq!(
accounts[0].lamports() + accounts[1].lamports(),
stake_lamports + initial_balance
);
if let StakeState::Stake(meta, stake) = state {
assert_eq!(
Ok(StakeState::Stake(
meta,
Stake {
delegation: Delegation {
stake: stake_lamports - rent_exempt_reserve,
..stake.delegation
},
..stake
}
)),
accounts[1].state()
);
assert_eq!(Ok(StakeState::Uninitialized), accounts[0].state());
}
}
}
#[test]
fn test_split_rent_exemptness() {
let rent = Rent::default();
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 minimum_delegation = crate::get_minimum_delegation(&FeatureSet::all_enabled());
let stake_lamports = source_rent_exempt_reserve + minimum_delegation;
let stake_address = solana_sdk::pubkey::new_rand();
let meta = Meta {
authorized: Authorized::auto(&stake_address),
rent_exempt_reserve: source_rent_exempt_reserve,
..Meta::default()
};
let split_to_address = solana_sdk::pubkey::new_rand();
let instruction_accounts = vec![
AccountMeta {
pubkey: stake_address,
is_signer: true,
is_writable: false,
},
AccountMeta {
pubkey: split_to_address,
is_signer: false,
is_writable: false,
},
];
for state in &[
StakeState::Initialized(meta),
just_stake(meta, stake_lamports - source_rent_exempt_reserve),
] {
// Test that splitting to a larger account fails
let stake_account = AccountSharedData::new_data_with_space(
stake_lamports,
&state,
std::mem::size_of::<StakeState>(),
&id(),
)
.unwrap();
let split_to_account = AccountSharedData::new_data_with_space(
0,
&StakeState::Uninitialized,
std::mem::size_of::<StakeState>() + 10000,
&id(),
)
.unwrap();
let transaction_accounts = vec![
(stake_address, stake_account),
(split_to_address, split_to_account),
(
sysvar::rent::id(),
account::create_account_shared_data_for_test(&rent),
),
];
process_instruction(
&serialize(&StakeInstruction::Split(stake_lamports)).unwrap(),
transaction_accounts,
instruction_accounts.clone(),
Err(InstructionError::InvalidAccountData),
);
// Test that splitting from a larger account to a smaller one works.
// Split amount should not matter, assuming other fund criteria are met
let stake_account = AccountSharedData::new_data_with_space(
stake_lamports,
&state,
std::mem::size_of::<StakeState>() + 100,
&id(),
)
.unwrap();
let split_to_account = AccountSharedData::new_data_with_space(
0,
&StakeState::Uninitialized,
std::mem::size_of::<StakeState>(),
&id(),
)
.unwrap();
let transaction_accounts = vec![
(stake_address, stake_account),
(split_to_address, split_to_account),
(
sysvar::rent::id(),
account::create_account_shared_data_for_test(&rent),
),
];
let accounts = process_instruction(
&serialize(&StakeInstruction::Split(stake_lamports)).unwrap(),
transaction_accounts,
instruction_accounts.clone(),
Ok(()),
);
assert_eq!(accounts[1].lamports(), stake_lamports);
let expected_split_meta = Meta {
authorized: Authorized::auto(&stake_address),
rent_exempt_reserve: split_rent_exempt_reserve,
..Meta::default()
};
match state {
StakeState::Initialized(_) => {
assert_eq!(
Ok(StakeState::Initialized(expected_split_meta)),
accounts[1].state()
);
assert_eq!(Ok(StakeState::Uninitialized), accounts[0].state());
}
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 - source_rent_exempt_reserve;
assert_eq!(
Ok(StakeState::Stake(
expected_split_meta,
Stake {
delegation: Delegation {
stake: expected_stake,
..stake.delegation
},
..*stake
}
)),
accounts[1].state()
);
assert_eq!(
accounts[1].lamports(),
expected_stake + source_rent_exempt_reserve,
);
assert_eq!(Ok(StakeState::Uninitialized), accounts[0].state());
}
_ => unreachable!(),
}
}
}
}

View File

@ -1444,7 +1444,7 @@ mod tests {
proptest::prelude::*,
solana_program_runtime::invoke_context::InvokeContext,
solana_sdk::{
account::{create_account_shared_data_for_test, AccountSharedData, WritableAccount},
account::{create_account_shared_data_for_test, AccountSharedData},
native_token,
pubkey::Pubkey,
sysvar::SysvarId,
@ -2529,149 +2529,6 @@ mod tests {
)
}
#[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 minimum_delegation = crate::get_minimum_delegation(&invoke_context.feature_set);
let stake_pubkey = solana_sdk::pubkey::new_rand();
let stake_lamports = (rent_exempt_reserve + minimum_delegation) * 2;
let stake_account = AccountSharedData::new_ref_data_with_space(
stake_lamports,
&StakeState::Uninitialized,
std::mem::size_of::<StakeState>(),
&id(),
)
.expect("stake_account");
let split_stake_pubkey = solana_sdk::pubkey::new_rand();
let split_stake_account = AccountSharedData::new_ref_data_with_space(
0,
&StakeState::Uninitialized,
std::mem::size_of::<StakeState>(),
&id(),
)
.expect("stake_account");
let stake_keyed_account = KeyedAccount::new(&stake_pubkey, false, &stake_account);
let split_stake_keyed_account =
KeyedAccount::new(&split_stake_pubkey, false, &split_stake_account);
// no signers should fail
assert_eq!(
stake_keyed_account.split(
&invoke_context,
stake_lamports / 2,
&split_stake_keyed_account,
&HashSet::default(), // no signers
),
Err(InstructionError::MissingRequiredSignature)
);
let signers = HashSet::from([stake_pubkey]);
// splitting an uninitialized account where the destination is the same as the source
{
// splitting should work when...
// - when split amount is the full balance
// - when split amount is zero
// - when split amount is non-zero and less than the full balance
//
// and splitting should fail when the split amount is greater than the balance
assert_eq!(
stake_keyed_account.split(
&invoke_context,
stake_lamports,
&stake_keyed_account,
&signers
),
Ok(()),
);
assert_eq!(
stake_keyed_account.split(&invoke_context, 0, &stake_keyed_account, &signers),
Ok(()),
);
assert_eq!(
stake_keyed_account.split(
&invoke_context,
stake_lamports / 2,
&stake_keyed_account,
&signers
),
Ok(()),
);
assert_eq!(
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(
&invoke_context,
stake_lamports / 2,
&split_stake_keyed_account,
&signers
),
Ok(())
);
assert_eq!(
stake_keyed_account.account.borrow().lamports(),
split_stake_keyed_account.account.borrow().lamports()
);
}
#[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(
stake_lamports,
&StakeState::Stake(Meta::auto(&stake_pubkey), just_stake(stake_lamports)),
std::mem::size_of::<StakeState>(),
&id(),
)
.expect("stake_account");
let stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &stake_account);
let signers = vec![stake_pubkey].into_iter().collect();
for split_stake_state in &[
StakeState::Initialized(Meta::default()),
StakeState::Stake(Meta::default(), Stake::default()),
StakeState::RewardsPool,
] {
let split_stake_pubkey = solana_sdk::pubkey::new_rand();
let split_stake_account = AccountSharedData::new_ref_data_with_space(
0,
split_stake_state,
std::mem::size_of::<StakeState>(),
&id(),
)
.expect("split_stake_account");
let split_stake_keyed_account =
KeyedAccount::new(&split_stake_pubkey, true, &split_stake_account);
assert_eq!(
stake_keyed_account.split(
&invoke_context,
stake_lamports / 2,
&split_stake_keyed_account,
&signers
),
Err(InstructionError::InvalidAccountData)
);
}
}
fn just_stake(stake: u64) -> Stake {
Stake {
delegation: Delegation {
@ -2682,805 +2539,6 @@ 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 minimum_delegation = crate::get_minimum_delegation(&invoke_context.feature_set);
let stake_pubkey = solana_sdk::pubkey::new_rand();
let stake_lamports = (rent_exempt_reserve + minimum_delegation) * 2;
let stake_account = AccountSharedData::new_ref_data_with_space(
stake_lamports,
&StakeState::Stake(
Meta {
rent_exempt_reserve,
..Meta::auto(&stake_pubkey)
},
just_stake(stake_lamports / 2 - 1),
),
std::mem::size_of::<StakeState>(),
&id(),
)
.expect("stake_account");
let split_stake_pubkey = solana_sdk::pubkey::new_rand();
let split_stake_account = AccountSharedData::new_ref_data_with_space(
0,
&StakeState::Uninitialized,
std::mem::size_of::<StakeState>(),
&id(),
)
.expect("stake_account");
let signers = vec![stake_pubkey].into_iter().collect();
let stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &stake_account);
let split_stake_keyed_account =
KeyedAccount::new(&split_stake_pubkey, true, &split_stake_account);
assert_eq!(
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_delegation = crate::get_minimum_delegation(&invoke_context.feature_set);
let minimum_balance = rent_exempt_reserve + minimum_delegation;
let stake_pubkey = solana_sdk::pubkey::new_rand();
let split_stake_pubkey = solana_sdk::pubkey::new_rand();
let stake_lamports = minimum_balance * 2;
let signers = vec![stake_pubkey].into_iter().collect();
let meta = Meta {
authorized: Authorized::auto(&stake_pubkey),
rent_exempt_reserve,
..Meta::default()
};
// test splitting both an Initialized stake and a Staked stake
for state in &[
StakeState::Initialized(meta),
StakeState::Stake(meta, just_stake(stake_lamports - rent_exempt_reserve)),
] {
let stake_account = AccountSharedData::new_ref_data_with_space(
stake_lamports,
state,
std::mem::size_of::<StakeState>(),
&id(),
)
.expect("stake_account");
let stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &stake_account);
let split_stake_account = AccountSharedData::new_ref_data_with_space(
0,
&StakeState::Uninitialized,
std::mem::size_of::<StakeState>(),
&id(),
)
.expect("stake_account");
let split_stake_keyed_account =
KeyedAccount::new(&split_stake_pubkey, true, &split_stake_account);
// 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,
),
Err(InstructionError::InsufficientFunds)
);
// 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,
),
Err(InstructionError::InsufficientFunds)
);
// split account already has way enough lamports
split_stake_keyed_account
.account
.borrow_mut()
.set_lamports(minimum_balance);
assert_eq!(
stake_keyed_account.split(
&invoke_context,
stake_lamports - minimum_balance,
&split_stake_keyed_account,
&signers,
),
Ok(())
);
// verify no stake leakage in the case of a stake
if let StakeState::Stake(meta, stake) = state {
assert_eq!(
split_stake_keyed_account.state(),
Ok(StakeState::Stake(
*meta,
Stake {
delegation: Delegation {
stake: stake_lamports - minimum_balance,
..stake.delegation
},
..*stake
}
))
);
assert_eq!(
stake_keyed_account.account.borrow().lamports(),
minimum_balance,
);
assert_eq!(
split_stake_keyed_account.account.borrow().lamports(),
stake_lamports,
);
}
}
}
#[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>());
let minimum_delegation = crate::get_minimum_delegation(&invoke_context.feature_set);
let stake_lamports = (rent_exempt_reserve + minimum_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,
..Meta::default()
};
let state = StakeState::Stake(meta, just_stake(stake_lamports - rent_exempt_reserve));
// Test various account prefunding, including empty, less than rent_exempt_reserve, exactly
// rent_exempt_reserve, and more than rent_exempt_reserve. The empty case is not covered in
// test_split, since that test uses a Meta with rent_exempt_reserve = 0
let split_lamport_balances = vec![
0,
rent_exempt_reserve - 1,
rent_exempt_reserve,
rent_exempt_reserve + minimum_delegation - 1,
rent_exempt_reserve + minimum_delegation,
];
for initial_balance in split_lamport_balances {
let split_stake_account = AccountSharedData::new_ref_data_with_space(
initial_balance,
&StakeState::Uninitialized,
std::mem::size_of::<StakeState>(),
&id(),
)
.expect("stake_account");
let split_stake_keyed_account =
KeyedAccount::new(&split_stake_pubkey, true, &split_stake_account);
let stake_account = AccountSharedData::new_ref_data_with_space(
stake_lamports,
&state,
std::mem::size_of::<StakeState>(),
&id(),
)
.expect("stake_account");
let stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &stake_account);
// split more than available fails
assert_eq!(
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(
&invoke_context,
stake_lamports / 2,
&split_stake_keyed_account,
&signers
),
Ok(())
);
// no lamport leakage
assert_eq!(
stake_keyed_account.account.borrow().lamports()
+ split_stake_keyed_account.account.borrow().lamports(),
stake_lamports + initial_balance
);
if let StakeState::Stake(meta, stake) = state {
let expected_stake =
stake_lamports / 2 - (rent_exempt_reserve.saturating_sub(initial_balance));
assert_eq!(
Ok(StakeState::Stake(
meta,
Stake {
delegation: Delegation {
stake: stake_lamports / 2
- (rent_exempt_reserve.saturating_sub(initial_balance)),
..stake.delegation
},
..stake
}
)),
split_stake_keyed_account.state()
);
assert_eq!(
split_stake_keyed_account.account.borrow().lamports(),
expected_stake
+ rent_exempt_reserve
+ initial_balance.saturating_sub(rent_exempt_reserve)
);
assert_eq!(
Ok(StakeState::Stake(
meta,
Stake {
delegation: Delegation {
stake: stake_lamports / 2 - rent_exempt_reserve,
..stake.delegation
},
..stake
}
)),
stake_keyed_account.state()
);
}
}
}
#[test]
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 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 minimum_delegation = crate::get_minimum_delegation(&invoke_context.feature_set);
let stake_lamports = (source_larger_rent_exempt_reserve + minimum_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: source_larger_rent_exempt_reserve,
..Meta::default()
};
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
// rent_exempt_reserve, and more than rent_exempt_reserve. The empty case is not covered in
// test_split, since that test uses a Meta with rent_exempt_reserve = 0
let split_lamport_balances = vec![
0,
split_rent_exempt_reserve - 1,
split_rent_exempt_reserve,
split_rent_exempt_reserve + minimum_delegation - 1,
split_rent_exempt_reserve + minimum_delegation,
];
for initial_balance in split_lamport_balances {
let split_stake_account = AccountSharedData::new_ref_data_with_space(
initial_balance,
&StakeState::Uninitialized,
std::mem::size_of::<StakeState>(),
&id(),
)
.expect("stake_account");
let split_stake_keyed_account =
KeyedAccount::new(&split_stake_pubkey, true, &split_stake_account);
let stake_account = AccountSharedData::new_ref_data_with_space(
stake_lamports,
&state,
std::mem::size_of::<StakeState>() + 100,
&id(),
)
.expect("stake_account");
let stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &stake_account);
// split more than available fails
assert_eq!(
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(
&invoke_context,
stake_lamports / 2,
&split_stake_keyed_account,
&signers
),
Ok(())
);
// no lamport leakage
assert_eq!(
stake_keyed_account.account.borrow().lamports()
+ split_stake_keyed_account.account.borrow().lamports(),
stake_lamports + initial_balance
);
if let StakeState::Stake(meta, stake) = state {
let expected_split_meta = Meta {
authorized: Authorized::auto(&stake_pubkey),
rent_exempt_reserve: split_rent_exempt_reserve,
..Meta::default()
};
let expected_stake = stake_lamports / 2
- (split_rent_exempt_reserve.saturating_sub(initial_balance));
assert_eq!(
Ok(StakeState::Stake(
expected_split_meta,
Stake {
delegation: Delegation {
stake: expected_stake,
..stake.delegation
},
..stake
}
)),
split_stake_keyed_account.state()
);
assert_eq!(
split_stake_keyed_account.account.borrow().lamports(),
expected_stake
+ 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 - source_larger_rent_exempt_reserve,
..stake.delegation
},
..stake
}
)),
stake_keyed_account.state()
);
}
}
}
#[test]
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 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: source_smaller_rent_exempt_reserve,
..Meta::default()
};
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 - source_smaller_rent_exempt_reserve),
);
let split_lamport_balances = vec![
0,
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(
initial_balance,
&StakeState::Uninitialized,
std::mem::size_of::<StakeState>() + 100,
&id(),
)
.expect("stake_account");
let split_stake_keyed_account =
KeyedAccount::new(&split_stake_pubkey, true, &split_stake_account);
let stake_account = AccountSharedData::new_ref_data_with_space(
stake_lamports,
&state,
std::mem::size_of::<StakeState>(),
&id(),
)
.expect("stake_account");
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(
&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(
&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>());
let minimum_delegation = crate::get_minimum_delegation(&invoke_context.feature_set);
let stake_lamports = rent_exempt_reserve + minimum_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,
..Meta::default()
};
// test splitting both an Initialized stake and a Staked stake
for state in &[
StakeState::Initialized(meta),
StakeState::Stake(meta, just_stake(stake_lamports - rent_exempt_reserve)),
] {
let split_stake_account = AccountSharedData::new_ref_data_with_space(
0,
&StakeState::Uninitialized,
std::mem::size_of::<StakeState>(),
&id(),
)
.expect("stake_account");
let split_stake_keyed_account =
KeyedAccount::new(&split_stake_pubkey, true, &split_stake_account);
let stake_account = AccountSharedData::new_ref_data_with_space(
stake_lamports,
state,
std::mem::size_of::<StakeState>(),
&id(),
)
.expect("stake_account");
let stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &stake_account);
// split 100% over to dest
assert_eq!(
stake_keyed_account.split(
&invoke_context,
stake_lamports,
&split_stake_keyed_account,
&signers
),
Ok(())
);
// no lamport leakage
assert_eq!(
stake_keyed_account.account.borrow().lamports()
+ split_stake_keyed_account.account.borrow().lamports(),
stake_lamports
);
match state {
StakeState::Initialized(_) => {
assert_eq!(Ok(*state), split_stake_keyed_account.state());
assert_eq!(Ok(StakeState::Uninitialized), stake_keyed_account.state());
}
StakeState::Stake(meta, stake) => {
assert_eq!(
Ok(StakeState::Stake(
*meta,
Stake {
delegation: Delegation {
stake: stake_lamports - rent_exempt_reserve,
..stake.delegation
},
..*stake
}
)),
split_stake_keyed_account.state()
);
assert_eq!(Ok(StakeState::Uninitialized), stake_keyed_account.state());
}
_ => unreachable!(),
}
// reset
stake_keyed_account
.account
.borrow_mut()
.set_lamports(stake_lamports);
}
}
#[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>());
let minimum_delegation = crate::get_minimum_delegation(&invoke_context.feature_set);
let stake_lamports = rent_exempt_reserve + minimum_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,
..Meta::default()
};
let state = StakeState::Stake(meta, just_stake(stake_lamports - rent_exempt_reserve));
// Test various account prefunding, including empty, less than rent_exempt_reserve, exactly
// rent_exempt_reserve, and more than rent_exempt_reserve. Technically, the empty case is
// covered in test_split_100_percent_of_source, but included here as well for readability
let split_lamport_balances = vec![
0,
rent_exempt_reserve - 1,
rent_exempt_reserve,
rent_exempt_reserve + minimum_delegation - 1,
rent_exempt_reserve + minimum_delegation,
];
for initial_balance in split_lamport_balances {
let split_stake_account = AccountSharedData::new_ref_data_with_space(
initial_balance,
&StakeState::Uninitialized,
std::mem::size_of::<StakeState>(),
&id(),
)
.expect("stake_account");
let split_stake_keyed_account =
KeyedAccount::new(&split_stake_pubkey, true, &split_stake_account);
let stake_account = AccountSharedData::new_ref_data_with_space(
stake_lamports,
&state,
std::mem::size_of::<StakeState>(),
&id(),
)
.expect("stake_account");
let stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &stake_account);
// split 100% over to dest
assert_eq!(
stake_keyed_account.split(
&invoke_context,
stake_lamports,
&split_stake_keyed_account,
&signers
),
Ok(())
);
// no lamport leakage
assert_eq!(
stake_keyed_account.account.borrow().lamports()
+ split_stake_keyed_account.account.borrow().lamports(),
stake_lamports + initial_balance
);
if let StakeState::Stake(meta, stake) = state {
assert_eq!(
Ok(StakeState::Stake(
meta,
Stake {
delegation: Delegation {
stake: stake_lamports - rent_exempt_reserve,
..stake.delegation
},
..stake
}
)),
split_stake_keyed_account.state()
);
assert_eq!(Ok(StakeState::Uninitialized), stake_keyed_account.state());
}
}
}
#[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 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 minimum_delegation = crate::get_minimum_delegation(&invoke_context.feature_set);
let stake_lamports = source_rent_exempt_reserve + minimum_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: source_rent_exempt_reserve,
..Meta::default()
};
for state in &[
StakeState::Initialized(meta),
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(
0,
&StakeState::Uninitialized,
std::mem::size_of::<StakeState>() + 10000,
&id(),
)
.expect("stake_account");
let split_stake_keyed_account =
KeyedAccount::new(&split_stake_pubkey, true, &split_stake_account);
let stake_account = AccountSharedData::new_ref_data_with_space(
stake_lamports,
&state,
std::mem::size_of::<StakeState>(),
&id(),
)
.expect("stake_account");
let stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &stake_account);
assert_eq!(
stake_keyed_account.split(
&invoke_context,
stake_lamports,
&split_stake_keyed_account,
&signers
),
Err(InstructionError::InvalidAccountData)
);
// Test that splitting from a larger account to a smaller one works.
// Split amount should not matter, assuming other fund criteria are met
let split_stake_account = AccountSharedData::new_ref_data_with_space(
0,
&StakeState::Uninitialized,
std::mem::size_of::<StakeState>(),
&id(),
)
.expect("stake_account");
let split_stake_keyed_account =
KeyedAccount::new(&split_stake_pubkey, true, &split_stake_account);
let stake_account = AccountSharedData::new_ref_data_with_space(
stake_lamports,
&state,
std::mem::size_of::<StakeState>() + 100,
&id(),
)
.expect("stake_account");
let stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &stake_account);
assert_eq!(
stake_keyed_account.split(
&invoke_context,
stake_lamports,
&split_stake_keyed_account,
&signers
),
Ok(())
);
assert_eq!(
split_stake_keyed_account.account.borrow().lamports(),
stake_lamports
);
let expected_split_meta = Meta {
authorized: Authorized::auto(&stake_pubkey),
rent_exempt_reserve: split_rent_exempt_reserve,
..Meta::default()
};
match state {
StakeState::Initialized(_) => {
assert_eq!(
Ok(StakeState::Initialized(expected_split_meta)),
split_stake_keyed_account.state()
);
assert_eq!(Ok(StakeState::Uninitialized), stake_keyed_account.state());
}
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 - source_rent_exempt_reserve;
assert_eq!(
Ok(StakeState::Stake(
expected_split_meta,
Stake {
delegation: Delegation {
stake: expected_stake,
..stake.delegation
},
..*stake
}
)),
split_stake_keyed_account.state()
);
assert_eq!(
split_stake_keyed_account.account.borrow().lamports(),
expected_stake + source_rent_exempt_reserve,
);
assert_eq!(Ok(StakeState::Uninitialized), stake_keyed_account.state());
}
_ => unreachable!(),
}
}
}
#[test]
fn test_merge() {
let mut transaction_context = TransactionContext::new(Vec::new(), 1, 1);