disallow withdraw of stake unless deactivated (#5457)

This commit is contained in:
Rob Walker 2019-08-07 20:29:22 -07:00 committed by GitHub
parent 5b51bb27b6
commit 7a603d72bf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1 changed files with 48 additions and 31 deletions

View File

@ -287,14 +287,12 @@ impl<'a> StakeAccount for KeyedAccount<'a> {
match self.state()? { match self.state()? {
StakeState::Stake(mut stake) => { StakeState::Stake(mut stake) => {
// still activated, no can do // if deactivated and in cooldown
if stake.deactivated == std::u64::MAX { let staked = if clock.epoch >= stake.deactivated {
return Err(InstructionError::InsufficientFunds); stake.stake(clock.epoch)
}
let staked = if stake.stake(clock.epoch) == 0 {
0
} else { } else {
// Assume full stake if the stake is under warmup/cooldown // Assume full stake if the stake is under warmup, or
// hasn't been de-activated
stake.stake stake.stake
}; };
if lamports > self.account.lamports.saturating_sub(staked) { if lamports > self.account.lamports.saturating_sub(staked) {
@ -500,7 +498,7 @@ mod tests {
#[test] #[test]
fn test_withdraw_stake() { fn test_withdraw_stake() {
let stake_pubkey = Pubkey::new_rand(); let stake_pubkey = Pubkey::new_rand();
let mut total_lamports = 100; let total_lamports = 100;
let stake_lamports = 42; let stake_lamports = 42;
let mut stake_account = let mut stake_account =
Account::new(total_lamports, std::mem::size_of::<StakeState>(), &id()); Account::new(total_lamports, std::mem::size_of::<StakeState>(), &id());
@ -511,30 +509,32 @@ mod tests {
let mut to_account = Account::new(1, 0, &system_program::id()); let mut to_account = Account::new(1, 0, &system_program::id());
let mut to_keyed_account = KeyedAccount::new(&to, false, &mut to_account); let mut to_keyed_account = KeyedAccount::new(&to, false, &mut to_account);
// unsigned keyed account // unsigned keyed account should fail
let mut stake_keyed_account = KeyedAccount::new(&stake_pubkey, false, &mut stake_account); let mut stake_keyed_account = KeyedAccount::new(&stake_pubkey, false, &mut stake_account);
assert_eq!( assert_eq!(
stake_keyed_account.withdraw(total_lamports, &mut to_keyed_account, &clock), stake_keyed_account.withdraw(total_lamports, &mut to_keyed_account, &clock),
Err(InstructionError::MissingRequiredSignature) Err(InstructionError::MissingRequiredSignature)
); );
// signed keyed account but uninitialized // signed keyed account and uninitialized should work
// try withdrawing more than balance let mut stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &mut stake_account);
assert_eq!(
stake_keyed_account.withdraw(total_lamports, &mut to_keyed_account, &clock),
Ok(())
);
assert_eq!(stake_account.lamports, 0);
// reset balance
stake_account.lamports = total_lamports;
// signed keyed account and uninitialized, more than available should fail
let mut stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &mut stake_account); let mut stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &mut stake_account);
assert_eq!( assert_eq!(
stake_keyed_account.withdraw(total_lamports + 1, &mut to_keyed_account, &clock), stake_keyed_account.withdraw(total_lamports + 1, &mut to_keyed_account, &clock),
Err(InstructionError::InsufficientFunds) Err(InstructionError::InsufficientFunds)
); );
// try withdrawing some (enough for rest of the test to carry forward) // Stake some lamports (available lampoorts for withdrawals will reduce)
let mut stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &mut stake_account);
assert_eq!(
stake_keyed_account.withdraw(5, &mut to_keyed_account, &clock),
Ok(())
);
total_lamports -= 5;
// Stake some lamports (available lampoorts for withdrawls will reduce)
let vote_pubkey = Pubkey::new_rand(); let vote_pubkey = Pubkey::new_rand();
let mut vote_account = let mut vote_account =
vote_state::create_account(&vote_pubkey, &Pubkey::new_rand(), 0, 100); vote_state::create_account(&vote_pubkey, &Pubkey::new_rand(), 0, 100);
@ -545,12 +545,21 @@ mod tests {
Ok(()) Ok(())
); );
// deactivate the stake before withdrawal // withdrawal before deactivate works for some portion
assert_eq!(stake_keyed_account.deactivate_stake(&clock), Ok(())); let mut stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &mut stake_account);
// simulate time passing assert_eq!(
clock.epoch += STAKE_WARMUP_EPOCHS; stake_keyed_account.withdraw(
total_lamports - stake_lamports,
&mut to_keyed_account,
&clock
),
Ok(())
);
// reset
stake_account.lamports = total_lamports;
// Try to withdraw more than what's available // withdrawal before deactivate fails if not in excess of stake
let mut stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &mut stake_account);
assert_eq!( assert_eq!(
stake_keyed_account.withdraw( stake_keyed_account.withdraw(
total_lamports - stake_lamports + 1, total_lamports - stake_lamports + 1,
@ -560,15 +569,23 @@ mod tests {
Err(InstructionError::InsufficientFunds) Err(InstructionError::InsufficientFunds)
); );
// Try to withdraw all unstaked lamports // deactivate the stake before withdrawal
assert_eq!(stake_keyed_account.deactivate_stake(&clock), Ok(()));
// simulate time passing
clock.epoch += STAKE_WARMUP_EPOCHS * 2;
// Try to withdraw more than what's available
assert_eq!( assert_eq!(
stake_keyed_account.withdraw( stake_keyed_account.withdraw(total_lamports + 1, &mut to_keyed_account, &clock),
total_lamports - stake_lamports, Err(InstructionError::InsufficientFunds)
&mut to_keyed_account, );
&clock
), // Try to withdraw all lamports
assert_eq!(
stake_keyed_account.withdraw(total_lamports, &mut to_keyed_account, &clock),
Ok(()) Ok(())
); );
assert_eq!(stake_account.lamports, 0);
} }
#[test] #[test]