diff --git a/programs/stake_api/src/stake_instruction.rs b/programs/stake_api/src/stake_instruction.rs index 750a71f3f..f4fe9f69f 100644 --- a/programs/stake_api/src/stake_instruction.rs +++ b/programs/stake_api/src/stake_instruction.rs @@ -237,15 +237,14 @@ pub fn withdraw( to_pubkey: &Pubkey, lamports: u64, ) -> Instruction { - let account_metas = metas_for_authorized_signer( - stake_pubkey, - authorized_pubkey, - &[ - AccountMeta::new_credit_only(*to_pubkey, false), - AccountMeta::new_credit_only(sysvar::clock::id(), false), - AccountMeta::new_credit_only(sysvar::stake_history::id(), false), - ], - ); + let mut accounts = vec![ + AccountMeta::new_credit_only(sysvar::clock::id(), false), + AccountMeta::new_credit_only(sysvar::stake_history::id(), false), + ]; + if to_pubkey != authorized_pubkey { + accounts.push(AccountMeta::new_credit_only(*to_pubkey, false)); + } + let account_metas = metas_for_authorized_signer(stake_pubkey, authorized_pubkey, &accounts); Instruction::new(id(), &StakeInstruction::Withdraw(lamports), account_metas) } @@ -320,15 +319,12 @@ pub fn process_instruction( if rest.len() < 3 { return Err(InstructionError::InvalidInstructionData); } - 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(&rest[0])?, &sysvar::stake_history::from_keyed_account(&rest[1])?, - &rest[2..], + &mut rest[2..], ) } StakeInstruction::Deactivate => { @@ -399,7 +395,7 @@ mod tests { process_instruction(&withdraw( &Pubkey::default(), &Pubkey::default(), - &Pubkey::default(), + &Pubkey::new_rand(), 100 )), Err(InstructionError::InvalidAccountData), diff --git a/programs/stake_api/src/stake_state.rs b/programs/stake_api/src/stake_state.rs index afc16135a..045080733 100644 --- a/programs/stake_api/src/stake_state.rs +++ b/programs/stake_api/src/stake_state.rs @@ -443,10 +443,9 @@ pub trait StakeAccount { fn withdraw( &mut self, lamports: u64, - to: &mut KeyedAccount, clock: &sysvar::clock::Clock, stake_history: &sysvar::stake_history::StakeHistory, - other_signers: &[KeyedAccount], + recipient_and_signer_accounts: &mut [KeyedAccount], ) -> Result<(), InstructionError>; } @@ -575,14 +574,17 @@ impl<'a> StakeAccount for KeyedAccount<'a> { fn withdraw( &mut self, lamports: u64, - to: &mut KeyedAccount, clock: &sysvar::clock::Clock, stake_history: &sysvar::stake_history::StakeHistory, - other_signers: &[KeyedAccount], + recipient_and_signer_accounts: &mut [KeyedAccount], ) -> Result<(), InstructionError> { let lockup = match self.state()? { StakeState::Stake(authorized, lockup, stake) => { - authorized.check(self.signer_key(), other_signers, StakeAuthorize::Withdrawer)?; + authorized.check( + self.signer_key(), + recipient_and_signer_accounts, + StakeAuthorize::Withdrawer, + )?; // 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)) @@ -599,7 +601,11 @@ impl<'a> StakeAccount for KeyedAccount<'a> { lockup } StakeState::Initialized(authorized, lockup) => { - authorized.check(self.signer_key(), other_signers, StakeAuthorize::Withdrawer)?; + authorized.check( + self.signer_key(), + recipient_and_signer_accounts, + StakeAuthorize::Withdrawer, + )?; lockup } StakeState::Uninitialized => { @@ -610,6 +616,7 @@ impl<'a> StakeAccount for KeyedAccount<'a> { } _ => return Err(InstructionError::InvalidAccountData), }; + let mut to = &mut recipient_and_signer_accounts[0]; if lockup.slot > clock.slot && lockup.custodian != *to.unsigned_key() { return Err(StakeError::LockupInForce.into()); @@ -1205,30 +1212,29 @@ mod tests { 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 to_keyed_account = KeyedAccount::new(&to, false, &mut to_account); // unsigned keyed account should fail let mut stake_keyed_account = KeyedAccount::new(&stake_pubkey, false, &mut stake_account); assert_eq!( stake_keyed_account.withdraw( stake_lamports, - &mut to_keyed_account, &clock, &StakeHistory::default(), - &[], + &mut [to_keyed_account], ), Err(InstructionError::MissingRequiredSignature) ); // signed keyed account and uninitialized should work let mut stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &mut stake_account); + let to_keyed_account = KeyedAccount::new(&to, false, &mut to_account); assert_eq!( stake_keyed_account.withdraw( stake_lamports, - &mut to_keyed_account, &clock, &StakeHistory::default(), - &[], + &mut [to_keyed_account], ), Ok(()) ); @@ -1249,13 +1255,13 @@ mod tests { // signed keyed account and locked up, more than available should fail let mut stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &mut stake_account); + let to_keyed_account = KeyedAccount::new(&to, false, &mut to_account); assert_eq!( stake_keyed_account.withdraw( stake_lamports + 1, - &mut to_keyed_account, &clock, &StakeHistory::default(), - &[], + &mut [to_keyed_account], ), Err(InstructionError::InsufficientFunds) ); @@ -1280,13 +1286,13 @@ mod tests { stake_account.lamports += 10; // withdrawal before deactivate works for rewards amount let mut stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &mut stake_account); + let to_keyed_account = KeyedAccount::new(&to, false, &mut to_account); assert_eq!( stake_keyed_account.withdraw( 10, - &mut to_keyed_account, &clock, &StakeHistory::default(), - &[], + &mut [to_keyed_account], ), Ok(()) ); @@ -1295,13 +1301,13 @@ mod tests { stake_account.lamports += 10; // withdrawal of rewards fails if not in excess of stake let mut stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &mut stake_account); + let to_keyed_account = KeyedAccount::new(&to, false, &mut to_account); assert_eq!( stake_keyed_account.withdraw( 10 + 1, - &mut to_keyed_account, &clock, &StakeHistory::default(), - &[], + &mut [to_keyed_account], ), Err(InstructionError::InsufficientFunds) ); @@ -1312,25 +1318,25 @@ mod tests { clock.epoch += 100; // Try to withdraw more than what's available + let to_keyed_account = KeyedAccount::new(&to, false, &mut to_account); assert_eq!( stake_keyed_account.withdraw( stake_lamports + 10 + 1, - &mut to_keyed_account, &clock, &StakeHistory::default(), - &[], + &mut [to_keyed_account], ), Err(InstructionError::InsufficientFunds) ); // Try to withdraw all lamports + let to_keyed_account = KeyedAccount::new(&to, false, &mut to_account); assert_eq!( stake_keyed_account.withdraw( stake_lamports + 10, - &mut to_keyed_account, &clock, &StakeHistory::default(), - &[], + &mut [to_keyed_account], ), Ok(()) ); @@ -1356,7 +1362,7 @@ mod tests { 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 to_keyed_account = KeyedAccount::new(&to, false, &mut to_account); let mut stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &mut stake_account); @@ -1386,10 +1392,9 @@ mod tests { assert_eq!( stake_keyed_account.withdraw( total_lamports - stake_lamports + 1, - &mut to_keyed_account, &clock, &stake_history, - &[], + &mut [to_keyed_account], ), Err(InstructionError::InsufficientFunds) ); @@ -1409,16 +1414,15 @@ mod tests { 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 to_keyed_account = KeyedAccount::new(&to, false, &mut to_account); 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, &sysvar::clock::Clock::default(), &StakeHistory::default(), - &[], + &mut [to_keyed_account], ), Err(InstructionError::InvalidAccountData) ); @@ -1442,7 +1446,7 @@ mod tests { 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 to_keyed_account = KeyedAccount::new(&to, false, &mut to_account); let mut stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &mut stake_account); @@ -1451,25 +1455,22 @@ mod tests { assert_eq!( stake_keyed_account.withdraw( total_lamports, - &mut to_keyed_account, &clock, &StakeHistory::default(), - &[], + &mut [to_keyed_account], ), Err(StakeError::LockupInForce.into()) ); // but we *can* send to the custodian let mut custodian_account = Account::new(1, 0, &system_program::id()); - let mut custodian_keyed_account = - KeyedAccount::new(&custodian, false, &mut custodian_account); + let custodian_keyed_account = KeyedAccount::new(&custodian, false, &mut custodian_account); assert_eq!( stake_keyed_account.withdraw( total_lamports, - &mut custodian_keyed_account, &clock, &StakeHistory::default(), - &[], + &mut [custodian_keyed_account], ), Ok(()) ); @@ -1477,14 +1478,14 @@ mod tests { stake_keyed_account.account.lamports = total_lamports; // lockup has expired + let to_keyed_account = KeyedAccount::new(&to, false, &mut to_account); clock.slot += 1; assert_eq!( stake_keyed_account.withdraw( total_lamports, - &mut to_keyed_account, &clock, &StakeHistory::default(), - &[], + &mut [to_keyed_account], ), Ok(()) ); @@ -1724,7 +1725,7 @@ mod tests { 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 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); @@ -1791,14 +1792,25 @@ mod tests { 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 withdrawer + // Test that withdrawal to account fails without authorized withdrawer assert_eq!( stake_keyed_account.withdraw( stake_lamports, - &mut to_keyed_account, &clock, &StakeHistory::default(), - &[staker_keyed_account2], + &mut [to_keyed_account], + ), + Err(InstructionError::MissingRequiredSignature) + ); + + // Test a successful action by the currently authorized withdrawer + let to_keyed_account = KeyedAccount::new(&to, false, &mut to_account); + assert_eq!( + stake_keyed_account.withdraw( + stake_lamports, + &clock, + &StakeHistory::default(), + &mut [to_keyed_account, staker_keyed_account2], ), Ok(()) );