Add `solana-close-vote-account` to CLI (#19756)

This commit is contained in:
Drew Taylor 2021-09-13 08:13:59 -07:00 committed by GitHub
parent 4e7a100705
commit 2a29072019
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 211 additions and 3 deletions

View File

@ -308,6 +308,12 @@ pub enum CliCommand {
withdraw_amount: SpendAmount,
memo: Option<String>,
},
CloseVoteAccount {
vote_account_pubkey: Pubkey,
destination_account_pubkey: Pubkey,
withdraw_authority: SignerIndex,
memo: Option<String>,
},
VoteAuthorize {
vote_account_pubkey: Pubkey,
new_authorized_pubkey: Pubkey,
@ -810,6 +816,9 @@ pub fn parse_command(
("withdraw-from-vote-account", Some(matches)) => {
parse_withdraw_from_vote_account(matches, default_signer, wallet_manager)
}
("close-vote-account", Some(matches)) => {
parse_close_vote_account(matches, default_signer, wallet_manager)
}
// Wallet Commands
("account", Some(matches)) => parse_account(matches, wallet_manager),
("address", Some(matches)) => Ok(CliCommandInfo {
@ -1409,6 +1418,19 @@ pub fn process_command(config: &CliConfig) -> ProcessResult {
destination_account_pubkey,
memo.as_ref(),
),
CliCommand::CloseVoteAccount {
vote_account_pubkey,
withdraw_authority,
destination_account_pubkey,
memo,
} => process_close_vote_account(
&rpc_client,
config,
vote_account_pubkey,
*withdraw_authority,
destination_account_pubkey,
memo.as_ref(),
),
CliCommand::VoteAuthorize {
vote_account_pubkey,
new_authorized_pubkey,

View File

@ -15,7 +15,7 @@ use solana_clap_utils::{
memo::{memo_arg, MEMO_ARG},
};
use solana_cli_output::{CliEpochVotingHistory, CliLockout, CliVoteAccount};
use solana_client::rpc_client::RpcClient;
use solana_client::{rpc_client::RpcClient, rpc_config::RpcGetVoteAccountsConfig};
use solana_remote_wallet::remote_wallet::RemoteWalletManager;
use solana_sdk::{
account::Account, commitment_config::CommitmentConfig, message::Message,
@ -335,7 +335,36 @@ impl VoteSubCommands for App<'_, '_> {
.validator(is_valid_signer)
.help("Authorized withdrawer [default: cli config keypair]"),
)
.arg(memo_arg())
.arg(memo_arg()
)
)
.subcommand(
SubCommand::with_name("close-vote-account")
.about("Close a vote account and withdraw all funds remaining")
.arg(
pubkey!(Arg::with_name("vote_account_pubkey")
.index(1)
.value_name("VOTE_ACCOUNT_ADDRESS")
.required(true),
"Vote account to be closed. "),
)
.arg(
pubkey!(Arg::with_name("destination_account_pubkey")
.index(2)
.value_name("RECIPIENT_ADDRESS")
.required(true),
"The recipient of all withdrawn SOL. "),
)
.arg(
Arg::with_name("authorized_withdrawer")
.long("authorized-withdrawer")
.value_name("AUTHORIZED_KEYPAIR")
.takes_value(true)
.validator(is_valid_signer)
.help("Authorized withdrawer [default: cli config keypair]"),
)
.arg(memo_arg()
)
)
}
}
@ -554,6 +583,38 @@ pub fn parse_withdraw_from_vote_account(
})
}
pub fn parse_close_vote_account(
matches: &ArgMatches<'_>,
default_signer: &DefaultSigner,
wallet_manager: &mut Option<Arc<RemoteWalletManager>>,
) -> Result<CliCommandInfo, CliError> {
let vote_account_pubkey =
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 (withdraw_authority, withdraw_authority_pubkey) =
signer_of(matches, "authorized_withdrawer", wallet_manager)?;
let payer_provided = None;
let signer_info = default_signer.generate_unique_signers(
vec![payer_provided, withdraw_authority],
matches,
wallet_manager,
)?;
let memo = matches.value_of(MEMO_ARG.name).map(String::from);
Ok(CliCommandInfo {
command: CliCommand::CloseVoteAccount {
vote_account_pubkey,
destination_account_pubkey,
withdraw_authority: signer_info.index_of(withdraw_authority_pubkey).unwrap(),
memo,
},
signers: signer_info.signers,
})
}
pub fn process_create_vote_account(
rpc_client: &RpcClient,
config: &CliConfig,
@ -907,6 +968,62 @@ pub fn process_withdraw_from_vote_account(
log_instruction_custom_error::<VoteError>(result, config)
}
pub fn process_close_vote_account(
rpc_client: &RpcClient,
config: &CliConfig,
vote_account_pubkey: &Pubkey,
withdraw_authority: SignerIndex,
destination_account_pubkey: &Pubkey,
memo: Option<&String>,
) -> ProcessResult {
let vote_account_status =
rpc_client.get_vote_accounts_with_config(RpcGetVoteAccountsConfig {
vote_pubkey: Some(vote_account_pubkey.to_string()),
..RpcGetVoteAccountsConfig::default()
})?;
if let Some(vote_account) = vote_account_status
.current
.into_iter()
.chain(vote_account_status.delinquent.into_iter())
.next()
{
if vote_account.activated_stake != 0 {
return Err(format!(
"Cannot close a vote account with active stake: {}",
vote_account_pubkey
)
.into());
}
}
let latest_blockhash = rpc_client.get_latest_blockhash()?;
let withdraw_authority = config.signers[withdraw_authority];
let current_balance = rpc_client.get_balance(vote_account_pubkey)?;
let ixs = vec![withdraw(
vote_account_pubkey,
&withdraw_authority.pubkey(),
current_balance,
destination_account_pubkey,
)]
.with_memo(memo);
let message = Message::new(&ixs, Some(&config.signers[0].pubkey()));
let mut transaction = Transaction::new_unsigned(message);
transaction.try_sign(&config.signers, latest_blockhash)?;
check_account_for_fee_with_commitment(
rpc_client,
&config.signers[0].pubkey(),
&latest_blockhash,
&transaction.message,
config.commitment,
)?;
let result = rpc_client.send_and_confirm_transaction_with_spinner(&transaction);
log_instruction_custom_error::<VoteError>(result, config)
}
#[cfg(test)]
mod tests {
use super::*;
@ -1304,5 +1421,53 @@ mod tests {
],
}
);
// Test CloseVoteAccount subcommand
let test_close_vote_account = test_commands.clone().get_matches_from(vec![
"test",
"close-vote-account",
&keypair_file,
&pubkey_string,
]);
assert_eq!(
parse_command(&test_close_vote_account, &default_signer, &mut None).unwrap(),
CliCommandInfo {
command: CliCommand::CloseVoteAccount {
vote_account_pubkey: read_keypair_file(&keypair_file).unwrap().pubkey(),
destination_account_pubkey: pubkey,
withdraw_authority: 0,
memo: None,
},
signers: vec![read_keypair_file(&default_keypair_file).unwrap().into()],
}
);
// Test CloseVoteAccount subcommand with authority
let withdraw_authority = Keypair::new();
let (withdraw_authority_file, mut tmp_file) = make_tmp_file();
write_keypair(&withdraw_authority, tmp_file.as_file_mut()).unwrap();
let test_close_vote_account = test_commands.clone().get_matches_from(vec![
"test",
"close-vote-account",
&keypair_file,
&pubkey_string,
"--authorized-withdrawer",
&withdraw_authority_file,
]);
assert_eq!(
parse_command(&test_close_vote_account, &default_signer, &mut None).unwrap(),
CliCommandInfo {
command: CliCommand::CloseVoteAccount {
vote_account_pubkey: read_keypair_file(&keypair_file).unwrap().pubkey(),
destination_account_pubkey: pubkey,
withdraw_authority: 1,
memo: None,
},
signers: vec![
read_keypair_file(&default_keypair_file).unwrap().into(),
read_keypair_file(&withdraw_authority_file).unwrap().into()
],
}
);
}
}

