Skip to main content

solana_cli/
vote.rs

1use {
2    crate::{
3        checks::{check_account_for_fee_with_commitment, check_unique_pubkeys},
4        cli::{
5            CliCommand, CliCommandInfo, CliConfig, CliError, ProcessResult,
6            log_instruction_custom_error,
7        },
8        compute_budget::{
9            ComputeUnitConfig, WithComputeUnitConfig, simulate_and_update_compute_unit_limit,
10        },
11        feature::get_feature_is_active,
12        memo::WithMemo,
13        nonce::check_nonce_account,
14        spend_utils::{SpendAmount, resolve_spend_tx_and_check_account_balances},
15        stake::check_current_authority,
16    },
17    agave_feature_set::{
18        alpenglow, bls_pubkey_management_in_vote_account, vote_account_initialize_v2,
19    },
20    agave_votor_messages::consensus_message::BLS_KEYPAIR_DERIVE_SEED,
21    clap::{App, Arg, ArgMatches, SubCommand, value_t_or_exit},
22    solana_account::Account,
23    solana_bls_signatures::keypair::Keypair as BLSKeypair,
24    solana_clap_utils::{
25        compute_budget::{COMPUTE_UNIT_PRICE_ARG, ComputeUnitLimit, compute_unit_price_arg},
26        fee_payer::{FEE_PAYER_ARG, fee_payer_arg},
27        input_parsers::*,
28        input_validators::*,
29        keypair::{DefaultSigner, SignerIndex},
30        memo::{MEMO_ARG, memo_arg},
31        nonce::*,
32        offline::*,
33    },
34    solana_cli_output::{
35        CliVoteAccount, ReturnSignersConfig, VotesObserved, display::build_balance_message,
36        get_epoch_history, return_signers_with_config,
37    },
38    solana_commitment_config::CommitmentConfig,
39    solana_feature_gate_interface::from_account,
40    solana_message::Message,
41    solana_pubkey::Pubkey,
42    solana_remote_wallet::remote_wallet::RemoteWalletManager,
43    solana_rpc_client::nonblocking::rpc_client::RpcClient,
44    solana_rpc_client_api::config::RpcGetVoteAccountsConfig,
45    solana_rpc_client_nonce_utils::nonblocking::blockhash_query::BlockhashQuery,
46    solana_system_interface::error::SystemError,
47    solana_transaction::Transaction,
48    solana_vote_program::{
49        vote_error::VoteError,
50        vote_instruction::{self, CreateVoteAccountConfig, withdraw},
51        vote_state::{
52            VoteAuthorize, VoteInit, VoteInitV2, VoteStateV4, VoterWithBLSArgs,
53            create_bls_proof_of_possession,
54        },
55    },
56    std::rc::Rc,
57};
58
59pub trait VoteSubCommands {
60    fn vote_subcommands(self) -> Self;
61}
62
63impl VoteSubCommands for App<'_, '_> {
64    fn vote_subcommands(self) -> Self {
65        self.subcommand(
66            SubCommand::with_name("create-vote-account")
67                .about("Create a vote account")
68                .arg(
69                    Arg::with_name("vote_account")
70                        .index(1)
71                        .value_name("ACCOUNT_KEYPAIR")
72                        .takes_value(true)
73                        .required(true)
74                        .validator(is_valid_signer)
75                        .help("Vote account keypair to create"),
76                )
77                .arg(
78                    Arg::with_name("identity_account")
79                        .index(2)
80                        .value_name("IDENTITY_KEYPAIR")
81                        .takes_value(true)
82                        .required(true)
83                        .validator(is_valid_signer)
84                        .help("Keypair of validator that will vote with this account"),
85                )
86                .arg(pubkey!(
87                    Arg::with_name("authorized_withdrawer")
88                        .index(3)
89                        .value_name("WITHDRAWER_PUBKEY")
90                        .takes_value(true)
91                        .required(true)
92                        .long("authorized-withdrawer"),
93                    "Authorized withdrawer."
94                ))
95                .arg(
96                    Arg::with_name("commission")
97                        .long("commission")
98                        .value_name("PERCENTAGE")
99                        .takes_value(true)
100                        .help(
101                            "The commission taken on reward redemption (0-100). Only valid for \
102                             VoteInit (v1). Cannot be used with --use-v2-instruction. [default: \
103                             100]",
104                        ),
105                )
106                .arg(pubkey!(
107                    Arg::with_name("authorized_voter")
108                        .long("authorized-voter")
109                        .value_name("VOTER_PUBKEY"),
110                    "Authorized voter [default: validator identity pubkey]."
111                ))
112                // SIMD-0464 VoteInitV2 arguments.
113                .arg(
114                    Arg::with_name("use_v2_instruction")
115                        .long("use-v2-instruction")
116                        .takes_value(false)
117                        .help(
118                            "Force use of VoteInitV2 (SIMD-0464). Required in sign-only mode \
119                             after feature activation. In normal mode, instruction version is \
120                             auto-detected based on feature status.",
121                        ),
122                )
123                .arg(
124                    Arg::with_name("inflation_rewards_commission_bps")
125                        .long("inflation-rewards-commission-bps")
126                        .value_name("BASIS_POINTS")
127                        .takes_value(true)
128                        .validator(is_valid_basis_points)
129                        .help(
130                            "Commission rate in basis points (0-10000) for inflation rewards. 100 \
131                             basis points = 1%. Only valid with VoteInitV2 (--use-v2-instruction \
132                             or when SIMD-0464 feature is active). [default: 10000 (100%)]",
133                        ),
134                )
135                .arg(pubkey!(
136                    Arg::with_name("inflation_rewards_collector")
137                        .long("inflation-rewards-collector")
138                        .value_name("COLLECTOR_PUBKEY")
139                        .takes_value(true),
140                    "Account to collect inflation rewards commission. Only valid with VoteInitV2 \
141                     (--use-v2-instruction or when SIMD-0464 feature is active). [default: vote \
142                     account address]"
143                ))
144                .arg(
145                    Arg::with_name("block_revenue_commission_bps")
146                        .long("block-revenue-commission-bps")
147                        .value_name("BASIS_POINTS")
148                        .takes_value(true)
149                        .validator(is_valid_basis_points)
150                        .help(
151                            "Commission rate in basis points (0-10000) for block revenue. 100 \
152                             basis points = 1%. Only valid with VoteInitV2 (--use-v2-instruction \
153                             or when SIMD-0464 feature is active). [default: 10000 (100%)]",
154                        ),
155                )
156                .arg(pubkey!(
157                    Arg::with_name("block_revenue_collector")
158                        .long("block-revenue-collector")
159                        .value_name("COLLECTOR_PUBKEY")
160                        .takes_value(true),
161                    "Account to collect block revenue commission. Only valid with VoteInitV2 \
162                     (--use-v2-instruction or when SIMD-0464 feature is active). [default: \
163                     identity account address]"
164                ))
165                .arg(
166                    Arg::with_name("allow_unsafe_authorized_withdrawer")
167                        .long("allow-unsafe-authorized-withdrawer")
168                        .takes_value(false)
169                        .help(
170                            "Allow an authorized withdrawer pubkey to be identical to the \
171                             validator identity account pubkey or vote account pubkey, which is \
172                             normally an unsafe configuration and should be avoided.",
173                        ),
174                )
175                .arg(
176                    Arg::with_name("seed")
177                        .long("seed")
178                        .value_name("STRING")
179                        .takes_value(true)
180                        .help(
181                            "Seed for address generation; if specified, the resulting account \
182                             will be at a derived address of the VOTE ACCOUNT pubkey",
183                        ),
184                )
185                .offline_args()
186                .nonce_args(false)
187                .arg(fee_payer_arg())
188                .arg(memo_arg())
189                .arg(compute_unit_price_arg()),
190        )
191        .subcommand(
192            SubCommand::with_name("vote-authorize-voter")
193                .about("Authorize a new vote signing keypair for the given vote account")
194                .arg(pubkey!(
195                    Arg::with_name("vote_account_pubkey")
196                        .index(1)
197                        .value_name("VOTE_ACCOUNT_ADDRESS")
198                        .required(true),
199                    "Vote account in which to set the authorized voter."
200                ))
201                .arg(
202                    Arg::with_name("authorized")
203                        .index(2)
204                        .value_name("AUTHORIZED_KEYPAIR")
205                        .required(true)
206                        .validator(is_valid_signer)
207                        .help("Current authorized vote signer."),
208                )
209                .arg(pubkey!(
210                    Arg::with_name("new_authorized_pubkey")
211                        .index(3)
212                        .value_name("NEW_AUTHORIZED_PUBKEY")
213                        .required(true),
214                    "New authorized vote signer."
215                ))
216                .offline_args()
217                .nonce_args(false)
218                .arg(fee_payer_arg())
219                .arg(memo_arg())
220                .arg(compute_unit_price_arg()),
221        )
222        .subcommand(
223            SubCommand::with_name("vote-authorize-withdrawer")
224                .about("Authorize a new withdraw signing keypair for the given vote account")
225                .arg(pubkey!(
226                    Arg::with_name("vote_account_pubkey")
227                        .index(1)
228                        .value_name("VOTE_ACCOUNT_ADDRESS")
229                        .required(true),
230                    "Vote account in which to set the authorized withdrawer."
231                ))
232                .arg(
233                    Arg::with_name("authorized")
234                        .index(2)
235                        .value_name("AUTHORIZED_KEYPAIR")
236                        .required(true)
237                        .validator(is_valid_signer)
238                        .help("Current authorized withdrawer."),
239                )
240                .arg(pubkey!(
241                    Arg::with_name("new_authorized_pubkey")
242                        .index(3)
243                        .value_name("AUTHORIZED_PUBKEY")
244                        .required(true),
245                    "New authorized withdrawer."
246                ))
247                .offline_args()
248                .nonce_args(false)
249                .arg(fee_payer_arg())
250                .arg(memo_arg())
251                .arg(compute_unit_price_arg()),
252        )
253        .subcommand(
254            SubCommand::with_name("vote-authorize-voter-checked")
255                .about(
256                    "Authorize a new vote signing keypair for the given vote account, checking \
257                     the new authority as a signer",
258                )
259                .arg(pubkey!(
260                    Arg::with_name("vote_account_pubkey")
261                        .index(1)
262                        .value_name("VOTE_ACCOUNT_ADDRESS")
263                        .required(true),
264                    "Vote account in which to set the authorized voter."
265                ))
266                .arg(
267                    Arg::with_name("authorized")
268                        .index(2)
269                        .value_name("AUTHORIZED_KEYPAIR")
270                        .required(true)
271                        .validator(is_valid_signer)
272                        .help("Current authorized vote signer."),
273                )
274                .arg(
275                    Arg::with_name("new_authorized")
276                        .index(3)
277                        .value_name("NEW_AUTHORIZED_KEYPAIR")
278                        .required(true)
279                        .validator(is_valid_signer)
280                        .help("New authorized vote signer."),
281                )
282                .arg(
283                    Arg::with_name("use_v2_instruction")
284                        .long("use-v2-instruction")
285                        .takes_value(false)
286                        .help(
287                            "Force BLS key derivation (SIMD-0387). Required in sign-only mode \
288                             after feature activation. In normal mode, BLS usage is auto-detected \
289                             based on feature status.",
290                        ),
291                )
292                .offline_args()
293                .nonce_args(false)
294                .arg(fee_payer_arg())
295                .arg(memo_arg())
296                .arg(compute_unit_price_arg()),
297        )
298        .subcommand(
299            SubCommand::with_name("vote-authorize-withdrawer-checked")
300                .about(
301                    "Authorize a new withdraw signing keypair for the given vote account, \
302                     checking the new authority as a signer",
303                )
304                .arg(pubkey!(
305                    Arg::with_name("vote_account_pubkey")
306                        .index(1)
307                        .value_name("VOTE_ACCOUNT_ADDRESS")
308                        .required(true),
309                    "Vote account in which to set the authorized withdrawer."
310                ))
311                .arg(
312                    Arg::with_name("authorized")
313                        .index(2)
314                        .value_name("AUTHORIZED_KEYPAIR")
315                        .required(true)
316                        .validator(is_valid_signer)
317                        .help("Current authorized withdrawer."),
318                )
319                .arg(
320                    Arg::with_name("new_authorized")
321                        .index(3)
322                        .value_name("NEW_AUTHORIZED_KEYPAIR")
323                        .required(true)
324                        .validator(is_valid_signer)
325                        .help("New authorized withdrawer."),
326                )
327                .offline_args()
328                .nonce_args(false)
329                .arg(fee_payer_arg())
330                .arg(memo_arg())
331                .arg(compute_unit_price_arg()),
332        )
333        .subcommand(
334            SubCommand::with_name("vote-update-validator")
335                .about("Update the vote account's validator identity")
336                .arg(pubkey!(
337                    Arg::with_name("vote_account_pubkey")
338                        .index(1)
339                        .value_name("VOTE_ACCOUNT_ADDRESS")
340                        .required(true),
341                    "Vote account to update."
342                ))
343                .arg(
344                    Arg::with_name("new_identity_account")
345                        .index(2)
346                        .value_name("IDENTITY_KEYPAIR")
347                        .takes_value(true)
348                        .required(true)
349                        .validator(is_valid_signer)
350                        .help("Keypair of new validator that will vote with this account"),
351                )
352                .arg(
353                    Arg::with_name("authorized_withdrawer")
354                        .index(3)
355                        .value_name("AUTHORIZED_KEYPAIR")
356                        .takes_value(true)
357                        .required(true)
358                        .validator(is_valid_signer)
359                        .help("Authorized withdrawer keypair"),
360                )
361                .offline_args()
362                .nonce_args(false)
363                .arg(fee_payer_arg())
364                .arg(memo_arg())
365                .arg(compute_unit_price_arg()),
366        )
367        .subcommand(
368            SubCommand::with_name("vote-update-commission")
369                .about("Update the vote account's commission")
370                .arg(pubkey!(
371                    Arg::with_name("vote_account_pubkey")
372                        .index(1)
373                        .value_name("VOTE_ACCOUNT_ADDRESS")
374                        .required(true),
375                    "Vote account to update."
376                ))
377                .arg(
378                    Arg::with_name("commission")
379                        .index(2)
380                        .value_name("PERCENTAGE")
381                        .takes_value(true)
382                        .required(true)
383                        .validator(is_valid_percentage)
384                        .help("The new commission"),
385                )
386                .arg(
387                    Arg::with_name("authorized_withdrawer")
388                        .index(3)
389                        .value_name("AUTHORIZED_KEYPAIR")
390                        .takes_value(true)
391                        .required(true)
392                        .validator(is_valid_signer)
393                        .help("Authorized withdrawer keypair"),
394                )
395                .offline_args()
396                .nonce_args(false)
397                .arg(fee_payer_arg())
398                .arg(memo_arg())
399                .arg(compute_unit_price_arg()),
400        )
401        .subcommand(
402            SubCommand::with_name("vote-account")
403                .about("Show the contents of a vote account")
404                .alias("show-vote-account")
405                .arg(pubkey!(
406                    Arg::with_name("vote_account_pubkey")
407                        .index(1)
408                        .value_name("VOTE_ACCOUNT_ADDRESS")
409                        .required(true),
410                    "Vote account."
411                ))
412                .arg(
413                    Arg::with_name("lamports")
414                        .long("lamports")
415                        .takes_value(false)
416                        .help("Display balance in lamports instead of SOL"),
417                )
418                .arg(
419                    Arg::with_name("with_rewards")
420                        .long("with-rewards")
421                        .takes_value(false)
422                        .help("Display inflation rewards"),
423                )
424                .arg(
425                    Arg::with_name("csv")
426                        .long("csv")
427                        .takes_value(false)
428                        .help("Format rewards in a CSV table"),
429                )
430                .arg(
431                    Arg::with_name("starting_epoch")
432                        .long("starting-epoch")
433                        .takes_value(true)
434                        .value_name("NUM")
435                        .requires("with_rewards")
436                        .help("Start displaying from epoch NUM"),
437                )
438                .arg(
439                    Arg::with_name("num_rewards_epochs")
440                        .long("num-rewards-epochs")
441                        .takes_value(true)
442                        .value_name("NUM")
443                        .validator(|s| is_within_range(s, 1..=50))
444                        .default_value_if("with_rewards", None, "1")
445                        .requires("with_rewards")
446                        .help(
447                            "Display rewards for NUM recent epochs, max 10 [default: latest epoch \
448                             only]",
449                        ),
450                ),
451        )
452        .subcommand(
453            SubCommand::with_name("withdraw-from-vote-account")
454                .about("Withdraw lamports from a vote account into a specified account")
455                .arg(pubkey!(
456                    Arg::with_name("vote_account_pubkey")
457                        .index(1)
458                        .value_name("VOTE_ACCOUNT_ADDRESS")
459                        .required(true),
460                    "Vote account from which to withdraw."
461                ))
462                .arg(pubkey!(
463                    Arg::with_name("destination_account_pubkey")
464                        .index(2)
465                        .value_name("RECIPIENT_ADDRESS")
466                        .required(true),
467                    "The recipient of withdrawn SOL."
468                ))
469                .arg(
470                    Arg::with_name("amount")
471                        .index(3)
472                        .value_name("AMOUNT")
473                        .takes_value(true)
474                        .required(true)
475                        .validator(is_amount_or_all)
476                        .help(
477                            "The amount to withdraw, in SOL; accepts keyword ALL, which for this \
478                             command means account balance minus rent-exempt minimum",
479                        ),
480                )
481                .arg(
482                    Arg::with_name("authorized_withdrawer")
483                        .long("authorized-withdrawer")
484                        .value_name("AUTHORIZED_KEYPAIR")
485                        .takes_value(true)
486                        .validator(is_valid_signer)
487                        .help("Authorized withdrawer [default: cli config keypair]"),
488                )
489                .offline_args()
490                .nonce_args(false)
491                .arg(fee_payer_arg())
492                .arg(memo_arg())
493                .arg(compute_unit_price_arg()),
494        )
495        .subcommand(
496            SubCommand::with_name("close-vote-account")
497                .about("Close a vote account and withdraw all funds remaining")
498                .arg(pubkey!(
499                    Arg::with_name("vote_account_pubkey")
500                        .index(1)
501                        .value_name("VOTE_ACCOUNT_ADDRESS")
502                        .required(true),
503                    "Vote account to be closed."
504                ))
505                .arg(pubkey!(
506                    Arg::with_name("destination_account_pubkey")
507                        .index(2)
508                        .value_name("RECIPIENT_ADDRESS")
509                        .required(true),
510                    "The recipient of all withdrawn SOL."
511                ))
512                .arg(
513                    Arg::with_name("authorized_withdrawer")
514                        .long("authorized-withdrawer")
515                        .value_name("AUTHORIZED_KEYPAIR")
516                        .takes_value(true)
517                        .validator(is_valid_signer)
518                        .help("Authorized withdrawer [default: cli config keypair]"),
519                )
520                .arg(fee_payer_arg())
521                .arg(memo_arg())
522                .arg(compute_unit_price_arg()),
523        )
524    }
525}
526
527pub fn parse_create_vote_account(
528    matches: &ArgMatches<'_>,
529    default_signer: &DefaultSigner,
530    wallet_manager: &mut Option<Rc<RemoteWalletManager>>,
531) -> Result<CliCommandInfo, CliError> {
532    let (vote_account, vote_account_pubkey) = signer_of(matches, "vote_account", wallet_manager)?;
533    let seed = matches.value_of("seed").map(|s| s.to_string());
534    let (identity_account, identity_pubkey) =
535        signer_of(matches, "identity_account", wallet_manager)?;
536    let authorized_voter = pubkey_of_signer(matches, "authorized_voter", wallet_manager)?;
537    let authorized_withdrawer =
538        pubkey_of_signer(matches, "authorized_withdrawer", wallet_manager)?.unwrap();
539    let allow_unsafe = matches.is_present("allow_unsafe_authorized_withdrawer");
540    let sign_only = matches.is_present(SIGN_ONLY_ARG.name);
541    let dump_transaction_message = matches.is_present(DUMP_TRANSACTION_MESSAGE.name);
542    let blockhash_query = BlockhashQuery::new_from_matches(matches);
543    let nonce_account = pubkey_of_signer(matches, NONCE_ARG.name, wallet_manager)?;
544    let memo = matches.value_of(MEMO_ARG.name).map(String::from);
545    let (nonce_authority, nonce_authority_pubkey) =
546        signer_of(matches, NONCE_AUTHORITY_ARG.name, wallet_manager)?;
547    let (fee_payer, fee_payer_pubkey) = signer_of(matches, FEE_PAYER_ARG.name, wallet_manager)?;
548    let compute_unit_price = value_of(matches, COMPUTE_UNIT_PRICE_ARG.name);
549
550    // VoteInit (v1) args.
551    let commission: Option<u8> = value_of(matches, "commission");
552
553    // VoteInitV2 args (SIMD-0464).
554    let use_v2_instruction = matches.is_present("use_v2_instruction");
555    let inflation_rewards_commission_bps: Option<u16> =
556        value_of(matches, "inflation_rewards_commission_bps");
557    let inflation_rewards_collector =
558        pubkey_of_signer(matches, "inflation_rewards_collector", wallet_manager)?;
559    let block_revenue_commission_bps: Option<u16> =
560        value_of(matches, "block_revenue_commission_bps");
561    let block_revenue_collector =
562        pubkey_of_signer(matches, "block_revenue_collector", wallet_manager)?;
563
564    // Check for argument conflicts.
565    // --commission is only allowed with VoteInitV2 when no VoteInitV2-specific
566    // arguments have been provided, including --use-v2-instruction.
567    let has_v2_args = use_v2_instruction
568        || inflation_rewards_commission_bps.is_some()
569        || inflation_rewards_collector.is_some()
570        || block_revenue_commission_bps.is_some()
571        || block_revenue_collector.is_some();
572
573    if commission.is_some() && has_v2_args {
574        return Err(CliError::BadParameter(
575            "--commission cannot be used with --use-v2-instruction or VoteInitV2 arguments \
576             (--inflation-rewards-commission-bps, --inflation-rewards-collector, \
577             --block-revenue-commission-bps, --block-revenue-collector). For VoteInitV2, use \
578             --inflation-rewards-commission-bps instead."
579                .to_owned(),
580        ));
581    }
582
583    if !allow_unsafe {
584        if authorized_withdrawer == vote_account_pubkey.unwrap() {
585            return Err(CliError::BadParameter(
586                "Authorized withdrawer pubkey is identical to vote account pubkey, an unsafe \
587                 configuration"
588                    .to_owned(),
589            ));
590        }
591        if authorized_withdrawer == identity_pubkey.unwrap() {
592            return Err(CliError::BadParameter(
593                "Authorized withdrawer pubkey is identical to identity account pubkey, an unsafe \
594                 configuration"
595                    .to_owned(),
596            ));
597        }
598    }
599
600    let mut bulk_signers = vec![fee_payer, vote_account, identity_account];
601    if nonce_account.is_some() {
602        bulk_signers.push(nonce_authority);
603    }
604    let signer_info =
605        default_signer.generate_unique_signers(bulk_signers, matches, wallet_manager)?;
606
607    Ok(CliCommandInfo {
608        command: CliCommand::CreateVoteAccount {
609            vote_account: signer_info.index_of(vote_account_pubkey).unwrap(),
610            seed,
611            identity_account: signer_info.index_of(identity_pubkey).unwrap(),
612            authorized_voter,
613            authorized_withdrawer,
614            commission,
615            use_v2_instruction,
616            inflation_rewards_commission_bps,
617            inflation_rewards_collector,
618            block_revenue_commission_bps,
619            block_revenue_collector,
620            sign_only,
621            dump_transaction_message,
622            blockhash_query,
623            nonce_account,
624            nonce_authority: signer_info.index_of(nonce_authority_pubkey).unwrap(),
625            memo,
626            fee_payer: signer_info.index_of(fee_payer_pubkey).unwrap(),
627            compute_unit_price,
628        },
629        signers: signer_info.signers,
630    })
631}
632
633pub fn parse_vote_authorize(
634    matches: &ArgMatches<'_>,
635    default_signer: &DefaultSigner,
636    wallet_manager: &mut Option<Rc<RemoteWalletManager>>,
637    vote_authorize: VoteAuthorize,
638    checked: bool,
639) -> Result<CliCommandInfo, CliError> {
640    let vote_account_pubkey =
641        pubkey_of_signer(matches, "vote_account_pubkey", wallet_manager)?.unwrap();
642    let (authorized, authorized_pubkey) = signer_of(matches, "authorized", wallet_manager)?;
643
644    let sign_only = matches.is_present(SIGN_ONLY_ARG.name);
645    let dump_transaction_message = matches.is_present(DUMP_TRANSACTION_MESSAGE.name);
646    let blockhash_query = BlockhashQuery::new_from_matches(matches);
647    let nonce_account = pubkey_of(matches, NONCE_ARG.name);
648    let memo = matches.value_of(MEMO_ARG.name).map(String::from);
649    let (nonce_authority, nonce_authority_pubkey) =
650        signer_of(matches, NONCE_AUTHORITY_ARG.name, wallet_manager)?;
651    let (fee_payer, fee_payer_pubkey) = signer_of(matches, FEE_PAYER_ARG.name, wallet_manager)?;
652    let compute_unit_price = value_of(matches, COMPUTE_UNIT_PRICE_ARG.name);
653
654    let use_v2_instruction = matches.is_present("use_v2_instruction");
655    if use_v2_instruction && vote_authorize != VoteAuthorize::Voter {
656        return Err(CliError::BadParameter(
657            "--use-v2-instruction is only supported for voter authorization".to_owned(),
658        ));
659    }
660
661    let mut bulk_signers = vec![fee_payer, authorized];
662
663    let new_authorized_pubkey = if checked {
664        let (new_authorized_signer, new_authorized_pubkey) =
665            signer_of(matches, "new_authorized", wallet_manager)?;
666        bulk_signers.push(new_authorized_signer);
667        new_authorized_pubkey.unwrap()
668    } else {
669        pubkey_of_signer(matches, "new_authorized_pubkey", wallet_manager)?.unwrap()
670    };
671    if nonce_account.is_some() {
672        bulk_signers.push(nonce_authority);
673    }
674    let signer_info =
675        default_signer.generate_unique_signers(bulk_signers, matches, wallet_manager)?;
676
677    Ok(CliCommandInfo {
678        command: CliCommand::VoteAuthorize {
679            vote_account_pubkey,
680            new_authorized_pubkey,
681            vote_authorize,
682            use_v2_instruction,
683            sign_only,
684            dump_transaction_message,
685            blockhash_query,
686            nonce_account,
687            nonce_authority: signer_info.index_of(nonce_authority_pubkey).unwrap(),
688            memo,
689            fee_payer: signer_info.index_of(fee_payer_pubkey).unwrap(),
690            authorized: signer_info.index_of(authorized_pubkey).unwrap(),
691            new_authorized: if checked {
692                signer_info.index_of(Some(new_authorized_pubkey))
693            } else {
694                None
695            },
696            compute_unit_price,
697        },
698        signers: signer_info.signers,
699    })
700}
701
702pub fn parse_vote_update_validator(
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 (new_identity_account, new_identity_pubkey) =
710        signer_of(matches, "new_identity_account", wallet_manager)?;
711    let (authorized_withdrawer, authorized_withdrawer_pubkey) =
712        signer_of(matches, "authorized_withdrawer", wallet_manager)?;
713
714    let sign_only = matches.is_present(SIGN_ONLY_ARG.name);
715    let dump_transaction_message = matches.is_present(DUMP_TRANSACTION_MESSAGE.name);
716    let blockhash_query = BlockhashQuery::new_from_matches(matches);
717    let nonce_account = pubkey_of(matches, NONCE_ARG.name);
718    let memo = matches.value_of(MEMO_ARG.name).map(String::from);
719    let (nonce_authority, nonce_authority_pubkey) =
720        signer_of(matches, NONCE_AUTHORITY_ARG.name, wallet_manager)?;
721    let (fee_payer, fee_payer_pubkey) = signer_of(matches, FEE_PAYER_ARG.name, wallet_manager)?;
722    let compute_unit_price = value_of(matches, COMPUTE_UNIT_PRICE_ARG.name);
723
724    let mut bulk_signers = vec![fee_payer, authorized_withdrawer, new_identity_account];
725    if nonce_account.is_some() {
726        bulk_signers.push(nonce_authority);
727    }
728    let signer_info =
729        default_signer.generate_unique_signers(bulk_signers, matches, wallet_manager)?;
730
731    Ok(CliCommandInfo {
732        command: CliCommand::VoteUpdateValidator {
733            vote_account_pubkey,
734            new_identity_account: signer_info.index_of(new_identity_pubkey).unwrap(),
735            withdraw_authority: signer_info.index_of(authorized_withdrawer_pubkey).unwrap(),
736            sign_only,
737            dump_transaction_message,
738            blockhash_query,
739            nonce_account,
740            nonce_authority: signer_info.index_of(nonce_authority_pubkey).unwrap(),
741            memo,
742            fee_payer: signer_info.index_of(fee_payer_pubkey).unwrap(),
743            compute_unit_price,
744        },
745        signers: signer_info.signers,
746    })
747}
748
749pub fn parse_vote_update_commission(
750    matches: &ArgMatches<'_>,
751    default_signer: &DefaultSigner,
752    wallet_manager: &mut Option<Rc<RemoteWalletManager>>,
753) -> Result<CliCommandInfo, CliError> {
754    let vote_account_pubkey =
755        pubkey_of_signer(matches, "vote_account_pubkey", wallet_manager)?.unwrap();
756    let (authorized_withdrawer, authorized_withdrawer_pubkey) =
757        signer_of(matches, "authorized_withdrawer", wallet_manager)?;
758    let commission = value_t_or_exit!(matches, "commission", u8);
759
760    let sign_only = matches.is_present(SIGN_ONLY_ARG.name);
761    let dump_transaction_message = matches.is_present(DUMP_TRANSACTION_MESSAGE.name);
762    let blockhash_query = BlockhashQuery::new_from_matches(matches);
763    let nonce_account = pubkey_of(matches, NONCE_ARG.name);
764    let memo = matches.value_of(MEMO_ARG.name).map(String::from);
765    let (nonce_authority, nonce_authority_pubkey) =
766        signer_of(matches, NONCE_AUTHORITY_ARG.name, wallet_manager)?;
767    let (fee_payer, fee_payer_pubkey) = signer_of(matches, FEE_PAYER_ARG.name, wallet_manager)?;
768    let compute_unit_price = value_of(matches, COMPUTE_UNIT_PRICE_ARG.name);
769
770    let mut bulk_signers = vec![fee_payer, authorized_withdrawer];
771    if nonce_account.is_some() {
772        bulk_signers.push(nonce_authority);
773    }
774    let signer_info =
775        default_signer.generate_unique_signers(bulk_signers, matches, wallet_manager)?;
776
777    Ok(CliCommandInfo {
778        command: CliCommand::VoteUpdateCommission {
779            vote_account_pubkey,
780            commission,
781            withdraw_authority: signer_info.index_of(authorized_withdrawer_pubkey).unwrap(),
782            sign_only,
783            dump_transaction_message,
784            blockhash_query,
785            nonce_account,
786            nonce_authority: signer_info.index_of(nonce_authority_pubkey).unwrap(),
787            memo,
788            fee_payer: signer_info.index_of(fee_payer_pubkey).unwrap(),
789            compute_unit_price,
790        },
791        signers: signer_info.signers,
792    })
793}
794
795pub fn parse_vote_get_account_command(
796    matches: &ArgMatches<'_>,
797    wallet_manager: &mut Option<Rc<RemoteWalletManager>>,
798) -> Result<CliCommandInfo, CliError> {
799    let vote_account_pubkey =
800        pubkey_of_signer(matches, "vote_account_pubkey", wallet_manager)?.unwrap();
801    let use_lamports_unit = matches.is_present("lamports");
802    let use_csv = matches.is_present("csv");
803    let with_rewards = if matches.is_present("with_rewards") {
804        Some(value_of(matches, "num_rewards_epochs").unwrap())
805    } else {
806        None
807    };
808    let starting_epoch = value_of(matches, "starting_epoch");
809    Ok(CliCommandInfo::without_signers(
810        CliCommand::ShowVoteAccount {
811            pubkey: vote_account_pubkey,
812            use_lamports_unit,
813            use_csv,
814            with_rewards,
815            starting_epoch,
816        },
817    ))
818}
819
820pub fn parse_withdraw_from_vote_account(
821    matches: &ArgMatches<'_>,
822    default_signer: &DefaultSigner,
823    wallet_manager: &mut Option<Rc<RemoteWalletManager>>,
824) -> Result<CliCommandInfo, CliError> {
825    let vote_account_pubkey =
826        pubkey_of_signer(matches, "vote_account_pubkey", wallet_manager)?.unwrap();
827    let destination_account_pubkey =
828        pubkey_of_signer(matches, "destination_account_pubkey", wallet_manager)?.unwrap();
829    let mut withdraw_amount = SpendAmount::new_from_matches(matches, "amount")?;
830    // As a safeguard for vote accounts for running validators, `ALL` withdraws only the amount in
831    // excess of the rent-exempt minimum. In order to close the account with this subcommand, a
832    // validator must specify the withdrawal amount precisely.
833    if withdraw_amount == SpendAmount::All {
834        withdraw_amount = SpendAmount::RentExempt;
835    }
836
837    let (withdraw_authority, withdraw_authority_pubkey) =
838        signer_of(matches, "authorized_withdrawer", wallet_manager)?;
839
840    let sign_only = matches.is_present(SIGN_ONLY_ARG.name);
841    let dump_transaction_message = matches.is_present(DUMP_TRANSACTION_MESSAGE.name);
842    let blockhash_query = BlockhashQuery::new_from_matches(matches);
843    let nonce_account = pubkey_of(matches, NONCE_ARG.name);
844    let memo = matches.value_of(MEMO_ARG.name).map(String::from);
845    let (nonce_authority, nonce_authority_pubkey) =
846        signer_of(matches, NONCE_AUTHORITY_ARG.name, wallet_manager)?;
847    let (fee_payer, fee_payer_pubkey) = signer_of(matches, FEE_PAYER_ARG.name, wallet_manager)?;
848    let compute_unit_price = value_of(matches, COMPUTE_UNIT_PRICE_ARG.name);
849
850    let mut bulk_signers = vec![fee_payer, withdraw_authority];
851    if nonce_account.is_some() {
852        bulk_signers.push(nonce_authority);
853    }
854    let signer_info =
855        default_signer.generate_unique_signers(bulk_signers, matches, wallet_manager)?;
856
857    Ok(CliCommandInfo {
858        command: CliCommand::WithdrawFromVoteAccount {
859            vote_account_pubkey,
860            destination_account_pubkey,
861            withdraw_authority: signer_info.index_of(withdraw_authority_pubkey).unwrap(),
862            withdraw_amount,
863            sign_only,
864            dump_transaction_message,
865            blockhash_query,
866            nonce_account,
867            nonce_authority: signer_info.index_of(nonce_authority_pubkey).unwrap(),
868            memo,
869            fee_payer: signer_info.index_of(fee_payer_pubkey).unwrap(),
870            compute_unit_price,
871        },
872        signers: signer_info.signers,
873    })
874}
875
876pub fn parse_close_vote_account(
877    matches: &ArgMatches<'_>,
878    default_signer: &DefaultSigner,
879    wallet_manager: &mut Option<Rc<RemoteWalletManager>>,
880) -> Result<CliCommandInfo, CliError> {
881    let vote_account_pubkey =
882        pubkey_of_signer(matches, "vote_account_pubkey", wallet_manager)?.unwrap();
883    let destination_account_pubkey =
884        pubkey_of_signer(matches, "destination_account_pubkey", wallet_manager)?.unwrap();
885
886    let (withdraw_authority, withdraw_authority_pubkey) =
887        signer_of(matches, "authorized_withdrawer", wallet_manager)?;
888    let (fee_payer, fee_payer_pubkey) = signer_of(matches, FEE_PAYER_ARG.name, wallet_manager)?;
889
890    let signer_info = default_signer.generate_unique_signers(
891        vec![fee_payer, withdraw_authority],
892        matches,
893        wallet_manager,
894    )?;
895    let memo = matches.value_of(MEMO_ARG.name).map(String::from);
896    let compute_unit_price = value_of(matches, COMPUTE_UNIT_PRICE_ARG.name);
897
898    Ok(CliCommandInfo {
899        command: CliCommand::CloseVoteAccount {
900            vote_account_pubkey,
901            destination_account_pubkey,
902            withdraw_authority: signer_info.index_of(withdraw_authority_pubkey).unwrap(),
903            memo,
904            fee_payer: signer_info.index_of(fee_payer_pubkey).unwrap(),
905            compute_unit_price,
906        },
907        signers: signer_info.signers,
908    })
909}
910
911#[allow(clippy::too_many_arguments)]
912pub async fn process_create_vote_account(
913    rpc_client: &RpcClient,
914    config: &CliConfig<'_>,
915    vote_account: SignerIndex,
916    seed: &Option<String>,
917    identity_account: SignerIndex,
918    authorized_voter: &Option<Pubkey>,
919    authorized_withdrawer: Pubkey,
920    // VoteInit (v1) args.
921    commission: Option<u8>,
922    // VoteInitV2 args (SIMD-0464).
923    use_v2_instruction: bool,
924    inflation_rewards_commission_bps: Option<u16>,
925    inflation_rewards_collector: Option<&Pubkey>,
926    block_revenue_commission_bps: Option<u16>,
927    block_revenue_collector: Option<&Pubkey>,
928    // Common args.
929    sign_only: bool,
930    dump_transaction_message: bool,
931    blockhash_query: &BlockhashQuery,
932    nonce_account: Option<&Pubkey>,
933    nonce_authority: SignerIndex,
934    memo: Option<&String>,
935    fee_payer: SignerIndex,
936    compute_unit_price: Option<u64>,
937) -> ProcessResult {
938    let vote_account = config.signers[vote_account];
939    let vote_account_pubkey = vote_account.pubkey();
940    let vote_account_address = if let Some(seed) = seed {
941        Pubkey::create_with_seed(&vote_account_pubkey, seed, &solana_vote_program::id())?
942    } else {
943        vote_account_pubkey
944    };
945    check_unique_pubkeys(
946        (&config.signers[0].pubkey(), "cli keypair".to_string()),
947        (&vote_account_address, "vote_account".to_string()),
948    )?;
949
950    let identity_account = config.signers[identity_account];
951    let identity_pubkey = identity_account.pubkey();
952    check_unique_pubkeys(
953        (&vote_account_address, "vote_account".to_string()),
954        (&identity_pubkey, "identity_pubkey".to_string()),
955    )?;
956
957    // Determine whether to use VoteInit (v1) or VoteInitV2.
958    // --use-v2-instruction trumps everything else; always uses VoteInitV2.
959    // If the flag is not provided:
960    // * If sign-only, default to VoteInit (v1).
961    // * If !sign-only, check the feature status.
962    let use_v2 = if use_v2_instruction {
963        // --use-v2-instruction provided; use VoteInitV2.
964        true
965    } else if sign_only {
966        // Sign-only without explicit flag, default to VoteInit (v1).
967        false
968    } else {
969        // Check SIMD-0464 feature gate status.
970        get_feature_is_active(rpc_client, &vote_account_initialize_v2::id())
971            .await
972            .unwrap_or(false)
973    };
974
975    // Validate that VoteInitV2-only args aren't provided when using
976    // VoteInit (v1).
977    let has_v2_args = inflation_rewards_commission_bps.is_some()
978        || inflation_rewards_collector.is_some()
979        || block_revenue_commission_bps.is_some()
980        || block_revenue_collector.is_some();
981
982    if !use_v2 && has_v2_args {
983        return Err(CliError::BadParameter(
984            "VoteInitV2 arguments (--inflation-rewards-commission-bps, \
985             --inflation-rewards-collector, --block-revenue-commission-bps, \
986             --block-revenue-collector) require --use-v2-instruction flag or SIMD-0464 feature to \
987             be active."
988                .to_owned(),
989        )
990        .into());
991    }
992
993    let required_balance = rpc_client
994        .get_minimum_balance_for_rent_exemption(VoteStateV4::size_of())
995        .await?
996        .max(1);
997    let amount = SpendAmount::Some(required_balance);
998
999    let fee_payer = config.signers[fee_payer];
1000    let nonce_authority = config.signers[nonce_authority];
1001    let space = VoteStateV4::size_of() as u64;
1002
1003    let compute_unit_limit = match blockhash_query {
1004        BlockhashQuery::Static(_) | BlockhashQuery::Validated(_, _) => ComputeUnitLimit::Default,
1005        BlockhashQuery::Rpc(_) => ComputeUnitLimit::Simulated,
1006    };
1007
1008    // Derive BLS keypair from the identity keypair and generate proof of
1009    // possession for VoteInitV2.
1010    let bls_data = if use_v2 {
1011        let derived_bls_keypair =
1012            BLSKeypair::derive_from_signer(identity_account, BLS_KEYPAIR_DERIVE_SEED).map_err(
1013                |e| CliError::BadParameter(format!("Failed to derive BLS keypair: {e}")),
1014            )?;
1015        let (bls_pubkey, bls_proof_of_possession) =
1016            create_bls_proof_of_possession(&vote_account_address, &derived_bls_keypair);
1017        Some((bls_pubkey, bls_proof_of_possession))
1018    } else {
1019        None
1020    };
1021
1022    let build_message = |lamports| {
1023        let mut create_vote_account_config = CreateVoteAccountConfig {
1024            space,
1025            ..CreateVoteAccountConfig::default()
1026        };
1027        let to = if let Some(seed) = seed {
1028            create_vote_account_config.with_seed = Some((&vote_account_pubkey, seed));
1029            &vote_account_address
1030        } else {
1031            &vote_account_pubkey
1032        };
1033
1034        let ixs = if use_v2 {
1035            let (bls_pubkey, bls_proof_of_possession) = bls_data.unwrap();
1036            let vote_init = VoteInitV2 {
1037                node_pubkey: identity_pubkey,
1038                authorized_voter: authorized_voter.unwrap_or(identity_pubkey),
1039                authorized_voter_bls_pubkey: bls_pubkey,
1040                authorized_voter_bls_proof_of_possession: bls_proof_of_possession,
1041                authorized_withdrawer,
1042                inflation_rewards_commission_bps: inflation_rewards_commission_bps
1043                    .or_else(|| commission.map(|c| (c as u16).saturating_mul(100))) // u16::MAX > u8::MAX * 100
1044                    .unwrap_or(10000),
1045                block_revenue_commission_bps: block_revenue_commission_bps.unwrap_or(10000),
1046            };
1047            let inflation_rewards_collector_key = inflation_rewards_collector
1048                .copied()
1049                .unwrap_or(vote_account_address);
1050            let block_revenue_collector_key =
1051                block_revenue_collector.copied().unwrap_or(identity_pubkey);
1052            vote_instruction::create_account_with_config_v2(
1053                &config.signers[0].pubkey(),
1054                to,
1055                &vote_init,
1056                &inflation_rewards_collector_key,
1057                &block_revenue_collector_key,
1058                lamports,
1059                create_vote_account_config,
1060            )
1061        } else {
1062            let vote_init = VoteInit {
1063                node_pubkey: identity_pubkey,
1064                authorized_voter: authorized_voter.unwrap_or(identity_pubkey),
1065                authorized_withdrawer,
1066                commission: commission.unwrap_or(100),
1067            };
1068            vote_instruction::create_account_with_config(
1069                &config.signers[0].pubkey(),
1070                to,
1071                &vote_init,
1072                lamports,
1073                create_vote_account_config,
1074            )
1075        };
1076
1077        let ixs = ixs
1078            .with_memo(memo)
1079            .with_compute_unit_config(&ComputeUnitConfig {
1080                compute_unit_price,
1081                compute_unit_limit,
1082            });
1083
1084        if let Some(nonce_account) = &nonce_account {
1085            Message::new_with_nonce(
1086                ixs,
1087                Some(&fee_payer.pubkey()),
1088                nonce_account,
1089                &nonce_authority.pubkey(),
1090            )
1091        } else {
1092            Message::new(&ixs, Some(&fee_payer.pubkey()))
1093        }
1094    };
1095
1096    let recent_blockhash = blockhash_query
1097        .get_blockhash(rpc_client, config.commitment)
1098        .await?;
1099
1100    let (message, _) = resolve_spend_tx_and_check_account_balances(
1101        rpc_client,
1102        sign_only,
1103        amount,
1104        &recent_blockhash,
1105        &config.signers[0].pubkey(),
1106        &fee_payer.pubkey(),
1107        compute_unit_limit,
1108        build_message,
1109        config.commitment,
1110    )
1111    .await?;
1112
1113    if !sign_only {
1114        if let Ok(response) = rpc_client
1115            .get_account_with_commitment(&vote_account_address, config.commitment)
1116            .await
1117            && let Some(vote_account) = response.value
1118        {
1119            let err_msg = if vote_account.owner == solana_vote_program::id() {
1120                format!("Vote account {vote_account_address} already exists")
1121            } else {
1122                format!("Account {vote_account_address} already exists and is not a vote account")
1123            };
1124            return Err(CliError::BadParameter(err_msg).into());
1125        }
1126
1127        if let Some(nonce_account) = &nonce_account {
1128            let nonce_account =
1129                solana_rpc_client_nonce_utils::nonblocking::get_account_with_commitment(
1130                    rpc_client,
1131                    nonce_account,
1132                    config.commitment,
1133                )
1134                .await?;
1135            check_nonce_account(&nonce_account, &nonce_authority.pubkey(), &recent_blockhash)?;
1136        }
1137    }
1138
1139    let mut tx = Transaction::new_unsigned(message);
1140    if sign_only {
1141        tx.try_partial_sign(&config.signers, recent_blockhash)?;
1142        return_signers_with_config(
1143            &tx,
1144            &config.output_format,
1145            &ReturnSignersConfig {
1146                dump_transaction_message,
1147            },
1148        )
1149    } else {
1150        tx.try_sign(&config.signers, recent_blockhash)?;
1151        let result = rpc_client
1152            .send_and_confirm_transaction_with_spinner_and_config(
1153                &tx,
1154                config.commitment,
1155                config.send_transaction_config,
1156            )
1157            .await;
1158        log_instruction_custom_error::<SystemError>(result, config)
1159    }
1160}
1161
1162#[allow(clippy::too_many_arguments)]
1163pub async fn process_vote_authorize(
1164    rpc_client: &RpcClient,
1165    config: &CliConfig<'_>,
1166    vote_account_pubkey: &Pubkey,
1167    new_authorized_pubkey: &Pubkey,
1168    vote_authorize: VoteAuthorize,
1169    use_v2_instruction: bool,
1170    authorized: SignerIndex,
1171    new_authorized: Option<SignerIndex>,
1172    sign_only: bool,
1173    dump_transaction_message: bool,
1174    blockhash_query: &BlockhashQuery,
1175    nonce_account: Option<Pubkey>,
1176    nonce_authority: SignerIndex,
1177    memo: Option<&String>,
1178    fee_payer: SignerIndex,
1179    compute_unit_price: Option<u64>,
1180) -> ProcessResult {
1181    let authorized = config.signers[authorized];
1182    let new_authorized_signer = new_authorized.map(|index| config.signers[index]);
1183    let is_checked = new_authorized_signer.is_some();
1184
1185    let vote_state = if !sign_only {
1186        Some(
1187            get_vote_account(rpc_client, vote_account_pubkey, config.commitment)
1188                .await?
1189                .1,
1190        )
1191    } else {
1192        None
1193    };
1194
1195    // Determine whether to use Voter or VoterWithBLS for voter authorization.
1196    // 1. If not VoteAuthorize::Voter -> false (Withdrawer doesn't use BLS)
1197    // 2. If --use-v2-instruction provided: true (explicit request)
1198    // 3. If sign_only (no flag): false (default to v1)
1199    // 4. If vote account has BLS key: true (must use VoterWithBLS, Voter will fail)
1200    // 5. If feature active: true
1201    // 6. Otherwise: false
1202    let use_bls = if !matches!(vote_authorize, VoteAuthorize::Voter) {
1203        // Withdrawer authorization doesn't use BLS.
1204        false
1205    } else if use_v2_instruction {
1206        // Explicit request via flag.
1207        true
1208    } else if sign_only {
1209        // Sign-only without explicit flag, default to Voter (v1).
1210        false
1211    } else if vote_state
1212        .as_ref()
1213        .map(|vs| vs.bls_pubkey_compressed.is_some())
1214        .unwrap_or(false)
1215    {
1216        // Account has BLS key - must use VoterWithBLS (Voter will fail).
1217        true
1218    } else {
1219        // Check SIMD-0387 feature gate status.
1220        get_feature_is_active(rpc_client, &bls_pubkey_management_in_vote_account::id())
1221            .await
1222            .unwrap_or(false)
1223    };
1224
1225    match vote_authorize {
1226        VoteAuthorize::Voter => {
1227            if let Some(vote_state) = vote_state {
1228                let current_epoch = rpc_client.get_epoch_info().await?.epoch;
1229                let current_authorized_voter = vote_state
1230                    .authorized_voters
1231                    .get_authorized_voter(current_epoch)
1232                    .ok_or_else(|| {
1233                        CliError::RpcRequestError(
1234                            "Invalid vote account state; no authorized voters found".to_string(),
1235                        )
1236                    })?;
1237                check_current_authority(
1238                    &[current_authorized_voter, vote_state.authorized_withdrawer],
1239                    &authorized.pubkey(),
1240                )?;
1241                if let Some(signer) = new_authorized_signer
1242                    && signer.is_interactive()
1243                {
1244                    return Err(CliError::BadParameter(format!(
1245                        "invalid new authorized vote signer {new_authorized_pubkey:?}. \
1246                         Interactive vote signers not supported"
1247                    ))
1248                    .into());
1249                }
1250            }
1251        }
1252        VoteAuthorize::Withdrawer => {
1253            check_unique_pubkeys(
1254                (&authorized.pubkey(), "authorized_account".to_string()),
1255                (new_authorized_pubkey, "new_authorized_pubkey".to_string()),
1256            )?;
1257            if let Some(vote_state) = vote_state {
1258                check_current_authority(&[vote_state.authorized_withdrawer], &authorized.pubkey())?
1259            }
1260        }
1261        VoteAuthorize::VoterWithBLS(_) => {
1262            // We should never reach here.
1263            // This variant is constructed below, not passed in.
1264            unreachable!("VoterWithBLS should not be passed as vote_authorize parameter");
1265        }
1266    }
1267
1268    // Derive BLS keypair from the new authorized voter and generate proof of
1269    // possession for VoterWithBLS.
1270    let effective_vote_authorize = if use_bls {
1271        if !is_checked {
1272            return Err(CliError::BadParameter(
1273                "BLS key derivation requires the new voter to be a signer. Use \
1274                 `vote-authorize-voter-checked` instead."
1275                    .to_owned(),
1276            )
1277            .into());
1278        }
1279        let new_authorized_signer = new_authorized_signer.unwrap();
1280        let derived_bls_keypair =
1281            BLSKeypair::derive_from_signer(new_authorized_signer, BLS_KEYPAIR_DERIVE_SEED)
1282                .map_err(|e| {
1283                    CliError::BadParameter(format!("Failed to derive BLS keypair: {e}"))
1284                })?;
1285        let (bls_pubkey, bls_proof_of_possession) =
1286            create_bls_proof_of_possession(vote_account_pubkey, &derived_bls_keypair);
1287        VoteAuthorize::VoterWithBLS(VoterWithBLSArgs {
1288            bls_pubkey,
1289            bls_proof_of_possession,
1290        })
1291    } else {
1292        vote_authorize
1293    };
1294
1295    let vote_ix = if is_checked {
1296        vote_instruction::authorize_checked(
1297            vote_account_pubkey,      // vote account to update
1298            &authorized.pubkey(),     // current authorized
1299            new_authorized_pubkey,    // new vote signer/withdrawer
1300            effective_vote_authorize, // vote or withdraw
1301        )
1302    } else {
1303        vote_instruction::authorize(
1304            vote_account_pubkey,      // vote account to update
1305            &authorized.pubkey(),     // current authorized
1306            new_authorized_pubkey,    // new vote signer/withdrawer
1307            effective_vote_authorize, // vote or withdraw
1308        )
1309    };
1310
1311    let compute_unit_limit = match blockhash_query {
1312        BlockhashQuery::Static(_) | BlockhashQuery::Validated(_, _) => ComputeUnitLimit::Default,
1313        BlockhashQuery::Rpc(_) => ComputeUnitLimit::Simulated,
1314    };
1315    let ixs = vec![vote_ix]
1316        .with_memo(memo)
1317        .with_compute_unit_config(&ComputeUnitConfig {
1318            compute_unit_price,
1319            compute_unit_limit,
1320        });
1321
1322    let recent_blockhash = blockhash_query
1323        .get_blockhash(rpc_client, config.commitment)
1324        .await?;
1325
1326    let nonce_authority = config.signers[nonce_authority];
1327    let fee_payer = config.signers[fee_payer];
1328
1329    let mut message = if let Some(nonce_account) = &nonce_account {
1330        Message::new_with_nonce(
1331            ixs,
1332            Some(&fee_payer.pubkey()),
1333            nonce_account,
1334            &nonce_authority.pubkey(),
1335        )
1336    } else {
1337        Message::new(&ixs, Some(&fee_payer.pubkey()))
1338    };
1339    simulate_and_update_compute_unit_limit(&compute_unit_limit, rpc_client, &mut message).await?;
1340    let mut tx = Transaction::new_unsigned(message);
1341
1342    if sign_only {
1343        tx.try_partial_sign(&config.signers, recent_blockhash)?;
1344        return_signers_with_config(
1345            &tx,
1346            &config.output_format,
1347            &ReturnSignersConfig {
1348                dump_transaction_message,
1349            },
1350        )
1351    } else {
1352        tx.try_sign(&config.signers, recent_blockhash)?;
1353        if let Some(nonce_account) = &nonce_account {
1354            let nonce_account =
1355                solana_rpc_client_nonce_utils::nonblocking::get_account_with_commitment(
1356                    rpc_client,
1357                    nonce_account,
1358                    config.commitment,
1359                )
1360                .await?;
1361            check_nonce_account(&nonce_account, &nonce_authority.pubkey(), &recent_blockhash)?;
1362        }
1363        check_account_for_fee_with_commitment(
1364            rpc_client,
1365            &config.signers[0].pubkey(),
1366            &tx.message,
1367            config.commitment,
1368        )
1369        .await?;
1370        let result = rpc_client
1371            .send_and_confirm_transaction_with_spinner_and_config(
1372                &tx,
1373                config.commitment,
1374                config.send_transaction_config,
1375            )
1376            .await;
1377        log_instruction_custom_error::<VoteError>(result, config)
1378    }
1379}
1380
1381#[allow(clippy::too_many_arguments)]
1382pub async fn process_vote_update_validator(
1383    rpc_client: &RpcClient,
1384    config: &CliConfig<'_>,
1385    vote_account_pubkey: &Pubkey,
1386    new_identity_account: SignerIndex,
1387    withdraw_authority: SignerIndex,
1388    sign_only: bool,
1389    dump_transaction_message: bool,
1390    blockhash_query: &BlockhashQuery,
1391    nonce_account: Option<Pubkey>,
1392    nonce_authority: SignerIndex,
1393    memo: Option<&String>,
1394    fee_payer: SignerIndex,
1395    compute_unit_price: Option<u64>,
1396) -> ProcessResult {
1397    let authorized_withdrawer = config.signers[withdraw_authority];
1398    let new_identity_account = config.signers[new_identity_account];
1399    let new_identity_pubkey = new_identity_account.pubkey();
1400    check_unique_pubkeys(
1401        (vote_account_pubkey, "vote_account_pubkey".to_string()),
1402        (&new_identity_pubkey, "new_identity_account".to_string()),
1403    )?;
1404    let recent_blockhash = blockhash_query
1405        .get_blockhash(rpc_client, config.commitment)
1406        .await?;
1407    let compute_unit_limit = match blockhash_query {
1408        BlockhashQuery::Static(_) | BlockhashQuery::Validated(_, _) => ComputeUnitLimit::Default,
1409        BlockhashQuery::Rpc(_) => ComputeUnitLimit::Simulated,
1410    };
1411    let ixs = vec![vote_instruction::update_validator_identity(
1412        vote_account_pubkey,
1413        &authorized_withdrawer.pubkey(),
1414        &new_identity_pubkey,
1415    )]
1416    .with_memo(memo)
1417    .with_compute_unit_config(&ComputeUnitConfig {
1418        compute_unit_price,
1419        compute_unit_limit,
1420    });
1421    let nonce_authority = config.signers[nonce_authority];
1422    let fee_payer = config.signers[fee_payer];
1423
1424    let mut message = if let Some(nonce_account) = &nonce_account {
1425        Message::new_with_nonce(
1426            ixs,
1427            Some(&fee_payer.pubkey()),
1428            nonce_account,
1429            &nonce_authority.pubkey(),
1430        )
1431    } else {
1432        Message::new(&ixs, Some(&fee_payer.pubkey()))
1433    };
1434    simulate_and_update_compute_unit_limit(&compute_unit_limit, rpc_client, &mut message).await?;
1435    let mut tx = Transaction::new_unsigned(message);
1436
1437    if sign_only {
1438        tx.try_partial_sign(&config.signers, recent_blockhash)?;
1439        return_signers_with_config(
1440            &tx,
1441            &config.output_format,
1442            &ReturnSignersConfig {
1443                dump_transaction_message,
1444            },
1445        )
1446    } else {
1447        tx.try_sign(&config.signers, recent_blockhash)?;
1448        if let Some(nonce_account) = &nonce_account {
1449            let nonce_account =
1450                solana_rpc_client_nonce_utils::nonblocking::get_account_with_commitment(
1451                    rpc_client,
1452                    nonce_account,
1453                    config.commitment,
1454                )
1455                .await?;
1456            check_nonce_account(&nonce_account, &nonce_authority.pubkey(), &recent_blockhash)?;
1457        }
1458        check_account_for_fee_with_commitment(
1459            rpc_client,
1460            &config.signers[0].pubkey(),
1461            &tx.message,
1462            config.commitment,
1463        )
1464        .await?;
1465        let result = rpc_client
1466            .send_and_confirm_transaction_with_spinner_and_config(
1467                &tx,
1468                config.commitment,
1469                config.send_transaction_config,
1470            )
1471            .await;
1472        log_instruction_custom_error::<VoteError>(result, config)
1473    }
1474}
1475
1476#[allow(clippy::too_many_arguments)]
1477pub async fn process_vote_update_commission(
1478    rpc_client: &RpcClient,
1479    config: &CliConfig<'_>,
1480    vote_account_pubkey: &Pubkey,
1481    commission: u8,
1482    withdraw_authority: SignerIndex,
1483    sign_only: bool,
1484    dump_transaction_message: bool,
1485    blockhash_query: &BlockhashQuery,
1486    nonce_account: Option<Pubkey>,
1487    nonce_authority: SignerIndex,
1488    memo: Option<&String>,
1489    fee_payer: SignerIndex,
1490    compute_unit_price: Option<u64>,
1491) -> ProcessResult {
1492    let authorized_withdrawer = config.signers[withdraw_authority];
1493    let recent_blockhash = blockhash_query
1494        .get_blockhash(rpc_client, config.commitment)
1495        .await?;
1496    let compute_unit_limit = match blockhash_query {
1497        BlockhashQuery::Static(_) | BlockhashQuery::Validated(_, _) => ComputeUnitLimit::Default,
1498        BlockhashQuery::Rpc(_) => ComputeUnitLimit::Simulated,
1499    };
1500    let ixs = vec![vote_instruction::update_commission(
1501        vote_account_pubkey,
1502        &authorized_withdrawer.pubkey(),
1503        commission,
1504    )]
1505    .with_memo(memo)
1506    .with_compute_unit_config(&ComputeUnitConfig {
1507        compute_unit_price,
1508        compute_unit_limit,
1509    });
1510    let nonce_authority = config.signers[nonce_authority];
1511    let fee_payer = config.signers[fee_payer];
1512
1513    let mut message = if let Some(nonce_account) = &nonce_account {
1514        Message::new_with_nonce(
1515            ixs,
1516            Some(&fee_payer.pubkey()),
1517            nonce_account,
1518            &nonce_authority.pubkey(),
1519        )
1520    } else {
1521        Message::new(&ixs, Some(&fee_payer.pubkey()))
1522    };
1523    simulate_and_update_compute_unit_limit(&compute_unit_limit, rpc_client, &mut message).await?;
1524    let mut tx = Transaction::new_unsigned(message);
1525    if sign_only {
1526        tx.try_partial_sign(&config.signers, recent_blockhash)?;
1527        return_signers_with_config(
1528            &tx,
1529            &config.output_format,
1530            &ReturnSignersConfig {
1531                dump_transaction_message,
1532            },
1533        )
1534    } else {
1535        tx.try_sign(&config.signers, recent_blockhash)?;
1536        if let Some(nonce_account) = &nonce_account {
1537            let nonce_account =
1538                solana_rpc_client_nonce_utils::nonblocking::get_account_with_commitment(
1539                    rpc_client,
1540                    nonce_account,
1541                    config.commitment,
1542                )
1543                .await?;
1544            check_nonce_account(&nonce_account, &nonce_authority.pubkey(), &recent_blockhash)?;
1545        }
1546        check_account_for_fee_with_commitment(
1547            rpc_client,
1548            &config.signers[0].pubkey(),
1549            &tx.message,
1550            config.commitment,
1551        )
1552        .await?;
1553        let result = rpc_client
1554            .send_and_confirm_transaction_with_spinner_and_config(
1555                &tx,
1556                config.commitment,
1557                config.send_transaction_config,
1558            )
1559            .await;
1560        log_instruction_custom_error::<VoteError>(result, config)
1561    }
1562}
1563
1564pub(crate) async fn get_vote_account(
1565    rpc_client: &RpcClient,
1566    vote_account_pubkey: &Pubkey,
1567    commitment_config: CommitmentConfig,
1568) -> Result<(Account, VoteStateV4), Box<dyn std::error::Error>> {
1569    let vote_account = rpc_client
1570        .get_account_with_commitment(vote_account_pubkey, commitment_config)
1571        .await?
1572        .value
1573        .ok_or_else(|| {
1574            CliError::RpcRequestError(format!("{vote_account_pubkey:?} account does not exist"))
1575        })?;
1576
1577    if vote_account.owner != solana_vote_program::id() {
1578        return Err(CliError::RpcRequestError(format!(
1579            "{vote_account_pubkey:?} is not a vote account"
1580        ))
1581        .into());
1582    }
1583    let vote_state =
1584        VoteStateV4::deserialize(&vote_account.data, vote_account_pubkey).map_err(|_| {
1585            CliError::RpcRequestError(
1586                "Account data could not be deserialized to vote state".to_string(),
1587            )
1588        })?;
1589
1590    Ok((vote_account, vote_state))
1591}
1592
1593pub async fn process_show_vote_account(
1594    rpc_client: &RpcClient,
1595    config: &CliConfig<'_>,
1596    vote_account_address: &Pubkey,
1597    use_lamports_unit: bool,
1598    use_csv: bool,
1599    with_rewards: Option<usize>,
1600    starting_epoch: Option<u64>,
1601) -> ProcessResult {
1602    let (vote_account, vote_state) =
1603        get_vote_account(rpc_client, vote_account_address, config.commitment).await?;
1604
1605    let epoch_schedule = rpc_client.get_epoch_schedule().await?;
1606    let tvc_activation_slot = rpc_client
1607        .get_account_with_commitment(
1608            &agave_feature_set::timely_vote_credits::id(),
1609            config.commitment,
1610        )
1611        .await
1612        .ok()
1613        .and_then(|response| response.value)
1614        .and_then(|account| from_account(&account))
1615        .and_then(|feature| feature.activated_at);
1616    let tvc_activation_epoch = tvc_activation_slot.map(|s| epoch_schedule.get_epoch(s));
1617
1618    let ag_is_active = get_feature_is_active(rpc_client, &alpenglow::id())
1619        .await
1620        .unwrap_or(false);
1621    let ag_genesis_cert = if ag_is_active {
1622        rpc_client.get_ag_genesis_cert().await?
1623    } else {
1624        None
1625    };
1626    let votes_observed = VotesObserved::new(&vote_state, &ag_genesis_cert);
1627    let epoch_voting_history = get_epoch_history(
1628        &epoch_schedule,
1629        &vote_state,
1630        &ag_genesis_cert,
1631        tvc_activation_epoch,
1632    );
1633
1634    let epoch_rewards = if let Some(num_epochs) = with_rewards {
1635        match crate::stake::fetch_epoch_rewards(
1636            rpc_client,
1637            vote_account_address,
1638            num_epochs,
1639            starting_epoch,
1640        )
1641        .await
1642        {
1643            Ok(rewards) => Some(rewards),
1644            Err(error) => {
1645                eprintln!("Failed to fetch epoch rewards: {error:?}");
1646                None
1647            }
1648        }
1649    } else {
1650        None
1651    };
1652
1653    let vote_account_data = CliVoteAccount {
1654        account_balance: vote_account.lamports,
1655        validator_identity: vote_state.node_pubkey.to_string(),
1656        authorized_voters: (&vote_state.authorized_voters).into(),
1657        authorized_withdrawer: vote_state.authorized_withdrawer.to_string(),
1658        credits: vote_state.credits(),
1659        commission: vote_state
1660            .inflation_rewards_commission_bps
1661            .div_ceil(100)
1662            .min(u8::MAX as u16) as u8,
1663        root_slot: vote_state.root_slot,
1664        recent_timestamp: vote_state.last_timestamp.clone(),
1665        votes_observed,
1666        epoch_voting_history,
1667        use_lamports_unit,
1668        use_csv,
1669        epoch_rewards,
1670        inflation_rewards_commission_bps: vote_state.inflation_rewards_commission_bps,
1671        inflation_rewards_collector: vote_state.inflation_rewards_collector.to_string(),
1672        block_revenue_collector: vote_state.block_revenue_collector.to_string(),
1673        block_revenue_commission_bps: vote_state.block_revenue_commission_bps,
1674        pending_delegator_rewards: vote_state.pending_delegator_rewards,
1675        bls_pubkey_compressed: vote_state
1676            .bls_pubkey_compressed
1677            .map(|bytes| bs58::encode(bytes).into_string()),
1678    };
1679
1680    Ok(config.output_format.formatted_string(&vote_account_data))
1681}
1682
1683#[allow(clippy::too_many_arguments)]
1684pub async fn process_withdraw_from_vote_account(
1685    rpc_client: &RpcClient,
1686    config: &CliConfig<'_>,
1687    vote_account_pubkey: &Pubkey,
1688    withdraw_authority: SignerIndex,
1689    withdraw_amount: SpendAmount,
1690    destination_account_pubkey: &Pubkey,
1691    sign_only: bool,
1692    dump_transaction_message: bool,
1693    blockhash_query: &BlockhashQuery,
1694    nonce_account: Option<&Pubkey>,
1695    nonce_authority: SignerIndex,
1696    memo: Option<&String>,
1697    fee_payer: SignerIndex,
1698    compute_unit_price: Option<u64>,
1699) -> ProcessResult {
1700    let withdraw_authority = config.signers[withdraw_authority];
1701    let recent_blockhash = blockhash_query
1702        .get_blockhash(rpc_client, config.commitment)
1703        .await?;
1704
1705    let fee_payer = config.signers[fee_payer];
1706    let nonce_authority = config.signers[nonce_authority];
1707
1708    let compute_unit_limit = match blockhash_query {
1709        BlockhashQuery::Static(_) | BlockhashQuery::Validated(_, _) => ComputeUnitLimit::Default,
1710        BlockhashQuery::Rpc(_) => ComputeUnitLimit::Simulated,
1711    };
1712    let build_message = |lamports| {
1713        let ixs = vec![withdraw(
1714            vote_account_pubkey,
1715            &withdraw_authority.pubkey(),
1716            lamports,
1717            destination_account_pubkey,
1718        )]
1719        .with_memo(memo)
1720        .with_compute_unit_config(&ComputeUnitConfig {
1721            compute_unit_price,
1722            compute_unit_limit,
1723        });
1724
1725        if let Some(nonce_account) = &nonce_account {
1726            Message::new_with_nonce(
1727                ixs,
1728                Some(&fee_payer.pubkey()),
1729                nonce_account,
1730                &nonce_authority.pubkey(),
1731            )
1732        } else {
1733            Message::new(&ixs, Some(&fee_payer.pubkey()))
1734        }
1735    };
1736
1737    let (message, _) = resolve_spend_tx_and_check_account_balances(
1738        rpc_client,
1739        sign_only,
1740        withdraw_amount,
1741        &recent_blockhash,
1742        vote_account_pubkey,
1743        &fee_payer.pubkey(),
1744        compute_unit_limit,
1745        build_message,
1746        config.commitment,
1747    )
1748    .await?;
1749
1750    if !sign_only {
1751        let current_balance = rpc_client.get_balance(vote_account_pubkey).await?;
1752        let minimum_balance = rpc_client
1753            .get_minimum_balance_for_rent_exemption(VoteStateV4::size_of())
1754            .await?;
1755        if let SpendAmount::Some(withdraw_amount) = withdraw_amount {
1756            let balance_remaining = current_balance.saturating_sub(withdraw_amount);
1757            if balance_remaining < minimum_balance && balance_remaining != 0 {
1758                return Err(CliError::BadParameter(format!(
1759                    "Withdraw amount too large. The vote account balance must be at least {} SOL \
1760                     to remain rent exempt",
1761                    build_balance_message(minimum_balance, false, false)
1762                ))
1763                .into());
1764            }
1765        }
1766    }
1767
1768    let mut tx = Transaction::new_unsigned(message);
1769
1770    if sign_only {
1771        tx.try_partial_sign(&config.signers, recent_blockhash)?;
1772        return_signers_with_config(
1773            &tx,
1774            &config.output_format,
1775            &ReturnSignersConfig {
1776                dump_transaction_message,
1777            },
1778        )
1779    } else {
1780        tx.try_sign(&config.signers, recent_blockhash)?;
1781        if let Some(nonce_account) = &nonce_account {
1782            let nonce_account =
1783                solana_rpc_client_nonce_utils::nonblocking::get_account_with_commitment(
1784                    rpc_client,
1785                    nonce_account,
1786                    config.commitment,
1787                )
1788                .await?;
1789            check_nonce_account(&nonce_account, &nonce_authority.pubkey(), &recent_blockhash)?;
1790        }
1791        check_account_for_fee_with_commitment(
1792            rpc_client,
1793            &tx.message.account_keys[0],
1794            &tx.message,
1795            config.commitment,
1796        )
1797        .await?;
1798        let result = rpc_client
1799            .send_and_confirm_transaction_with_spinner_and_config(
1800                &tx,
1801                config.commitment,
1802                config.send_transaction_config,
1803            )
1804            .await;
1805        log_instruction_custom_error::<VoteError>(result, config)
1806    }
1807}
1808
1809pub async fn process_close_vote_account(
1810    rpc_client: &RpcClient,
1811    config: &CliConfig<'_>,
1812    vote_account_pubkey: &Pubkey,
1813    withdraw_authority: SignerIndex,
1814    destination_account_pubkey: &Pubkey,
1815    memo: Option<&String>,
1816    fee_payer: SignerIndex,
1817    compute_unit_price: Option<u64>,
1818) -> ProcessResult {
1819    let vote_account_status = rpc_client
1820        .get_vote_accounts_with_config(RpcGetVoteAccountsConfig {
1821            vote_pubkey: Some(vote_account_pubkey.to_string()),
1822            ..RpcGetVoteAccountsConfig::default()
1823        })
1824        .await?;
1825
1826    if let Some(vote_account) = vote_account_status
1827        .current
1828        .into_iter()
1829        .chain(vote_account_status.delinquent)
1830        .next()
1831        && vote_account.activated_stake != 0
1832    {
1833        return Err(format!(
1834            "Cannot close a vote account with active stake: {vote_account_pubkey}"
1835        )
1836        .into());
1837    }
1838
1839    let latest_blockhash = rpc_client.get_latest_blockhash().await?;
1840    let withdraw_authority = config.signers[withdraw_authority];
1841    let fee_payer = config.signers[fee_payer];
1842
1843    let current_balance = rpc_client.get_balance(vote_account_pubkey).await?;
1844
1845    let compute_unit_limit = ComputeUnitLimit::Simulated;
1846    let ixs = vec![withdraw(
1847        vote_account_pubkey,
1848        &withdraw_authority.pubkey(),
1849        current_balance,
1850        destination_account_pubkey,
1851    )]
1852    .with_memo(memo)
1853    .with_compute_unit_config(&ComputeUnitConfig {
1854        compute_unit_price,
1855        compute_unit_limit,
1856    });
1857
1858    let mut message = Message::new(&ixs, Some(&fee_payer.pubkey()));
1859    simulate_and_update_compute_unit_limit(&compute_unit_limit, rpc_client, &mut message).await?;
1860    let mut tx = Transaction::new_unsigned(message);
1861    tx.try_sign(&config.signers, latest_blockhash)?;
1862    check_account_for_fee_with_commitment(
1863        rpc_client,
1864        &tx.message.account_keys[0],
1865        &tx.message,
1866        config.commitment,
1867    )
1868    .await?;
1869    let result = rpc_client
1870        .send_and_confirm_transaction_with_spinner_and_config(
1871            &tx,
1872            config.commitment,
1873            config.send_transaction_config,
1874        )
1875        .await;
1876    log_instruction_custom_error::<VoteError>(result, config)
1877}
1878
1879#[cfg(test)]
1880mod tests {
1881    use {
1882        super::*,
1883        crate::{clap_app::get_clap_app, cli::parse_command},
1884        solana_hash::Hash,
1885        solana_keypair::{Keypair, read_keypair_file, write_keypair},
1886        solana_presigner::Presigner,
1887        solana_rpc_client_nonce_utils::nonblocking::blockhash_query::Source,
1888        solana_signer::Signer,
1889        tempfile::NamedTempFile,
1890    };
1891
1892    fn make_tmp_file() -> (String, NamedTempFile) {
1893        let tmp_file = NamedTempFile::new().unwrap();
1894        (String::from(tmp_file.path().to_str().unwrap()), tmp_file)
1895    }
1896
1897    #[test]
1898    fn test_parse_command() {
1899        let test_commands = get_clap_app("test", "desc", "version");
1900        let keypair = Keypair::new();
1901        let pubkey = keypair.pubkey();
1902        let pubkey_string = pubkey.to_string();
1903        let keypair2 = Keypair::new();
1904        let pubkey2 = keypair2.pubkey();
1905        let pubkey2_string = pubkey2.to_string();
1906        let sig2 = keypair2.sign_message(&[0u8]);
1907        let signer2 = format!("{}={}", keypair2.pubkey(), sig2);
1908
1909        let default_keypair = Keypair::new();
1910        let (default_keypair_file, mut tmp_file) = make_tmp_file();
1911        write_keypair(&default_keypair, tmp_file.as_file_mut()).unwrap();
1912        let default_signer = DefaultSigner::new("", &default_keypair_file);
1913
1914        let blockhash = Hash::default();
1915        let blockhash_string = format!("{blockhash}");
1916        let nonce_account = Pubkey::new_unique();
1917
1918        // Test VoteAuthorize SubCommand
1919        let test_authorize_voter = test_commands.clone().get_matches_from(vec![
1920            "test",
1921            "vote-authorize-voter",
1922            &pubkey_string,
1923            &default_keypair_file,
1924            &pubkey2_string,
1925        ]);
1926        assert_eq!(
1927            parse_command(&test_authorize_voter, &default_signer, &mut None).unwrap(),
1928            CliCommandInfo {
1929                command: CliCommand::VoteAuthorize {
1930                    vote_account_pubkey: pubkey,
1931                    new_authorized_pubkey: pubkey2,
1932                    vote_authorize: VoteAuthorize::Voter,
1933                    use_v2_instruction: false,
1934                    sign_only: false,
1935                    dump_transaction_message: false,
1936                    blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
1937                    nonce_account: None,
1938                    nonce_authority: 0,
1939                    memo: None,
1940                    fee_payer: 0,
1941                    authorized: 0,
1942                    new_authorized: None,
1943                    compute_unit_price: None,
1944                },
1945                signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap())],
1946            }
1947        );
1948
1949        let authorized_keypair = Keypair::new();
1950        let (authorized_keypair_file, mut tmp_file) = make_tmp_file();
1951        write_keypair(&authorized_keypair, tmp_file.as_file_mut()).unwrap();
1952
1953        let test_authorize_voter = test_commands.clone().get_matches_from(vec![
1954            "test",
1955            "vote-authorize-voter",
1956            &pubkey_string,
1957            &authorized_keypair_file,
1958            &pubkey2_string,
1959        ]);
1960        assert_eq!(
1961            parse_command(&test_authorize_voter, &default_signer, &mut None).unwrap(),
1962            CliCommandInfo {
1963                command: CliCommand::VoteAuthorize {
1964                    vote_account_pubkey: pubkey,
1965                    new_authorized_pubkey: pubkey2,
1966                    vote_authorize: VoteAuthorize::Voter,
1967                    use_v2_instruction: false,
1968                    sign_only: false,
1969                    dump_transaction_message: false,
1970                    blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
1971                    nonce_account: None,
1972                    nonce_authority: 0,
1973                    memo: None,
1974                    fee_payer: 0,
1975                    authorized: 1,
1976                    new_authorized: None,
1977                    compute_unit_price: None,
1978                },
1979                signers: vec![
1980                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
1981                    Box::new(read_keypair_file(&authorized_keypair_file).unwrap()),
1982                ],
1983            }
1984        );
1985
1986        let test_authorize_voter = test_commands.clone().get_matches_from(vec![
1987            "test",
1988            "vote-authorize-voter",
1989            &pubkey_string,
1990            &authorized_keypair_file,
1991            &pubkey2_string,
1992            "--blockhash",
1993            &blockhash_string,
1994            "--sign-only",
1995        ]);
1996        assert_eq!(
1997            parse_command(&test_authorize_voter, &default_signer, &mut None).unwrap(),
1998            CliCommandInfo {
1999                command: CliCommand::VoteAuthorize {
2000                    vote_account_pubkey: pubkey,
2001                    new_authorized_pubkey: pubkey2,
2002                    vote_authorize: VoteAuthorize::Voter,
2003                    use_v2_instruction: false,
2004                    sign_only: true,
2005                    dump_transaction_message: false,
2006                    blockhash_query: BlockhashQuery::Static(blockhash),
2007                    nonce_account: None,
2008                    nonce_authority: 0,
2009                    memo: None,
2010                    fee_payer: 0,
2011                    authorized: 1,
2012                    new_authorized: None,
2013                    compute_unit_price: None,
2014                },
2015                signers: vec![
2016                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
2017                    Box::new(read_keypair_file(&authorized_keypair_file).unwrap()),
2018                ],
2019            }
2020        );
2021
2022        let authorized_sig = authorized_keypair.sign_message(&[0u8]);
2023        let authorized_signer = format!("{}={}", authorized_keypair.pubkey(), authorized_sig);
2024        let test_authorize_voter = test_commands.clone().get_matches_from(vec![
2025            "test",
2026            "vote-authorize-voter",
2027            &pubkey_string,
2028            &authorized_keypair.pubkey().to_string(),
2029            &pubkey2_string,
2030            "--blockhash",
2031            &blockhash_string,
2032            "--signer",
2033            &authorized_signer,
2034            "--signer",
2035            &signer2,
2036            "--fee-payer",
2037            &pubkey2_string,
2038            "--nonce",
2039            &nonce_account.to_string(),
2040            "--nonce-authority",
2041            &pubkey2_string,
2042        ]);
2043        assert_eq!(
2044            parse_command(&test_authorize_voter, &default_signer, &mut None).unwrap(),
2045            CliCommandInfo {
2046                command: CliCommand::VoteAuthorize {
2047                    vote_account_pubkey: pubkey,
2048                    new_authorized_pubkey: pubkey2,
2049                    vote_authorize: VoteAuthorize::Voter,
2050                    use_v2_instruction: false,
2051                    sign_only: false,
2052                    dump_transaction_message: false,
2053                    blockhash_query: BlockhashQuery::Validated(
2054                        Source::NonceAccount(nonce_account),
2055                        blockhash
2056                    ),
2057                    nonce_account: Some(nonce_account),
2058                    nonce_authority: 0,
2059                    memo: None,
2060                    fee_payer: 0,
2061                    authorized: 1,
2062                    new_authorized: None,
2063                    compute_unit_price: None,
2064                },
2065                signers: vec![
2066                    Box::new(Presigner::new(&pubkey2, &sig2)),
2067                    Box::new(Presigner::new(
2068                        &authorized_keypair.pubkey(),
2069                        &authorized_sig
2070                    )),
2071                ],
2072            }
2073        );
2074
2075        // Test checked VoteAuthorize SubCommand
2076        let (voter_keypair_file, mut tmp_file) = make_tmp_file();
2077        let voter_keypair = Keypair::new();
2078        write_keypair(&voter_keypair, tmp_file.as_file_mut()).unwrap();
2079
2080        let test_authorize_voter = test_commands.clone().get_matches_from(vec![
2081            "test",
2082            "vote-authorize-voter-checked",
2083            &pubkey_string,
2084            &default_keypair_file,
2085            &voter_keypair_file,
2086        ]);
2087        assert_eq!(
2088            parse_command(&test_authorize_voter, &default_signer, &mut None).unwrap(),
2089            CliCommandInfo {
2090                command: CliCommand::VoteAuthorize {
2091                    vote_account_pubkey: pubkey,
2092                    new_authorized_pubkey: voter_keypair.pubkey(),
2093                    vote_authorize: VoteAuthorize::Voter,
2094                    use_v2_instruction: false,
2095                    sign_only: false,
2096                    dump_transaction_message: false,
2097                    blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
2098                    nonce_account: None,
2099                    nonce_authority: 0,
2100                    memo: None,
2101                    fee_payer: 0,
2102                    authorized: 0,
2103                    new_authorized: Some(1),
2104                    compute_unit_price: None,
2105                },
2106                signers: vec![
2107                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
2108                    Box::new(read_keypair_file(&voter_keypair_file).unwrap())
2109                ],
2110            }
2111        );
2112
2113        let test_authorize_voter = test_commands.clone().get_matches_from(vec![
2114            "test",
2115            "vote-authorize-voter-checked",
2116            &pubkey_string,
2117            &authorized_keypair_file,
2118            &voter_keypair_file,
2119        ]);
2120        assert_eq!(
2121            parse_command(&test_authorize_voter, &default_signer, &mut None).unwrap(),
2122            CliCommandInfo {
2123                command: CliCommand::VoteAuthorize {
2124                    vote_account_pubkey: pubkey,
2125                    new_authorized_pubkey: voter_keypair.pubkey(),
2126                    vote_authorize: VoteAuthorize::Voter,
2127                    use_v2_instruction: false,
2128                    sign_only: false,
2129                    dump_transaction_message: false,
2130                    blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
2131                    nonce_account: None,
2132                    nonce_authority: 0,
2133                    memo: None,
2134                    fee_payer: 0,
2135                    authorized: 1,
2136                    new_authorized: Some(2),
2137                    compute_unit_price: None,
2138                },
2139                signers: vec![
2140                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
2141                    Box::new(read_keypair_file(&authorized_keypair_file).unwrap()),
2142                    Box::new(read_keypair_file(&voter_keypair_file).unwrap()),
2143                ],
2144            }
2145        );
2146
2147        let test_authorize_voter = test_commands.clone().get_matches_from(vec![
2148            "test",
2149            "vote-authorize-voter-checked",
2150            &pubkey_string,
2151            &authorized_keypair_file,
2152            &pubkey2_string,
2153        ]);
2154        assert!(parse_command(&test_authorize_voter, &default_signer, &mut None).is_err());
2155
2156        // Test vote-authorize-voter-checked with --use-v2-instruction flag.
2157        let (new_voter_keypair_file, mut tmp_file) = make_tmp_file();
2158        let new_voter_keypair = Keypair::new();
2159        write_keypair(&new_voter_keypair, tmp_file.as_file_mut()).unwrap();
2160
2161        let test_authorize_voter_checked_with_bls = test_commands.clone().get_matches_from(vec![
2162            "test",
2163            "vote-authorize-voter-checked",
2164            &pubkey_string,
2165            &authorized_keypair_file,
2166            &new_voter_keypair_file,
2167            "--use-v2-instruction",
2168        ]);
2169        assert_eq!(
2170            parse_command(
2171                &test_authorize_voter_checked_with_bls,
2172                &default_signer,
2173                &mut None
2174            )
2175            .unwrap(),
2176            CliCommandInfo {
2177                command: CliCommand::VoteAuthorize {
2178                    vote_account_pubkey: pubkey,
2179                    new_authorized_pubkey: new_voter_keypair.pubkey(),
2180                    vote_authorize: VoteAuthorize::Voter,
2181                    use_v2_instruction: true,
2182                    sign_only: false,
2183                    dump_transaction_message: false,
2184                    blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
2185                    nonce_account: None,
2186                    nonce_authority: 0,
2187                    memo: None,
2188                    fee_payer: 0,
2189                    authorized: 1,
2190                    new_authorized: Some(2),
2191                    compute_unit_price: None,
2192                },
2193                signers: vec![
2194                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
2195                    Box::new(read_keypair_file(&authorized_keypair_file).unwrap()),
2196                    Box::new(read_keypair_file(&new_voter_keypair_file).unwrap()),
2197                ],
2198            }
2199        );
2200
2201        // Test CreateVoteAccount SubCommand
2202        let (identity_keypair_file, mut tmp_file) = make_tmp_file();
2203        let identity_keypair = Keypair::new();
2204        let authorized_withdrawer = Keypair::new().pubkey();
2205        write_keypair(&identity_keypair, tmp_file.as_file_mut()).unwrap();
2206        let (keypair_file, mut tmp_file) = make_tmp_file();
2207        let keypair = Keypair::new();
2208        write_keypair(&keypair, tmp_file.as_file_mut()).unwrap();
2209
2210        let test_create_vote_account = test_commands.clone().get_matches_from(vec![
2211            "test",
2212            "create-vote-account",
2213            &keypair_file,
2214            &identity_keypair_file,
2215            &authorized_withdrawer.to_string(),
2216            "--commission",
2217            "10",
2218        ]);
2219        assert_eq!(
2220            parse_command(&test_create_vote_account, &default_signer, &mut None).unwrap(),
2221            CliCommandInfo {
2222                command: CliCommand::CreateVoteAccount {
2223                    vote_account: 1,
2224                    seed: None,
2225                    identity_account: 2,
2226                    authorized_voter: None,
2227                    authorized_withdrawer,
2228                    commission: Some(10),
2229                    use_v2_instruction: false,
2230
2231                    inflation_rewards_commission_bps: None,
2232                    inflation_rewards_collector: None,
2233                    block_revenue_commission_bps: None,
2234                    block_revenue_collector: None,
2235                    sign_only: false,
2236                    dump_transaction_message: false,
2237                    blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
2238                    nonce_account: None,
2239                    nonce_authority: 0,
2240                    memo: None,
2241                    fee_payer: 0,
2242                    compute_unit_price: None,
2243                },
2244                signers: vec![
2245                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
2246                    Box::new(read_keypair_file(&keypair_file).unwrap()),
2247                    Box::new(read_keypair_file(&identity_keypair_file).unwrap()),
2248                ],
2249            }
2250        );
2251
2252        let test_create_vote_account2 = test_commands.clone().get_matches_from(vec![
2253            "test",
2254            "create-vote-account",
2255            &keypair_file,
2256            &identity_keypair_file,
2257            &authorized_withdrawer.to_string(),
2258        ]);
2259        assert_eq!(
2260            parse_command(&test_create_vote_account2, &default_signer, &mut None).unwrap(),
2261            CliCommandInfo {
2262                command: CliCommand::CreateVoteAccount {
2263                    vote_account: 1,
2264                    seed: None,
2265                    identity_account: 2,
2266                    authorized_voter: None,
2267                    authorized_withdrawer,
2268                    commission: None, // No --commission; uses default at runtime.
2269                    use_v2_instruction: false,
2270
2271                    inflation_rewards_commission_bps: None,
2272                    inflation_rewards_collector: None,
2273                    block_revenue_commission_bps: None,
2274                    block_revenue_collector: None,
2275                    sign_only: false,
2276                    dump_transaction_message: false,
2277                    blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
2278                    nonce_account: None,
2279                    nonce_authority: 0,
2280                    memo: None,
2281                    fee_payer: 0,
2282                    compute_unit_price: None,
2283                },
2284                signers: vec![
2285                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
2286                    Box::new(read_keypair_file(&keypair_file).unwrap()),
2287                    Box::new(read_keypair_file(&identity_keypair_file).unwrap()),
2288                ],
2289            }
2290        );
2291
2292        let test_create_vote_account = test_commands.clone().get_matches_from(vec![
2293            "test",
2294            "create-vote-account",
2295            &keypair_file,
2296            &identity_keypair_file,
2297            &authorized_withdrawer.to_string(),
2298            "--commission",
2299            "10",
2300            "--blockhash",
2301            &blockhash_string,
2302            "--sign-only",
2303            "--fee-payer",
2304            &default_keypair.pubkey().to_string(),
2305        ]);
2306        assert_eq!(
2307            parse_command(&test_create_vote_account, &default_signer, &mut None).unwrap(),
2308            CliCommandInfo {
2309                command: CliCommand::CreateVoteAccount {
2310                    vote_account: 1,
2311                    seed: None,
2312                    identity_account: 2,
2313                    authorized_voter: None,
2314                    authorized_withdrawer,
2315                    commission: Some(10), // Explicitly set.
2316                    use_v2_instruction: false,
2317
2318                    inflation_rewards_commission_bps: None,
2319                    inflation_rewards_collector: None,
2320                    block_revenue_commission_bps: None,
2321                    block_revenue_collector: None,
2322                    sign_only: true,
2323                    dump_transaction_message: false,
2324                    blockhash_query: BlockhashQuery::Static(blockhash),
2325                    nonce_account: None,
2326                    nonce_authority: 0,
2327                    memo: None,
2328                    fee_payer: 0,
2329                    compute_unit_price: None,
2330                },
2331                signers: vec![
2332                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
2333                    Box::new(read_keypair_file(&keypair_file).unwrap()),
2334                    Box::new(read_keypair_file(&identity_keypair_file).unwrap()),
2335                ],
2336            }
2337        );
2338
2339        let identity_sig = identity_keypair.sign_message(&[0u8]);
2340        let identity_signer = format!("{}={}", identity_keypair.pubkey(), identity_sig);
2341        let test_create_vote_account = test_commands.clone().get_matches_from(vec![
2342            "test",
2343            "create-vote-account",
2344            &keypair_file,
2345            &identity_keypair.pubkey().to_string(),
2346            &authorized_withdrawer.to_string(),
2347            "--commission",
2348            "10",
2349            "--blockhash",
2350            &blockhash_string,
2351            "--signer",
2352            &identity_signer,
2353            "--signer",
2354            &signer2,
2355            "--fee-payer",
2356            &default_keypair_file,
2357            "--nonce",
2358            &nonce_account.to_string(),
2359            "--nonce-authority",
2360            &pubkey2_string,
2361        ]);
2362        assert_eq!(
2363            parse_command(&test_create_vote_account, &default_signer, &mut None).unwrap(),
2364            CliCommandInfo {
2365                command: CliCommand::CreateVoteAccount {
2366                    vote_account: 1,
2367                    seed: None,
2368                    identity_account: 2,
2369                    authorized_voter: None,
2370                    authorized_withdrawer,
2371                    commission: Some(10),
2372                    use_v2_instruction: false,
2373
2374                    inflation_rewards_commission_bps: None,
2375                    inflation_rewards_collector: None,
2376                    block_revenue_commission_bps: None,
2377                    block_revenue_collector: None,
2378                    sign_only: false,
2379                    dump_transaction_message: false,
2380                    blockhash_query: BlockhashQuery::Validated(
2381                        Source::NonceAccount(nonce_account),
2382                        blockhash
2383                    ),
2384                    nonce_account: Some(nonce_account),
2385                    nonce_authority: 3,
2386                    memo: None,
2387                    fee_payer: 0,
2388                    compute_unit_price: None,
2389                },
2390                signers: vec![
2391                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
2392                    Box::new(read_keypair_file(&keypair_file).unwrap()),
2393                    Box::new(Presigner::new(&identity_keypair.pubkey(), &identity_sig)),
2394                    Box::new(Presigner::new(&pubkey2, &sig2)),
2395                ],
2396            }
2397        );
2398
2399        // test init with an authed voter
2400        let authed = solana_pubkey::new_rand();
2401        let (keypair_file, mut tmp_file) = make_tmp_file();
2402        let keypair = Keypair::new();
2403        write_keypair(&keypair, tmp_file.as_file_mut()).unwrap();
2404
2405        let test_create_vote_account3 = test_commands.clone().get_matches_from(vec![
2406            "test",
2407            "create-vote-account",
2408            &keypair_file,
2409            &identity_keypair_file,
2410            &authorized_withdrawer.to_string(),
2411            "--authorized-voter",
2412            &authed.to_string(),
2413        ]);
2414        assert_eq!(
2415            parse_command(&test_create_vote_account3, &default_signer, &mut None).unwrap(),
2416            CliCommandInfo {
2417                command: CliCommand::CreateVoteAccount {
2418                    vote_account: 1,
2419                    seed: None,
2420                    identity_account: 2,
2421                    authorized_voter: Some(authed),
2422                    authorized_withdrawer,
2423                    commission: None, // No --commission specified.
2424                    use_v2_instruction: false,
2425
2426                    inflation_rewards_commission_bps: None,
2427                    inflation_rewards_collector: None,
2428                    block_revenue_commission_bps: None,
2429                    block_revenue_collector: None,
2430                    sign_only: false,
2431                    dump_transaction_message: false,
2432                    blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
2433                    nonce_account: None,
2434                    nonce_authority: 0,
2435                    memo: None,
2436                    fee_payer: 0,
2437                    compute_unit_price: None,
2438                },
2439                signers: vec![
2440                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
2441                    Box::new(keypair),
2442                    Box::new(read_keypair_file(&identity_keypair_file).unwrap()),
2443                ],
2444            }
2445        );
2446
2447        let (keypair_file, mut tmp_file) = make_tmp_file();
2448        let keypair = Keypair::new();
2449        write_keypair(&keypair, tmp_file.as_file_mut()).unwrap();
2450        // succeed even though withdrawer unsafe (because forcefully allowed)
2451        let test_create_vote_account4 = test_commands.clone().get_matches_from(vec![
2452            "test",
2453            "create-vote-account",
2454            &keypair_file,
2455            &identity_keypair_file,
2456            &identity_keypair_file,
2457            "--allow-unsafe-authorized-withdrawer",
2458        ]);
2459        assert_eq!(
2460            parse_command(&test_create_vote_account4, &default_signer, &mut None).unwrap(),
2461            CliCommandInfo {
2462                command: CliCommand::CreateVoteAccount {
2463                    vote_account: 1,
2464                    seed: None,
2465                    identity_account: 2,
2466                    authorized_voter: None,
2467                    authorized_withdrawer: identity_keypair.pubkey(),
2468                    commission: None, // No --commission specified.
2469                    use_v2_instruction: false,
2470
2471                    inflation_rewards_commission_bps: None,
2472                    inflation_rewards_collector: None,
2473                    block_revenue_commission_bps: None,
2474                    block_revenue_collector: None,
2475                    sign_only: false,
2476                    dump_transaction_message: false,
2477                    blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
2478                    nonce_account: None,
2479                    nonce_authority: 0,
2480                    memo: None,
2481                    fee_payer: 0,
2482                    compute_unit_price: None,
2483                },
2484                signers: vec![
2485                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
2486                    Box::new(read_keypair_file(&keypair_file).unwrap()),
2487                    Box::new(read_keypair_file(&identity_keypair_file).unwrap()),
2488                ],
2489            }
2490        );
2491
2492        // Test create-vote-account with --use-v2-instruction flag (VoteInitV2).
2493        let (keypair_file, mut tmp_file) = make_tmp_file();
2494        let keypair = Keypair::new();
2495        write_keypair(&keypair, tmp_file.as_file_mut()).unwrap();
2496        let inflation_rewards_collector = Keypair::new().pubkey();
2497        let block_revenue_collector = Keypair::new().pubkey();
2498
2499        // Test with VoteInitV2 and all optional arguments specified.
2500        let test_create_vote_account_v2 = test_commands.clone().get_matches_from(vec![
2501            "test",
2502            "create-vote-account",
2503            &keypair_file,
2504            &identity_keypair_file,
2505            &authorized_withdrawer.to_string(),
2506            "--use-v2-instruction",
2507            "--authorized-voter",
2508            &authed.to_string(),
2509            "--inflation-rewards-commission-bps",
2510            "500",
2511            "--inflation-rewards-collector",
2512            &inflation_rewards_collector.to_string(),
2513            "--block-revenue-commission-bps",
2514            "1000",
2515            "--block-revenue-collector",
2516            &block_revenue_collector.to_string(),
2517        ]);
2518        assert_eq!(
2519            parse_command(&test_create_vote_account_v2, &default_signer, &mut None).unwrap(),
2520            CliCommandInfo {
2521                command: CliCommand::CreateVoteAccount {
2522                    vote_account: 1,
2523                    seed: None,
2524                    identity_account: 2,
2525                    authorized_voter: Some(authed),
2526                    authorized_withdrawer,
2527                    commission: None,
2528                    use_v2_instruction: true,
2529                    inflation_rewards_commission_bps: Some(500),
2530                    inflation_rewards_collector: Some(inflation_rewards_collector),
2531                    block_revenue_commission_bps: Some(1000),
2532                    block_revenue_collector: Some(block_revenue_collector),
2533                    sign_only: false,
2534                    dump_transaction_message: false,
2535                    blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
2536                    nonce_account: None,
2537                    nonce_authority: 0,
2538                    memo: None,
2539                    fee_payer: 0,
2540                    compute_unit_price: None,
2541                },
2542                signers: vec![
2543                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
2544                    Box::new(read_keypair_file(&keypair_file).unwrap()),
2545                    Box::new(read_keypair_file(&identity_keypair_file).unwrap()),
2546                ],
2547            }
2548        );
2549
2550        // Test with VoteInitV2 in sign-only mode.
2551        let (keypair_file, mut tmp_file) = make_tmp_file();
2552        let sign_only_vote_keypair = Keypair::new();
2553        write_keypair(&sign_only_vote_keypair, tmp_file.as_file_mut()).unwrap();
2554
2555        let test_create_vote_account_v2_sign_only = test_commands.clone().get_matches_from(vec![
2556            "test",
2557            "create-vote-account",
2558            &keypair_file,
2559            &identity_keypair_file,
2560            &authorized_withdrawer.to_string(),
2561            "--use-v2-instruction",
2562            "--blockhash",
2563            &blockhash_string,
2564            "--sign-only",
2565        ]);
2566        assert_eq!(
2567            parse_command(
2568                &test_create_vote_account_v2_sign_only,
2569                &default_signer,
2570                &mut None
2571            )
2572            .unwrap(),
2573            CliCommandInfo {
2574                command: CliCommand::CreateVoteAccount {
2575                    vote_account: 1,
2576                    seed: None,
2577                    identity_account: 2,
2578                    authorized_voter: None,
2579                    authorized_withdrawer,
2580                    commission: None,
2581                    use_v2_instruction: true,
2582
2583                    inflation_rewards_commission_bps: None,
2584                    inflation_rewards_collector: None,
2585                    block_revenue_commission_bps: None,
2586                    block_revenue_collector: None,
2587                    sign_only: true,
2588                    dump_transaction_message: false,
2589                    blockhash_query: BlockhashQuery::Static(blockhash),
2590                    nonce_account: None,
2591                    nonce_authority: 0,
2592                    memo: None,
2593                    fee_payer: 0,
2594                    compute_unit_price: None,
2595                },
2596                signers: vec![
2597                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
2598                    Box::new(read_keypair_file(&keypair_file).unwrap()),
2599                    Box::new(read_keypair_file(&identity_keypair_file).unwrap()),
2600                ],
2601            }
2602        );
2603
2604        // Test that --commission conflicts with any VoteInitV2 args.
2605        let (keypair_file, mut tmp_file) = make_tmp_file();
2606        let conflict_vote_keypair = Keypair::new();
2607        write_keypair(&conflict_vote_keypair, tmp_file.as_file_mut()).unwrap();
2608
2609        // --commission with --use-v2-instruction should error.
2610        let test_conflict = test_commands.clone().get_matches_from(vec![
2611            "test",
2612            "create-vote-account",
2613            &keypair_file,
2614            &identity_keypair_file,
2615            &authorized_withdrawer.to_string(),
2616            "--commission",
2617            "10",
2618            "--use-v2-instruction",
2619        ]);
2620        assert!(parse_command(&test_conflict, &default_signer, &mut None).is_err());
2621
2622        // --commission with --inflation-rewards-commission-bps should error.
2623        let test_conflict = test_commands.clone().get_matches_from(vec![
2624            "test",
2625            "create-vote-account",
2626            &keypair_file,
2627            &identity_keypair_file,
2628            &authorized_withdrawer.to_string(),
2629            "--commission",
2630            "10",
2631            "--inflation-rewards-commission-bps",
2632            "1000",
2633        ]);
2634        assert!(parse_command(&test_conflict, &default_signer, &mut None).is_err());
2635
2636        // --commission with --block-revenue-commission-bps should error.
2637        let test_conflict = test_commands.clone().get_matches_from(vec![
2638            "test",
2639            "create-vote-account",
2640            &keypair_file,
2641            &identity_keypair_file,
2642            &authorized_withdrawer.to_string(),
2643            "--commission",
2644            "10",
2645            "--block-revenue-commission-bps",
2646            "500",
2647        ]);
2648        assert!(parse_command(&test_conflict, &default_signer, &mut None).is_err());
2649
2650        let test_update_validator = test_commands.clone().get_matches_from(vec![
2651            "test",
2652            "vote-update-validator",
2653            &pubkey_string,
2654            &identity_keypair_file,
2655            &keypair_file,
2656        ]);
2657        assert_eq!(
2658            parse_command(&test_update_validator, &default_signer, &mut None).unwrap(),
2659            CliCommandInfo {
2660                command: CliCommand::VoteUpdateValidator {
2661                    vote_account_pubkey: pubkey,
2662                    new_identity_account: 2,
2663                    withdraw_authority: 1,
2664                    sign_only: false,
2665                    dump_transaction_message: false,
2666                    blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
2667                    nonce_account: None,
2668                    nonce_authority: 0,
2669                    memo: None,
2670                    fee_payer: 0,
2671                    compute_unit_price: None,
2672                },
2673                signers: vec![
2674                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
2675                    Box::new(read_keypair_file(&keypair_file).unwrap()),
2676                    Box::new(read_keypair_file(&identity_keypair_file).unwrap()),
2677                ],
2678            }
2679        );
2680
2681        let test_update_commission = test_commands.clone().get_matches_from(vec![
2682            "test",
2683            "vote-update-commission",
2684            &pubkey_string,
2685            "42",
2686            &keypair_file,
2687        ]);
2688        assert_eq!(
2689            parse_command(&test_update_commission, &default_signer, &mut None).unwrap(),
2690            CliCommandInfo {
2691                command: CliCommand::VoteUpdateCommission {
2692                    vote_account_pubkey: pubkey,
2693                    commission: 42,
2694                    withdraw_authority: 1,
2695                    sign_only: false,
2696                    dump_transaction_message: false,
2697                    blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
2698                    nonce_account: None,
2699                    nonce_authority: 0,
2700                    memo: None,
2701                    fee_payer: 0,
2702                    compute_unit_price: None,
2703                },
2704                signers: vec![
2705                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
2706                    Box::new(read_keypair_file(&keypair_file).unwrap()),
2707                ],
2708            }
2709        );
2710
2711        // Test WithdrawFromVoteAccount subcommand
2712        let test_withdraw_from_vote_account = test_commands.clone().get_matches_from(vec![
2713            "test",
2714            "withdraw-from-vote-account",
2715            &keypair_file,
2716            &pubkey_string,
2717            "42",
2718        ]);
2719        assert_eq!(
2720            parse_command(&test_withdraw_from_vote_account, &default_signer, &mut None).unwrap(),
2721            CliCommandInfo {
2722                command: CliCommand::WithdrawFromVoteAccount {
2723                    vote_account_pubkey: read_keypair_file(&keypair_file).unwrap().pubkey(),
2724                    destination_account_pubkey: pubkey,
2725                    withdraw_authority: 0,
2726                    withdraw_amount: SpendAmount::Some(42_000_000_000),
2727                    sign_only: false,
2728                    dump_transaction_message: false,
2729                    blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
2730                    nonce_account: None,
2731                    nonce_authority: 0,
2732                    memo: None,
2733                    fee_payer: 0,
2734                    compute_unit_price: None,
2735                },
2736                signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap())],
2737            }
2738        );
2739
2740        // Test WithdrawFromVoteAccount subcommand
2741        let test_withdraw_from_vote_account = test_commands.clone().get_matches_from(vec![
2742            "test",
2743            "withdraw-from-vote-account",
2744            &keypair_file,
2745            &pubkey_string,
2746            "ALL",
2747        ]);
2748        assert_eq!(
2749            parse_command(&test_withdraw_from_vote_account, &default_signer, &mut None).unwrap(),
2750            CliCommandInfo {
2751                command: CliCommand::WithdrawFromVoteAccount {
2752                    vote_account_pubkey: read_keypair_file(&keypair_file).unwrap().pubkey(),
2753                    destination_account_pubkey: pubkey,
2754                    withdraw_authority: 0,
2755                    withdraw_amount: SpendAmount::RentExempt,
2756                    sign_only: false,
2757                    dump_transaction_message: false,
2758                    blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
2759                    nonce_account: None,
2760                    nonce_authority: 0,
2761                    memo: None,
2762                    fee_payer: 0,
2763                    compute_unit_price: None,
2764                },
2765                signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap())],
2766            }
2767        );
2768
2769        // Test WithdrawFromVoteAccount subcommand with authority
2770        let withdraw_authority = Keypair::new();
2771        let (withdraw_authority_file, mut tmp_file) = make_tmp_file();
2772        write_keypair(&withdraw_authority, tmp_file.as_file_mut()).unwrap();
2773        let test_withdraw_from_vote_account = test_commands.clone().get_matches_from(vec![
2774            "test",
2775            "withdraw-from-vote-account",
2776            &keypair_file,
2777            &pubkey_string,
2778            "42",
2779            "--authorized-withdrawer",
2780            &withdraw_authority_file,
2781        ]);
2782        assert_eq!(
2783            parse_command(&test_withdraw_from_vote_account, &default_signer, &mut None).unwrap(),
2784            CliCommandInfo {
2785                command: CliCommand::WithdrawFromVoteAccount {
2786                    vote_account_pubkey: read_keypair_file(&keypair_file).unwrap().pubkey(),
2787                    destination_account_pubkey: pubkey,
2788                    withdraw_authority: 1,
2789                    withdraw_amount: SpendAmount::Some(42_000_000_000),
2790                    sign_only: false,
2791                    dump_transaction_message: false,
2792                    blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
2793                    nonce_account: None,
2794                    nonce_authority: 0,
2795                    memo: None,
2796                    fee_payer: 0,
2797                    compute_unit_price: None,
2798                },
2799                signers: vec![
2800                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
2801                    Box::new(read_keypair_file(&withdraw_authority_file).unwrap())
2802                ],
2803            }
2804        );
2805
2806        // Test WithdrawFromVoteAccount subcommand with offline authority
2807        let test_withdraw_from_vote_account = test_commands.clone().get_matches_from(vec![
2808            "test",
2809            "withdraw-from-vote-account",
2810            &keypair.pubkey().to_string(),
2811            &pubkey_string,
2812            "42",
2813            "--authorized-withdrawer",
2814            &withdraw_authority_file,
2815            "--blockhash",
2816            &blockhash_string,
2817            "--sign-only",
2818            "--fee-payer",
2819            &withdraw_authority_file,
2820        ]);
2821        assert_eq!(
2822            parse_command(&test_withdraw_from_vote_account, &default_signer, &mut None).unwrap(),
2823            CliCommandInfo {
2824                command: CliCommand::WithdrawFromVoteAccount {
2825                    vote_account_pubkey: keypair.pubkey(),
2826                    destination_account_pubkey: pubkey,
2827                    withdraw_authority: 0,
2828                    withdraw_amount: SpendAmount::Some(42_000_000_000),
2829                    sign_only: true,
2830                    dump_transaction_message: false,
2831                    blockhash_query: BlockhashQuery::Static(blockhash),
2832                    nonce_account: None,
2833                    nonce_authority: 0,
2834                    memo: None,
2835                    fee_payer: 0,
2836                    compute_unit_price: None,
2837                },
2838                signers: vec![Box::new(
2839                    read_keypair_file(&withdraw_authority_file).unwrap()
2840                )],
2841            }
2842        );
2843
2844        let authorized_sig = withdraw_authority.sign_message(&[0u8]);
2845        let authorized_signer = format!("{}={}", withdraw_authority.pubkey(), authorized_sig);
2846        let test_withdraw_from_vote_account = test_commands.clone().get_matches_from(vec![
2847            "test",
2848            "withdraw-from-vote-account",
2849            &keypair.pubkey().to_string(),
2850            &pubkey_string,
2851            "42",
2852            "--authorized-withdrawer",
2853            &withdraw_authority.pubkey().to_string(),
2854            "--blockhash",
2855            &blockhash_string,
2856            "--signer",
2857            &authorized_signer,
2858            "--fee-payer",
2859            &withdraw_authority.pubkey().to_string(),
2860        ]);
2861        assert_eq!(
2862            parse_command(&test_withdraw_from_vote_account, &default_signer, &mut None).unwrap(),
2863            CliCommandInfo {
2864                command: CliCommand::WithdrawFromVoteAccount {
2865                    vote_account_pubkey: keypair.pubkey(),
2866                    destination_account_pubkey: pubkey,
2867                    withdraw_authority: 0,
2868                    withdraw_amount: SpendAmount::Some(42_000_000_000),
2869                    sign_only: false,
2870                    dump_transaction_message: false,
2871                    blockhash_query: BlockhashQuery::Validated(Source::Cluster, blockhash),
2872                    nonce_account: None,
2873                    nonce_authority: 0,
2874                    memo: None,
2875                    fee_payer: 0,
2876                    compute_unit_price: None,
2877                },
2878                signers: vec![Box::new(Presigner::new(
2879                    &withdraw_authority.pubkey(),
2880                    &authorized_sig
2881                )),],
2882            }
2883        );
2884
2885        // Test CloseVoteAccount subcommand
2886        let test_close_vote_account = test_commands.clone().get_matches_from(vec![
2887            "test",
2888            "close-vote-account",
2889            &keypair_file,
2890            &pubkey_string,
2891        ]);
2892        assert_eq!(
2893            parse_command(&test_close_vote_account, &default_signer, &mut None).unwrap(),
2894            CliCommandInfo {
2895                command: CliCommand::CloseVoteAccount {
2896                    vote_account_pubkey: read_keypair_file(&keypair_file).unwrap().pubkey(),
2897                    destination_account_pubkey: pubkey,
2898                    withdraw_authority: 0,
2899                    memo: None,
2900                    fee_payer: 0,
2901                    compute_unit_price: None,
2902                },
2903                signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap())],
2904            }
2905        );
2906
2907        // Test CloseVoteAccount subcommand with authority
2908        let withdraw_authority = Keypair::new();
2909        let (withdraw_authority_file, mut tmp_file) = make_tmp_file();
2910        write_keypair(&withdraw_authority, tmp_file.as_file_mut()).unwrap();
2911        let test_close_vote_account = test_commands.clone().get_matches_from(vec![
2912            "test",
2913            "close-vote-account",
2914            &keypair_file,
2915            &pubkey_string,
2916            "--authorized-withdrawer",
2917            &withdraw_authority_file,
2918        ]);
2919        assert_eq!(
2920            parse_command(&test_close_vote_account, &default_signer, &mut None).unwrap(),
2921            CliCommandInfo {
2922                command: CliCommand::CloseVoteAccount {
2923                    vote_account_pubkey: read_keypair_file(&keypair_file).unwrap().pubkey(),
2924                    destination_account_pubkey: pubkey,
2925                    withdraw_authority: 1,
2926                    memo: None,
2927                    fee_payer: 0,
2928                    compute_unit_price: None,
2929                },
2930                signers: vec![
2931                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
2932                    Box::new(read_keypair_file(&withdraw_authority_file).unwrap())
2933                ],
2934            }
2935        );
2936
2937        // Test CloseVoteAccount subcommand with authority w/ ComputeUnitPrice
2938        let withdraw_authority = Keypair::new();
2939        let (withdraw_authority_file, mut tmp_file) = make_tmp_file();
2940        write_keypair(&withdraw_authority, tmp_file.as_file_mut()).unwrap();
2941        let test_close_vote_account = test_commands.clone().get_matches_from(vec![
2942            "test",
2943            "close-vote-account",
2944            &keypair_file,
2945            &pubkey_string,
2946            "--authorized-withdrawer",
2947            &withdraw_authority_file,
2948            "--with-compute-unit-price",
2949            "99",
2950        ]);
2951        assert_eq!(
2952            parse_command(&test_close_vote_account, &default_signer, &mut None).unwrap(),
2953            CliCommandInfo {
2954                command: CliCommand::CloseVoteAccount {
2955                    vote_account_pubkey: read_keypair_file(&keypair_file).unwrap().pubkey(),
2956                    destination_account_pubkey: pubkey,
2957                    withdraw_authority: 1,
2958                    memo: None,
2959                    fee_payer: 0,
2960                    compute_unit_price: Some(99),
2961                },
2962                signers: vec![
2963                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
2964                    Box::new(read_keypair_file(&withdraw_authority_file).unwrap())
2965                ],
2966            }
2967        );
2968    }
2969}