Add test to check destination for stake splitting (#23303)
This commit is contained in:
parent
856d29c7b7
commit
fcaf01e243
|
@ -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
|
/// Ensure that `withdraw()` respects the MINIMUM_STAKE_DELEGATION requirements
|
||||||
/// - Assert 1: withdrawing so remaining stake is equal-to the minimum is OK
|
/// - 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
|
/// - Assert 2: withdrawing so remaining stake is less-than the minimum is not OK
|
||||||
|
|
Loading…
Reference in New Issue