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