Add authorize_staker functionality (#5880)
* Add authorized_staker functionality * Generalize authorize names; implement for Lockup * Fix authorize() usage and improve tests
This commit is contained in:
		| @@ -45,7 +45,11 @@ pub enum StakeInstruction { | ||||
|     ///    will allow withdrawal from the stake account. | ||||
|     /// | ||||
|     Lockup(Slot), | ||||
|  | ||||
|     /// Authorize a system account to manage stake | ||||
|     /// | ||||
|     /// Expects 1 Account: | ||||
|     ///     0 - Locked-up or delegated StakeAccount to be updated with authorized staker | ||||
|     Authorize(Pubkey), | ||||
|     /// `Delegate` a stake to a particular vote account | ||||
|     /// | ||||
|     /// Expects 4 Accounts: | ||||
| @@ -133,6 +137,42 @@ pub fn create_stake_account_and_delegate_stake( | ||||
|     instructions | ||||
| } | ||||
|  | ||||
| fn metas_for_authorized_staker( | ||||
|     stake_pubkey: &Pubkey, | ||||
|     authorized_pubkey: &Pubkey, // currently authorized | ||||
|     other_params: &[AccountMeta], | ||||
| ) -> Vec<AccountMeta> { | ||||
|     let is_own_signer = authorized_pubkey == stake_pubkey; | ||||
|  | ||||
|     // stake account | ||||
|     let mut account_metas = vec![AccountMeta::new(*stake_pubkey, is_own_signer)]; | ||||
|  | ||||
|     for meta in other_params { | ||||
|         account_metas.push(meta.clone()); | ||||
|     } | ||||
|  | ||||
|     // append signer at the end | ||||
|     if !is_own_signer { | ||||
|         account_metas.push(AccountMeta::new_credit_only(*authorized_pubkey, true)) // signer | ||||
|     } | ||||
|  | ||||
|     account_metas | ||||
| } | ||||
|  | ||||
| pub fn authorize( | ||||
|     stake_pubkey: &Pubkey, | ||||
|     authorized_pubkey: &Pubkey, | ||||
|     new_authorized_pubkey: &Pubkey, | ||||
| ) -> Instruction { | ||||
|     let account_metas = metas_for_authorized_staker(stake_pubkey, authorized_pubkey, &[]); | ||||
|  | ||||
|     Instruction::new( | ||||
|         id(), | ||||
|         &StakeInstruction::Authorize(*new_authorized_pubkey), | ||||
|         account_metas, | ||||
|     ) | ||||
| } | ||||
|  | ||||
| pub fn redeem_vote_credits(stake_pubkey: &Pubkey, vote_pubkey: &Pubkey) -> Instruction { | ||||
|     let account_metas = vec![ | ||||
|         AccountMeta::new(*stake_pubkey, false), | ||||
| @@ -193,8 +233,9 @@ pub fn process_instruction( | ||||
|     // TODO: data-driven unpack and dispatch of KeyedAccounts | ||||
|     match deserialize(data).map_err(|_| InstructionError::InvalidInstructionData)? { | ||||
|         StakeInstruction::Lockup(slot) => me.lockup(slot), | ||||
|         StakeInstruction::Authorize(authorized_pubkey) => me.authorize(&authorized_pubkey, &rest), | ||||
|         StakeInstruction::DelegateStake => { | ||||
|             if rest.len() != 3 { | ||||
|             if rest.len() < 3 { | ||||
|                 Err(InstructionError::InvalidInstructionData)?; | ||||
|             } | ||||
|             let vote = &rest[0]; | ||||
| @@ -203,6 +244,7 @@ pub fn process_instruction( | ||||
|                 vote, | ||||
|                 &sysvar::clock::from_keyed_account(&rest[1])?, | ||||
|                 &config::from_keyed_account(&rest[2])?, | ||||
|                 &rest[3..], | ||||
|             ) | ||||
|         } | ||||
|         StakeInstruction::RedeemVoteCredits => { | ||||
| @@ -222,28 +264,32 @@ pub fn process_instruction( | ||||
|             ) | ||||
|         } | ||||
|         StakeInstruction::Withdraw(lamports) => { | ||||
|             if rest.len() != 3 { | ||||
|             if rest.len() < 3 { | ||||
|                 Err(InstructionError::InvalidInstructionData)?; | ||||
|             } | ||||
|             let (to, sysvar) = &mut rest.split_at_mut(1); | ||||
|             let (to, rest) = &mut rest.split_at_mut(1); | ||||
|             let mut to = &mut to[0]; | ||||
|  | ||||
|             me.withdraw( | ||||
|                 lamports, | ||||
|                 &mut to, | ||||
|                 &sysvar::clock::from_keyed_account(&sysvar[0])?, | ||||
|                 &sysvar::stake_history::from_keyed_account(&sysvar[1])?, | ||||
|                 &sysvar::clock::from_keyed_account(&rest[0])?, | ||||
|                 &sysvar::stake_history::from_keyed_account(&rest[1])?, | ||||
|                 &rest[2..], | ||||
|             ) | ||||
|         } | ||||
|         StakeInstruction::Deactivate => { | ||||
|             if rest.len() != 2 { | ||||
|             if rest.len() < 2 { | ||||
|                 Err(InstructionError::InvalidInstructionData)?; | ||||
|             } | ||||
|             let (vote, rest) = rest.split_at_mut(1); | ||||
|             let vote = &mut vote[0]; | ||||
|             let clock = &rest[0]; | ||||
|  | ||||
|             me.deactivate_stake(vote, &sysvar::clock::from_keyed_account(&clock)?) | ||||
|             me.deactivate_stake( | ||||
|                 vote, | ||||
|                 &sysvar::clock::from_keyed_account(&rest[0])?, | ||||
|                 &rest[1..], | ||||
|             ) | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -22,7 +22,7 @@ use solana_vote_api::vote_state::VoteState; | ||||
| #[allow(clippy::large_enum_variant)] | ||||
| pub enum StakeState { | ||||
|     Uninitialized, | ||||
|     Lockup(Slot), | ||||
|     Lockup(Lockup), | ||||
|     Stake(Stake), | ||||
|     RewardsPool, | ||||
| } | ||||
| @@ -51,8 +51,18 @@ impl StakeState { | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Serialize, Deserialize, PartialEq, Clone)] | ||||
| pub struct Lockup { | ||||
|     /// slot height at which this stake will allow withdrawal | ||||
|     pub slot: Slot, | ||||
|     /// alternate signer that is enabled to act on the Stake account after lockup | ||||
|     pub authorized_pubkey: Pubkey, | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Serialize, Deserialize, PartialEq, Clone)] | ||||
| pub struct Stake { | ||||
|     /// alternate signer that is enabled to act on the Stake account | ||||
|     pub authorized_pubkey: Pubkey, | ||||
|     /// most recently delegated vote account pubkey | ||||
|     pub voter_pubkey: Pubkey, | ||||
|     /// the epoch when voter_pubkey was most recently set | ||||
| @@ -81,6 +91,7 @@ const MAX_PRIOR_DELEGATES: usize = 32; // this is how many epochs a stake is exp | ||||
| impl Default for Stake { | ||||
|     fn default() -> Self { | ||||
|         Self { | ||||
|             authorized_pubkey: Pubkey::default(), | ||||
|             voter_pubkey: Pubkey::default(), | ||||
|             voter_pubkey_epoch: 0, | ||||
|             credits_observed: 0, | ||||
| @@ -286,6 +297,7 @@ impl Stake { | ||||
|     fn new_bootstrap(stake: u64, voter_pubkey: &Pubkey, vote_state: &VoteState) -> Self { | ||||
|         Self::new( | ||||
|             stake, | ||||
|             &Pubkey::default(), | ||||
|             voter_pubkey, | ||||
|             vote_state, | ||||
|             std::u64::MAX, | ||||
| @@ -316,6 +328,7 @@ impl Stake { | ||||
|  | ||||
|     fn new( | ||||
|         stake: u64, | ||||
|         stake_pubkey: &Pubkey, | ||||
|         voter_pubkey: &Pubkey, | ||||
|         vote_state: &VoteState, | ||||
|         activation_epoch: Epoch, | ||||
| @@ -325,6 +338,7 @@ impl Stake { | ||||
|         Self { | ||||
|             stake, | ||||
|             activation_epoch, | ||||
|             authorized_pubkey: *stake_pubkey, | ||||
|             voter_pubkey: *voter_pubkey, | ||||
|             voter_pubkey_epoch: activation_epoch, | ||||
|             credits_observed: vote_state.credits(), | ||||
| @@ -339,18 +353,69 @@ impl Stake { | ||||
|     } | ||||
| } | ||||
|  | ||||
| trait Authorized { | ||||
|     fn check_authorized( | ||||
|         &self, | ||||
|         stake_pubkey_signer: Option<&Pubkey>, | ||||
|         other_signers: &[KeyedAccount], | ||||
|     ) -> Result<(), InstructionError>; | ||||
| } | ||||
|  | ||||
| impl Authorized for Lockup { | ||||
|     fn check_authorized( | ||||
|         &self, | ||||
|         stake_pubkey_signer: Option<&Pubkey>, | ||||
|         other_signers: &[KeyedAccount], | ||||
|     ) -> Result<(), InstructionError> { | ||||
|         let authorized = Some(&self.authorized_pubkey); | ||||
|         if stake_pubkey_signer != authorized | ||||
|             && other_signers | ||||
|                 .iter() | ||||
|                 .all(|account| account.signer_key() != authorized) | ||||
|         { | ||||
|             return Err(InstructionError::MissingRequiredSignature); | ||||
|         } | ||||
|         Ok(()) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl Authorized for Stake { | ||||
|     fn check_authorized( | ||||
|         &self, | ||||
|         stake_pubkey_signer: Option<&Pubkey>, | ||||
|         other_signers: &[KeyedAccount], | ||||
|     ) -> Result<(), InstructionError> { | ||||
|         let authorized = Some(&self.authorized_pubkey); | ||||
|         if stake_pubkey_signer != authorized | ||||
|             && other_signers | ||||
|                 .iter() | ||||
|                 .all(|account| account.signer_key() != authorized) | ||||
|         { | ||||
|             return Err(InstructionError::MissingRequiredSignature); | ||||
|         } | ||||
|         Ok(()) | ||||
|     } | ||||
| } | ||||
|  | ||||
| pub trait StakeAccount { | ||||
|     fn lockup(&mut self, slot: Slot) -> Result<(), InstructionError>; | ||||
|     fn authorize( | ||||
|         &mut self, | ||||
|         authorized_pubkey: &Pubkey, | ||||
|         other_signers: &[KeyedAccount], | ||||
|     ) -> Result<(), InstructionError>; | ||||
|     fn delegate_stake( | ||||
|         &mut self, | ||||
|         vote_account: &KeyedAccount, | ||||
|         clock: &sysvar::clock::Clock, | ||||
|         config: &Config, | ||||
|         other_signers: &[KeyedAccount], | ||||
|     ) -> Result<(), InstructionError>; | ||||
|     fn deactivate_stake( | ||||
|         &mut self, | ||||
|         vote_account: &KeyedAccount, | ||||
|         clock: &sysvar::clock::Clock, | ||||
|         other_signers: &[KeyedAccount], | ||||
|     ) -> Result<(), InstructionError>; | ||||
|     fn redeem_vote_credits( | ||||
|         &mut self, | ||||
| @@ -365,12 +430,37 @@ pub trait StakeAccount { | ||||
|         to: &mut KeyedAccount, | ||||
|         clock: &sysvar::clock::Clock, | ||||
|         stake_history: &sysvar::stake_history::StakeHistory, | ||||
|         other_signers: &[KeyedAccount], | ||||
|     ) -> Result<(), InstructionError>; | ||||
| } | ||||
|  | ||||
| impl<'a> StakeAccount for KeyedAccount<'a> { | ||||
|     fn lockup(&mut self, lockup: Slot) -> Result<(), InstructionError> { | ||||
|         if let StakeState::Uninitialized = self.state()? { | ||||
|             self.set_state(&StakeState::Lockup(Lockup { | ||||
|                 slot: lockup, | ||||
|                 authorized_pubkey: *self.unsigned_key(), | ||||
|             })) | ||||
|         } else { | ||||
|             Err(InstructionError::InvalidAccountData) | ||||
|         } | ||||
|     } | ||||
|     /// Authorize the given pubkey to manage stake (deactivate, withdraw). This may be called | ||||
|     /// multiple times, but will implicitly withdraw authorization from the previously authorized | ||||
|     /// staker. The default staker is the owner of the stake account's pubkey. | ||||
|     fn authorize( | ||||
|         &mut self, | ||||
|         authorized_pubkey: &Pubkey, | ||||
|         other_signers: &[KeyedAccount], | ||||
|     ) -> Result<(), InstructionError> { | ||||
|         let stake_state = self.state()?; | ||||
|         if let StakeState::Stake(mut stake) = stake_state { | ||||
|             stake.check_authorized(self.signer_key(), other_signers)?; | ||||
|             stake.authorized_pubkey = *authorized_pubkey; | ||||
|             self.set_state(&StakeState::Stake(stake)) | ||||
|         } else if let StakeState::Lockup(mut lockup) = stake_state { | ||||
|             lockup.check_authorized(self.signer_key(), other_signers)?; | ||||
|             lockup.authorized_pubkey = *authorized_pubkey; | ||||
|             self.set_state(&StakeState::Lockup(lockup)) | ||||
|         } else { | ||||
|             Err(InstructionError::InvalidAccountData) | ||||
| @@ -381,23 +471,23 @@ impl<'a> StakeAccount for KeyedAccount<'a> { | ||||
|         vote_account: &KeyedAccount, | ||||
|         clock: &sysvar::clock::Clock, | ||||
|         config: &Config, | ||||
|         other_signers: &[KeyedAccount], | ||||
|     ) -> Result<(), InstructionError> { | ||||
|         if self.signer_key().is_none() { | ||||
|             return Err(InstructionError::MissingRequiredSignature); | ||||
|         } | ||||
|  | ||||
|         if let StakeState::Lockup(lockup) = self.state()? { | ||||
|             lockup.check_authorized(self.signer_key(), other_signers)?; | ||||
|             let stake = Stake::new( | ||||
|                 self.account.lamports, | ||||
|                 &lockup.authorized_pubkey, | ||||
|                 vote_account.unsigned_key(), | ||||
|                 &vote_account.state()?, | ||||
|                 clock.epoch, | ||||
|                 config, | ||||
|                 lockup, | ||||
|                 lockup.slot, | ||||
|             ); | ||||
|  | ||||
|             self.set_state(&StakeState::Stake(stake)) | ||||
|         } else if let StakeState::Stake(mut stake) = self.state()? { | ||||
|             stake.check_authorized(self.signer_key(), other_signers)?; | ||||
|             stake.redelegate( | ||||
|                 vote_account.unsigned_key(), | ||||
|                 &vote_account.state()?, | ||||
| @@ -412,12 +502,10 @@ impl<'a> StakeAccount for KeyedAccount<'a> { | ||||
|         &mut self, | ||||
|         _vote_account: &KeyedAccount, // TODO: used in slashing | ||||
|         clock: &sysvar::clock::Clock, | ||||
|         other_signers: &[KeyedAccount], | ||||
|     ) -> Result<(), InstructionError> { | ||||
|         if self.signer_key().is_none() { | ||||
|             return Err(InstructionError::MissingRequiredSignature); | ||||
|         } | ||||
|  | ||||
|         if let StakeState::Stake(mut stake) = self.state()? { | ||||
|             stake.check_authorized(self.signer_key(), other_signers)?; | ||||
|             stake.deactivate(clock.epoch); | ||||
|  | ||||
|             self.set_state(&StakeState::Stake(stake)) | ||||
| @@ -475,11 +563,8 @@ impl<'a> StakeAccount for KeyedAccount<'a> { | ||||
|         to: &mut KeyedAccount, | ||||
|         clock: &sysvar::clock::Clock, | ||||
|         stake_history: &sysvar::stake_history::StakeHistory, | ||||
|         other_signers: &[KeyedAccount], | ||||
|     ) -> Result<(), InstructionError> { | ||||
|         if self.signer_key().is_none() { | ||||
|             return Err(InstructionError::MissingRequiredSignature); | ||||
|         } | ||||
|  | ||||
|         fn transfer( | ||||
|             from: &mut Account, | ||||
|             to: &mut Account, | ||||
| @@ -495,6 +580,7 @@ impl<'a> StakeAccount for KeyedAccount<'a> { | ||||
|  | ||||
|         match self.state()? { | ||||
|             StakeState::Stake(stake) => { | ||||
|                 stake.check_authorized(self.signer_key(), other_signers)?; | ||||
|                 // if we have a deactivation epoch and we're in cooldown | ||||
|                 let staked = if clock.epoch >= stake.deactivation_epoch { | ||||
|                     stake.stake(clock.epoch, Some(stake_history)) | ||||
| @@ -510,11 +596,16 @@ impl<'a> StakeAccount for KeyedAccount<'a> { | ||||
|                 } | ||||
|             } | ||||
|             StakeState::Lockup(lockup) => { | ||||
|                 if lockup > clock.slot { | ||||
|                 lockup.check_authorized(self.signer_key(), other_signers)?; | ||||
|                 if lockup.slot > clock.slot { | ||||
|                     return Err(InstructionError::InsufficientFunds); | ||||
|                 } | ||||
|             } | ||||
|             StakeState::Uninitialized => {} | ||||
|             StakeState::Uninitialized => { | ||||
|                 if self.signer_key().is_none() { | ||||
|                     return Err(InstructionError::MissingRequiredSignature); | ||||
|                 } | ||||
|             } | ||||
|             _ => return Err(InstructionError::InvalidAccountData), | ||||
|         } | ||||
|         transfer(&mut self.account, &mut to.account, lamports) | ||||
| @@ -623,7 +714,10 @@ mod tests { | ||||
|         let stake_lamports = 42; | ||||
|         let mut stake_account = Account::new_data_with_space( | ||||
|             stake_lamports, | ||||
|             &StakeState::Lockup(0), | ||||
|             &StakeState::Lockup(Lockup { | ||||
|                 slot: 0, | ||||
|                 authorized_pubkey: stake_pubkey, | ||||
|             }), | ||||
|             std::mem::size_of::<StakeState>(), | ||||
|             &id(), | ||||
|         ) | ||||
| @@ -634,18 +728,29 @@ mod tests { | ||||
|  | ||||
|         { | ||||
|             let stake_state: StakeState = stake_keyed_account.state().unwrap(); | ||||
|             assert_eq!(stake_state, StakeState::Lockup(0)); | ||||
|             assert_eq!( | ||||
|                 stake_state, | ||||
|                 StakeState::Lockup(Lockup { | ||||
|                     slot: 0, | ||||
|                     authorized_pubkey: stake_pubkey | ||||
|                 }) | ||||
|             ); | ||||
|         } | ||||
|  | ||||
|         assert_eq!( | ||||
|             stake_keyed_account.delegate_stake(&vote_keyed_account, &clock, &Config::default()), | ||||
|             stake_keyed_account.delegate_stake( | ||||
|                 &vote_keyed_account, | ||||
|                 &clock, | ||||
|                 &Config::default(), | ||||
|                 &[] | ||||
|             ), | ||||
|             Err(InstructionError::MissingRequiredSignature) | ||||
|         ); | ||||
|  | ||||
|         // signed keyed account | ||||
|         let mut stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &mut stake_account); | ||||
|         assert!(stake_keyed_account | ||||
|             .delegate_stake(&vote_keyed_account, &clock, &Config::default()) | ||||
|             .delegate_stake(&vote_keyed_account, &clock, &Config::default(), &[]) | ||||
|             .is_ok()); | ||||
|  | ||||
|         // verify that delegate_stake() looks right, compare against hand-rolled | ||||
| @@ -653,6 +758,7 @@ mod tests { | ||||
|         assert_eq!( | ||||
|             stake, | ||||
|             Stake { | ||||
|                 authorized_pubkey: stake_pubkey, | ||||
|                 voter_pubkey: vote_pubkey, | ||||
|                 voter_pubkey_epoch: clock.epoch, | ||||
|                 credits_observed: vote_state.credits(), | ||||
| @@ -670,7 +776,7 @@ mod tests { | ||||
|  | ||||
|         // verify that delegate_stake can be called twice, 2nd is redelegate | ||||
|         assert!(stake_keyed_account | ||||
|             .delegate_stake(&vote_keyed_account, &clock, &Config::default()) | ||||
|             .delegate_stake(&vote_keyed_account, &clock, &Config::default(), &[]) | ||||
|             .is_ok()); | ||||
|  | ||||
|         // verify that non-stakes fail delegate_stake() | ||||
| @@ -678,7 +784,7 @@ mod tests { | ||||
|  | ||||
|         stake_keyed_account.set_state(&stake_state).unwrap(); | ||||
|         assert!(stake_keyed_account | ||||
|             .delegate_stake(&vote_keyed_account, &clock, &Config::default()) | ||||
|             .delegate_stake(&vote_keyed_account, &clock, &Config::default(), &[]) | ||||
|             .is_err()); | ||||
|     } | ||||
|  | ||||
| @@ -980,7 +1086,10 @@ mod tests { | ||||
|         // first time works, as is uninit | ||||
|         assert_eq!( | ||||
|             StakeState::from(&stake_keyed_account.account).unwrap(), | ||||
|             StakeState::Lockup(1) | ||||
|             StakeState::Lockup(Lockup { | ||||
|                 slot: 1, | ||||
|                 authorized_pubkey: stake_pubkey | ||||
|             }) | ||||
|         ); | ||||
|  | ||||
|         // 2nd time fails, can't move it from anything other than uninit->lockup | ||||
| @@ -996,7 +1105,10 @@ mod tests { | ||||
|         let stake_lamports = 42; | ||||
|         let mut stake_account = Account::new_data_with_space( | ||||
|             stake_lamports, | ||||
|             &StakeState::Lockup(0), | ||||
|             &StakeState::Lockup(Lockup { | ||||
|                 slot: 0, | ||||
|                 authorized_pubkey: stake_pubkey, | ||||
|             }), | ||||
|             std::mem::size_of::<StakeState>(), | ||||
|             &id(), | ||||
|         ) | ||||
| @@ -1012,17 +1124,10 @@ mod tests { | ||||
|             vote_state::create_account(&vote_pubkey, &Pubkey::new_rand(), 0, 100); | ||||
|         let vote_keyed_account = KeyedAccount::new(&vote_pubkey, false, &mut vote_account); | ||||
|  | ||||
|         // unsigned keyed account | ||||
|         let mut stake_keyed_account = KeyedAccount::new(&stake_pubkey, false, &mut stake_account); | ||||
|         assert_eq!( | ||||
|             stake_keyed_account.deactivate_stake(&vote_keyed_account, &clock), | ||||
|             Err(InstructionError::MissingRequiredSignature) | ||||
|         ); | ||||
|  | ||||
|         // signed keyed account but not staked yet | ||||
|         let mut stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &mut stake_account); | ||||
|         assert_eq!( | ||||
|             stake_keyed_account.deactivate_stake(&vote_keyed_account, &clock), | ||||
|             stake_keyed_account.deactivate_stake(&vote_keyed_account, &clock, &[]), | ||||
|             Err(InstructionError::InvalidAccountData) | ||||
|         ); | ||||
|  | ||||
| @@ -1033,13 +1138,26 @@ mod tests { | ||||
|         let mut vote_keyed_account = KeyedAccount::new(&vote_pubkey, false, &mut vote_account); | ||||
|         vote_keyed_account.set_state(&VoteState::default()).unwrap(); | ||||
|         assert_eq!( | ||||
|             stake_keyed_account.delegate_stake(&vote_keyed_account, &clock, &Config::default()), | ||||
|             stake_keyed_account.delegate_stake( | ||||
|                 &vote_keyed_account, | ||||
|                 &clock, | ||||
|                 &Config::default(), | ||||
|                 &[] | ||||
|             ), | ||||
|             Ok(()) | ||||
|         ); | ||||
|  | ||||
|         // Deactivate after staking | ||||
|         // unsigned keyed account | ||||
|         let mut stake_keyed_account = KeyedAccount::new(&stake_pubkey, false, &mut stake_account); | ||||
|         assert_eq!( | ||||
|             stake_keyed_account.deactivate_stake(&vote_keyed_account, &clock), | ||||
|             stake_keyed_account.deactivate_stake(&vote_keyed_account, &clock, &[]), | ||||
|             Err(InstructionError::MissingRequiredSignature) | ||||
|         ); | ||||
|  | ||||
|         // Deactivate after staking | ||||
|         let mut stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &mut stake_account); | ||||
|         assert_eq!( | ||||
|             stake_keyed_account.deactivate_stake(&vote_keyed_account, &clock, &[]), | ||||
|             Ok(()) | ||||
|         ); | ||||
|     } | ||||
| @@ -1050,7 +1168,7 @@ mod tests { | ||||
|         let stake_lamports = 42; | ||||
|         let mut stake_account = Account::new_data_with_space( | ||||
|             stake_lamports, | ||||
|             &StakeState::Lockup(0), | ||||
|             &StakeState::Uninitialized, | ||||
|             std::mem::size_of::<StakeState>(), | ||||
|             &id(), | ||||
|         ) | ||||
| @@ -1069,7 +1187,8 @@ mod tests { | ||||
|                 stake_lamports, | ||||
|                 &mut to_keyed_account, | ||||
|                 &clock, | ||||
|                 &StakeHistory::default() | ||||
|                 &StakeHistory::default(), | ||||
|                 &[], | ||||
|             ), | ||||
|             Err(InstructionError::MissingRequiredSignature) | ||||
|         ); | ||||
| @@ -1081,7 +1200,8 @@ mod tests { | ||||
|                 stake_lamports, | ||||
|                 &mut to_keyed_account, | ||||
|                 &clock, | ||||
|                 &StakeHistory::default() | ||||
|                 &StakeHistory::default(), | ||||
|                 &[], | ||||
|             ), | ||||
|             Ok(()) | ||||
|         ); | ||||
| @@ -1090,14 +1210,19 @@ mod tests { | ||||
|         // reset balance | ||||
|         stake_account.lamports = stake_lamports; | ||||
|  | ||||
|         // signed keyed account and uninitialized, more than available should fail | ||||
|         // lockup | ||||
|         let mut stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &mut stake_account); | ||||
|         stake_keyed_account.lockup(0).unwrap(); | ||||
|  | ||||
|         // signed keyed account and locked up, more than available should fail | ||||
|         let mut stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &mut stake_account); | ||||
|         assert_eq!( | ||||
|             stake_keyed_account.withdraw( | ||||
|                 stake_lamports + 1, | ||||
|                 &mut to_keyed_account, | ||||
|                 &clock, | ||||
|                 &StakeHistory::default() | ||||
|                 &StakeHistory::default(), | ||||
|                 &[], | ||||
|             ), | ||||
|             Err(InstructionError::InsufficientFunds) | ||||
|         ); | ||||
| @@ -1109,7 +1234,12 @@ mod tests { | ||||
|         let mut vote_keyed_account = KeyedAccount::new(&vote_pubkey, false, &mut vote_account); | ||||
|         vote_keyed_account.set_state(&VoteState::default()).unwrap(); | ||||
|         assert_eq!( | ||||
|             stake_keyed_account.delegate_stake(&vote_keyed_account, &clock, &Config::default()), | ||||
|             stake_keyed_account.delegate_stake( | ||||
|                 &vote_keyed_account, | ||||
|                 &clock, | ||||
|                 &Config::default(), | ||||
|                 &[] | ||||
|             ), | ||||
|             Ok(()) | ||||
|         ); | ||||
|  | ||||
| @@ -1122,7 +1252,8 @@ mod tests { | ||||
|                 10, | ||||
|                 &mut to_keyed_account, | ||||
|                 &clock, | ||||
|                 &StakeHistory::default() | ||||
|                 &StakeHistory::default(), | ||||
|                 &[], | ||||
|             ), | ||||
|             Ok(()) | ||||
|         ); | ||||
| @@ -1136,14 +1267,15 @@ mod tests { | ||||
|                 10 + 1, | ||||
|                 &mut to_keyed_account, | ||||
|                 &clock, | ||||
|                 &StakeHistory::default() | ||||
|                 &StakeHistory::default(), | ||||
|                 &[], | ||||
|             ), | ||||
|             Err(InstructionError::InsufficientFunds) | ||||
|         ); | ||||
|  | ||||
|         // deactivate the stake before withdrawal | ||||
|         assert_eq!( | ||||
|             stake_keyed_account.deactivate_stake(&vote_keyed_account, &clock), | ||||
|             stake_keyed_account.deactivate_stake(&vote_keyed_account, &clock, &[]), | ||||
|             Ok(()) | ||||
|         ); | ||||
|         // simulate time passing | ||||
| @@ -1155,7 +1287,8 @@ mod tests { | ||||
|                 stake_lamports + 10 + 1, | ||||
|                 &mut to_keyed_account, | ||||
|                 &clock, | ||||
|                 &StakeHistory::default() | ||||
|                 &StakeHistory::default(), | ||||
|                 &[], | ||||
|             ), | ||||
|             Err(InstructionError::InsufficientFunds) | ||||
|         ); | ||||
| @@ -1166,7 +1299,8 @@ mod tests { | ||||
|                 stake_lamports + 10, | ||||
|                 &mut to_keyed_account, | ||||
|                 &clock, | ||||
|                 &StakeHistory::default() | ||||
|                 &StakeHistory::default(), | ||||
|                 &[], | ||||
|             ), | ||||
|             Ok(()) | ||||
|         ); | ||||
| @@ -1180,7 +1314,10 @@ mod tests { | ||||
|         let stake_lamports = 42; | ||||
|         let mut stake_account = Account::new_data_with_space( | ||||
|             total_lamports, | ||||
|             &StakeState::Lockup(0), | ||||
|             &StakeState::Lockup(Lockup { | ||||
|                 slot: 0, | ||||
|                 authorized_pubkey: stake_pubkey, | ||||
|             }), | ||||
|             std::mem::size_of::<StakeState>(), | ||||
|             &id(), | ||||
|         ) | ||||
| @@ -1203,7 +1340,12 @@ mod tests { | ||||
|         let mut vote_keyed_account = KeyedAccount::new(&vote_pubkey, false, &mut vote_account); | ||||
|         vote_keyed_account.set_state(&VoteState::default()).unwrap(); | ||||
|         assert_eq!( | ||||
|             stake_keyed_account.delegate_stake(&vote_keyed_account, &future, &Config::default()), | ||||
|             stake_keyed_account.delegate_stake( | ||||
|                 &vote_keyed_account, | ||||
|                 &future, | ||||
|                 &Config::default(), | ||||
|                 &[] | ||||
|             ), | ||||
|             Ok(()) | ||||
|         ); | ||||
|  | ||||
| @@ -1219,7 +1361,8 @@ mod tests { | ||||
|                 total_lamports - stake_lamports + 1, | ||||
|                 &mut to_keyed_account, | ||||
|                 &clock, | ||||
|                 &stake_history | ||||
|                 &stake_history, | ||||
|                 &[], | ||||
|             ), | ||||
|             Err(InstructionError::InsufficientFunds) | ||||
|         ); | ||||
| @@ -1247,7 +1390,8 @@ mod tests { | ||||
|                 total_lamports, | ||||
|                 &mut to_keyed_account, | ||||
|                 &sysvar::clock::Clock::default(), | ||||
|                 &StakeHistory::default() | ||||
|                 &StakeHistory::default(), | ||||
|                 &[], | ||||
|             ), | ||||
|             Err(InstructionError::InvalidAccountData) | ||||
|         ); | ||||
| @@ -1259,7 +1403,10 @@ mod tests { | ||||
|         let total_lamports = 100; | ||||
|         let mut stake_account = Account::new_data_with_space( | ||||
|             total_lamports, | ||||
|             &StakeState::Lockup(1), | ||||
|             &StakeState::Lockup(Lockup { | ||||
|                 slot: 1, | ||||
|                 authorized_pubkey: stake_pubkey, | ||||
|             }), | ||||
|             std::mem::size_of::<StakeState>(), | ||||
|             &id(), | ||||
|         ) | ||||
| @@ -1277,7 +1424,8 @@ mod tests { | ||||
|                 total_lamports, | ||||
|                 &mut to_keyed_account, | ||||
|                 &clock, | ||||
|                 &StakeHistory::default() | ||||
|                 &StakeHistory::default(), | ||||
|                 &[], | ||||
|             ), | ||||
|             Err(InstructionError::InsufficientFunds) | ||||
|         ); | ||||
| @@ -1288,7 +1436,8 @@ mod tests { | ||||
|                 total_lamports, | ||||
|                 &mut to_keyed_account, | ||||
|                 &clock, | ||||
|                 &StakeHistory::default() | ||||
|                 &StakeHistory::default(), | ||||
|                 &[], | ||||
|             ), | ||||
|             Ok(()) | ||||
|         ); | ||||
| @@ -1385,17 +1534,20 @@ mod tests { | ||||
|         let mut rewards_pool_keyed_account = | ||||
|             KeyedAccount::new(&rewards_pool_pubkey, false, &mut rewards_pool_account); | ||||
|  | ||||
|         let pubkey = Pubkey::default(); | ||||
|         let stake_pubkey = Pubkey::default(); | ||||
|         let stake_lamports = 100; | ||||
|         let mut stake_account = Account::new_data_with_space( | ||||
|             stake_lamports, | ||||
|             &StakeState::Lockup(0), | ||||
|             &StakeState::Lockup(Lockup { | ||||
|                 slot: 0, | ||||
|                 authorized_pubkey: stake_pubkey, | ||||
|             }), | ||||
|             std::mem::size_of::<StakeState>(), | ||||
|             &id(), | ||||
|         ) | ||||
|         .expect("stake_account"); | ||||
|  | ||||
|         let mut stake_keyed_account = KeyedAccount::new(&pubkey, true, &mut stake_account); | ||||
|         let mut stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &mut stake_account); | ||||
|  | ||||
|         let vote_pubkey = Pubkey::new_rand(); | ||||
|         let mut vote_account = | ||||
| @@ -1415,7 +1567,7 @@ mod tests { | ||||
|  | ||||
|         // delegate the stake | ||||
|         assert!(stake_keyed_account | ||||
|             .delegate_stake(&vote_keyed_account, &clock, &Config::default()) | ||||
|             .delegate_stake(&vote_keyed_account, &clock, &Config::default(), &[]) | ||||
|             .is_ok()); | ||||
|  | ||||
|         let stake_history = create_stake_history_from_stakes( | ||||
| @@ -1499,4 +1651,160 @@ mod tests { | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     #[test] | ||||
|     fn test_authorize_lockup() { | ||||
|         let stake_pubkey = Pubkey::new_rand(); | ||||
|         let stake_lamports = 42; | ||||
|         let mut stake_account = Account::new_data_with_space( | ||||
|             stake_lamports, | ||||
|             &StakeState::Lockup(Lockup { | ||||
|                 slot: 0, | ||||
|                 authorized_pubkey: stake_pubkey, | ||||
|             }), | ||||
|             std::mem::size_of::<StakeState>(), | ||||
|             &id(), | ||||
|         ) | ||||
|         .expect("stake_account"); | ||||
|  | ||||
|         let to = Pubkey::new_rand(); | ||||
|         let mut to_account = Account::new(1, 0, &system_program::id()); | ||||
|         let mut to_keyed_account = KeyedAccount::new(&to, false, &mut to_account); | ||||
|  | ||||
|         let clock = sysvar::clock::Clock::default(); | ||||
|         let mut stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &mut stake_account); | ||||
|  | ||||
|         let stake_pubkey0 = Pubkey::new_rand(); | ||||
|         assert_eq!(stake_keyed_account.authorize(&stake_pubkey0, &[]), Ok(())); | ||||
|         if let StakeState::Lockup(lockup) = StakeState::from(&stake_keyed_account.account).unwrap() | ||||
|         { | ||||
|             assert_eq!(lockup.authorized_pubkey, stake_pubkey0); | ||||
|         } | ||||
|  | ||||
|         // A second authorization signed by the stake_keyed_account should fail | ||||
|         let stake_pubkey1 = Pubkey::new_rand(); | ||||
|         assert_eq!( | ||||
|             stake_keyed_account.authorize(&stake_pubkey1, &[]), | ||||
|             Err(InstructionError::MissingRequiredSignature) | ||||
|         ); | ||||
|  | ||||
|         let mut staker_account0 = Account::new(1, 0, &system_program::id()); | ||||
|         let staker_keyed_account0 = KeyedAccount::new(&stake_pubkey0, true, &mut staker_account0); | ||||
|  | ||||
|         // Test a second authorization by the newly authorized pubkey | ||||
|         let stake_pubkey2 = Pubkey::new_rand(); | ||||
|         assert_eq!( | ||||
|             stake_keyed_account.authorize(&stake_pubkey2, &[staker_keyed_account0]), | ||||
|             Ok(()) | ||||
|         ); | ||||
|         if let StakeState::Lockup(lockup) = StakeState::from(&stake_keyed_account.account).unwrap() | ||||
|         { | ||||
|             assert_eq!(lockup.authorized_pubkey, stake_pubkey2); | ||||
|         } | ||||
|  | ||||
|         let mut staker_account2 = Account::new(1, 0, &system_program::id()); | ||||
|         let staker_keyed_account2 = KeyedAccount::new(&stake_pubkey2, true, &mut staker_account2); | ||||
|  | ||||
|         // Test an action by the currently authorized pubkey | ||||
|         assert_eq!( | ||||
|             stake_keyed_account.withdraw( | ||||
|                 stake_lamports, | ||||
|                 &mut to_keyed_account, | ||||
|                 &clock, | ||||
|                 &StakeHistory::default(), | ||||
|                 &[staker_keyed_account2], | ||||
|             ), | ||||
|             Ok(()) | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     #[test] | ||||
|     fn test_authorize_delegated_stake() { | ||||
|         let stake_pubkey = Pubkey::new_rand(); | ||||
|         let stake_lamports = 42; | ||||
|         let mut stake_account = Account::new_data_with_space( | ||||
|             stake_lamports, | ||||
|             &StakeState::Lockup(Lockup { | ||||
|                 slot: 0, | ||||
|                 authorized_pubkey: stake_pubkey, | ||||
|             }), | ||||
|             std::mem::size_of::<StakeState>(), | ||||
|             &id(), | ||||
|         ) | ||||
|         .expect("stake_account"); | ||||
|  | ||||
|         let clock = sysvar::clock::Clock::default(); | ||||
|  | ||||
|         let vote_pubkey = Pubkey::new_rand(); | ||||
|         let mut vote_account = | ||||
|             vote_state::create_account(&vote_pubkey, &Pubkey::new_rand(), 0, 100); | ||||
|         let vote_keyed_account = KeyedAccount::new(&vote_pubkey, false, &mut vote_account); | ||||
|  | ||||
|         let mut stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &mut stake_account); | ||||
|         stake_keyed_account | ||||
|             .delegate_stake(&vote_keyed_account, &clock, &Config::default(), &[]) | ||||
|             .unwrap(); | ||||
|  | ||||
|         let new_staker_pubkey = Pubkey::new_rand(); | ||||
|         assert_eq!( | ||||
|             stake_keyed_account.authorize(&new_staker_pubkey, &[]), | ||||
|             Ok(()) | ||||
|         ); | ||||
|         let stake = StakeState::stake_from(&stake_keyed_account.account).unwrap(); | ||||
|         assert_eq!(stake.authorized_pubkey, new_staker_pubkey); | ||||
|  | ||||
|         let other_pubkey = Pubkey::new_rand(); | ||||
|         let mut other_account = Account::new(1, 0, &system_program::id()); | ||||
|         let other_keyed_account = KeyedAccount::new(&other_pubkey, true, &mut other_account); | ||||
|  | ||||
|         // Use unsigned stake_keyed_account to test other signers | ||||
|         let mut stake_keyed_account = KeyedAccount::new(&stake_pubkey, false, &mut stake_account); | ||||
|  | ||||
|         let new_voter_pubkey = Pubkey::new_rand(); | ||||
|         let vote_state = VoteState::default(); | ||||
|         let mut new_vote_account = | ||||
|             vote_state::create_account(&new_voter_pubkey, &Pubkey::new_rand(), 0, 100); | ||||
|         let mut new_vote_keyed_account = | ||||
|             KeyedAccount::new(&new_voter_pubkey, false, &mut new_vote_account); | ||||
|         new_vote_keyed_account.set_state(&vote_state).unwrap(); | ||||
|  | ||||
|         // Random other account should fail | ||||
|         assert_eq!( | ||||
|             stake_keyed_account.delegate_stake( | ||||
|                 &new_vote_keyed_account, | ||||
|                 &clock, | ||||
|                 &Config::default(), | ||||
|                 &[other_keyed_account] | ||||
|             ), | ||||
|             Err(InstructionError::MissingRequiredSignature) | ||||
|         ); | ||||
|  | ||||
|         let mut new_staker_account = Account::new(1, 0, &system_program::id()); | ||||
|         let new_staker_keyed_account = | ||||
|             KeyedAccount::new(&new_staker_pubkey, true, &mut new_staker_account); | ||||
|  | ||||
|         // Authorized staker should succeed | ||||
|         assert_eq!( | ||||
|             stake_keyed_account.delegate_stake( | ||||
|                 &new_vote_keyed_account, | ||||
|                 &clock, | ||||
|                 &Config::default(), | ||||
|                 &[new_staker_keyed_account] | ||||
|             ), | ||||
|             Ok(()) | ||||
|         ); | ||||
|         let stake = StakeState::stake_from(&stake_keyed_account.account).unwrap(); | ||||
|         assert_eq!(stake.voter_pubkey(0), &new_voter_pubkey); | ||||
|  | ||||
|         // Test another staking action | ||||
|         let new_staker_keyed_account = | ||||
|             KeyedAccount::new(&new_staker_pubkey, true, &mut new_staker_account); | ||||
|         assert_eq!( | ||||
|             stake_keyed_account.deactivate_stake( | ||||
|                 &vote_keyed_account, | ||||
|                 &clock, | ||||
|                 &[new_staker_keyed_account] | ||||
|             ), | ||||
|             Ok(()) | ||||
|         ); | ||||
|     } | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user