Add test to check destination for stake splitting (#23303)

This commit is contained in:
Brooks Prumo 2022-02-23 22:47:57 -06:00 committed by GitHub
parent 856d29c7b7
commit fcaf01e243
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1 changed files with 145 additions and 0 deletions

View File

@ -6666,6 +6666,151 @@ mod tests {
}
}
/// Ensure that `split()` correctly handles prefunded destination accounts. When a destination
/// account already has funds, ensure the minimum split amount reduces accordingly.
#[test]
fn test_split_destination_minimum_stake_delegation() {
let rent = Rent::default();
let rent_exempt_reserve = rent.minimum_balance(std::mem::size_of::<StakeState>());
for (destination_starting_balance, split_amount, expected_result) in [
// split amount must be non zero
(
rent_exempt_reserve + MINIMUM_STAKE_DELEGATION,
0,
Err(InstructionError::InsufficientFunds),
),
// any split amount is OK when destination account is already fully funded
(rent_exempt_reserve + MINIMUM_STAKE_DELEGATION, 1, Ok(())),
// if destination is only short by 1 lamport, then split amount can be 1 lamport
(
rent_exempt_reserve + MINIMUM_STAKE_DELEGATION - 1,
1,
Ok(()),
),
// destination short by 2 lamports, so 1 isn't enough (non-zero split amount)
(
rent_exempt_reserve + MINIMUM_STAKE_DELEGATION - 2,
1,
Err(InstructionError::InsufficientFunds),
),
// destination is rent exempt, so split enough for minimum delegation
(rent_exempt_reserve, MINIMUM_STAKE_DELEGATION, Ok(())),
// destination is rent exempt, but split amount less than minimum delegation
(
rent_exempt_reserve,
MINIMUM_STAKE_DELEGATION - 1,
Err(InstructionError::InsufficientFunds),
),
// destination is not rent exempt, so split enough for rent and minimum delegation
(
rent_exempt_reserve - 1,
MINIMUM_STAKE_DELEGATION + 1,
Ok(()),
),
// destination is not rent exempt, but split amount only for minimum delegation
(
rent_exempt_reserve - 1,
MINIMUM_STAKE_DELEGATION,
Err(InstructionError::InsufficientFunds),
),
// destination has smallest non-zero balance, so can split the minimum balance
// requirements minus what destination already has
(
1,
rent_exempt_reserve + MINIMUM_STAKE_DELEGATION - 1,
Ok(()),
),
// destination has smallest non-zero balance, but cannot split less than the minimum
// balance requirements minus what destination already has
(
1,
rent_exempt_reserve + MINIMUM_STAKE_DELEGATION - 2,
Err(InstructionError::InsufficientFunds),
),
// destination has zero lamports, so split must be at least rent exempt reserve plus
// minimum delegation
(0, rent_exempt_reserve + MINIMUM_STAKE_DELEGATION, Ok(())),
// destination has zero lamports, but split amount is less than rent exempt reserve
// plus minimum delegation
(
0,
rent_exempt_reserve + MINIMUM_STAKE_DELEGATION - 1,
Err(InstructionError::InsufficientFunds),
),
] {
let source_pubkey = Pubkey::new_unique();
let source_meta = Meta {
rent_exempt_reserve,
..Meta::auto(&source_pubkey)
};
// Set the source's starting balance and stake delegation amount to something large
// to ensure its post-split balance meets all the requirements
let source_balance = u64::MAX;
let source_stake_delegation = source_balance - rent_exempt_reserve;
for source_stake_state in &[
StakeState::Initialized(source_meta),
StakeState::Stake(source_meta, just_stake(source_stake_delegation)),
] {
let source_account = AccountSharedData::new_ref_data_with_space(
source_balance,
&source_stake_state,
std::mem::size_of::<StakeState>(),
&id(),
)
.unwrap();
let source_keyed_account = KeyedAccount::new(&source_pubkey, true, &source_account);
let destination_pubkey = Pubkey::new_unique();
let destination_account = AccountSharedData::new_ref_data_with_space(
destination_starting_balance,
&StakeState::Uninitialized,
std::mem::size_of::<StakeState>(),
&id(),
)
.unwrap();
let destination_keyed_account =
KeyedAccount::new(&destination_pubkey, true, &destination_account);
assert_eq!(
expected_result,
source_keyed_account.split(
split_amount,
&destination_keyed_account,
&HashSet::from([source_pubkey]),
),
);
// For the expected OK cases, when the source's StakeState is Stake, then the
// destination's StakeState *must* also end up as Stake as well. Additionally,
// check to ensure the destination's delegation amount is correct. If the
// destination is already rent exempt, then the destination's stake delegation
// *must* equal the split amount. Otherwise, the split amount must first be used to
// make the destination rent exempt, and then the leftover lamports are delegated.
if expected_result.is_ok() {
if let StakeState::Stake(_, _) = source_keyed_account.state().unwrap() {
if let StakeState::Stake(_, destination_stake) =
destination_keyed_account.state().unwrap()
{
let destination_initial_rent_deficit =
rent_exempt_reserve.saturating_sub(destination_starting_balance);
let expected_destination_stake_delegation =
split_amount - destination_initial_rent_deficit;
assert_eq!(
expected_destination_stake_delegation,
destination_stake.delegation.stake
);
} else {
panic!("destination state must be StakeStake::Stake after successful split when source is also StakeState::Stake!");
}
}
}
}
}
}
/// Ensure that `withdraw()` respects the MINIMUM_STAKE_DELEGATION requirements
/// - Assert 1: withdrawing so remaining stake is equal-to the minimum is OK
/// - Assert 2: withdrawing so remaining stake is less-than the minimum is not OK