View File

@ -147,7 +147,8 @@ fn test_vote_authorize_and_withdraw() {
memo: None,
};
process_command(&config).unwrap();
check_recent_balance(expected_balance - 100, &rpc_client, &vote_account_pubkey);
let expected_balance = expected_balance - 100;
check_recent_balance(expected_balance, &rpc_client, &vote_account_pubkey);
check_recent_balance(100, &rpc_client, &destination_account);
// Re-assign validator identity
@ -160,4 +161,17 @@ fn test_vote_authorize_and_withdraw() {
memo: None,
};
process_command(&config).unwrap();
// Close vote account
let destination_account = solana_sdk::pubkey::new_rand(); // Send withdrawal to new account to make balance check easy
config.signers = vec![&default_signer, &withdraw_authority];
config.command = CliCommand::CloseVoteAccount {
vote_account_pubkey,
withdraw_authority: 1,
destination_account_pubkey: destination_account,
memo: None,
};
process_command(&config).unwrap();
check_recent_balance(0, &rpc_client, &vote_account_pubkey);
check_recent_balance(expected_balance, &rpc_client, &destination_account);
}

View File

@ -210,3 +210,10 @@ migration.
### Vote Account Authorized Withdrawer
No special handling is required. Use the `solana vote-authorize-withdrawer` command as needed.
## Close a Vote Account
A vote account can be closed with the
[close-vote-account](../cli/usage.md#solana-close-vote-account) command.
Closing a vote account withdraws all remaining SOL funds to a supplied recipient address and renders it invalid as a vote account.
It is not possible to close a vote account with active stake.