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