From 5dceeec1ca0769726ef2fd89286111da2401aaa7 Mon Sep 17 00:00:00 2001 From: Tyera Eulberg Date: Thu, 12 Sep 2019 20:03:28 -0600 Subject: [PATCH] Add authorize_staker functionality (#5880) * Add authorized_staker functionality * Generalize authorize names; implement for Lockup * Fix authorize() usage and improve tests --- programs/stake_api/src/stake_instruction.rs | 64 ++- programs/stake_api/src/stake_state.rs | 422 +++++++++++++++++--- 2 files changed, 420 insertions(+), 66 deletions(-) diff --git a/programs/stake_api/src/stake_instruction.rs b/programs/stake_api/src/stake_instruction.rs index d9e22acb6..9a5ffd7ab 100644 --- a/programs/stake_api/src/stake_instruction.rs +++ b/programs/stake_api/src/stake_instruction.rs @@ -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 { + 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..], + ) } } } diff --git a/programs/stake_api/src/stake_state.rs b/programs/stake_api/src/stake_state.rs index 65872cd07..a3c8f6148 100644 --- a/programs/stake_api/src/stake_state.rs +++ b/programs/stake_api/src/stake_state.rs @@ -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::(), &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::(), &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::(), &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::(), &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::(), &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::(), &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::(), + &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::(), + &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(()) + ); + } }