Program instruction to withdraw un-staked lamports from stake account (#4780)
This commit is contained in:
		@@ -31,6 +31,17 @@ pub enum StakeInstruction {
 | 
				
			|||||||
    ///    2 - RewardsPool Stake Account from which to redeem credits
 | 
					    ///    2 - RewardsPool Stake Account from which to redeem credits
 | 
				
			||||||
    ///    3 - Rewards syscall Account that carries points values
 | 
					    ///    3 - Rewards syscall Account that carries points values
 | 
				
			||||||
    RedeemVoteCredits,
 | 
					    RedeemVoteCredits,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// Withdraw unstaked lamports from the stake account
 | 
				
			||||||
 | 
					    ///
 | 
				
			||||||
 | 
					    /// Expects 3 Accounts:
 | 
				
			||||||
 | 
					    ///    0 - Delegate StakeAccount
 | 
				
			||||||
 | 
					    ///    1 - System account to which the lamports will be transferred,
 | 
				
			||||||
 | 
					    ///    2 - Syscall Account that carries epoch
 | 
				
			||||||
 | 
					    ///
 | 
				
			||||||
 | 
					    /// The u64 is the portion of the Stake account balance to be withdrawn,
 | 
				
			||||||
 | 
					    ///    must be <= StakeAccount.lamports - staked lamports
 | 
				
			||||||
 | 
					    Withdraw(u64),
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub fn create_stake_account(
 | 
					pub fn create_stake_account(
 | 
				
			||||||
@@ -77,6 +88,15 @@ pub fn delegate_stake(stake_pubkey: &Pubkey, vote_pubkey: &Pubkey, stake: u64) -
 | 
				
			|||||||
    Instruction::new(id(), &StakeInstruction::DelegateStake(stake), account_metas)
 | 
					    Instruction::new(id(), &StakeInstruction::DelegateStake(stake), account_metas)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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(syscall::current::id(), false),
 | 
				
			||||||
 | 
					    ];
 | 
				
			||||||
 | 
					    Instruction::new(id(), &StakeInstruction::Withdraw(lamports), account_metas)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub fn process_instruction(
 | 
					pub fn process_instruction(
 | 
				
			||||||
    _program_id: &Pubkey,
 | 
					    _program_id: &Pubkey,
 | 
				
			||||||
    keyed_accounts: &mut [KeyedAccount],
 | 
					    keyed_accounts: &mut [KeyedAccount],
 | 
				
			||||||
@@ -123,6 +143,19 @@ pub fn process_instruction(
 | 
				
			|||||||
                &syscall::rewards::from_keyed_account(&rest[0])?,
 | 
					                &syscall::rewards::from_keyed_account(&rest[0])?,
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					        StakeInstruction::Withdraw(lamports) => {
 | 
				
			||||||
 | 
					            if rest.len() != 2 {
 | 
				
			||||||
 | 
					                Err(InstructionError::InvalidInstructionData)?;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            let (to, syscall) = &mut rest.split_at_mut(1);
 | 
				
			||||||
 | 
					            let mut to = &mut to[0];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            me.withdraw(
 | 
				
			||||||
 | 
					                lamports,
 | 
				
			||||||
 | 
					                &mut to,
 | 
				
			||||||
 | 
					                &syscall::current::from_keyed_account(&syscall[0])?,
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -168,6 +201,10 @@ mod tests {
 | 
				
			|||||||
            process_instruction(&delegate_stake(&Pubkey::default(), &Pubkey::default(), 0)),
 | 
					            process_instruction(&delegate_stake(&Pubkey::default(), &Pubkey::default(), 0)),
 | 
				
			||||||
            Err(InstructionError::InvalidAccountData),
 | 
					            Err(InstructionError::InvalidAccountData),
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
 | 
					        assert_eq!(
 | 
				
			||||||
 | 
					            process_instruction(&withdraw(&Pubkey::default(), &Pubkey::new_rand(), 100)),
 | 
				
			||||||
 | 
					            Err(InstructionError::InvalidAccountData),
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    #[test]
 | 
					    #[test]
 | 
				
			||||||
@@ -250,6 +287,41 @@ mod tests {
 | 
				
			|||||||
            ),
 | 
					            ),
 | 
				
			||||||
            Err(InstructionError::InvalidAccountData),
 | 
					            Err(InstructionError::InvalidAccountData),
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Tests 3rd keyed account is of correct type (Current instead of rewards) in withdraw
 | 
				
			||||||
 | 
					        assert_eq!(
 | 
				
			||||||
 | 
					            super::process_instruction(
 | 
				
			||||||
 | 
					                &Pubkey::default(),
 | 
				
			||||||
 | 
					                &mut [
 | 
				
			||||||
 | 
					                    KeyedAccount::new(&Pubkey::default(), false, &mut Account::default()),
 | 
				
			||||||
 | 
					                    KeyedAccount::new(&Pubkey::default(), false, &mut Account::default()),
 | 
				
			||||||
 | 
					                    KeyedAccount::new(
 | 
				
			||||||
 | 
					                        &syscall::rewards::id(),
 | 
				
			||||||
 | 
					                        false,
 | 
				
			||||||
 | 
					                        &mut syscall::rewards::create_account(1, 0.0, 0.0)
 | 
				
			||||||
 | 
					                    ),
 | 
				
			||||||
 | 
					                ],
 | 
				
			||||||
 | 
					                &serialize(&StakeInstruction::Withdraw(42)).unwrap(),
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					            Err(InstructionError::InvalidArgument),
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Tests correct number of accounts are provided in withdraw
 | 
				
			||||||
 | 
					        assert_eq!(
 | 
				
			||||||
 | 
					            super::process_instruction(
 | 
				
			||||||
 | 
					                &Pubkey::default(),
 | 
				
			||||||
 | 
					                &mut [
 | 
				
			||||||
 | 
					                    KeyedAccount::new(&Pubkey::default(), false, &mut Account::default()),
 | 
				
			||||||
 | 
					                    KeyedAccount::new(
 | 
				
			||||||
 | 
					                        &syscall::current::id(),
 | 
				
			||||||
 | 
					                        false,
 | 
				
			||||||
 | 
					                        &mut syscall::rewards::create_account(1, 0.0, 0.0)
 | 
				
			||||||
 | 
					                    ),
 | 
				
			||||||
 | 
					                ],
 | 
				
			||||||
 | 
					                &serialize(&StakeInstruction::Withdraw(42)).unwrap(),
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					            Err(InstructionError::InvalidInstructionData),
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -12,6 +12,7 @@ use solana_sdk::pubkey::Pubkey;
 | 
				
			|||||||
use solana_sdk::syscall;
 | 
					use solana_sdk::syscall;
 | 
				
			||||||
use solana_sdk::timing::Epoch;
 | 
					use solana_sdk::timing::Epoch;
 | 
				
			||||||
use solana_vote_api::vote_state::VoteState;
 | 
					use solana_vote_api::vote_state::VoteState;
 | 
				
			||||||
 | 
					use std::cmp;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
 | 
					#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
 | 
				
			||||||
pub enum StakeState {
 | 
					pub enum StakeState {
 | 
				
			||||||
@@ -196,6 +197,12 @@ pub trait StakeAccount {
 | 
				
			|||||||
        rewards_account: &mut KeyedAccount,
 | 
					        rewards_account: &mut KeyedAccount,
 | 
				
			||||||
        rewards: &syscall::rewards::Rewards,
 | 
					        rewards: &syscall::rewards::Rewards,
 | 
				
			||||||
    ) -> Result<(), InstructionError>;
 | 
					    ) -> Result<(), InstructionError>;
 | 
				
			||||||
 | 
					    fn withdraw(
 | 
				
			||||||
 | 
					        &mut self,
 | 
				
			||||||
 | 
					        lamports: u64,
 | 
				
			||||||
 | 
					        to: &mut KeyedAccount,
 | 
				
			||||||
 | 
					        current: &syscall::current::Current,
 | 
				
			||||||
 | 
					    ) -> Result<(), InstructionError>;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl<'a> StakeAccount for KeyedAccount<'a> {
 | 
					impl<'a> StakeAccount for KeyedAccount<'a> {
 | 
				
			||||||
@@ -281,6 +288,44 @@ impl<'a> StakeAccount for KeyedAccount<'a> {
 | 
				
			|||||||
            Err(InstructionError::InvalidAccountData)
 | 
					            Err(InstructionError::InvalidAccountData)
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					    fn withdraw(
 | 
				
			||||||
 | 
					        &mut self,
 | 
				
			||||||
 | 
					        lamports: u64,
 | 
				
			||||||
 | 
					        to: &mut KeyedAccount,
 | 
				
			||||||
 | 
					        current: &syscall::current::Current,
 | 
				
			||||||
 | 
					    ) -> Result<(), InstructionError> {
 | 
				
			||||||
 | 
					        if self.signer_key().is_none() {
 | 
				
			||||||
 | 
					            return Err(InstructionError::MissingRequiredSignature);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        match self.state()? {
 | 
				
			||||||
 | 
					            StakeState::Stake(mut stake) => {
 | 
				
			||||||
 | 
					                let staked = if stake.stake(current.epoch) == 0 {
 | 
				
			||||||
 | 
					                    0
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    // Assume full stake if the stake is under warmup/cooldown
 | 
				
			||||||
 | 
					                    stake.stake
 | 
				
			||||||
 | 
					                };
 | 
				
			||||||
 | 
					                if lamports > self.account.lamports.saturating_sub(staked) {
 | 
				
			||||||
 | 
					                    return Err(InstructionError::InsufficientFunds);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                self.account.lamports -= lamports;
 | 
				
			||||||
 | 
					                // Adjust the stake (in case balance dropped below stake)
 | 
				
			||||||
 | 
					                stake.stake = cmp::min(stake.stake, self.account.lamports);
 | 
				
			||||||
 | 
					                to.account.lamports += lamports;
 | 
				
			||||||
 | 
					                Ok(())
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            StakeState::Uninitialized => {
 | 
				
			||||||
 | 
					                if lamports > self.account.lamports {
 | 
				
			||||||
 | 
					                    return Err(InstructionError::InsufficientFunds);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                self.account.lamports -= lamports;
 | 
				
			||||||
 | 
					                to.account.lamports += lamports;
 | 
				
			||||||
 | 
					                Ok(())
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            _ => Err(InstructionError::InvalidAccountData),
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// utility function, used by Bank, tests, genesis
 | 
					// utility function, used by Bank, tests, genesis
 | 
				
			||||||
@@ -316,6 +361,7 @@ mod tests {
 | 
				
			|||||||
    use solana_sdk::account::Account;
 | 
					    use solana_sdk::account::Account;
 | 
				
			||||||
    use solana_sdk::pubkey::Pubkey;
 | 
					    use solana_sdk::pubkey::Pubkey;
 | 
				
			||||||
    use solana_sdk::signature::{Keypair, KeypairUtil};
 | 
					    use solana_sdk::signature::{Keypair, KeypairUtil};
 | 
				
			||||||
 | 
					    use solana_sdk::system_program;
 | 
				
			||||||
    use solana_vote_api::vote_state;
 | 
					    use solana_vote_api::vote_state;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    #[test]
 | 
					    #[test]
 | 
				
			||||||
@@ -418,6 +464,143 @@ mod tests {
 | 
				
			|||||||
        assert_eq!(stake.stake(STAKE_WARMUP_EPOCHS * 42), 0);
 | 
					        assert_eq!(stake.stake(STAKE_WARMUP_EPOCHS * 42), 0);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[test]
 | 
				
			||||||
 | 
					    fn test_withdraw_stake() {
 | 
				
			||||||
 | 
					        let stake_pubkey = Pubkey::new_rand();
 | 
				
			||||||
 | 
					        let mut total_lamports = 100;
 | 
				
			||||||
 | 
					        let stake_lamports = 42;
 | 
				
			||||||
 | 
					        let mut stake_account =
 | 
				
			||||||
 | 
					            Account::new(total_lamports, std::mem::size_of::<StakeState>(), &id());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let current = syscall::current::Current::default();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        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);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // unsigned keyed account
 | 
				
			||||||
 | 
					        let mut stake_keyed_account = KeyedAccount::new(&stake_pubkey, false, &mut stake_account);
 | 
				
			||||||
 | 
					        assert_eq!(
 | 
				
			||||||
 | 
					            stake_keyed_account.withdraw(total_lamports, &mut to_keyed_account, ¤t),
 | 
				
			||||||
 | 
					            Err(InstructionError::MissingRequiredSignature)
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // signed keyed account but uninitialized
 | 
				
			||||||
 | 
					        // try withdrawing more than balance
 | 
				
			||||||
 | 
					        let mut stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &mut stake_account);
 | 
				
			||||||
 | 
					        assert_eq!(
 | 
				
			||||||
 | 
					            stake_keyed_account.withdraw(total_lamports + 1, &mut to_keyed_account, ¤t),
 | 
				
			||||||
 | 
					            Err(InstructionError::InsufficientFunds)
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // try withdrawing some (enough for rest of the test to carry forward)
 | 
				
			||||||
 | 
					        let mut stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &mut stake_account);
 | 
				
			||||||
 | 
					        assert_eq!(
 | 
				
			||||||
 | 
					            stake_keyed_account.withdraw(5, &mut to_keyed_account, ¤t),
 | 
				
			||||||
 | 
					            Ok(())
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					        total_lamports -= 5;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Stake some lamports (available lampoorts for withdrawls will reduce)
 | 
				
			||||||
 | 
					        let vote_pubkey = Pubkey::new_rand();
 | 
				
			||||||
 | 
					        let mut vote_account =
 | 
				
			||||||
 | 
					            vote_state::create_account(&vote_pubkey, &Pubkey::new_rand(), 0, 100);
 | 
				
			||||||
 | 
					        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, stake_lamports, ¤t),
 | 
				
			||||||
 | 
					            Ok(())
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Try to withdraw more than what's available
 | 
				
			||||||
 | 
					        assert_eq!(
 | 
				
			||||||
 | 
					            stake_keyed_account.withdraw(
 | 
				
			||||||
 | 
					                total_lamports - stake_lamports + 1,
 | 
				
			||||||
 | 
					                &mut to_keyed_account,
 | 
				
			||||||
 | 
					                ¤t
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					            Err(InstructionError::InsufficientFunds)
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Try to withdraw all unstaked lamports
 | 
				
			||||||
 | 
					        assert_eq!(
 | 
				
			||||||
 | 
					            stake_keyed_account.withdraw(
 | 
				
			||||||
 | 
					                total_lamports - stake_lamports,
 | 
				
			||||||
 | 
					                &mut to_keyed_account,
 | 
				
			||||||
 | 
					                ¤t
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					            Ok(())
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[test]
 | 
				
			||||||
 | 
					    fn test_withdraw_stake_before_warmup() {
 | 
				
			||||||
 | 
					        let stake_pubkey = Pubkey::new_rand();
 | 
				
			||||||
 | 
					        let total_lamports = 100;
 | 
				
			||||||
 | 
					        let stake_lamports = 42;
 | 
				
			||||||
 | 
					        let mut stake_account =
 | 
				
			||||||
 | 
					            Account::new(total_lamports, std::mem::size_of::<StakeState>(), &id());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let current = syscall::current::Current::default();
 | 
				
			||||||
 | 
					        let mut future = syscall::current::Current::default();
 | 
				
			||||||
 | 
					        future.epoch += 16;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        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 mut stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &mut stake_account);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Stake some lamports (available lampoorts for withdrawls will reduce)
 | 
				
			||||||
 | 
					        let vote_pubkey = Pubkey::new_rand();
 | 
				
			||||||
 | 
					        let mut vote_account =
 | 
				
			||||||
 | 
					            vote_state::create_account(&vote_pubkey, &Pubkey::new_rand(), 0, 100);
 | 
				
			||||||
 | 
					        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, stake_lamports, &future),
 | 
				
			||||||
 | 
					            Ok(())
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Try to withdraw including staked
 | 
				
			||||||
 | 
					        assert_eq!(
 | 
				
			||||||
 | 
					            stake_keyed_account.withdraw(
 | 
				
			||||||
 | 
					                total_lamports - stake_lamports + 1,
 | 
				
			||||||
 | 
					                &mut to_keyed_account,
 | 
				
			||||||
 | 
					                ¤t
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					            Ok(())
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[test]
 | 
				
			||||||
 | 
					    fn test_withdraw_stake_invalid_state() {
 | 
				
			||||||
 | 
					        let stake_pubkey = Pubkey::new_rand();
 | 
				
			||||||
 | 
					        let total_lamports = 100;
 | 
				
			||||||
 | 
					        let mut stake_account =
 | 
				
			||||||
 | 
					            Account::new(total_lamports, std::mem::size_of::<StakeState>(), &id());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let current = syscall::current::Current::default();
 | 
				
			||||||
 | 
					        let mut future = syscall::current::Current::default();
 | 
				
			||||||
 | 
					        future.epoch += 16;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        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 mut stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &mut stake_account);
 | 
				
			||||||
 | 
					        let stake_state = StakeState::MiningPool {
 | 
				
			||||||
 | 
					            epoch: 0,
 | 
				
			||||||
 | 
					            point_value: 0.0,
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					        stake_keyed_account.set_state(&stake_state).unwrap();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        assert_eq!(
 | 
				
			||||||
 | 
					            stake_keyed_account.withdraw(total_lamports, &mut to_keyed_account, ¤t),
 | 
				
			||||||
 | 
					            Err(InstructionError::InvalidAccountData)
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    #[test]
 | 
					    #[test]
 | 
				
			||||||
    fn test_stake_state_calculate_rewards() {
 | 
					    fn test_stake_state_calculate_rewards() {
 | 
				
			||||||
        let mut vote_state = VoteState::default();
 | 
					        let mut vote_state = VoteState::default();
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -52,6 +52,7 @@ pub enum WalletCommand {
 | 
				
			|||||||
    ShowVoteAccount(Pubkey),
 | 
					    ShowVoteAccount(Pubkey),
 | 
				
			||||||
    CreateStakeAccount(Pubkey, u64),
 | 
					    CreateStakeAccount(Pubkey, u64),
 | 
				
			||||||
    DelegateStake(Keypair, Pubkey, u64),
 | 
					    DelegateStake(Keypair, Pubkey, u64),
 | 
				
			||||||
 | 
					    WithdrawStake(Keypair, Pubkey, u64),
 | 
				
			||||||
    RedeemVoteCredits(Pubkey, Pubkey),
 | 
					    RedeemVoteCredits(Pubkey, Pubkey),
 | 
				
			||||||
    ShowStakeAccount(Pubkey),
 | 
					    ShowStakeAccount(Pubkey),
 | 
				
			||||||
    CreateStorageMiningPoolAccount(Pubkey, u64),
 | 
					    CreateStorageMiningPoolAccount(Pubkey, u64),
 | 
				
			||||||
@@ -248,6 +249,18 @@ pub fn parse_command(
 | 
				
			|||||||
                stake,
 | 
					                stake,
 | 
				
			||||||
            ))
 | 
					            ))
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					        ("withdraw-stake", Some(matches)) => {
 | 
				
			||||||
 | 
					            let staking_account_keypair =
 | 
				
			||||||
 | 
					                keypair_of(matches, "staking_account_keypair_file").unwrap();
 | 
				
			||||||
 | 
					            let destination_account_pubkey =
 | 
				
			||||||
 | 
					                value_of(matches, "destination_account_pubkey").unwrap();
 | 
				
			||||||
 | 
					            let lamports = matches.value_of("lamports").unwrap().parse()?;
 | 
				
			||||||
 | 
					            Ok(WalletCommand::WithdrawStake(
 | 
				
			||||||
 | 
					                staking_account_keypair,
 | 
				
			||||||
 | 
					                destination_account_pubkey,
 | 
				
			||||||
 | 
					                lamports,
 | 
				
			||||||
 | 
					            ))
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
        ("redeem-vote-credits", Some(matches)) => {
 | 
					        ("redeem-vote-credits", Some(matches)) => {
 | 
				
			||||||
            let staking_account_pubkey = value_of(matches, "staking_account_pubkey").unwrap();
 | 
					            let staking_account_pubkey = value_of(matches, "staking_account_pubkey").unwrap();
 | 
				
			||||||
            let voting_account_pubkey = value_of(matches, "voting_account_pubkey").unwrap();
 | 
					            let voting_account_pubkey = value_of(matches, "voting_account_pubkey").unwrap();
 | 
				
			||||||
@@ -584,6 +597,32 @@ fn process_delegate_stake(
 | 
				
			|||||||
    Ok(signature_str.to_string())
 | 
					    Ok(signature_str.to_string())
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn process_withdraw_stake(
 | 
				
			||||||
 | 
					    rpc_client: &RpcClient,
 | 
				
			||||||
 | 
					    config: &WalletConfig,
 | 
				
			||||||
 | 
					    staking_account_keypair: &Keypair,
 | 
				
			||||||
 | 
					    destination_account_pubkey: &Pubkey,
 | 
				
			||||||
 | 
					    lamports: u64,
 | 
				
			||||||
 | 
					) -> ProcessResult {
 | 
				
			||||||
 | 
					    let (recent_blockhash, _fee_calculator) = rpc_client.get_recent_blockhash()?;
 | 
				
			||||||
 | 
					    let ixs = vec![stake_instruction::withdraw(
 | 
				
			||||||
 | 
					        &staking_account_keypair.pubkey(),
 | 
				
			||||||
 | 
					        destination_account_pubkey,
 | 
				
			||||||
 | 
					        lamports,
 | 
				
			||||||
 | 
					    )];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let mut tx = Transaction::new_signed_with_payer(
 | 
				
			||||||
 | 
					        ixs,
 | 
				
			||||||
 | 
					        Some(&config.keypair.pubkey()),
 | 
				
			||||||
 | 
					        &[&config.keypair, &staking_account_keypair],
 | 
				
			||||||
 | 
					        recent_blockhash,
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let signature_str = rpc_client
 | 
				
			||||||
 | 
					        .send_and_confirm_transaction(&mut tx, &[&config.keypair, &staking_account_keypair])?;
 | 
				
			||||||
 | 
					    Ok(signature_str.to_string())
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
fn process_redeem_vote_credits(
 | 
					fn process_redeem_vote_credits(
 | 
				
			||||||
    rpc_client: &RpcClient,
 | 
					    rpc_client: &RpcClient,
 | 
				
			||||||
    config: &WalletConfig,
 | 
					    config: &WalletConfig,
 | 
				
			||||||
@@ -1022,6 +1061,18 @@ pub fn process_command(config: &WalletConfig) -> ProcessResult {
 | 
				
			|||||||
            )
 | 
					            )
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        WalletCommand::WithdrawStake(
 | 
				
			||||||
 | 
					            staking_account_keypair,
 | 
				
			||||||
 | 
					            destination_account_pubkey,
 | 
				
			||||||
 | 
					            lamports,
 | 
				
			||||||
 | 
					        ) => process_withdraw_stake(
 | 
				
			||||||
 | 
					            &rpc_client,
 | 
				
			||||||
 | 
					            config,
 | 
				
			||||||
 | 
					            &staking_account_keypair,
 | 
				
			||||||
 | 
					            &destination_account_pubkey,
 | 
				
			||||||
 | 
					            *lamports,
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        WalletCommand::RedeemVoteCredits(staking_account_pubkey, voting_account_pubkey) => {
 | 
					        WalletCommand::RedeemVoteCredits(staking_account_pubkey, voting_account_pubkey) => {
 | 
				
			||||||
            process_redeem_vote_credits(
 | 
					            process_redeem_vote_credits(
 | 
				
			||||||
                &rpc_client,
 | 
					                &rpc_client,
 | 
				
			||||||
@@ -1401,6 +1452,35 @@ pub fn app<'ab, 'v>(name: &str, about: &'ab str, version: &'v str) -> App<'ab, '
 | 
				
			|||||||
                        .help("The number of lamports to stake, must be less than the stake account's balance."),
 | 
					                        .help("The number of lamports to stake, must be less than the stake account's balance."),
 | 
				
			||||||
                ),
 | 
					                ),
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
 | 
					        .subcommand(
 | 
				
			||||||
 | 
					            SubCommand::with_name("withdraw-stake")
 | 
				
			||||||
 | 
					                .about("Withdraw the unstaked lamports from the stake account")
 | 
				
			||||||
 | 
					                .arg(
 | 
				
			||||||
 | 
					                    Arg::with_name("staking_account_keypair_file")
 | 
				
			||||||
 | 
					                        .index(1)
 | 
				
			||||||
 | 
					                        .value_name("KEYPAIR_FILE")
 | 
				
			||||||
 | 
					                        .takes_value(true)
 | 
				
			||||||
 | 
					                        .required(true)
 | 
				
			||||||
 | 
					                        .help("Keypair file for the staking account, for signing the withdraw transaction."),
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					                .arg(
 | 
				
			||||||
 | 
					                    Arg::with_name("destination_account_pubkey")
 | 
				
			||||||
 | 
					                        .index(2)
 | 
				
			||||||
 | 
					                        .value_name("PUBKEY")
 | 
				
			||||||
 | 
					                        .takes_value(true)
 | 
				
			||||||
 | 
					                        .required(true)
 | 
				
			||||||
 | 
					                        .validator(is_pubkey)
 | 
				
			||||||
 | 
					                        .help("The account where the lamports should be transfered"),
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					                .arg(
 | 
				
			||||||
 | 
					                    Arg::with_name("lamports")
 | 
				
			||||||
 | 
					                        .index(3)
 | 
				
			||||||
 | 
					                        .value_name("NUM")
 | 
				
			||||||
 | 
					                        .takes_value(true)
 | 
				
			||||||
 | 
					                        .required(true)
 | 
				
			||||||
 | 
					                        .help("The number of lamports to to withdraw from the stake account."),
 | 
				
			||||||
 | 
					                ),
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
        .subcommand(
 | 
					        .subcommand(
 | 
				
			||||||
            SubCommand::with_name("redeem-vote-credits")
 | 
					            SubCommand::with_name("redeem-vote-credits")
 | 
				
			||||||
                .about("Redeem credits in the staking account")
 | 
					                .about("Redeem credits in the staking account")
 | 
				
			||||||
@@ -1837,6 +1917,22 @@ mod tests {
 | 
				
			|||||||
            WalletCommand::DelegateStake(keypair, pubkey, 42)
 | 
					            WalletCommand::DelegateStake(keypair, pubkey, 42)
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let keypair_file = make_tmp_path("keypair_file");
 | 
				
			||||||
 | 
					        gen_keypair_file(&keypair_file).unwrap();
 | 
				
			||||||
 | 
					        let keypair = read_keypair(&keypair_file).unwrap();
 | 
				
			||||||
 | 
					        // Test Withdraw from Stake Account
 | 
				
			||||||
 | 
					        let test_withdraw_stake = test_commands.clone().get_matches_from(vec![
 | 
				
			||||||
 | 
					            "test",
 | 
				
			||||||
 | 
					            "withdraw-stake",
 | 
				
			||||||
 | 
					            &keypair_file,
 | 
				
			||||||
 | 
					            &pubkey_string,
 | 
				
			||||||
 | 
					            "42",
 | 
				
			||||||
 | 
					        ]);
 | 
				
			||||||
 | 
					        assert_eq!(
 | 
				
			||||||
 | 
					            parse_command(&pubkey, &test_withdraw_stake).unwrap(),
 | 
				
			||||||
 | 
					            WalletCommand::WithdrawStake(keypair, pubkey, 42)
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Test Deploy Subcommand
 | 
					        // Test Deploy Subcommand
 | 
				
			||||||
        let test_deploy =
 | 
					        let test_deploy =
 | 
				
			||||||
            test_commands
 | 
					            test_commands
 | 
				
			||||||
@@ -2006,6 +2102,12 @@ mod tests {
 | 
				
			|||||||
        let signature = process_command(&config);
 | 
					        let signature = process_command(&config);
 | 
				
			||||||
        assert_eq!(signature.unwrap(), SIGNATURE.to_string());
 | 
					        assert_eq!(signature.unwrap(), SIGNATURE.to_string());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let bob_keypair = Keypair::new();
 | 
				
			||||||
 | 
					        let to_pubkey = Pubkey::new_rand();
 | 
				
			||||||
 | 
					        config.command = WalletCommand::WithdrawStake(bob_keypair.into(), to_pubkey, 100);
 | 
				
			||||||
 | 
					        let signature = process_command(&config);
 | 
				
			||||||
 | 
					        assert_eq!(signature.unwrap(), SIGNATURE.to_string());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        config.command = WalletCommand::GetTransactionCount;
 | 
					        config.command = WalletCommand::GetTransactionCount;
 | 
				
			||||||
        assert_eq!(process_command(&config).unwrap(), "1234");
 | 
					        assert_eq!(process_command(&config).unwrap(), "1234");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user