From 296ac10b3abca5d4972ff99047aca4e88c6cd72a Mon Sep 17 00:00:00 2001 From: Michael Vines Date: Mon, 15 Jun 2020 14:36:47 -0700 Subject: [PATCH] |solana withdraw-from-vote-account| now supports ALL, and refuses to deallocate the vote account (#10602) --- cli/src/cli.rs | 6 ++--- cli/src/vote.rs | 62 ++++++++++++++++++++++++++++++++++++++++------- cli/tests/vote.rs | 27 ++++++++++++++++++--- 3 files changed, 80 insertions(+), 15 deletions(-) diff --git a/cli/src/cli.rs b/cli/src/cli.rs index 2747795872..2ce883816c 100644 --- a/cli/src/cli.rs +++ b/cli/src/cli.rs @@ -399,7 +399,7 @@ pub enum CliCommand { vote_account_pubkey: Pubkey, destination_account_pubkey: Pubkey, withdraw_authority: SignerIndex, - lamports: u64, + withdraw_amount: SpendAmount, }, VoteAuthorize { vote_account_pubkey: Pubkey, @@ -2170,14 +2170,14 @@ pub fn process_command(config: &CliConfig) -> ProcessResult { CliCommand::WithdrawFromVoteAccount { vote_account_pubkey, withdraw_authority, - lamports, + withdraw_amount, destination_account_pubkey, } => process_withdraw_from_vote_account( &rpc_client, config, vote_account_pubkey, *withdraw_authority, - *lamports, + *withdraw_amount, destination_account_pubkey, ), CliCommand::VoteAuthorize { diff --git a/cli/src/vote.rs b/cli/src/vote.rs index e13198d78f..143f4401ed 100644 --- a/cli/src/vote.rs +++ b/cli/src/vote.rs @@ -16,8 +16,9 @@ use solana_clap_utils::{ use solana_client::rpc_client::RpcClient; use solana_remote_wallet::remote_wallet::RemoteWalletManager; use solana_sdk::{ - account::Account, commitment_config::CommitmentConfig, message::Message, pubkey::Pubkey, - system_instruction::SystemError, transaction::Transaction, + account::Account, commitment_config::CommitmentConfig, message::Message, + native_token::lamports_to_sol, pubkey::Pubkey, system_instruction::SystemError, + transaction::Transaction, }; use solana_vote_program::{ vote_instruction::{self, withdraw, VoteError}, @@ -232,8 +233,8 @@ impl VoteSubCommands for App<'_, '_> { .value_name("AMOUNT") .takes_value(true) .required(true) - .validator(is_amount) - .help("The amount to withdraw, in SOL"), + .validator(is_amount_or_all) + .help("The amount to withdraw, in SOL; accepts keyword ALL"), ) .arg( Arg::with_name("authorized_withdrawer") @@ -392,7 +393,8 @@ pub fn parse_withdraw_from_vote_account( pubkey_of_signer(matches, "vote_account_pubkey", wallet_manager)?.unwrap(); let destination_account_pubkey = pubkey_of_signer(matches, "destination_account_pubkey", wallet_manager)?.unwrap(); - let lamports = lamports_of_sol(matches, "amount").unwrap(); + let withdraw_amount = SpendAmount::new_from_matches(matches, "amount"); + let (withdraw_authority, withdraw_authority_pubkey) = signer_of(matches, "authorized_withdrawer", wallet_manager)?; @@ -409,7 +411,7 @@ pub fn parse_withdraw_from_vote_account( vote_account_pubkey, destination_account_pubkey, withdraw_authority: signer_info.index_of(withdraw_authority_pubkey).unwrap(), - lamports, + withdraw_amount, }, signers: signer_info.signers, }) @@ -683,12 +685,28 @@ pub fn process_withdraw_from_vote_account( config: &CliConfig, vote_account_pubkey: &Pubkey, withdraw_authority: SignerIndex, - lamports: u64, + withdraw_amount: SpendAmount, destination_account_pubkey: &Pubkey, ) -> ProcessResult { let (recent_blockhash, fee_calculator) = rpc_client.get_recent_blockhash()?; let withdraw_authority = config.signers[withdraw_authority]; + let current_balance = rpc_client.get_balance(&vote_account_pubkey)?; + let minimum_balance = rpc_client.get_minimum_balance_for_rent_exemption(VoteState::size_of())?; + + let lamports = match withdraw_amount { + SpendAmount::All => current_balance.saturating_sub(minimum_balance), + SpendAmount::Some(withdraw_amount) => { + if current_balance.saturating_sub(withdraw_amount) < minimum_balance { + return Err(CliError::BadParameter(format!( + "Withdraw amount too large. The vote account balance must be at least {} SOL to remain rent exempt", lamports_to_sol(minimum_balance) + )) + .into()); + } + withdraw_amount + } + }; + let ix = withdraw( vote_account_pubkey, &withdraw_authority.pubkey(), @@ -966,7 +984,33 @@ mod tests { vote_account_pubkey: read_keypair_file(&keypair_file).unwrap().pubkey(), destination_account_pubkey: pubkey, withdraw_authority: 0, - lamports: 42_000_000_000 + withdraw_amount: SpendAmount::Some(42_000_000_000), + }, + signers: vec![read_keypair_file(&default_keypair_file).unwrap().into()], + } + ); + + // Test WithdrawFromVoteAccount subcommand + let test_withdraw_from_vote_account = test_commands.clone().get_matches_from(vec![ + "test", + "withdraw-from-vote-account", + &keypair_file, + &pubkey_string, + "ALL", + ]); + assert_eq!( + parse_command( + &test_withdraw_from_vote_account, + &default_keypair_file, + &mut None + ) + .unwrap(), + CliCommandInfo { + command: CliCommand::WithdrawFromVoteAccount { + vote_account_pubkey: read_keypair_file(&keypair_file).unwrap().pubkey(), + destination_account_pubkey: pubkey, + withdraw_authority: 0, + withdraw_amount: SpendAmount::All, }, signers: vec![read_keypair_file(&default_keypair_file).unwrap().into()], } @@ -997,7 +1041,7 @@ mod tests { vote_account_pubkey: read_keypair_file(&keypair_file).unwrap().pubkey(), destination_account_pubkey: pubkey, withdraw_authority: 1, - lamports: 42_000_000_000 + withdraw_amount: SpendAmount::Some(42_000_000_000), }, signers: vec![ read_keypair_file(&default_keypair_file).unwrap().into(), diff --git a/cli/tests/vote.rs b/cli/tests/vote.rs index 548bfd704b..edc6abc7a3 100644 --- a/cli/tests/vote.rs +++ b/cli/tests/vote.rs @@ -1,5 +1,9 @@ -use solana_cli::cli::{process_command, request_and_confirm_airdrop, CliCommand, CliConfig}; -use solana_cli::test_utils::check_balance; +use solana_cli::{ + cli::{process_command, request_and_confirm_airdrop, CliCommand, CliConfig}, + offline::{blockhash_query::BlockhashQuery, *}, + spend_utils::SpendAmount, + test_utils::check_balance, +}; use solana_client::rpc_client::RpcClient; use solana_core::validator::TestValidator; use solana_faucet::faucet::run_local_faucet; @@ -64,6 +68,23 @@ fn test_vote_authorize_and_withdraw() { .max(1); check_balance(expected_balance, &rpc_client, &vote_account_pubkey); + // Transfer in some more SOL + config.signers = vec![&default_signer]; + config.command = CliCommand::Transfer { + amount: SpendAmount::Some(1_000), + to: vote_account_pubkey, + from: 0, + sign_only: false, + no_wait: false, + blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster), + nonce_account: None, + nonce_authority: 0, + fee_payer: 0, + }; + process_command(&config).unwrap(); + let expected_balance = expected_balance + 1_000; + check_balance(expected_balance, &rpc_client, &vote_account_pubkey); + // Authorize vote account withdrawal to another signer let withdraw_authority = Keypair::new(); config.signers = vec![&default_signer]; @@ -86,7 +107,7 @@ fn test_vote_authorize_and_withdraw() { config.command = CliCommand::WithdrawFromVoteAccount { vote_account_pubkey, withdraw_authority: 1, - lamports: 100, + withdraw_amount: SpendAmount::Some(100), destination_account_pubkey: destination_account, }; process_command(&config).unwrap();