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,
|
withdraw_amount: SpendAmount,
|
||||||
memo: Option<String>,
|
memo: Option<String>,
|
||||||
},
|
},
|
||||||
|
CloseVoteAccount {
|
||||||
|
vote_account_pubkey: Pubkey,
|
||||||
|
destination_account_pubkey: Pubkey,
|
||||||
|
withdraw_authority: SignerIndex,
|
||||||
|
memo: Option<String>,
|
||||||
|
},
|
||||||
VoteAuthorize {
|
VoteAuthorize {
|
||||||
vote_account_pubkey: Pubkey,
|
vote_account_pubkey: Pubkey,
|
||||||
new_authorized_pubkey: Pubkey,
|
new_authorized_pubkey: Pubkey,
|
||||||
|
@ -810,6 +816,9 @@ pub fn parse_command(
|
||||||
("withdraw-from-vote-account", Some(matches)) => {
|
("withdraw-from-vote-account", Some(matches)) => {
|
||||||
parse_withdraw_from_vote_account(matches, default_signer, wallet_manager)
|
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
|
// Wallet Commands
|
||||||
("account", Some(matches)) => parse_account(matches, wallet_manager),
|
("account", Some(matches)) => parse_account(matches, wallet_manager),
|
||||||
("address", Some(matches)) => Ok(CliCommandInfo {
|
("address", Some(matches)) => Ok(CliCommandInfo {
|
||||||
|
@ -1409,6 +1418,19 @@ pub fn process_command(config: &CliConfig) -> ProcessResult {
|
||||||
destination_account_pubkey,
|
destination_account_pubkey,
|
||||||
memo.as_ref(),
|
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 {
|
CliCommand::VoteAuthorize {
|
||||||
vote_account_pubkey,
|
vote_account_pubkey,
|
||||||
new_authorized_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},
|
memo::{memo_arg, MEMO_ARG},
|
||||||
};
|
};
|
||||||
use solana_cli_output::{CliEpochVotingHistory, CliLockout, CliVoteAccount};
|
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_remote_wallet::remote_wallet::RemoteWalletManager;
|
||||||
use solana_sdk::{
|
use solana_sdk::{
|
||||||
account::Account, commitment_config::CommitmentConfig, message::Message,
|
account::Account, commitment_config::CommitmentConfig, message::Message,
|
||||||
|
@ -335,7 +335,36 @@ impl VoteSubCommands for App<'_, '_> {
|
||||||
.validator(is_valid_signer)
|
.validator(is_valid_signer)
|
||||||
.help("Authorized withdrawer [default: cli config keypair]"),
|
.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(
|
pub fn process_create_vote_account(
|
||||||
rpc_client: &RpcClient,
|
rpc_client: &RpcClient,
|
||||||
config: &CliConfig,
|
config: &CliConfig,
|
||||||
|
@ -907,6 +968,62 @@ pub fn process_withdraw_from_vote_account(
|
||||||
log_instruction_custom_error::<VoteError>(result, config)
|
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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
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,
|
memo: None,
|
||||||
};
|
};
|
||||||
process_command(&config).unwrap();
|
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);
|
check_recent_balance(100, &rpc_client, &destination_account);
|
||||||
|
|
||||||
// Re-assign validator identity
|
// Re-assign validator identity
|
||||||
|
@ -160,4 +161,17 @@ fn test_vote_authorize_and_withdraw() {
|
||||||
memo: None,
|
memo: None,
|
||||||
};
|
};
|
||||||
process_command(&config).unwrap();
|
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
|
### Vote Account Authorized Withdrawer
|
||||||
|
|
||||||
No special handling is required. Use the `solana vote-authorize-withdrawer` command as needed.
|
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