programs/stake: cancel deactivate (#17344)

fix: remove stray println

add error for inconsistent input.

fix: lamports don't need to match when redelegating to same vote account

Improve comments

bump

Apply suggestions from code review


Add assert in test

Use stake_program_v4

Co-Authored-By: Trent Nelson <trent.a.b.nelson@gmail.com>

Co-authored-by: Trent Nelson <trent.a.b.nelson@gmail.com>
This commit is contained in:
jon-chuang
2021-05-21 13:31:55 +08:00
committed by GitHub
parent e320af99a0
commit 662c2aaeec
2 changed files with 121 additions and 16 deletions

View File

@ -576,6 +576,8 @@ pub fn process_instruction(
} }
} }
StakeInstruction::DelegateStake => { StakeInstruction::DelegateStake => {
let can_reverse_deactivation =
invoke_context.is_feature_active(&feature_set::stake_program_v4::id());
let vote = keyed_account_at_index(keyed_accounts, 1)?; let vote = keyed_account_at_index(keyed_accounts, 1)?;
me.delegate( me.delegate(
@ -584,6 +586,7 @@ pub fn process_instruction(
&from_keyed_account::<StakeHistory>(keyed_account_at_index(keyed_accounts, 3)?)?, &from_keyed_account::<StakeHistory>(keyed_account_at_index(keyed_accounts, 3)?)?,
&config::from_keyed_account(keyed_account_at_index(keyed_accounts, 4)?)?, &config::from_keyed_account(keyed_account_at_index(keyed_accounts, 4)?)?,
&signers, &signers,
can_reverse_deactivation,
) )
} }
StakeInstruction::Split(lamports) => { StakeInstruction::Split(lamports) => {

View File

@ -755,13 +755,28 @@ impl Stake {
clock: &Clock, clock: &Clock,
stake_history: &StakeHistory, stake_history: &StakeHistory,
config: &Config, config: &Config,
can_reverse_deactivation: bool,
) -> Result<(), StakeError> { ) -> Result<(), StakeError> {
// can't redelegate if stake is active. either the stake // If stake is currently active:
// is freshly activated or has fully de-activated. redelegation
// implies re-activation
if self.stake(clock.epoch, Some(stake_history), true) != 0 { if self.stake(clock.epoch, Some(stake_history), true) != 0 {
// If pubkey of new voter is the same as current,
// and we are scheduled to start deactivating this epoch,
// we rescind deactivation
if self.delegation.voter_pubkey == *voter_pubkey
&& clock.epoch == self.delegation.deactivation_epoch
&& can_reverse_deactivation
{
self.delegation.deactivation_epoch = std::u64::MAX;
return Ok(());
} else {
// can't redelegate to another pubkey if stake is active.
return Err(StakeError::TooSoonToRedelegate); return Err(StakeError::TooSoonToRedelegate);
} }
}
// Either the stake is freshly activated, is active but has been
// deactivated this epoch, or has fully de-activated.
// Redelegation implies either re-activation or un-deactivation
self.delegation.stake = stake_lamports; self.delegation.stake = stake_lamports;
self.delegation.activation_epoch = clock.epoch; self.delegation.activation_epoch = clock.epoch;
self.delegation.deactivation_epoch = std::u64::MAX; self.delegation.deactivation_epoch = std::u64::MAX;
@ -852,6 +867,7 @@ pub trait StakeAccount {
stake_history: &StakeHistory, stake_history: &StakeHistory,
config: &Config, config: &Config,
signers: &HashSet<Pubkey>, signers: &HashSet<Pubkey>,
can_reverse_deactivation: bool,
) -> Result<(), InstructionError>; ) -> Result<(), InstructionError>;
fn deactivate(&self, clock: &Clock, signers: &HashSet<Pubkey>) -> Result<(), InstructionError>; fn deactivate(&self, clock: &Clock, signers: &HashSet<Pubkey>) -> Result<(), InstructionError>;
fn set_lockup( fn set_lockup(
@ -989,6 +1005,7 @@ impl<'a> StakeAccount for KeyedAccount<'a> {
stake_history: &StakeHistory, stake_history: &StakeHistory,
config: &Config, config: &Config,
signers: &HashSet<Pubkey>, signers: &HashSet<Pubkey>,
can_reverse_deactivation: bool,
) -> Result<(), InstructionError> { ) -> Result<(), InstructionError> {
if vote_account.owner()? != solana_vote_program::id() { if vote_account.owner()? != solana_vote_program::id() {
return Err(InstructionError::IncorrectProgramId); return Err(InstructionError::IncorrectProgramId);
@ -1015,6 +1032,7 @@ impl<'a> StakeAccount for KeyedAccount<'a> {
clock, clock,
stake_history, stake_history,
config, config,
can_reverse_deactivation,
)?; )?;
self.set_state(&StakeState::Stake(meta, stake)) self.set_state(&StakeState::Stake(meta, stake))
} }
@ -1908,10 +1926,16 @@ mod tests {
}; };
let vote_pubkey = solana_sdk::pubkey::new_rand(); let vote_pubkey = solana_sdk::pubkey::new_rand();
let vote_pubkey_2 = solana_sdk::pubkey::new_rand();
let mut vote_state = VoteState::default(); let mut vote_state = VoteState::default();
for i in 0..1000 { for i in 0..1000 {
vote_state.process_slot_vote_unchecked(i); vote_state.process_slot_vote_unchecked(i);
} }
let mut vote_state_2 = VoteState::default();
for i in 0..1000 {
vote_state_2.process_slot_vote_unchecked(i);
}
let vote_account = RefCell::new(vote_state::create_account( let vote_account = RefCell::new(vote_state::create_account(
&vote_pubkey, &vote_pubkey,
@ -1919,11 +1943,23 @@ mod tests {
0, 0,
100, 100,
)); ));
let vote_account_2 = RefCell::new(vote_state::create_account(
&vote_pubkey_2,
&solana_sdk::pubkey::new_rand(),
0,
100,
));
let vote_keyed_account = KeyedAccount::new(&vote_pubkey, false, &vote_account); let vote_keyed_account = KeyedAccount::new(&vote_pubkey, false, &vote_account);
let vote_keyed_account_2 = KeyedAccount::new(&vote_pubkey_2, false, &vote_account_2);
let vote_state_credits = vote_state.credits(); let vote_state_credits = vote_state.credits();
vote_keyed_account vote_keyed_account
.set_state(&VoteStateVersions::new_current(vote_state)) .set_state(&VoteStateVersions::new_current(vote_state))
.unwrap(); .unwrap();
let vote_state_credits_2 = vote_state_2.credits();
vote_keyed_account_2
.set_state(&VoteStateVersions::new_current(vote_state_2))
.unwrap();
let stake_pubkey = solana_sdk::pubkey::new_rand(); let stake_pubkey = solana_sdk::pubkey::new_rand();
let stake_lamports = 42; let stake_lamports = 42;
@ -1952,6 +1988,7 @@ mod tests {
&StakeHistory::default(), &StakeHistory::default(),
&Config::default(), &Config::default(),
&signers, &signers,
true,
), ),
Err(InstructionError::MissingRequiredSignature) Err(InstructionError::MissingRequiredSignature)
); );
@ -1966,6 +2003,7 @@ mod tests {
&StakeHistory::default(), &StakeHistory::default(),
&Config::default(), &Config::default(),
&signers, &signers,
true,
) )
.is_ok()); .is_ok());
@ -1987,14 +2025,63 @@ mod tests {
clock.epoch += 1; clock.epoch += 1;
// verify that delegate fails if stake is still active // verify that delegate fails as stake is active and not deactivating
assert_eq!( assert_eq!(
stake_keyed_account.delegate( stake_keyed_account.delegate(
&vote_keyed_account, &vote_keyed_account,
&clock, &clock,
&StakeHistory::default(), &StakeHistory::default(),
&Config::default(), &Config::default(),
&signers &signers,
true
),
Err(StakeError::TooSoonToRedelegate.into())
);
// deactivate
stake_keyed_account.deactivate(&clock, &signers).unwrap();
// verify that delegate to a different vote account fails
// during deactivation
assert_eq!(
stake_keyed_account.delegate(
&vote_keyed_account_2,
&clock,
&StakeHistory::default(),
&Config::default(),
&signers,
true,
),
Err(StakeError::TooSoonToRedelegate.into())
);
// verify that delegate succeeds to same vote account
// when stake is deactivating
stake_keyed_account
.delegate(
&vote_keyed_account,
&clock,
&StakeHistory::default(),
&Config::default(),
&signers,
true,
)
.unwrap();
// verify that deactivation has been cleared
let stake = StakeState::stake_from(&stake_keyed_account.account.borrow()).unwrap();
assert_eq!(stake.delegation.deactivation_epoch, std::u64::MAX);
// verify that delegate to a different vote account fails
// if stake is still active
assert_eq!(
stake_keyed_account.delegate(
&vote_keyed_account_2,
&clock,
&StakeHistory::default(),
&Config::default(),
&signers,
true,
), ),
Err(StakeError::TooSoonToRedelegate.into()) Err(StakeError::TooSoonToRedelegate.into())
); );
@ -2005,23 +2092,25 @@ mod tests {
// without stake history, cool down is instantaneous // without stake history, cool down is instantaneous
clock.epoch += 1; clock.epoch += 1;
// verify that delegate can be called twice, 2nd is redelegate // verify that delegate can be called to new vote account, 2nd is redelegate
assert!(stake_keyed_account assert!(stake_keyed_account
.delegate( .delegate(
&vote_keyed_account, &vote_keyed_account_2,
&clock, &clock,
&StakeHistory::default(), &StakeHistory::default(),
&Config::default(), &Config::default(),
&signers &signers,
true,
) )
.is_ok()); .is_ok());
// signed but faked vote account // signed but faked vote account
let faked_vote_account = vote_account.clone(); let faked_vote_account = vote_account_2.clone();
faked_vote_account faked_vote_account
.borrow_mut() .borrow_mut()
.set_owner(solana_sdk::pubkey::new_rand()); .set_owner(solana_sdk::pubkey::new_rand());
let faked_vote_keyed_account = KeyedAccount::new(&vote_pubkey, false, &faked_vote_account); let faked_vote_keyed_account =
KeyedAccount::new(&vote_pubkey_2, false, &faked_vote_account);
assert_eq!( assert_eq!(
stake_keyed_account.delegate( stake_keyed_account.delegate(
&faked_vote_keyed_account, &faked_vote_keyed_account,
@ -2029,6 +2118,7 @@ mod tests {
&StakeHistory::default(), &StakeHistory::default(),
&Config::default(), &Config::default(),
&signers, &signers,
true,
), ),
Err(solana_sdk::instruction::InstructionError::IncorrectProgramId) Err(solana_sdk::instruction::InstructionError::IncorrectProgramId)
); );
@ -2039,13 +2129,13 @@ mod tests {
stake, stake,
Stake { Stake {
delegation: Delegation { delegation: Delegation {
voter_pubkey: vote_pubkey, voter_pubkey: vote_pubkey_2,
stake: stake_lamports, stake: stake_lamports,
activation_epoch: clock.epoch, activation_epoch: clock.epoch,
deactivation_epoch: std::u64::MAX, deactivation_epoch: std::u64::MAX,
..Delegation::default() ..Delegation::default()
}, },
credits_observed: vote_state_credits, credits_observed: vote_state_credits_2,
} }
); );
@ -2059,7 +2149,8 @@ mod tests {
&clock, &clock,
&StakeHistory::default(), &StakeHistory::default(),
&Config::default(), &Config::default(),
&signers &signers,
true,
) )
.is_err()); .is_err());
} }
@ -2835,7 +2926,8 @@ mod tests {
&clock, &clock,
&StakeHistory::default(), &StakeHistory::default(),
&Config::default(), &Config::default(),
&signers &signers,
true,
), ),
Ok(()) Ok(())
); );
@ -2928,6 +3020,7 @@ mod tests {
&StakeHistory::default(), &StakeHistory::default(),
&Config::default(), &Config::default(),
&vec![stake_pubkey].into_iter().collect(), &vec![stake_pubkey].into_iter().collect(),
true,
) )
.unwrap(); .unwrap();
@ -3174,6 +3267,7 @@ mod tests {
&StakeHistory::default(), &StakeHistory::default(),
&Config::default(), &Config::default(),
&signers, &signers,
true,
), ),
Ok(()) Ok(())
); );
@ -3350,6 +3444,7 @@ mod tests {
&StakeHistory::default(), &StakeHistory::default(),
&Config::default(), &Config::default(),
&signers, &signers,
true,
), ),
Ok(()) Ok(())
); );
@ -5926,6 +6021,7 @@ mod tests {
&StakeHistory::default(), &StakeHistory::default(),
&Config::default(), &Config::default(),
&signers, &signers,
true,
) )
.unwrap(); .unwrap();
@ -5973,6 +6069,7 @@ mod tests {
&StakeHistory::default(), &StakeHistory::default(),
&Config::default(), &Config::default(),
&other_signers, &other_signers,
true,
), ),
Err(InstructionError::MissingRequiredSignature) Err(InstructionError::MissingRequiredSignature)
); );
@ -5985,7 +6082,8 @@ mod tests {
&clock, &clock,
&StakeHistory::default(), &StakeHistory::default(),
&Config::default(), &Config::default(),
&new_signers &new_signers,
true,
), ),
Ok(()) Ok(())
); );
@ -6038,6 +6136,7 @@ mod tests {
&stake_history, &stake_history,
&config, &config,
&signers, &signers,
true,
) )
.unwrap(); .unwrap();
@ -6045,6 +6144,7 @@ mod tests {
stake_keyed_account.deactivate(&clock, &signers).unwrap(); stake_keyed_account.deactivate(&clock, &signers).unwrap();
clock.epoch += 1; clock.epoch += 1;
// Once deactivated, we withdraw stake to new keyed account
let to = Pubkey::new_unique(); let to = Pubkey::new_unique();
let to_account = AccountSharedData::new_ref(1, 0, &system_program::id()); let to_account = AccountSharedData::new_ref(1, 0, &system_program::id());
let to_keyed_account = KeyedAccount::new(&to, false, &to_account); let to_keyed_account = KeyedAccount::new(&to, false, &to_account);
@ -6071,6 +6171,7 @@ mod tests {
&stake_history, &stake_history,
&config, &config,
&signers, &signers,
true,
) )
.unwrap(); .unwrap();
let stake = StakeState::stake_from(&stake_account.borrow()).unwrap(); let stake = StakeState::stake_from(&stake_account.borrow()).unwrap();
@ -6097,6 +6198,7 @@ mod tests {
&stake_history, &stake_history,
&config, &config,
&signers, &signers,
true,
) )
.unwrap(); .unwrap();
let stake = StakeState::stake_from(&stake_account.borrow()).unwrap(); let stake = StakeState::stake_from(&stake_account.borrow()).unwrap();