Add `solana-close-vote-account` to CLI (#19756)
This commit is contained in:
parent
4e7a100705
commit
2a29072019
|
@ -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,
|
||||
|
|
169
cli/src/vote.rs
169
cli/src/vote.rs
|
@ -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()
|
||||
],
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
Loading…
Reference in New Issue