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