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