solana_cli/
vote.rs

1use {
2    crate::{
3        checks::{check_account_for_fee_with_commitment, check_unique_pubkeys},
4        cli::{
5            log_instruction_custom_error, CliCommand, CliCommandInfo, CliConfig, CliError,
6            ProcessResult,
7        },
8        compute_budget::{
9            simulate_and_update_compute_unit_limit, ComputeUnitConfig, WithComputeUnitConfig,
10        },
11        memo::WithMemo,
12        nonce::check_nonce_account,
13        spend_utils::{resolve_spend_tx_and_check_account_balances, SpendAmount},
14        stake::check_current_authority,
15    },
16    clap::{value_t_or_exit, App, Arg, ArgMatches, SubCommand},
17    solana_account::Account,
18    solana_clap_utils::{
19        compute_budget::{compute_unit_price_arg, ComputeUnitLimit, COMPUTE_UNIT_PRICE_ARG},
20        fee_payer::{fee_payer_arg, FEE_PAYER_ARG},
21        input_parsers::*,
22        input_validators::*,
23        keypair::{DefaultSigner, SignerIndex},
24        memo::{memo_arg, MEMO_ARG},
25        nonce::*,
26        offline::*,
27    },
28    solana_cli_output::{
29        display::build_balance_message, return_signers_with_config, CliEpochVotingHistory,
30        CliLandedVote, CliVoteAccount, ReturnSignersConfig,
31    },
32    solana_commitment_config::CommitmentConfig,
33    solana_message::Message,
34    solana_pubkey::Pubkey,
35    solana_remote_wallet::remote_wallet::RemoteWalletManager,
36    solana_rpc_client::rpc_client::RpcClient,
37    solana_rpc_client_api::config::RpcGetVoteAccountsConfig,
38    solana_rpc_client_nonce_utils::blockhash_query::BlockhashQuery,
39    solana_system_interface::error::SystemError,
40    solana_transaction::Transaction,
41    solana_vote_program::{
42        vote_error::VoteError,
43        vote_instruction::{self, withdraw, CreateVoteAccountConfig},
44        vote_state::{VoteAuthorize, VoteInit, VoteStateV4, VOTE_CREDITS_MAXIMUM_PER_SLOT},
45    },
46    std::rc::Rc,
47};
48
49pub trait VoteSubCommands {
50    fn vote_subcommands(self) -> Self;
51}
52
53impl VoteSubCommands for App<'_, '_> {
54    fn vote_subcommands(self) -> Self {
55        self.subcommand(
56            SubCommand::with_name("create-vote-account")
57                .about("Create a vote account")
58                .arg(
59                    Arg::with_name("vote_account")
60                        .index(1)
61                        .value_name("ACCOUNT_KEYPAIR")
62                        .takes_value(true)
63                        .required(true)
64                        .validator(is_valid_signer)
65                        .help("Vote account keypair to create"),
66                )
67                .arg(
68                    Arg::with_name("identity_account")
69                        .index(2)
70                        .value_name("IDENTITY_KEYPAIR")
71                        .takes_value(true)
72                        .required(true)
73                        .validator(is_valid_signer)
74                        .help("Keypair of validator that will vote with this account"),
75                )
76                .arg(pubkey!(
77                    Arg::with_name("authorized_withdrawer")
78                        .index(3)
79                        .value_name("WITHDRAWER_PUBKEY")
80                        .takes_value(true)
81                        .required(true)
82                        .long("authorized-withdrawer"),
83                    "Authorized withdrawer."
84                ))
85                .arg(
86                    Arg::with_name("commission")
87                        .long("commission")
88                        .value_name("PERCENTAGE")
89                        .takes_value(true)
90                        .default_value("100")
91                        .help("The commission taken on reward redemption (0-100)"),
92                )
93                .arg(pubkey!(
94                    Arg::with_name("authorized_voter")
95                        .long("authorized-voter")
96                        .value_name("VOTER_PUBKEY"),
97                    "Authorized voter [default: validator identity pubkey]."
98                ))
99                .arg(
100                    Arg::with_name("allow_unsafe_authorized_withdrawer")
101                        .long("allow-unsafe-authorized-withdrawer")
102                        .takes_value(false)
103                        .help(
104                            "Allow an authorized withdrawer pubkey to be identical to the \
105                             validator identity account pubkey or vote account pubkey, which is \
106                             normally an unsafe configuration and should be avoided.",
107                        ),
108                )
109                .arg(
110                    Arg::with_name("seed")
111                        .long("seed")
112                        .value_name("STRING")
113                        .takes_value(true)
114                        .help(
115                            "Seed for address generation; if specified, the resulting account \
116                             will be at a derived address of the VOTE ACCOUNT pubkey",
117                        ),
118                )
119                .offline_args()
120                .nonce_args(false)
121                .arg(fee_payer_arg())
122                .arg(memo_arg())
123                .arg(compute_unit_price_arg()),
124        )
125        .subcommand(
126            SubCommand::with_name("vote-authorize-voter")
127                .about("Authorize a new vote signing keypair for the given vote account")
128                .arg(pubkey!(
129                    Arg::with_name("vote_account_pubkey")
130                        .index(1)
131                        .value_name("VOTE_ACCOUNT_ADDRESS")
132                        .required(true),
133                    "Vote account in which to set the authorized voter."
134                ))
135                .arg(
136                    Arg::with_name("authorized")
137                        .index(2)
138                        .value_name("AUTHORIZED_KEYPAIR")
139                        .required(true)
140                        .validator(is_valid_signer)
141                        .help("Current authorized vote signer."),
142                )
143                .arg(pubkey!(
144                    Arg::with_name("new_authorized_pubkey")
145                        .index(3)
146                        .value_name("NEW_AUTHORIZED_PUBKEY")
147                        .required(true),
148                    "New authorized vote signer."
149                ))
150                .offline_args()
151                .nonce_args(false)
152                .arg(fee_payer_arg())
153                .arg(memo_arg())
154                .arg(compute_unit_price_arg()),
155        )
156        .subcommand(
157            SubCommand::with_name("vote-authorize-withdrawer")
158                .about("Authorize a new withdraw signing keypair for the given vote account")
159                .arg(pubkey!(
160                    Arg::with_name("vote_account_pubkey")
161                        .index(1)
162                        .value_name("VOTE_ACCOUNT_ADDRESS")
163                        .required(true),
164                    "Vote account in which to set the authorized withdrawer."
165                ))
166                .arg(
167                    Arg::with_name("authorized")
168                        .index(2)
169                        .value_name("AUTHORIZED_KEYPAIR")
170                        .required(true)
171                        .validator(is_valid_signer)
172                        .help("Current authorized withdrawer."),
173                )
174                .arg(pubkey!(
175                    Arg::with_name("new_authorized_pubkey")
176                        .index(3)
177                        .value_name("AUTHORIZED_PUBKEY")
178                        .required(true),
179                    "New authorized withdrawer."
180                ))
181                .offline_args()
182                .nonce_args(false)
183                .arg(fee_payer_arg())
184                .arg(memo_arg())
185                .arg(compute_unit_price_arg()),
186        )
187        .subcommand(
188            SubCommand::with_name("vote-authorize-voter-checked")
189                .about(
190                    "Authorize a new vote signing keypair for the given vote account, checking \
191                     the new authority as a signer",
192                )
193                .arg(pubkey!(
194                    Arg::with_name("vote_account_pubkey")
195                        .index(1)
196                        .value_name("VOTE_ACCOUNT_ADDRESS")
197                        .required(true),
198                    "Vote account in which to set the authorized voter."
199                ))
200                .arg(
201                    Arg::with_name("authorized")
202                        .index(2)
203                        .value_name("AUTHORIZED_KEYPAIR")
204                        .required(true)
205                        .validator(is_valid_signer)
206                        .help("Current authorized vote signer."),
207                )
208                .arg(
209                    Arg::with_name("new_authorized")
210                        .index(3)
211                        .value_name("NEW_AUTHORIZED_KEYPAIR")
212                        .required(true)
213                        .validator(is_valid_signer)
214                        .help("New authorized vote signer."),
215                )
216                .offline_args()
217                .nonce_args(false)
218                .arg(fee_payer_arg())
219                .arg(memo_arg())
220                .arg(compute_unit_price_arg()),
221        )
222        .subcommand(
223            SubCommand::with_name("vote-authorize-withdrawer-checked")
224                .about(
225                    "Authorize a new withdraw signing keypair for the given vote account, \
226                     checking the new authority as a signer",
227                )
228                .arg(pubkey!(
229                    Arg::with_name("vote_account_pubkey")
230                        .index(1)
231                        .value_name("VOTE_ACCOUNT_ADDRESS")
232                        .required(true),
233                    "Vote account in which to set the authorized withdrawer."
234                ))
235                .arg(
236                    Arg::with_name("authorized")
237                        .index(2)
238                        .value_name("AUTHORIZED_KEYPAIR")
239                        .required(true)
240                        .validator(is_valid_signer)
241                        .help("Current authorized withdrawer."),
242                )
243                .arg(
244                    Arg::with_name("new_authorized")
245                        .index(3)
246                        .value_name("NEW_AUTHORIZED_KEYPAIR")
247                        .required(true)
248                        .validator(is_valid_signer)
249                        .help("New authorized withdrawer."),
250                )
251                .offline_args()
252                .nonce_args(false)
253                .arg(fee_payer_arg())
254                .arg(memo_arg())
255                .arg(compute_unit_price_arg()),
256        )
257        .subcommand(
258            SubCommand::with_name("vote-update-validator")
259                .about("Update the vote account's validator identity")
260                .arg(pubkey!(
261                    Arg::with_name("vote_account_pubkey")
262                        .index(1)
263                        .value_name("VOTE_ACCOUNT_ADDRESS")
264                        .required(true),
265                    "Vote account to update."
266                ))
267                .arg(
268                    Arg::with_name("new_identity_account")
269                        .index(2)
270                        .value_name("IDENTITY_KEYPAIR")
271                        .takes_value(true)
272                        .required(true)
273                        .validator(is_valid_signer)
274                        .help("Keypair of new validator that will vote with this account"),
275                )
276                .arg(
277                    Arg::with_name("authorized_withdrawer")
278                        .index(3)
279                        .value_name("AUTHORIZED_KEYPAIR")
280                        .takes_value(true)
281                        .required(true)
282                        .validator(is_valid_signer)
283                        .help("Authorized withdrawer keypair"),
284                )
285                .offline_args()
286                .nonce_args(false)
287                .arg(fee_payer_arg())
288                .arg(memo_arg())
289                .arg(compute_unit_price_arg()),
290        )
291        .subcommand(
292            SubCommand::with_name("vote-update-commission")
293                .about("Update the vote account's commission")
294                .arg(pubkey!(
295                    Arg::with_name("vote_account_pubkey")
296                        .index(1)
297                        .value_name("VOTE_ACCOUNT_ADDRESS")
298                        .required(true),
299                    "Vote account to update."
300                ))
301                .arg(
302                    Arg::with_name("commission")
303                        .index(2)
304                        .value_name("PERCENTAGE")
305                        .takes_value(true)
306                        .required(true)
307                        .validator(is_valid_percentage)
308                        .help("The new commission"),
309                )
310                .arg(
311                    Arg::with_name("authorized_withdrawer")
312                        .index(3)
313                        .value_name("AUTHORIZED_KEYPAIR")
314                        .takes_value(true)
315                        .required(true)
316                        .validator(is_valid_signer)
317                        .help("Authorized withdrawer keypair"),
318                )
319                .offline_args()
320                .nonce_args(false)
321                .arg(fee_payer_arg())
322                .arg(memo_arg())
323                .arg(compute_unit_price_arg()),
324        )
325        .subcommand(
326            SubCommand::with_name("vote-account")
327                .about("Show the contents of a vote account")
328                .alias("show-vote-account")
329                .arg(pubkey!(
330                    Arg::with_name("vote_account_pubkey")
331                        .index(1)
332                        .value_name("VOTE_ACCOUNT_ADDRESS")
333                        .required(true),
334                    "Vote account."
335                ))
336                .arg(
337                    Arg::with_name("lamports")
338                        .long("lamports")
339                        .takes_value(false)
340                        .help("Display balance in lamports instead of SOL"),
341                )
342                .arg(
343                    Arg::with_name("with_rewards")
344                        .long("with-rewards")
345                        .takes_value(false)
346                        .help("Display inflation rewards"),
347                )
348                .arg(
349                    Arg::with_name("csv")
350                        .long("csv")
351                        .takes_value(false)
352                        .help("Format rewards in a CSV table"),
353                )
354                .arg(
355                    Arg::with_name("starting_epoch")
356                        .long("starting-epoch")
357                        .takes_value(true)
358                        .value_name("NUM")
359                        .requires("with_rewards")
360                        .help("Start displaying from epoch NUM"),
361                )
362                .arg(
363                    Arg::with_name("num_rewards_epochs")
364                        .long("num-rewards-epochs")
365                        .takes_value(true)
366                        .value_name("NUM")
367                        .validator(|s| is_within_range(s, 1..=50))
368                        .default_value_if("with_rewards", None, "1")
369                        .requires("with_rewards")
370                        .help(
371                            "Display rewards for NUM recent epochs, max 10 [default: latest epoch \
372                             only]",
373                        ),
374                ),
375        )
376        .subcommand(
377            SubCommand::with_name("withdraw-from-vote-account")
378                .about("Withdraw lamports from a vote account into a specified account")
379                .arg(pubkey!(
380                    Arg::with_name("vote_account_pubkey")
381                        .index(1)
382                        .value_name("VOTE_ACCOUNT_ADDRESS")
383                        .required(true),
384                    "Vote account from which to withdraw."
385                ))
386                .arg(pubkey!(
387                    Arg::with_name("destination_account_pubkey")
388                        .index(2)
389                        .value_name("RECIPIENT_ADDRESS")
390                        .required(true),
391                    "The recipient of withdrawn SOL."
392                ))
393                .arg(
394                    Arg::with_name("amount")
395                        .index(3)
396                        .value_name("AMOUNT")
397                        .takes_value(true)
398                        .required(true)
399                        .validator(is_amount_or_all)
400                        .help(
401                            "The amount to withdraw, in SOL; accepts keyword ALL, which for this \
402                             command means account balance minus rent-exempt minimum",
403                        ),
404                )
405                .arg(
406                    Arg::with_name("authorized_withdrawer")
407                        .long("authorized-withdrawer")
408                        .value_name("AUTHORIZED_KEYPAIR")
409                        .takes_value(true)
410                        .validator(is_valid_signer)
411                        .help("Authorized withdrawer [default: cli config keypair]"),
412                )
413                .offline_args()
414                .nonce_args(false)
415                .arg(fee_payer_arg())
416                .arg(memo_arg())
417                .arg(compute_unit_price_arg()),
418        )
419        .subcommand(
420            SubCommand::with_name("close-vote-account")
421                .about("Close a vote account and withdraw all funds remaining")
422                .arg(pubkey!(
423                    Arg::with_name("vote_account_pubkey")
424                        .index(1)
425                        .value_name("VOTE_ACCOUNT_ADDRESS")
426                        .required(true),
427                    "Vote account to be closed."
428                ))
429                .arg(pubkey!(
430                    Arg::with_name("destination_account_pubkey")
431                        .index(2)
432                        .value_name("RECIPIENT_ADDRESS")
433                        .required(true),
434                    "The recipient of all withdrawn SOL."
435                ))
436                .arg(
437                    Arg::with_name("authorized_withdrawer")
438                        .long("authorized-withdrawer")
439                        .value_name("AUTHORIZED_KEYPAIR")
440                        .takes_value(true)
441                        .validator(is_valid_signer)
442                        .help("Authorized withdrawer [default: cli config keypair]"),
443                )
444                .arg(fee_payer_arg())
445                .arg(memo_arg())
446                .arg(compute_unit_price_arg()),
447        )
448    }
449}
450
451pub fn parse_create_vote_account(
452    matches: &ArgMatches<'_>,
453    default_signer: &DefaultSigner,
454    wallet_manager: &mut Option<Rc<RemoteWalletManager>>,
455) -> Result<CliCommandInfo, CliError> {
456    let (vote_account, vote_account_pubkey) = signer_of(matches, "vote_account", wallet_manager)?;
457    let seed = matches.value_of("seed").map(|s| s.to_string());
458    let (identity_account, identity_pubkey) =
459        signer_of(matches, "identity_account", wallet_manager)?;
460    let commission = value_t_or_exit!(matches, "commission", u8);
461    let authorized_voter = pubkey_of_signer(matches, "authorized_voter", wallet_manager)?;
462    let authorized_withdrawer =
463        pubkey_of_signer(matches, "authorized_withdrawer", wallet_manager)?.unwrap();
464    let allow_unsafe = matches.is_present("allow_unsafe_authorized_withdrawer");
465    let sign_only = matches.is_present(SIGN_ONLY_ARG.name);
466    let dump_transaction_message = matches.is_present(DUMP_TRANSACTION_MESSAGE.name);
467    let blockhash_query = BlockhashQuery::new_from_matches(matches);
468    let nonce_account = pubkey_of_signer(matches, NONCE_ARG.name, wallet_manager)?;
469    let memo = matches.value_of(MEMO_ARG.name).map(String::from);
470    let (nonce_authority, nonce_authority_pubkey) =
471        signer_of(matches, NONCE_AUTHORITY_ARG.name, wallet_manager)?;
472    let (fee_payer, fee_payer_pubkey) = signer_of(matches, FEE_PAYER_ARG.name, wallet_manager)?;
473    let compute_unit_price = value_of(matches, COMPUTE_UNIT_PRICE_ARG.name);
474
475    if !allow_unsafe {
476        if authorized_withdrawer == vote_account_pubkey.unwrap() {
477            return Err(CliError::BadParameter(
478                "Authorized withdrawer pubkey is identical to vote account pubkey, an unsafe \
479                 configuration"
480                    .to_owned(),
481            ));
482        }
483        if authorized_withdrawer == identity_pubkey.unwrap() {
484            return Err(CliError::BadParameter(
485                "Authorized withdrawer pubkey is identical to identity account pubkey, an unsafe \
486                 configuration"
487                    .to_owned(),
488            ));
489        }
490    }
491
492    let mut bulk_signers = vec![fee_payer, vote_account, identity_account];
493    if nonce_account.is_some() {
494        bulk_signers.push(nonce_authority);
495    }
496    let signer_info =
497        default_signer.generate_unique_signers(bulk_signers, matches, wallet_manager)?;
498
499    Ok(CliCommandInfo {
500        command: CliCommand::CreateVoteAccount {
501            vote_account: signer_info.index_of(vote_account_pubkey).unwrap(),
502            seed,
503            identity_account: signer_info.index_of(identity_pubkey).unwrap(),
504            authorized_voter,
505            authorized_withdrawer,
506            commission,
507            sign_only,
508            dump_transaction_message,
509            blockhash_query,
510            nonce_account,
511            nonce_authority: signer_info.index_of(nonce_authority_pubkey).unwrap(),
512            memo,
513            fee_payer: signer_info.index_of(fee_payer_pubkey).unwrap(),
514            compute_unit_price,
515        },
516        signers: signer_info.signers,
517    })
518}
519
520pub fn parse_vote_authorize(
521    matches: &ArgMatches<'_>,
522    default_signer: &DefaultSigner,
523    wallet_manager: &mut Option<Rc<RemoteWalletManager>>,
524    vote_authorize: VoteAuthorize,
525    checked: bool,
526) -> Result<CliCommandInfo, CliError> {
527    let vote_account_pubkey =
528        pubkey_of_signer(matches, "vote_account_pubkey", wallet_manager)?.unwrap();
529    let (authorized, authorized_pubkey) = signer_of(matches, "authorized", wallet_manager)?;
530
531    let sign_only = matches.is_present(SIGN_ONLY_ARG.name);
532    let dump_transaction_message = matches.is_present(DUMP_TRANSACTION_MESSAGE.name);
533    let blockhash_query = BlockhashQuery::new_from_matches(matches);
534    let nonce_account = pubkey_of(matches, NONCE_ARG.name);
535    let memo = matches.value_of(MEMO_ARG.name).map(String::from);
536    let (nonce_authority, nonce_authority_pubkey) =
537        signer_of(matches, NONCE_AUTHORITY_ARG.name, wallet_manager)?;
538    let (fee_payer, fee_payer_pubkey) = signer_of(matches, FEE_PAYER_ARG.name, wallet_manager)?;
539    let compute_unit_price = value_of(matches, COMPUTE_UNIT_PRICE_ARG.name);
540
541    let mut bulk_signers = vec![fee_payer, authorized];
542
543    let new_authorized_pubkey = if checked {
544        let (new_authorized_signer, new_authorized_pubkey) =
545            signer_of(matches, "new_authorized", wallet_manager)?;
546        bulk_signers.push(new_authorized_signer);
547        new_authorized_pubkey.unwrap()
548    } else {
549        pubkey_of_signer(matches, "new_authorized_pubkey", wallet_manager)?.unwrap()
550    };
551    if nonce_account.is_some() {
552        bulk_signers.push(nonce_authority);
553    }
554    let signer_info =
555        default_signer.generate_unique_signers(bulk_signers, matches, wallet_manager)?;
556
557    Ok(CliCommandInfo {
558        command: CliCommand::VoteAuthorize {
559            vote_account_pubkey,
560            new_authorized_pubkey,
561            vote_authorize,
562            sign_only,
563            dump_transaction_message,
564            blockhash_query,
565            nonce_account,
566            nonce_authority: signer_info.index_of(nonce_authority_pubkey).unwrap(),
567            memo,
568            fee_payer: signer_info.index_of(fee_payer_pubkey).unwrap(),
569            authorized: signer_info.index_of(authorized_pubkey).unwrap(),
570            new_authorized: if checked {
571                signer_info.index_of(Some(new_authorized_pubkey))
572            } else {
573                None
574            },
575            compute_unit_price,
576        },
577        signers: signer_info.signers,
578    })
579}
580
581pub fn parse_vote_update_validator(
582    matches: &ArgMatches<'_>,
583    default_signer: &DefaultSigner,
584    wallet_manager: &mut Option<Rc<RemoteWalletManager>>,
585) -> Result<CliCommandInfo, CliError> {
586    let vote_account_pubkey =
587        pubkey_of_signer(matches, "vote_account_pubkey", wallet_manager)?.unwrap();
588    let (new_identity_account, new_identity_pubkey) =
589        signer_of(matches, "new_identity_account", wallet_manager)?;
590    let (authorized_withdrawer, authorized_withdrawer_pubkey) =
591        signer_of(matches, "authorized_withdrawer", wallet_manager)?;
592
593    let sign_only = matches.is_present(SIGN_ONLY_ARG.name);
594    let dump_transaction_message = matches.is_present(DUMP_TRANSACTION_MESSAGE.name);
595    let blockhash_query = BlockhashQuery::new_from_matches(matches);
596    let nonce_account = pubkey_of(matches, NONCE_ARG.name);
597    let memo = matches.value_of(MEMO_ARG.name).map(String::from);
598    let (nonce_authority, nonce_authority_pubkey) =
599        signer_of(matches, NONCE_AUTHORITY_ARG.name, wallet_manager)?;
600    let (fee_payer, fee_payer_pubkey) = signer_of(matches, FEE_PAYER_ARG.name, wallet_manager)?;
601    let compute_unit_price = value_of(matches, COMPUTE_UNIT_PRICE_ARG.name);
602
603    let mut bulk_signers = vec![fee_payer, authorized_withdrawer, new_identity_account];
604    if nonce_account.is_some() {
605        bulk_signers.push(nonce_authority);
606    }
607    let signer_info =
608        default_signer.generate_unique_signers(bulk_signers, matches, wallet_manager)?;
609
610    Ok(CliCommandInfo {
611        command: CliCommand::VoteUpdateValidator {
612            vote_account_pubkey,
613            new_identity_account: signer_info.index_of(new_identity_pubkey).unwrap(),
614            withdraw_authority: signer_info.index_of(authorized_withdrawer_pubkey).unwrap(),
615            sign_only,
616            dump_transaction_message,
617            blockhash_query,
618            nonce_account,
619            nonce_authority: signer_info.index_of(nonce_authority_pubkey).unwrap(),
620            memo,
621            fee_payer: signer_info.index_of(fee_payer_pubkey).unwrap(),
622            compute_unit_price,
623        },
624        signers: signer_info.signers,
625    })
626}
627
628pub fn parse_vote_update_commission(
629    matches: &ArgMatches<'_>,
630    default_signer: &DefaultSigner,
631    wallet_manager: &mut Option<Rc<RemoteWalletManager>>,
632) -> Result<CliCommandInfo, CliError> {
633    let vote_account_pubkey =
634        pubkey_of_signer(matches, "vote_account_pubkey", wallet_manager)?.unwrap();
635    let (authorized_withdrawer, authorized_withdrawer_pubkey) =
636        signer_of(matches, "authorized_withdrawer", wallet_manager)?;
637    let commission = value_t_or_exit!(matches, "commission", u8);
638
639    let sign_only = matches.is_present(SIGN_ONLY_ARG.name);
640    let dump_transaction_message = matches.is_present(DUMP_TRANSACTION_MESSAGE.name);
641    let blockhash_query = BlockhashQuery::new_from_matches(matches);
642    let nonce_account = pubkey_of(matches, NONCE_ARG.name);
643    let memo = matches.value_of(MEMO_ARG.name).map(String::from);
644    let (nonce_authority, nonce_authority_pubkey) =
645        signer_of(matches, NONCE_AUTHORITY_ARG.name, wallet_manager)?;
646    let (fee_payer, fee_payer_pubkey) = signer_of(matches, FEE_PAYER_ARG.name, wallet_manager)?;
647    let compute_unit_price = value_of(matches, COMPUTE_UNIT_PRICE_ARG.name);
648
649    let mut bulk_signers = vec![fee_payer, authorized_withdrawer];
650    if nonce_account.is_some() {
651        bulk_signers.push(nonce_authority);
652    }
653    let signer_info =
654        default_signer.generate_unique_signers(bulk_signers, matches, wallet_manager)?;
655
656    Ok(CliCommandInfo {
657        command: CliCommand::VoteUpdateCommission {
658            vote_account_pubkey,
659            commission,
660            withdraw_authority: signer_info.index_of(authorized_withdrawer_pubkey).unwrap(),
661            sign_only,
662            dump_transaction_message,
663            blockhash_query,
664            nonce_account,
665            nonce_authority: signer_info.index_of(nonce_authority_pubkey).unwrap(),
666            memo,
667            fee_payer: signer_info.index_of(fee_payer_pubkey).unwrap(),
668            compute_unit_price,
669        },
670        signers: signer_info.signers,
671    })
672}
673
674pub fn parse_vote_get_account_command(
675    matches: &ArgMatches<'_>,
676    wallet_manager: &mut Option<Rc<RemoteWalletManager>>,
677) -> Result<CliCommandInfo, CliError> {
678    let vote_account_pubkey =
679        pubkey_of_signer(matches, "vote_account_pubkey", wallet_manager)?.unwrap();
680    let use_lamports_unit = matches.is_present("lamports");
681    let use_csv = matches.is_present("csv");
682    let with_rewards = if matches.is_present("with_rewards") {
683        Some(value_of(matches, "num_rewards_epochs").unwrap())
684    } else {
685        None
686    };
687    let starting_epoch = value_of(matches, "starting_epoch");
688    Ok(CliCommandInfo::without_signers(
689        CliCommand::ShowVoteAccount {
690            pubkey: vote_account_pubkey,
691            use_lamports_unit,
692            use_csv,
693            with_rewards,
694            starting_epoch,
695        },
696    ))
697}
698
699pub fn parse_withdraw_from_vote_account(
700    matches: &ArgMatches<'_>,
701    default_signer: &DefaultSigner,
702    wallet_manager: &mut Option<Rc<RemoteWalletManager>>,
703) -> Result<CliCommandInfo, CliError> {
704    let vote_account_pubkey =
705        pubkey_of_signer(matches, "vote_account_pubkey", wallet_manager)?.unwrap();
706    let destination_account_pubkey =
707        pubkey_of_signer(matches, "destination_account_pubkey", wallet_manager)?.unwrap();
708    let mut withdraw_amount = SpendAmount::new_from_matches(matches, "amount");
709    // As a safeguard for vote accounts for running validators, `ALL` withdraws only the amount in
710    // excess of the rent-exempt minimum. In order to close the account with this subcommand, a
711    // validator must specify the withdrawal amount precisely.
712    if withdraw_amount == SpendAmount::All {
713        withdraw_amount = SpendAmount::RentExempt;
714    }
715
716    let (withdraw_authority, withdraw_authority_pubkey) =
717        signer_of(matches, "authorized_withdrawer", wallet_manager)?;
718
719    let sign_only = matches.is_present(SIGN_ONLY_ARG.name);
720    let dump_transaction_message = matches.is_present(DUMP_TRANSACTION_MESSAGE.name);
721    let blockhash_query = BlockhashQuery::new_from_matches(matches);
722    let nonce_account = pubkey_of(matches, NONCE_ARG.name);
723    let memo = matches.value_of(MEMO_ARG.name).map(String::from);
724    let (nonce_authority, nonce_authority_pubkey) =
725        signer_of(matches, NONCE_AUTHORITY_ARG.name, wallet_manager)?;
726    let (fee_payer, fee_payer_pubkey) = signer_of(matches, FEE_PAYER_ARG.name, wallet_manager)?;
727    let compute_unit_price = value_of(matches, COMPUTE_UNIT_PRICE_ARG.name);
728
729    let mut bulk_signers = vec![fee_payer, withdraw_authority];
730    if nonce_account.is_some() {
731        bulk_signers.push(nonce_authority);
732    }
733    let signer_info =
734        default_signer.generate_unique_signers(bulk_signers, matches, wallet_manager)?;
735
736    Ok(CliCommandInfo {
737        command: CliCommand::WithdrawFromVoteAccount {
738            vote_account_pubkey,
739            destination_account_pubkey,
740            withdraw_authority: signer_info.index_of(withdraw_authority_pubkey).unwrap(),
741            withdraw_amount,
742            sign_only,
743            dump_transaction_message,
744            blockhash_query,
745            nonce_account,
746            nonce_authority: signer_info.index_of(nonce_authority_pubkey).unwrap(),
747            memo,
748            fee_payer: signer_info.index_of(fee_payer_pubkey).unwrap(),
749            compute_unit_price,
750        },
751        signers: signer_info.signers,
752    })
753}
754
755pub fn parse_close_vote_account(
756    matches: &ArgMatches<'_>,
757    default_signer: &DefaultSigner,
758    wallet_manager: &mut Option<Rc<RemoteWalletManager>>,
759) -> Result<CliCommandInfo, CliError> {
760    let vote_account_pubkey =
761        pubkey_of_signer(matches, "vote_account_pubkey", wallet_manager)?.unwrap();
762    let destination_account_pubkey =
763        pubkey_of_signer(matches, "destination_account_pubkey", wallet_manager)?.unwrap();
764
765    let (withdraw_authority, withdraw_authority_pubkey) =
766        signer_of(matches, "authorized_withdrawer", wallet_manager)?;
767    let (fee_payer, fee_payer_pubkey) = signer_of(matches, FEE_PAYER_ARG.name, wallet_manager)?;
768
769    let signer_info = default_signer.generate_unique_signers(
770        vec![fee_payer, withdraw_authority],
771        matches,
772        wallet_manager,
773    )?;
774    let memo = matches.value_of(MEMO_ARG.name).map(String::from);
775    let compute_unit_price = value_of(matches, COMPUTE_UNIT_PRICE_ARG.name);
776
777    Ok(CliCommandInfo {
778        command: CliCommand::CloseVoteAccount {
779            vote_account_pubkey,
780            destination_account_pubkey,
781            withdraw_authority: signer_info.index_of(withdraw_authority_pubkey).unwrap(),
782            memo,
783            fee_payer: signer_info.index_of(fee_payer_pubkey).unwrap(),
784            compute_unit_price,
785        },
786        signers: signer_info.signers,
787    })
788}
789
790#[allow(clippy::too_many_arguments)]
791pub fn process_create_vote_account(
792    rpc_client: &RpcClient,
793    config: &CliConfig,
794    vote_account: SignerIndex,
795    seed: &Option<String>,
796    identity_account: SignerIndex,
797    authorized_voter: &Option<Pubkey>,
798    authorized_withdrawer: Pubkey,
799    commission: u8,
800    sign_only: bool,
801    dump_transaction_message: bool,
802    blockhash_query: &BlockhashQuery,
803    nonce_account: Option<&Pubkey>,
804    nonce_authority: SignerIndex,
805    memo: Option<&String>,
806    fee_payer: SignerIndex,
807    compute_unit_price: Option<u64>,
808) -> ProcessResult {
809    let vote_account = config.signers[vote_account];
810    let vote_account_pubkey = vote_account.pubkey();
811    let vote_account_address = if let Some(seed) = seed {
812        Pubkey::create_with_seed(&vote_account_pubkey, seed, &solana_vote_program::id())?
813    } else {
814        vote_account_pubkey
815    };
816    check_unique_pubkeys(
817        (&config.signers[0].pubkey(), "cli keypair".to_string()),
818        (&vote_account_address, "vote_account".to_string()),
819    )?;
820
821    let identity_account = config.signers[identity_account];
822    let identity_pubkey = identity_account.pubkey();
823    check_unique_pubkeys(
824        (&vote_account_address, "vote_account".to_string()),
825        (&identity_pubkey, "identity_pubkey".to_string()),
826    )?;
827
828    let required_balance = rpc_client
829        .get_minimum_balance_for_rent_exemption(VoteStateV4::size_of())?
830        .max(1);
831    let amount = SpendAmount::Some(required_balance);
832
833    let fee_payer = config.signers[fee_payer];
834    let nonce_authority = config.signers[nonce_authority];
835    let space = VoteStateV4::size_of() as u64;
836
837    let compute_unit_limit = match blockhash_query {
838        BlockhashQuery::None(_) | BlockhashQuery::FeeCalculator(_, _) => ComputeUnitLimit::Default,
839        BlockhashQuery::All(_) => ComputeUnitLimit::Simulated,
840    };
841    let build_message = |lamports| {
842        let vote_init = VoteInit {
843            node_pubkey: identity_pubkey,
844            authorized_voter: authorized_voter.unwrap_or(identity_pubkey),
845            authorized_withdrawer,
846            commission,
847        };
848        let mut create_vote_account_config = CreateVoteAccountConfig {
849            space,
850            ..CreateVoteAccountConfig::default()
851        };
852        let to = if let Some(seed) = seed {
853            create_vote_account_config.with_seed = Some((&vote_account_pubkey, seed));
854            &vote_account_address
855        } else {
856            &vote_account_pubkey
857        };
858
859        let ixs = vote_instruction::create_account_with_config(
860            &config.signers[0].pubkey(),
861            to,
862            &vote_init,
863            lamports,
864            create_vote_account_config,
865        )
866        .with_memo(memo)
867        .with_compute_unit_config(&ComputeUnitConfig {
868            compute_unit_price,
869            compute_unit_limit,
870        });
871
872        if let Some(nonce_account) = &nonce_account {
873            Message::new_with_nonce(
874                ixs,
875                Some(&fee_payer.pubkey()),
876                nonce_account,
877                &nonce_authority.pubkey(),
878            )
879        } else {
880            Message::new(&ixs, Some(&fee_payer.pubkey()))
881        }
882    };
883
884    let recent_blockhash = blockhash_query.get_blockhash(rpc_client, config.commitment)?;
885
886    let (message, _) = resolve_spend_tx_and_check_account_balances(
887        rpc_client,
888        sign_only,
889        amount,
890        &recent_blockhash,
891        &config.signers[0].pubkey(),
892        &fee_payer.pubkey(),
893        compute_unit_limit,
894        build_message,
895        config.commitment,
896    )?;
897
898    if !sign_only {
899        if let Ok(response) =
900            rpc_client.get_account_with_commitment(&vote_account_address, config.commitment)
901        {
902            if let Some(vote_account) = response.value {
903                let err_msg = if vote_account.owner == solana_vote_program::id() {
904                    format!("Vote account {vote_account_address} already exists")
905                } else {
906                    format!(
907                        "Account {vote_account_address} already exists and is not a vote account"
908                    )
909                };
910                return Err(CliError::BadParameter(err_msg).into());
911            }
912        }
913
914        if let Some(nonce_account) = &nonce_account {
915            let nonce_account = solana_rpc_client_nonce_utils::get_account_with_commitment(
916                rpc_client,
917                nonce_account,
918                config.commitment,
919            )?;
920            check_nonce_account(&nonce_account, &nonce_authority.pubkey(), &recent_blockhash)?;
921        }
922    }
923
924    let mut tx = Transaction::new_unsigned(message);
925    if sign_only {
926        tx.try_partial_sign(&config.signers, recent_blockhash)?;
927        return_signers_with_config(
928            &tx,
929            &config.output_format,
930            &ReturnSignersConfig {
931                dump_transaction_message,
932            },
933        )
934    } else {
935        tx.try_sign(&config.signers, recent_blockhash)?;
936        let result = rpc_client.send_and_confirm_transaction_with_spinner_and_config(
937            &tx,
938            config.commitment,
939            config.send_transaction_config,
940        );
941        log_instruction_custom_error::<SystemError>(result, config)
942    }
943}
944
945#[allow(clippy::too_many_arguments)]
946pub fn process_vote_authorize(
947    rpc_client: &RpcClient,
948    config: &CliConfig,
949    vote_account_pubkey: &Pubkey,
950    new_authorized_pubkey: &Pubkey,
951    vote_authorize: VoteAuthorize,
952    authorized: SignerIndex,
953    new_authorized: Option<SignerIndex>,
954    sign_only: bool,
955    dump_transaction_message: bool,
956    blockhash_query: &BlockhashQuery,
957    nonce_account: Option<Pubkey>,
958    nonce_authority: SignerIndex,
959    memo: Option<&String>,
960    fee_payer: SignerIndex,
961    compute_unit_price: Option<u64>,
962) -> ProcessResult {
963    let authorized = config.signers[authorized];
964    let new_authorized_signer = new_authorized.map(|index| config.signers[index]);
965
966    let vote_state = if !sign_only {
967        Some(get_vote_account(rpc_client, vote_account_pubkey, config.commitment)?.1)
968    } else {
969        None
970    };
971    match vote_authorize {
972        VoteAuthorize::Voter => {
973            if let Some(vote_state) = vote_state {
974                let current_epoch = rpc_client.get_epoch_info()?.epoch;
975                let current_authorized_voter = vote_state
976                    .authorized_voters
977                    .get_authorized_voter(current_epoch)
978                    .ok_or_else(|| {
979                        CliError::RpcRequestError(
980                            "Invalid vote account state; no authorized voters found".to_string(),
981                        )
982                    })?;
983                check_current_authority(
984                    &[current_authorized_voter, vote_state.authorized_withdrawer],
985                    &authorized.pubkey(),
986                )?;
987                if let Some(signer) = new_authorized_signer {
988                    if signer.is_interactive() {
989                        return Err(CliError::BadParameter(format!(
990                            "invalid new authorized vote signer {new_authorized_pubkey:?}. \
991                             Interactive vote signers not supported"
992                        ))
993                        .into());
994                    }
995                }
996            }
997        }
998        VoteAuthorize::Withdrawer => {
999            check_unique_pubkeys(
1000                (&authorized.pubkey(), "authorized_account".to_string()),
1001                (new_authorized_pubkey, "new_authorized_pubkey".to_string()),
1002            )?;
1003            if let Some(vote_state) = vote_state {
1004                check_current_authority(&[vote_state.authorized_withdrawer], &authorized.pubkey())?
1005            }
1006        }
1007    }
1008
1009    let vote_ix = if new_authorized_signer.is_some() {
1010        vote_instruction::authorize_checked(
1011            vote_account_pubkey,   // vote account to update
1012            &authorized.pubkey(),  // current authorized
1013            new_authorized_pubkey, // new vote signer/withdrawer
1014            vote_authorize,        // vote or withdraw
1015        )
1016    } else {
1017        vote_instruction::authorize(
1018            vote_account_pubkey,   // vote account to update
1019            &authorized.pubkey(),  // current authorized
1020            new_authorized_pubkey, // new vote signer/withdrawer
1021            vote_authorize,        // vote or withdraw
1022        )
1023    };
1024
1025    let compute_unit_limit = match blockhash_query {
1026        BlockhashQuery::None(_) | BlockhashQuery::FeeCalculator(_, _) => ComputeUnitLimit::Default,
1027        BlockhashQuery::All(_) => ComputeUnitLimit::Simulated,
1028    };
1029    let ixs = vec![vote_ix]
1030        .with_memo(memo)
1031        .with_compute_unit_config(&ComputeUnitConfig {
1032            compute_unit_price,
1033            compute_unit_limit,
1034        });
1035
1036    let recent_blockhash = blockhash_query.get_blockhash(rpc_client, config.commitment)?;
1037
1038    let nonce_authority = config.signers[nonce_authority];
1039    let fee_payer = config.signers[fee_payer];
1040
1041    let mut message = if let Some(nonce_account) = &nonce_account {
1042        Message::new_with_nonce(
1043            ixs,
1044            Some(&fee_payer.pubkey()),
1045            nonce_account,
1046            &nonce_authority.pubkey(),
1047        )
1048    } else {
1049        Message::new(&ixs, Some(&fee_payer.pubkey()))
1050    };
1051    simulate_and_update_compute_unit_limit(&compute_unit_limit, rpc_client, &mut message)?;
1052    let mut tx = Transaction::new_unsigned(message);
1053
1054    if sign_only {
1055        tx.try_partial_sign(&config.signers, recent_blockhash)?;
1056        return_signers_with_config(
1057            &tx,
1058            &config.output_format,
1059            &ReturnSignersConfig {
1060                dump_transaction_message,
1061            },
1062        )
1063    } else {
1064        tx.try_sign(&config.signers, recent_blockhash)?;
1065        if let Some(nonce_account) = &nonce_account {
1066            let nonce_account = solana_rpc_client_nonce_utils::get_account_with_commitment(
1067                rpc_client,
1068                nonce_account,
1069                config.commitment,
1070            )?;
1071            check_nonce_account(&nonce_account, &nonce_authority.pubkey(), &recent_blockhash)?;
1072        }
1073        check_account_for_fee_with_commitment(
1074            rpc_client,
1075            &config.signers[0].pubkey(),
1076            &tx.message,
1077            config.commitment,
1078        )?;
1079        let result = rpc_client.send_and_confirm_transaction_with_spinner_and_config(
1080            &tx,
1081            config.commitment,
1082            config.send_transaction_config,
1083        );
1084        log_instruction_custom_error::<VoteError>(result, config)
1085    }
1086}
1087
1088#[allow(clippy::too_many_arguments)]
1089pub fn process_vote_update_validator(
1090    rpc_client: &RpcClient,
1091    config: &CliConfig,
1092    vote_account_pubkey: &Pubkey,
1093    new_identity_account: SignerIndex,
1094    withdraw_authority: SignerIndex,
1095    sign_only: bool,
1096    dump_transaction_message: bool,
1097    blockhash_query: &BlockhashQuery,
1098    nonce_account: Option<Pubkey>,
1099    nonce_authority: SignerIndex,
1100    memo: Option<&String>,
1101    fee_payer: SignerIndex,
1102    compute_unit_price: Option<u64>,
1103) -> ProcessResult {
1104    let authorized_withdrawer = config.signers[withdraw_authority];
1105    let new_identity_account = config.signers[new_identity_account];
1106    let new_identity_pubkey = new_identity_account.pubkey();
1107    check_unique_pubkeys(
1108        (vote_account_pubkey, "vote_account_pubkey".to_string()),
1109        (&new_identity_pubkey, "new_identity_account".to_string()),
1110    )?;
1111    let recent_blockhash = blockhash_query.get_blockhash(rpc_client, config.commitment)?;
1112    let compute_unit_limit = match blockhash_query {
1113        BlockhashQuery::None(_) | BlockhashQuery::FeeCalculator(_, _) => ComputeUnitLimit::Default,
1114        BlockhashQuery::All(_) => ComputeUnitLimit::Simulated,
1115    };
1116    let ixs = vec![vote_instruction::update_validator_identity(
1117        vote_account_pubkey,
1118        &authorized_withdrawer.pubkey(),
1119        &new_identity_pubkey,
1120    )]
1121    .with_memo(memo)
1122    .with_compute_unit_config(&ComputeUnitConfig {
1123        compute_unit_price,
1124        compute_unit_limit,
1125    });
1126    let nonce_authority = config.signers[nonce_authority];
1127    let fee_payer = config.signers[fee_payer];
1128
1129    let mut message = if let Some(nonce_account) = &nonce_account {
1130        Message::new_with_nonce(
1131            ixs,
1132            Some(&fee_payer.pubkey()),
1133            nonce_account,
1134            &nonce_authority.pubkey(),
1135        )
1136    } else {
1137        Message::new(&ixs, Some(&fee_payer.pubkey()))
1138    };
1139    simulate_and_update_compute_unit_limit(&compute_unit_limit, rpc_client, &mut message)?;
1140    let mut tx = Transaction::new_unsigned(message);
1141
1142    if sign_only {
1143        tx.try_partial_sign(&config.signers, recent_blockhash)?;
1144        return_signers_with_config(
1145            &tx,
1146            &config.output_format,
1147            &ReturnSignersConfig {
1148                dump_transaction_message,
1149            },
1150        )
1151    } else {
1152        tx.try_sign(&config.signers, recent_blockhash)?;
1153        if let Some(nonce_account) = &nonce_account {
1154            let nonce_account = solana_rpc_client_nonce_utils::get_account_with_commitment(
1155                rpc_client,
1156                nonce_account,
1157                config.commitment,
1158            )?;
1159            check_nonce_account(&nonce_account, &nonce_authority.pubkey(), &recent_blockhash)?;
1160        }
1161        check_account_for_fee_with_commitment(
1162            rpc_client,
1163            &config.signers[0].pubkey(),
1164            &tx.message,
1165            config.commitment,
1166        )?;
1167        let result = rpc_client.send_and_confirm_transaction_with_spinner_and_config(
1168            &tx,
1169            config.commitment,
1170            config.send_transaction_config,
1171        );
1172        log_instruction_custom_error::<VoteError>(result, config)
1173    }
1174}
1175
1176#[allow(clippy::too_many_arguments)]
1177pub fn process_vote_update_commission(
1178    rpc_client: &RpcClient,
1179    config: &CliConfig,
1180    vote_account_pubkey: &Pubkey,
1181    commission: u8,
1182    withdraw_authority: SignerIndex,
1183    sign_only: bool,
1184    dump_transaction_message: bool,
1185    blockhash_query: &BlockhashQuery,
1186    nonce_account: Option<Pubkey>,
1187    nonce_authority: SignerIndex,
1188    memo: Option<&String>,
1189    fee_payer: SignerIndex,
1190    compute_unit_price: Option<u64>,
1191) -> ProcessResult {
1192    let authorized_withdrawer = config.signers[withdraw_authority];
1193    let recent_blockhash = blockhash_query.get_blockhash(rpc_client, config.commitment)?;
1194    let compute_unit_limit = match blockhash_query {
1195        BlockhashQuery::None(_) | BlockhashQuery::FeeCalculator(_, _) => ComputeUnitLimit::Default,
1196        BlockhashQuery::All(_) => ComputeUnitLimit::Simulated,
1197    };
1198    let ixs = vec![vote_instruction::update_commission(
1199        vote_account_pubkey,
1200        &authorized_withdrawer.pubkey(),
1201        commission,
1202    )]
1203    .with_memo(memo)
1204    .with_compute_unit_config(&ComputeUnitConfig {
1205        compute_unit_price,
1206        compute_unit_limit,
1207    });
1208    let nonce_authority = config.signers[nonce_authority];
1209    let fee_payer = config.signers[fee_payer];
1210
1211    let mut message = if let Some(nonce_account) = &nonce_account {
1212        Message::new_with_nonce(
1213            ixs,
1214            Some(&fee_payer.pubkey()),
1215            nonce_account,
1216            &nonce_authority.pubkey(),
1217        )
1218    } else {
1219        Message::new(&ixs, Some(&fee_payer.pubkey()))
1220    };
1221    simulate_and_update_compute_unit_limit(&compute_unit_limit, rpc_client, &mut message)?;
1222    let mut tx = Transaction::new_unsigned(message);
1223    if sign_only {
1224        tx.try_partial_sign(&config.signers, recent_blockhash)?;
1225        return_signers_with_config(
1226            &tx,
1227            &config.output_format,
1228            &ReturnSignersConfig {
1229                dump_transaction_message,
1230            },
1231        )
1232    } else {
1233        tx.try_sign(&config.signers, recent_blockhash)?;
1234        if let Some(nonce_account) = &nonce_account {
1235            let nonce_account = solana_rpc_client_nonce_utils::get_account_with_commitment(
1236                rpc_client,
1237                nonce_account,
1238                config.commitment,
1239            )?;
1240            check_nonce_account(&nonce_account, &nonce_authority.pubkey(), &recent_blockhash)?;
1241        }
1242        check_account_for_fee_with_commitment(
1243            rpc_client,
1244            &config.signers[0].pubkey(),
1245            &tx.message,
1246            config.commitment,
1247        )?;
1248        let result = rpc_client.send_and_confirm_transaction_with_spinner_and_config(
1249            &tx,
1250            config.commitment,
1251            config.send_transaction_config,
1252        );
1253        log_instruction_custom_error::<VoteError>(result, config)
1254    }
1255}
1256
1257pub(crate) fn get_vote_account(
1258    rpc_client: &RpcClient,
1259    vote_account_pubkey: &Pubkey,
1260    commitment_config: CommitmentConfig,
1261) -> Result<(Account, VoteStateV4), Box<dyn std::error::Error>> {
1262    let vote_account = rpc_client
1263        .get_account_with_commitment(vote_account_pubkey, commitment_config)?
1264        .value
1265        .ok_or_else(|| {
1266            CliError::RpcRequestError(format!("{vote_account_pubkey:?} account does not exist"))
1267        })?;
1268
1269    if vote_account.owner != solana_vote_program::id() {
1270        return Err(CliError::RpcRequestError(format!(
1271            "{vote_account_pubkey:?} is not a vote account"
1272        ))
1273        .into());
1274    }
1275    let vote_state =
1276        VoteStateV4::deserialize(&vote_account.data, vote_account_pubkey).map_err(|_| {
1277            CliError::RpcRequestError(
1278                "Account data could not be deserialized to vote state".to_string(),
1279            )
1280        })?;
1281
1282    Ok((vote_account, vote_state))
1283}
1284
1285pub fn process_show_vote_account(
1286    rpc_client: &RpcClient,
1287    config: &CliConfig,
1288    vote_account_address: &Pubkey,
1289    use_lamports_unit: bool,
1290    use_csv: bool,
1291    with_rewards: Option<usize>,
1292    starting_epoch: Option<u64>,
1293) -> ProcessResult {
1294    let (vote_account, vote_state) =
1295        get_vote_account(rpc_client, vote_account_address, config.commitment)?;
1296
1297    let epoch_schedule = rpc_client.get_epoch_schedule()?;
1298    let tvc_activation_slot =
1299        rpc_client.get_feature_activation_slot(&agave_feature_set::timely_vote_credits::id())?;
1300    let tvc_activation_epoch = tvc_activation_slot.map(|s| epoch_schedule.get_epoch(s));
1301
1302    let mut votes: Vec<CliLandedVote> = vec![];
1303    let mut epoch_voting_history: Vec<CliEpochVotingHistory> = vec![];
1304    if !vote_state.votes.is_empty() {
1305        for vote in &vote_state.votes {
1306            votes.push(vote.into());
1307        }
1308        for (epoch, credits, prev_credits) in vote_state.epoch_credits.iter().copied() {
1309            let credits_earned = credits.saturating_sub(prev_credits);
1310            let slots_in_epoch = epoch_schedule.get_slots_in_epoch(epoch);
1311            let is_tvc_active = tvc_activation_epoch.map(|e| epoch >= e).unwrap_or_default();
1312            let max_credits_per_slot = if is_tvc_active {
1313                VOTE_CREDITS_MAXIMUM_PER_SLOT
1314            } else {
1315                1
1316            };
1317            epoch_voting_history.push(CliEpochVotingHistory {
1318                epoch,
1319                slots_in_epoch,
1320                credits_earned,
1321                credits,
1322                prev_credits,
1323                max_credits_per_slot,
1324            });
1325        }
1326    }
1327
1328    let epoch_rewards =
1329        with_rewards.and_then(|num_epochs| {
1330            match crate::stake::fetch_epoch_rewards(
1331                rpc_client,
1332                vote_account_address,
1333                num_epochs,
1334                starting_epoch,
1335            ) {
1336                Ok(rewards) => Some(rewards),
1337                Err(error) => {
1338                    eprintln!("Failed to fetch epoch rewards: {error:?}");
1339                    None
1340                }
1341            }
1342        });
1343
1344    let vote_account_data = CliVoteAccount {
1345        account_balance: vote_account.lamports,
1346        validator_identity: vote_state.node_pubkey.to_string(),
1347        authorized_voters: (&vote_state.authorized_voters).into(),
1348        authorized_withdrawer: vote_state.authorized_withdrawer.to_string(),
1349        credits: vote_state.credits(),
1350        commission: (vote_state.inflation_rewards_commission_bps / 100) as u8,
1351        root_slot: vote_state.root_slot,
1352        recent_timestamp: vote_state.last_timestamp.clone(),
1353        votes,
1354        epoch_voting_history,
1355        use_lamports_unit,
1356        use_csv,
1357        epoch_rewards,
1358        inflation_rewards_commission_bps: vote_state.inflation_rewards_commission_bps,
1359        inflation_rewards_collector: vote_state.inflation_rewards_collector.to_string(),
1360        block_revenue_collector: vote_state.block_revenue_collector.to_string(),
1361        block_revenue_commission_bps: vote_state.block_revenue_commission_bps,
1362        pending_delegator_rewards: vote_state.pending_delegator_rewards,
1363        bls_pubkey_compressed: vote_state
1364            .bls_pubkey_compressed
1365            .map(|bytes| bs58::encode(bytes).into_string()),
1366    };
1367
1368    Ok(config.output_format.formatted_string(&vote_account_data))
1369}
1370
1371#[allow(clippy::too_many_arguments)]
1372pub fn process_withdraw_from_vote_account(
1373    rpc_client: &RpcClient,
1374    config: &CliConfig,
1375    vote_account_pubkey: &Pubkey,
1376    withdraw_authority: SignerIndex,
1377    withdraw_amount: SpendAmount,
1378    destination_account_pubkey: &Pubkey,
1379    sign_only: bool,
1380    dump_transaction_message: bool,
1381    blockhash_query: &BlockhashQuery,
1382    nonce_account: Option<&Pubkey>,
1383    nonce_authority: SignerIndex,
1384    memo: Option<&String>,
1385    fee_payer: SignerIndex,
1386    compute_unit_price: Option<u64>,
1387) -> ProcessResult {
1388    let withdraw_authority = config.signers[withdraw_authority];
1389    let recent_blockhash = blockhash_query.get_blockhash(rpc_client, config.commitment)?;
1390
1391    let fee_payer = config.signers[fee_payer];
1392    let nonce_authority = config.signers[nonce_authority];
1393
1394    let compute_unit_limit = match blockhash_query {
1395        BlockhashQuery::None(_) | BlockhashQuery::FeeCalculator(_, _) => ComputeUnitLimit::Default,
1396        BlockhashQuery::All(_) => ComputeUnitLimit::Simulated,
1397    };
1398    let build_message = |lamports| {
1399        let ixs = vec![withdraw(
1400            vote_account_pubkey,
1401            &withdraw_authority.pubkey(),
1402            lamports,
1403            destination_account_pubkey,
1404        )]
1405        .with_memo(memo)
1406        .with_compute_unit_config(&ComputeUnitConfig {
1407            compute_unit_price,
1408            compute_unit_limit,
1409        });
1410
1411        if let Some(nonce_account) = &nonce_account {
1412            Message::new_with_nonce(
1413                ixs,
1414                Some(&fee_payer.pubkey()),
1415                nonce_account,
1416                &nonce_authority.pubkey(),
1417            )
1418        } else {
1419            Message::new(&ixs, Some(&fee_payer.pubkey()))
1420        }
1421    };
1422
1423    let (message, _) = resolve_spend_tx_and_check_account_balances(
1424        rpc_client,
1425        sign_only,
1426        withdraw_amount,
1427        &recent_blockhash,
1428        vote_account_pubkey,
1429        &fee_payer.pubkey(),
1430        compute_unit_limit,
1431        build_message,
1432        config.commitment,
1433    )?;
1434
1435    if !sign_only {
1436        let current_balance = rpc_client.get_balance(vote_account_pubkey)?;
1437        let minimum_balance =
1438            rpc_client.get_minimum_balance_for_rent_exemption(VoteStateV4::size_of())?;
1439        if let SpendAmount::Some(withdraw_amount) = withdraw_amount {
1440            let balance_remaining = current_balance.saturating_sub(withdraw_amount);
1441            if balance_remaining < minimum_balance && balance_remaining != 0 {
1442                return Err(CliError::BadParameter(format!(
1443                    "Withdraw amount too large. The vote account balance must be at least {} SOL \
1444                     to remain rent exempt",
1445                    build_balance_message(minimum_balance, false, false)
1446                ))
1447                .into());
1448            }
1449        }
1450    }
1451
1452    let mut tx = Transaction::new_unsigned(message);
1453
1454    if sign_only {
1455        tx.try_partial_sign(&config.signers, recent_blockhash)?;
1456        return_signers_with_config(
1457            &tx,
1458            &config.output_format,
1459            &ReturnSignersConfig {
1460                dump_transaction_message,
1461            },
1462        )
1463    } else {
1464        tx.try_sign(&config.signers, recent_blockhash)?;
1465        if let Some(nonce_account) = &nonce_account {
1466            let nonce_account = solana_rpc_client_nonce_utils::get_account_with_commitment(
1467                rpc_client,
1468                nonce_account,
1469                config.commitment,
1470            )?;
1471            check_nonce_account(&nonce_account, &nonce_authority.pubkey(), &recent_blockhash)?;
1472        }
1473        check_account_for_fee_with_commitment(
1474            rpc_client,
1475            &tx.message.account_keys[0],
1476            &tx.message,
1477            config.commitment,
1478        )?;
1479        let result = rpc_client.send_and_confirm_transaction_with_spinner_and_config(
1480            &tx,
1481            config.commitment,
1482            config.send_transaction_config,
1483        );
1484        log_instruction_custom_error::<VoteError>(result, config)
1485    }
1486}
1487
1488pub fn process_close_vote_account(
1489    rpc_client: &RpcClient,
1490    config: &CliConfig,
1491    vote_account_pubkey: &Pubkey,
1492    withdraw_authority: SignerIndex,
1493    destination_account_pubkey: &Pubkey,
1494    memo: Option<&String>,
1495    fee_payer: SignerIndex,
1496    compute_unit_price: Option<u64>,
1497) -> ProcessResult {
1498    let vote_account_status =
1499        rpc_client.get_vote_accounts_with_config(RpcGetVoteAccountsConfig {
1500            vote_pubkey: Some(vote_account_pubkey.to_string()),
1501            ..RpcGetVoteAccountsConfig::default()
1502        })?;
1503
1504    if let Some(vote_account) = vote_account_status
1505        .current
1506        .into_iter()
1507        .chain(vote_account_status.delinquent)
1508        .next()
1509    {
1510        if vote_account.activated_stake != 0 {
1511            return Err(format!(
1512                "Cannot close a vote account with active stake: {vote_account_pubkey}"
1513            )
1514            .into());
1515        }
1516    }
1517
1518    let latest_blockhash = rpc_client.get_latest_blockhash()?;
1519    let withdraw_authority = config.signers[withdraw_authority];
1520    let fee_payer = config.signers[fee_payer];
1521
1522    let current_balance = rpc_client.get_balance(vote_account_pubkey)?;
1523
1524    let compute_unit_limit = ComputeUnitLimit::Simulated;
1525    let ixs = vec![withdraw(
1526        vote_account_pubkey,
1527        &withdraw_authority.pubkey(),
1528        current_balance,
1529        destination_account_pubkey,
1530    )]
1531    .with_memo(memo)
1532    .with_compute_unit_config(&ComputeUnitConfig {
1533        compute_unit_price,
1534        compute_unit_limit,
1535    });
1536
1537    let mut message = Message::new(&ixs, Some(&fee_payer.pubkey()));
1538    simulate_and_update_compute_unit_limit(&compute_unit_limit, rpc_client, &mut message)?;
1539    let mut tx = Transaction::new_unsigned(message);
1540    tx.try_sign(&config.signers, latest_blockhash)?;
1541    check_account_for_fee_with_commitment(
1542        rpc_client,
1543        &tx.message.account_keys[0],
1544        &tx.message,
1545        config.commitment,
1546    )?;
1547    let result = rpc_client.send_and_confirm_transaction_with_spinner_and_config(
1548        &tx,
1549        config.commitment,
1550        config.send_transaction_config,
1551    );
1552    log_instruction_custom_error::<VoteError>(result, config)
1553}
1554
1555#[cfg(test)]
1556mod tests {
1557    use {
1558        super::*,
1559        crate::{clap_app::get_clap_app, cli::parse_command},
1560        solana_hash::Hash,
1561        solana_keypair::{read_keypair_file, write_keypair, Keypair},
1562        solana_presigner::Presigner,
1563        solana_rpc_client_nonce_utils::blockhash_query,
1564        solana_signer::Signer,
1565        tempfile::NamedTempFile,
1566    };
1567
1568    fn make_tmp_file() -> (String, NamedTempFile) {
1569        let tmp_file = NamedTempFile::new().unwrap();
1570        (String::from(tmp_file.path().to_str().unwrap()), tmp_file)
1571    }
1572
1573    #[test]
1574    fn test_parse_command() {
1575        let test_commands = get_clap_app("test", "desc", "version");
1576        let keypair = Keypair::new();
1577        let pubkey = keypair.pubkey();
1578        let pubkey_string = pubkey.to_string();
1579        let keypair2 = Keypair::new();
1580        let pubkey2 = keypair2.pubkey();
1581        let pubkey2_string = pubkey2.to_string();
1582        let sig2 = keypair2.sign_message(&[0u8]);
1583        let signer2 = format!("{}={}", keypair2.pubkey(), sig2);
1584
1585        let default_keypair = Keypair::new();
1586        let (default_keypair_file, mut tmp_file) = make_tmp_file();
1587        write_keypair(&default_keypair, tmp_file.as_file_mut()).unwrap();
1588        let default_signer = DefaultSigner::new("", &default_keypair_file);
1589
1590        let blockhash = Hash::default();
1591        let blockhash_string = format!("{blockhash}");
1592        let nonce_account = Pubkey::new_unique();
1593
1594        // Test VoteAuthorize SubCommand
1595        let test_authorize_voter = test_commands.clone().get_matches_from(vec![
1596            "test",
1597            "vote-authorize-voter",
1598            &pubkey_string,
1599            &default_keypair_file,
1600            &pubkey2_string,
1601        ]);
1602        assert_eq!(
1603            parse_command(&test_authorize_voter, &default_signer, &mut None).unwrap(),
1604            CliCommandInfo {
1605                command: CliCommand::VoteAuthorize {
1606                    vote_account_pubkey: pubkey,
1607                    new_authorized_pubkey: pubkey2,
1608                    vote_authorize: VoteAuthorize::Voter,
1609                    sign_only: false,
1610                    dump_transaction_message: false,
1611                    blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
1612                    nonce_account: None,
1613                    nonce_authority: 0,
1614                    memo: None,
1615                    fee_payer: 0,
1616                    authorized: 0,
1617                    new_authorized: None,
1618                    compute_unit_price: None,
1619                },
1620                signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap())],
1621            }
1622        );
1623
1624        let authorized_keypair = Keypair::new();
1625        let (authorized_keypair_file, mut tmp_file) = make_tmp_file();
1626        write_keypair(&authorized_keypair, tmp_file.as_file_mut()).unwrap();
1627
1628        let test_authorize_voter = test_commands.clone().get_matches_from(vec![
1629            "test",
1630            "vote-authorize-voter",
1631            &pubkey_string,
1632            &authorized_keypair_file,
1633            &pubkey2_string,
1634        ]);
1635        assert_eq!(
1636            parse_command(&test_authorize_voter, &default_signer, &mut None).unwrap(),
1637            CliCommandInfo {
1638                command: CliCommand::VoteAuthorize {
1639                    vote_account_pubkey: pubkey,
1640                    new_authorized_pubkey: pubkey2,
1641                    vote_authorize: VoteAuthorize::Voter,
1642                    sign_only: false,
1643                    dump_transaction_message: false,
1644                    blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
1645                    nonce_account: None,
1646                    nonce_authority: 0,
1647                    memo: None,
1648                    fee_payer: 0,
1649                    authorized: 1,
1650                    new_authorized: None,
1651                    compute_unit_price: None,
1652                },
1653                signers: vec![
1654                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
1655                    Box::new(read_keypair_file(&authorized_keypair_file).unwrap()),
1656                ],
1657            }
1658        );
1659
1660        let test_authorize_voter = test_commands.clone().get_matches_from(vec![
1661            "test",
1662            "vote-authorize-voter",
1663            &pubkey_string,
1664            &authorized_keypair_file,
1665            &pubkey2_string,
1666            "--blockhash",
1667            &blockhash_string,
1668            "--sign-only",
1669        ]);
1670        assert_eq!(
1671            parse_command(&test_authorize_voter, &default_signer, &mut None).unwrap(),
1672            CliCommandInfo {
1673                command: CliCommand::VoteAuthorize {
1674                    vote_account_pubkey: pubkey,
1675                    new_authorized_pubkey: pubkey2,
1676                    vote_authorize: VoteAuthorize::Voter,
1677                    sign_only: true,
1678                    dump_transaction_message: false,
1679                    blockhash_query: BlockhashQuery::None(blockhash),
1680                    nonce_account: None,
1681                    nonce_authority: 0,
1682                    memo: None,
1683                    fee_payer: 0,
1684                    authorized: 1,
1685                    new_authorized: None,
1686                    compute_unit_price: None,
1687                },
1688                signers: vec![
1689                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
1690                    Box::new(read_keypair_file(&authorized_keypair_file).unwrap()),
1691                ],
1692            }
1693        );
1694
1695        let authorized_sig = authorized_keypair.sign_message(&[0u8]);
1696        let authorized_signer = format!("{}={}", authorized_keypair.pubkey(), authorized_sig);
1697        let test_authorize_voter = test_commands.clone().get_matches_from(vec![
1698            "test",
1699            "vote-authorize-voter",
1700            &pubkey_string,
1701            &authorized_keypair.pubkey().to_string(),
1702            &pubkey2_string,
1703            "--blockhash",
1704            &blockhash_string,
1705            "--signer",
1706            &authorized_signer,
1707            "--signer",
1708            &signer2,
1709            "--fee-payer",
1710            &pubkey2_string,
1711            "--nonce",
1712            &nonce_account.to_string(),
1713            "--nonce-authority",
1714            &pubkey2_string,
1715        ]);
1716        assert_eq!(
1717            parse_command(&test_authorize_voter, &default_signer, &mut None).unwrap(),
1718            CliCommandInfo {
1719                command: CliCommand::VoteAuthorize {
1720                    vote_account_pubkey: pubkey,
1721                    new_authorized_pubkey: pubkey2,
1722                    vote_authorize: VoteAuthorize::Voter,
1723                    sign_only: false,
1724                    dump_transaction_message: false,
1725                    blockhash_query: BlockhashQuery::FeeCalculator(
1726                        blockhash_query::Source::NonceAccount(nonce_account),
1727                        blockhash
1728                    ),
1729                    nonce_account: Some(nonce_account),
1730                    nonce_authority: 0,
1731                    memo: None,
1732                    fee_payer: 0,
1733                    authorized: 1,
1734                    new_authorized: None,
1735                    compute_unit_price: None,
1736                },
1737                signers: vec![
1738                    Box::new(Presigner::new(&pubkey2, &sig2)),
1739                    Box::new(Presigner::new(
1740                        &authorized_keypair.pubkey(),
1741                        &authorized_sig
1742                    )),
1743                ],
1744            }
1745        );
1746
1747        // Test checked VoteAuthorize SubCommand
1748        let (voter_keypair_file, mut tmp_file) = make_tmp_file();
1749        let voter_keypair = Keypair::new();
1750        write_keypair(&voter_keypair, tmp_file.as_file_mut()).unwrap();
1751
1752        let test_authorize_voter = test_commands.clone().get_matches_from(vec![
1753            "test",
1754            "vote-authorize-voter-checked",
1755            &pubkey_string,
1756            &default_keypair_file,
1757            &voter_keypair_file,
1758        ]);
1759        assert_eq!(
1760            parse_command(&test_authorize_voter, &default_signer, &mut None).unwrap(),
1761            CliCommandInfo {
1762                command: CliCommand::VoteAuthorize {
1763                    vote_account_pubkey: pubkey,
1764                    new_authorized_pubkey: voter_keypair.pubkey(),
1765                    vote_authorize: VoteAuthorize::Voter,
1766                    sign_only: false,
1767                    dump_transaction_message: false,
1768                    blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
1769                    nonce_account: None,
1770                    nonce_authority: 0,
1771                    memo: None,
1772                    fee_payer: 0,
1773                    authorized: 0,
1774                    new_authorized: Some(1),
1775                    compute_unit_price: None,
1776                },
1777                signers: vec![
1778                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
1779                    Box::new(read_keypair_file(&voter_keypair_file).unwrap())
1780                ],
1781            }
1782        );
1783
1784        let test_authorize_voter = test_commands.clone().get_matches_from(vec![
1785            "test",
1786            "vote-authorize-voter-checked",
1787            &pubkey_string,
1788            &authorized_keypair_file,
1789            &voter_keypair_file,
1790        ]);
1791        assert_eq!(
1792            parse_command(&test_authorize_voter, &default_signer, &mut None).unwrap(),
1793            CliCommandInfo {
1794                command: CliCommand::VoteAuthorize {
1795                    vote_account_pubkey: pubkey,
1796                    new_authorized_pubkey: voter_keypair.pubkey(),
1797                    vote_authorize: VoteAuthorize::Voter,
1798                    sign_only: false,
1799                    dump_transaction_message: false,
1800                    blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
1801                    nonce_account: None,
1802                    nonce_authority: 0,
1803                    memo: None,
1804                    fee_payer: 0,
1805                    authorized: 1,
1806                    new_authorized: Some(2),
1807                    compute_unit_price: None,
1808                },
1809                signers: vec![
1810                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
1811                    Box::new(read_keypair_file(&authorized_keypair_file).unwrap()),
1812                    Box::new(read_keypair_file(&voter_keypair_file).unwrap()),
1813                ],
1814            }
1815        );
1816
1817        let test_authorize_voter = test_commands.clone().get_matches_from(vec![
1818            "test",
1819            "vote-authorize-voter-checked",
1820            &pubkey_string,
1821            &authorized_keypair_file,
1822            &pubkey2_string,
1823        ]);
1824        assert!(parse_command(&test_authorize_voter, &default_signer, &mut None).is_err());
1825
1826        // Test CreateVoteAccount SubCommand
1827        let (identity_keypair_file, mut tmp_file) = make_tmp_file();
1828        let identity_keypair = Keypair::new();
1829        let authorized_withdrawer = Keypair::new().pubkey();
1830        write_keypair(&identity_keypair, tmp_file.as_file_mut()).unwrap();
1831        let (keypair_file, mut tmp_file) = make_tmp_file();
1832        let keypair = Keypair::new();
1833        write_keypair(&keypair, tmp_file.as_file_mut()).unwrap();
1834
1835        let test_create_vote_account = test_commands.clone().get_matches_from(vec![
1836            "test",
1837            "create-vote-account",
1838            &keypair_file,
1839            &identity_keypair_file,
1840            &authorized_withdrawer.to_string(),
1841            "--commission",
1842            "10",
1843        ]);
1844        assert_eq!(
1845            parse_command(&test_create_vote_account, &default_signer, &mut None).unwrap(),
1846            CliCommandInfo {
1847                command: CliCommand::CreateVoteAccount {
1848                    vote_account: 1,
1849                    seed: None,
1850                    identity_account: 2,
1851                    authorized_voter: None,
1852                    authorized_withdrawer,
1853                    commission: 10,
1854                    sign_only: false,
1855                    dump_transaction_message: false,
1856                    blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
1857                    nonce_account: None,
1858                    nonce_authority: 0,
1859                    memo: None,
1860                    fee_payer: 0,
1861                    compute_unit_price: None,
1862                },
1863                signers: vec![
1864                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
1865                    Box::new(read_keypair_file(&keypair_file).unwrap()),
1866                    Box::new(read_keypair_file(&identity_keypair_file).unwrap()),
1867                ],
1868            }
1869        );
1870
1871        let test_create_vote_account2 = test_commands.clone().get_matches_from(vec![
1872            "test",
1873            "create-vote-account",
1874            &keypair_file,
1875            &identity_keypair_file,
1876            &authorized_withdrawer.to_string(),
1877        ]);
1878        assert_eq!(
1879            parse_command(&test_create_vote_account2, &default_signer, &mut None).unwrap(),
1880            CliCommandInfo {
1881                command: CliCommand::CreateVoteAccount {
1882                    vote_account: 1,
1883                    seed: None,
1884                    identity_account: 2,
1885                    authorized_voter: None,
1886                    authorized_withdrawer,
1887                    commission: 100,
1888                    sign_only: false,
1889                    dump_transaction_message: false,
1890                    blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
1891                    nonce_account: None,
1892                    nonce_authority: 0,
1893                    memo: None,
1894                    fee_payer: 0,
1895                    compute_unit_price: None,
1896                },
1897                signers: vec![
1898                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
1899                    Box::new(read_keypair_file(&keypair_file).unwrap()),
1900                    Box::new(read_keypair_file(&identity_keypair_file).unwrap()),
1901                ],
1902            }
1903        );
1904
1905        let test_create_vote_account = test_commands.clone().get_matches_from(vec![
1906            "test",
1907            "create-vote-account",
1908            &keypair_file,
1909            &identity_keypair_file,
1910            &authorized_withdrawer.to_string(),
1911            "--commission",
1912            "10",
1913            "--blockhash",
1914            &blockhash_string,
1915            "--sign-only",
1916            "--fee-payer",
1917            &default_keypair.pubkey().to_string(),
1918        ]);
1919        assert_eq!(
1920            parse_command(&test_create_vote_account, &default_signer, &mut None).unwrap(),
1921            CliCommandInfo {
1922                command: CliCommand::CreateVoteAccount {
1923                    vote_account: 1,
1924                    seed: None,
1925                    identity_account: 2,
1926                    authorized_voter: None,
1927                    authorized_withdrawer,
1928                    commission: 10,
1929                    sign_only: true,
1930                    dump_transaction_message: false,
1931                    blockhash_query: BlockhashQuery::None(blockhash),
1932                    nonce_account: None,
1933                    nonce_authority: 0,
1934                    memo: None,
1935                    fee_payer: 0,
1936                    compute_unit_price: None,
1937                },
1938                signers: vec![
1939                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
1940                    Box::new(read_keypair_file(&keypair_file).unwrap()),
1941                    Box::new(read_keypair_file(&identity_keypair_file).unwrap()),
1942                ],
1943            }
1944        );
1945
1946        let identity_sig = identity_keypair.sign_message(&[0u8]);
1947        let identity_signer = format!("{}={}", identity_keypair.pubkey(), identity_sig);
1948        let test_create_vote_account = test_commands.clone().get_matches_from(vec![
1949            "test",
1950            "create-vote-account",
1951            &keypair_file,
1952            &identity_keypair.pubkey().to_string(),
1953            &authorized_withdrawer.to_string(),
1954            "--commission",
1955            "10",
1956            "--blockhash",
1957            &blockhash_string,
1958            "--signer",
1959            &identity_signer,
1960            "--signer",
1961            &signer2,
1962            "--fee-payer",
1963            &default_keypair_file,
1964            "--nonce",
1965            &nonce_account.to_string(),
1966            "--nonce-authority",
1967            &pubkey2_string,
1968        ]);
1969        assert_eq!(
1970            parse_command(&test_create_vote_account, &default_signer, &mut None).unwrap(),
1971            CliCommandInfo {
1972                command: CliCommand::CreateVoteAccount {
1973                    vote_account: 1,
1974                    seed: None,
1975                    identity_account: 2,
1976                    authorized_voter: None,
1977                    authorized_withdrawer,
1978                    commission: 10,
1979                    sign_only: false,
1980                    dump_transaction_message: false,
1981                    blockhash_query: BlockhashQuery::FeeCalculator(
1982                        blockhash_query::Source::NonceAccount(nonce_account),
1983                        blockhash
1984                    ),
1985                    nonce_account: Some(nonce_account),
1986                    nonce_authority: 3,
1987                    memo: None,
1988                    fee_payer: 0,
1989                    compute_unit_price: None,
1990                },
1991                signers: vec![
1992                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
1993                    Box::new(read_keypair_file(&keypair_file).unwrap()),
1994                    Box::new(Presigner::new(&identity_keypair.pubkey(), &identity_sig)),
1995                    Box::new(Presigner::new(&pubkey2, &sig2)),
1996                ],
1997            }
1998        );
1999
2000        // test init with an authed voter
2001        let authed = solana_pubkey::new_rand();
2002        let (keypair_file, mut tmp_file) = make_tmp_file();
2003        let keypair = Keypair::new();
2004        write_keypair(&keypair, tmp_file.as_file_mut()).unwrap();
2005
2006        let test_create_vote_account3 = test_commands.clone().get_matches_from(vec![
2007            "test",
2008            "create-vote-account",
2009            &keypair_file,
2010            &identity_keypair_file,
2011            &authorized_withdrawer.to_string(),
2012            "--authorized-voter",
2013            &authed.to_string(),
2014        ]);
2015        assert_eq!(
2016            parse_command(&test_create_vote_account3, &default_signer, &mut None).unwrap(),
2017            CliCommandInfo {
2018                command: CliCommand::CreateVoteAccount {
2019                    vote_account: 1,
2020                    seed: None,
2021                    identity_account: 2,
2022                    authorized_voter: Some(authed),
2023                    authorized_withdrawer,
2024                    commission: 100,
2025                    sign_only: false,
2026                    dump_transaction_message: false,
2027                    blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
2028                    nonce_account: None,
2029                    nonce_authority: 0,
2030                    memo: None,
2031                    fee_payer: 0,
2032                    compute_unit_price: None,
2033                },
2034                signers: vec![
2035                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
2036                    Box::new(keypair),
2037                    Box::new(read_keypair_file(&identity_keypair_file).unwrap()),
2038                ],
2039            }
2040        );
2041
2042        let (keypair_file, mut tmp_file) = make_tmp_file();
2043        let keypair = Keypair::new();
2044        write_keypair(&keypair, tmp_file.as_file_mut()).unwrap();
2045        // succeed even though withdrawer unsafe (because forcefully allowed)
2046        let test_create_vote_account4 = test_commands.clone().get_matches_from(vec![
2047            "test",
2048            "create-vote-account",
2049            &keypair_file,
2050            &identity_keypair_file,
2051            &identity_keypair_file,
2052            "--allow-unsafe-authorized-withdrawer",
2053        ]);
2054        assert_eq!(
2055            parse_command(&test_create_vote_account4, &default_signer, &mut None).unwrap(),
2056            CliCommandInfo {
2057                command: CliCommand::CreateVoteAccount {
2058                    vote_account: 1,
2059                    seed: None,
2060                    identity_account: 2,
2061                    authorized_voter: None,
2062                    authorized_withdrawer: identity_keypair.pubkey(),
2063                    commission: 100,
2064                    sign_only: false,
2065                    dump_transaction_message: false,
2066                    blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
2067                    nonce_account: None,
2068                    nonce_authority: 0,
2069                    memo: None,
2070                    fee_payer: 0,
2071                    compute_unit_price: None,
2072                },
2073                signers: vec![
2074                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
2075                    Box::new(read_keypair_file(&keypair_file).unwrap()),
2076                    Box::new(read_keypair_file(&identity_keypair_file).unwrap()),
2077                ],
2078            }
2079        );
2080
2081        let test_update_validator = test_commands.clone().get_matches_from(vec![
2082            "test",
2083            "vote-update-validator",
2084            &pubkey_string,
2085            &identity_keypair_file,
2086            &keypair_file,
2087        ]);
2088        assert_eq!(
2089            parse_command(&test_update_validator, &default_signer, &mut None).unwrap(),
2090            CliCommandInfo {
2091                command: CliCommand::VoteUpdateValidator {
2092                    vote_account_pubkey: pubkey,
2093                    new_identity_account: 2,
2094                    withdraw_authority: 1,
2095                    sign_only: false,
2096                    dump_transaction_message: false,
2097                    blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
2098                    nonce_account: None,
2099                    nonce_authority: 0,
2100                    memo: None,
2101                    fee_payer: 0,
2102                    compute_unit_price: None,
2103                },
2104                signers: vec![
2105                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
2106                    Box::new(read_keypair_file(&keypair_file).unwrap()),
2107                    Box::new(read_keypair_file(&identity_keypair_file).unwrap()),
2108                ],
2109            }
2110        );
2111
2112        let test_update_commission = test_commands.clone().get_matches_from(vec![
2113            "test",
2114            "vote-update-commission",
2115            &pubkey_string,
2116            "42",
2117            &keypair_file,
2118        ]);
2119        assert_eq!(
2120            parse_command(&test_update_commission, &default_signer, &mut None).unwrap(),
2121            CliCommandInfo {
2122                command: CliCommand::VoteUpdateCommission {
2123                    vote_account_pubkey: pubkey,
2124                    commission: 42,
2125                    withdraw_authority: 1,
2126                    sign_only: false,
2127                    dump_transaction_message: false,
2128                    blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
2129                    nonce_account: None,
2130                    nonce_authority: 0,
2131                    memo: None,
2132                    fee_payer: 0,
2133                    compute_unit_price: None,
2134                },
2135                signers: vec![
2136                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
2137                    Box::new(read_keypair_file(&keypair_file).unwrap()),
2138                ],
2139            }
2140        );
2141
2142        // Test WithdrawFromVoteAccount subcommand
2143        let test_withdraw_from_vote_account = test_commands.clone().get_matches_from(vec![
2144            "test",
2145            "withdraw-from-vote-account",
2146            &keypair_file,
2147            &pubkey_string,
2148            "42",
2149        ]);
2150        assert_eq!(
2151            parse_command(&test_withdraw_from_vote_account, &default_signer, &mut None).unwrap(),
2152            CliCommandInfo {
2153                command: CliCommand::WithdrawFromVoteAccount {
2154                    vote_account_pubkey: read_keypair_file(&keypair_file).unwrap().pubkey(),
2155                    destination_account_pubkey: pubkey,
2156                    withdraw_authority: 0,
2157                    withdraw_amount: SpendAmount::Some(42_000_000_000),
2158                    sign_only: false,
2159                    dump_transaction_message: false,
2160                    blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
2161                    nonce_account: None,
2162                    nonce_authority: 0,
2163                    memo: None,
2164                    fee_payer: 0,
2165                    compute_unit_price: None,
2166                },
2167                signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap())],
2168            }
2169        );
2170
2171        // Test WithdrawFromVoteAccount subcommand
2172        let test_withdraw_from_vote_account = test_commands.clone().get_matches_from(vec![
2173            "test",
2174            "withdraw-from-vote-account",
2175            &keypair_file,
2176            &pubkey_string,
2177            "ALL",
2178        ]);
2179        assert_eq!(
2180            parse_command(&test_withdraw_from_vote_account, &default_signer, &mut None).unwrap(),
2181            CliCommandInfo {
2182                command: CliCommand::WithdrawFromVoteAccount {
2183                    vote_account_pubkey: read_keypair_file(&keypair_file).unwrap().pubkey(),
2184                    destination_account_pubkey: pubkey,
2185                    withdraw_authority: 0,
2186                    withdraw_amount: SpendAmount::RentExempt,
2187                    sign_only: false,
2188                    dump_transaction_message: false,
2189                    blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
2190                    nonce_account: None,
2191                    nonce_authority: 0,
2192                    memo: None,
2193                    fee_payer: 0,
2194                    compute_unit_price: None,
2195                },
2196                signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap())],
2197            }
2198        );
2199
2200        // Test WithdrawFromVoteAccount subcommand with authority
2201        let withdraw_authority = Keypair::new();
2202        let (withdraw_authority_file, mut tmp_file) = make_tmp_file();
2203        write_keypair(&withdraw_authority, tmp_file.as_file_mut()).unwrap();
2204        let test_withdraw_from_vote_account = test_commands.clone().get_matches_from(vec![
2205            "test",
2206            "withdraw-from-vote-account",
2207            &keypair_file,
2208            &pubkey_string,
2209            "42",
2210            "--authorized-withdrawer",
2211            &withdraw_authority_file,
2212        ]);
2213        assert_eq!(
2214            parse_command(&test_withdraw_from_vote_account, &default_signer, &mut None).unwrap(),
2215            CliCommandInfo {
2216                command: CliCommand::WithdrawFromVoteAccount {
2217                    vote_account_pubkey: read_keypair_file(&keypair_file).unwrap().pubkey(),
2218                    destination_account_pubkey: pubkey,
2219                    withdraw_authority: 1,
2220                    withdraw_amount: SpendAmount::Some(42_000_000_000),
2221                    sign_only: false,
2222                    dump_transaction_message: false,
2223                    blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
2224                    nonce_account: None,
2225                    nonce_authority: 0,
2226                    memo: None,
2227                    fee_payer: 0,
2228                    compute_unit_price: None,
2229                },
2230                signers: vec![
2231                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
2232                    Box::new(read_keypair_file(&withdraw_authority_file).unwrap())
2233                ],
2234            }
2235        );
2236
2237        // Test WithdrawFromVoteAccount subcommand with offline authority
2238        let test_withdraw_from_vote_account = test_commands.clone().get_matches_from(vec![
2239            "test",
2240            "withdraw-from-vote-account",
2241            &keypair.pubkey().to_string(),
2242            &pubkey_string,
2243            "42",
2244            "--authorized-withdrawer",
2245            &withdraw_authority_file,
2246            "--blockhash",
2247            &blockhash_string,
2248            "--sign-only",
2249            "--fee-payer",
2250            &withdraw_authority_file,
2251        ]);
2252        assert_eq!(
2253            parse_command(&test_withdraw_from_vote_account, &default_signer, &mut None).unwrap(),
2254            CliCommandInfo {
2255                command: CliCommand::WithdrawFromVoteAccount {
2256                    vote_account_pubkey: keypair.pubkey(),
2257                    destination_account_pubkey: pubkey,
2258                    withdraw_authority: 0,
2259                    withdraw_amount: SpendAmount::Some(42_000_000_000),
2260                    sign_only: true,
2261                    dump_transaction_message: false,
2262                    blockhash_query: BlockhashQuery::None(blockhash),
2263                    nonce_account: None,
2264                    nonce_authority: 0,
2265                    memo: None,
2266                    fee_payer: 0,
2267                    compute_unit_price: None,
2268                },
2269                signers: vec![Box::new(
2270                    read_keypair_file(&withdraw_authority_file).unwrap()
2271                )],
2272            }
2273        );
2274
2275        let authorized_sig = withdraw_authority.sign_message(&[0u8]);
2276        let authorized_signer = format!("{}={}", withdraw_authority.pubkey(), authorized_sig);
2277        let test_withdraw_from_vote_account = test_commands.clone().get_matches_from(vec![
2278            "test",
2279            "withdraw-from-vote-account",
2280            &keypair.pubkey().to_string(),
2281            &pubkey_string,
2282            "42",
2283            "--authorized-withdrawer",
2284            &withdraw_authority.pubkey().to_string(),
2285            "--blockhash",
2286            &blockhash_string,
2287            "--signer",
2288            &authorized_signer,
2289            "--fee-payer",
2290            &withdraw_authority.pubkey().to_string(),
2291        ]);
2292        assert_eq!(
2293            parse_command(&test_withdraw_from_vote_account, &default_signer, &mut None).unwrap(),
2294            CliCommandInfo {
2295                command: CliCommand::WithdrawFromVoteAccount {
2296                    vote_account_pubkey: keypair.pubkey(),
2297                    destination_account_pubkey: pubkey,
2298                    withdraw_authority: 0,
2299                    withdraw_amount: SpendAmount::Some(42_000_000_000),
2300                    sign_only: false,
2301                    dump_transaction_message: false,
2302                    blockhash_query: BlockhashQuery::FeeCalculator(
2303                        blockhash_query::Source::Cluster,
2304                        blockhash
2305                    ),
2306                    nonce_account: None,
2307                    nonce_authority: 0,
2308                    memo: None,
2309                    fee_payer: 0,
2310                    compute_unit_price: None,
2311                },
2312                signers: vec![Box::new(Presigner::new(
2313                    &withdraw_authority.pubkey(),
2314                    &authorized_sig
2315                )),],
2316            }
2317        );
2318
2319        // Test CloseVoteAccount subcommand
2320        let test_close_vote_account = test_commands.clone().get_matches_from(vec![
2321            "test",
2322            "close-vote-account",
2323            &keypair_file,
2324            &pubkey_string,
2325        ]);
2326        assert_eq!(
2327            parse_command(&test_close_vote_account, &default_signer, &mut None).unwrap(),
2328            CliCommandInfo {
2329                command: CliCommand::CloseVoteAccount {
2330                    vote_account_pubkey: read_keypair_file(&keypair_file).unwrap().pubkey(),
2331                    destination_account_pubkey: pubkey,
2332                    withdraw_authority: 0,
2333                    memo: None,
2334                    fee_payer: 0,
2335                    compute_unit_price: None,
2336                },
2337                signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap())],
2338            }
2339        );
2340
2341        // Test CloseVoteAccount subcommand with authority
2342        let withdraw_authority = Keypair::new();
2343        let (withdraw_authority_file, mut tmp_file) = make_tmp_file();
2344        write_keypair(&withdraw_authority, tmp_file.as_file_mut()).unwrap();
2345        let test_close_vote_account = test_commands.clone().get_matches_from(vec![
2346            "test",
2347            "close-vote-account",
2348            &keypair_file,
2349            &pubkey_string,
2350            "--authorized-withdrawer",
2351            &withdraw_authority_file,
2352        ]);
2353        assert_eq!(
2354            parse_command(&test_close_vote_account, &default_signer, &mut None).unwrap(),
2355            CliCommandInfo {
2356                command: CliCommand::CloseVoteAccount {
2357                    vote_account_pubkey: read_keypair_file(&keypair_file).unwrap().pubkey(),
2358                    destination_account_pubkey: pubkey,
2359                    withdraw_authority: 1,
2360                    memo: None,
2361                    fee_payer: 0,
2362                    compute_unit_price: None,
2363                },
2364                signers: vec![
2365                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
2366                    Box::new(read_keypair_file(&withdraw_authority_file).unwrap())
2367                ],
2368            }
2369        );
2370
2371        // Test CloseVoteAccount subcommand with authority w/ ComputeUnitPrice
2372        let withdraw_authority = Keypair::new();
2373        let (withdraw_authority_file, mut tmp_file) = make_tmp_file();
2374        write_keypair(&withdraw_authority, tmp_file.as_file_mut()).unwrap();
2375        let test_close_vote_account = test_commands.clone().get_matches_from(vec![
2376            "test",
2377            "close-vote-account",
2378            &keypair_file,
2379            &pubkey_string,
2380            "--authorized-withdrawer",
2381            &withdraw_authority_file,
2382            "--with-compute-unit-price",
2383            "99",
2384        ]);
2385        assert_eq!(
2386            parse_command(&test_close_vote_account, &default_signer, &mut None).unwrap(),
2387            CliCommandInfo {
2388                command: CliCommand::CloseVoteAccount {
2389                    vote_account_pubkey: read_keypair_file(&keypair_file).unwrap().pubkey(),
2390                    destination_account_pubkey: pubkey,
2391                    withdraw_authority: 1,
2392                    memo: None,
2393                    fee_payer: 0,
2394                    compute_unit_price: Some(99),
2395                },
2396                signers: vec![
2397                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
2398                    Box::new(read_keypair_file(&withdraw_authority_file).unwrap())
2399                ],
2400            }
2401        );
2402    }
2403}