diff --git a/programs/stake_api/src/stake_instruction.rs b/programs/stake_api/src/stake_instruction.rs index 7853727d9b..10540bfc49 100644 --- a/programs/stake_api/src/stake_instruction.rs +++ b/programs/stake_api/src/stake_instruction.rs @@ -89,7 +89,7 @@ pub fn redeem_vote_credits(stake_pubkey: &Pubkey, vote_pubkey: &Pubkey) -> Instr pub fn delegate_stake(stake_pubkey: &Pubkey, vote_pubkey: &Pubkey, stake: u64) -> Instruction { let account_metas = vec![ AccountMeta::new(*stake_pubkey, true), - AccountMeta::new(*vote_pubkey, false), + AccountMeta::new_credit_only(*vote_pubkey, false), AccountMeta::new_credit_only(sysvar::clock::id(), false), ]; Instruction::new(id(), &StakeInstruction::DelegateStake(stake), account_metas) @@ -98,7 +98,7 @@ pub fn delegate_stake(stake_pubkey: &Pubkey, vote_pubkey: &Pubkey, stake: u64) - pub fn withdraw(stake_pubkey: &Pubkey, to_pubkey: &Pubkey, lamports: u64) -> Instruction { let account_metas = vec![ AccountMeta::new(*stake_pubkey, true), - AccountMeta::new(*to_pubkey, false), + AccountMeta::new_credit_only(*to_pubkey, false), AccountMeta::new_credit_only(sysvar::clock::id(), false), ]; Instruction::new(id(), &StakeInstruction::Withdraw(lamports), account_metas) diff --git a/programs/vote_api/src/vote_instruction.rs b/programs/vote_api/src/vote_instruction.rs index f122bc9aa7..a4601bb4fc 100644 --- a/programs/vote_api/src/vote_instruction.rs +++ b/programs/vote_api/src/vote_instruction.rs @@ -24,6 +24,9 @@ pub enum VoteInstruction { /// A Vote instruction with recent votes Vote(Vec), + + /// Withdraw some amount of funds + Withdraw(u64), } fn initialize_account(vote_pubkey: &Pubkey, node_pubkey: &Pubkey, commission: u8) -> Instruction { @@ -71,7 +74,7 @@ fn metas_for_authorized_signer( // append signer at the end if !is_own_signer { - account_metas.push(AccountMeta::new(*authorized_voter_pubkey, true)) // signer + account_metas.push(AccountMeta::new_credit_only(*authorized_voter_pubkey, true)) // signer } account_metas @@ -110,6 +113,15 @@ pub fn vote( Instruction::new(id(), &VoteInstruction::Vote(recent_votes), account_metas) } +pub fn withdraw(vote_pubkey: &Pubkey, lamports: u64, to_pubkey: &Pubkey) -> Instruction { + let account_metas = vec![ + AccountMeta::new(*vote_pubkey, true), + AccountMeta::new_credit_only(*to_pubkey, false), + ]; + + Instruction::new(id(), &VoteInstruction::Withdraw(lamports), account_metas) +} + pub fn process_instruction( _program_id: &Pubkey, keyed_accounts: &mut [KeyedAccount], @@ -151,6 +163,12 @@ pub fn process_instruction( &votes, ) } + VoteInstruction::Withdraw(lamports) => { + if rest.is_empty() { + Err(InstructionError::InvalidInstructionData)?; + } + vote_state::withdraw(me, lamports, &mut rest[0]) + } } } diff --git a/programs/vote_api/src/vote_state.rs b/programs/vote_api/src/vote_state.rs index 093144d96f..604fad6689 100644 --- a/programs/vote_api/src/vote_state.rs +++ b/programs/vote_api/src/vote_state.rs @@ -300,6 +300,23 @@ pub fn authorize_voter( vote_account.set_state(&vote_state) } +/// Withdraw funds from the vote account +pub fn withdraw( + vote_account: &mut KeyedAccount, + lamports: u64, + to_account: &mut KeyedAccount, +) -> Result<(), InstructionError> { + if vote_account.signer_key().is_none() { + return Err(InstructionError::MissingRequiredSignature); + } + if vote_account.account.lamports < lamports { + return Err(InstructionError::InsufficientFunds); + } + vote_account.account.lamports -= lamports; + to_account.account.lamports += lamports; + Ok(()) +} + /// Initialize the vote_state for a vote account /// Assumes that the account is being init as part of a account creation or balance transfer and /// that the transaction must be signed by the staker's keys @@ -811,6 +828,39 @@ mod tests { ); } + #[test] + fn test_vote_state_withdraw() { + let (vote_pubkey, mut vote_account) = create_test_account(); + + // unsigned + let res = withdraw( + &mut KeyedAccount::new(&vote_pubkey, false, &mut vote_account), + 0, + &mut KeyedAccount::new(&Pubkey::new_rand(), false, &mut Account::default()), + ); + assert_eq!(res, Err(InstructionError::MissingRequiredSignature)); + + // insufficient funds + let res = withdraw( + &mut KeyedAccount::new(&vote_pubkey, true, &mut vote_account), + 101, + &mut KeyedAccount::new(&Pubkey::new_rand(), false, &mut Account::default()), + ); + assert_eq!(res, Err(InstructionError::InsufficientFunds)); + + // all good + let mut to_account = Account::default(); + let lamports = vote_account.lamports; + let res = withdraw( + &mut KeyedAccount::new(&vote_pubkey, true, &mut vote_account), + lamports, + &mut KeyedAccount::new(&Pubkey::new_rand(), false, &mut to_account), + ); + assert_eq!(res, Ok(())); + assert_eq!(vote_account.lamports, 0); + assert_eq!(to_account.lamports, lamports); + } + #[test] fn test_vote_state_epoch_credits() { let mut vote_state = VoteState::default();