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                block_revenue_commission_bps: block_revenue_commission_bps.unwrap_or(10000),
1044            };
1045            let inflation_rewards_collector_key = inflation_rewards_collector
1046                .copied()
1047                .unwrap_or(vote_account_address);
1048            let block_revenue_collector_key =
1049                block_revenue_collector.copied().unwrap_or(identity_pubkey);
1050            vote_instruction::create_account_with_config_v2(
1051                &config.signers[0].pubkey(),
1052                to,
1053                &vote_init,
1054                &inflation_rewards_collector_key,
1055                &block_revenue_collector_key,
1056                lamports,
1057                create_vote_account_config,
1058            )
1059        } else {
1060            let vote_init = VoteInit {
1061                node_pubkey: identity_pubkey,
1062                authorized_voter: authorized_voter.unwrap_or(identity_pubkey),
1063                authorized_withdrawer,
1064                commission: commission.unwrap_or(100),
1065            };
1066            vote_instruction::create_account_with_config(
1067                &config.signers[0].pubkey(),
1068                to,
1069                &vote_init,
1070                lamports,
1071                create_vote_account_config,
1072            )
1073        };
1074
1075        let ixs = ixs
1076            .with_memo(memo)
1077            .with_compute_unit_config(&ComputeUnitConfig {
1078                compute_unit_price,
1079                compute_unit_limit,
1080            });
1081
1082        if let Some(nonce_account) = &nonce_account {
1083            Message::new_with_nonce(
1084                ixs,
1085                Some(&fee_payer.pubkey()),
1086                nonce_account,
1087                &nonce_authority.pubkey(),
1088            )
1089        } else {
1090            Message::new(&ixs, Some(&fee_payer.pubkey()))
1091        }
1092    };
1093
1094    let recent_blockhash = blockhash_query
1095        .get_blockhash(rpc_client, config.commitment)
1096        .await?;
1097
1098    let (message, _) = resolve_spend_tx_and_check_account_balances(
1099        rpc_client,
1100        sign_only,
1101        amount,
1102        &recent_blockhash,
1103        &config.signers[0].pubkey(),
1104        &fee_payer.pubkey(),
1105        compute_unit_limit,
1106        build_message,
1107        config.commitment,
1108    )
1109    .await?;
1110
1111    if !sign_only {
1112        if let Ok(response) = rpc_client
1113            .get_account_with_commitment(&vote_account_address, config.commitment)
1114            .await
1115        {
1116            if let Some(vote_account) = response.value {
1117                let err_msg = if vote_account.owner == solana_vote_program::id() {
1118                    format!("Vote account {vote_account_address} already exists")
1119                } else {
1120                    format!(
1121                        "Account {vote_account_address} already exists and is not a vote account"
1122                    )
1123                };
1124                return Err(CliError::BadParameter(err_msg).into());
1125            }
1126        }
1127
1128        if let Some(nonce_account) = &nonce_account {
1129            let nonce_account =
1130                solana_rpc_client_nonce_utils::nonblocking::get_account_with_commitment(
1131                    rpc_client,
1132                    nonce_account,
1133                    config.commitment,
1134                )
1135                .await?;
1136            check_nonce_account(&nonce_account, &nonce_authority.pubkey(), &recent_blockhash)?;
1137        }
1138    }
1139
1140    let mut tx = Transaction::new_unsigned(message);
1141    if sign_only {
1142        tx.try_partial_sign(&config.signers, recent_blockhash)?;
1143        return_signers_with_config(
1144            &tx,
1145            &config.output_format,
1146            &ReturnSignersConfig {
1147                dump_transaction_message,
1148            },
1149        )
1150    } else {
1151        tx.try_sign(&config.signers, recent_blockhash)?;
1152        let result = rpc_client
1153            .send_and_confirm_transaction_with_spinner_and_config(
1154                &tx,
1155                config.commitment,
1156                config.send_transaction_config,
1157            )
1158            .await;
1159        log_instruction_custom_error::<SystemError>(result, config)
1160    }
1161}
1162
1163#[allow(clippy::too_many_arguments)]
1164pub async fn process_vote_authorize(
1165    rpc_client: &RpcClient,
1166    config: &CliConfig<'_>,
1167    vote_account_pubkey: &Pubkey,
1168    new_authorized_pubkey: &Pubkey,
1169    vote_authorize: VoteAuthorize,
1170    use_v2_instruction: bool,
1171    authorized: SignerIndex,
1172    new_authorized: Option<SignerIndex>,
1173    sign_only: bool,
1174    dump_transaction_message: bool,
1175    blockhash_query: &BlockhashQuery,
1176    nonce_account: Option<Pubkey>,
1177    nonce_authority: SignerIndex,
1178    memo: Option<&String>,
1179    fee_payer: SignerIndex,
1180    compute_unit_price: Option<u64>,
1181) -> ProcessResult {
1182    let authorized = config.signers[authorized];
1183    let new_authorized_signer = new_authorized.map(|index| config.signers[index]);
1184    let is_checked = new_authorized_signer.is_some();
1185
1186    let vote_state = if !sign_only {
1187        Some(
1188            get_vote_account(rpc_client, vote_account_pubkey, config.commitment)
1189                .await?
1190                .1,
1191        )
1192    } else {
1193        None
1194    };
1195
1196    // Determine whether to use Voter or VoterWithBLS for voter authorization.
1197    // 1. If not VoteAuthorize::Voter -> false (Withdrawer doesn't use BLS)
1198    // 2. If --use-v2-instruction provided: true (explicit request)
1199    // 3. If sign_only (no flag): false (default to v1)
1200    // 4. If vote account has BLS key: true (must use VoterWithBLS, Voter will fail)
1201    // 5. If feature active: true
1202    // 6. Otherwise: false
1203    let use_bls = if !matches!(vote_authorize, VoteAuthorize::Voter) {
1204        // Withdrawer authorization doesn't use BLS.
1205        false
1206    } else if use_v2_instruction {
1207        // Explicit request via flag.
1208        true
1209    } else if sign_only {
1210        // Sign-only without explicit flag, default to Voter (v1).
1211        false
1212    } else if vote_state
1213        .as_ref()
1214        .map(|vs| vs.bls_pubkey_compressed.is_some())
1215        .unwrap_or(false)
1216    {
1217        // Account has BLS key - must use VoterWithBLS (Voter will fail).
1218        true
1219    } else {
1220        // Check SIMD-0387 feature gate status.
1221        get_feature_is_active(rpc_client, &bls_pubkey_management_in_vote_account::id())
1222            .await
1223            .unwrap_or(false)
1224    };
1225
1226    match vote_authorize {
1227        VoteAuthorize::Voter => {
1228            if let Some(vote_state) = vote_state {
1229                let current_epoch = rpc_client.get_epoch_info().await?.epoch;
1230                let current_authorized_voter = vote_state
1231                    .authorized_voters
1232                    .get_authorized_voter(current_epoch)
1233                    .ok_or_else(|| {
1234                        CliError::RpcRequestError(
1235                            "Invalid vote account state; no authorized voters found".to_string(),
1236                        )
1237                    })?;
1238                check_current_authority(
1239                    &[current_authorized_voter, vote_state.authorized_withdrawer],
1240                    &authorized.pubkey(),
1241                )?;
1242                if let Some(signer) = new_authorized_signer {
1243                    if signer.is_interactive() {
1244                        return Err(CliError::BadParameter(format!(
1245                            "invalid new authorized vote signer {new_authorized_pubkey:?}. \
1246                             Interactive vote signers not supported"
1247                        ))
1248                        .into());
1249                    }
1250                }
1251            }
1252        }
1253        VoteAuthorize::Withdrawer => {
1254            check_unique_pubkeys(
1255                (&authorized.pubkey(), "authorized_account".to_string()),
1256                (new_authorized_pubkey, "new_authorized_pubkey".to_string()),
1257            )?;
1258            if let Some(vote_state) = vote_state {
1259                check_current_authority(&[vote_state.authorized_withdrawer], &authorized.pubkey())?
1260            }
1261        }
1262        VoteAuthorize::VoterWithBLS(_) => {
1263            // We should never reach here.
1264            // This variant is constructed below, not passed in.
1265            unreachable!("VoterWithBLS should not be passed as vote_authorize parameter");
1266        }
1267    }
1268
1269    // Derive BLS keypair from the new authorized voter and generate proof of
1270    // possession for VoterWithBLS.
1271    let effective_vote_authorize = if use_bls {
1272        if !is_checked {
1273            return Err(CliError::BadParameter(
1274                "BLS key derivation requires the new voter to be a signer. Use \
1275                 `vote-authorize-voter-checked` instead."
1276                    .to_owned(),
1277            )
1278            .into());
1279        }
1280        let new_authorized_signer = new_authorized_signer.unwrap();
1281        let derived_bls_keypair =
1282            BLSKeypair::derive_from_signer(new_authorized_signer, BLS_KEYPAIR_DERIVE_SEED)
1283                .map_err(|e| {
1284                    CliError::BadParameter(format!("Failed to derive BLS keypair: {e}"))
1285                })?;
1286        let (bls_pubkey, bls_proof_of_possession) =
1287            create_bls_proof_of_possession(vote_account_pubkey, &derived_bls_keypair);
1288        VoteAuthorize::VoterWithBLS(VoterWithBLSArgs {
1289            bls_pubkey,
1290            bls_proof_of_possession,
1291        })
1292    } else {
1293        vote_authorize
1294    };
1295
1296    let vote_ix = if is_checked {
1297        vote_instruction::authorize_checked(
1298            vote_account_pubkey,      // vote account to update
1299            &authorized.pubkey(),     // current authorized
1300            new_authorized_pubkey,    // new vote signer/withdrawer
1301            effective_vote_authorize, // vote or withdraw
1302        )
1303    } else {
1304        vote_instruction::authorize(
1305            vote_account_pubkey,      // vote account to update
1306            &authorized.pubkey(),     // current authorized
1307            new_authorized_pubkey,    // new vote signer/withdrawer
1308            effective_vote_authorize, // vote or withdraw
1309        )
1310    };
1311
1312    let compute_unit_limit = match blockhash_query {
1313        BlockhashQuery::Static(_) | BlockhashQuery::Validated(_, _) => ComputeUnitLimit::Default,
1314        BlockhashQuery::Rpc(_) => ComputeUnitLimit::Simulated,
1315    };
1316    let ixs = vec![vote_ix]
1317        .with_memo(memo)
1318        .with_compute_unit_config(&ComputeUnitConfig {
1319            compute_unit_price,
1320            compute_unit_limit,
1321        });
1322
1323    let recent_blockhash = blockhash_query
1324        .get_blockhash(rpc_client, config.commitment)
1325        .await?;
1326
1327    let nonce_authority = config.signers[nonce_authority];
1328    let fee_payer = config.signers[fee_payer];
1329
1330    let mut message = if let Some(nonce_account) = &nonce_account {
1331        Message::new_with_nonce(
1332            ixs,
1333            Some(&fee_payer.pubkey()),
1334            nonce_account,
1335            &nonce_authority.pubkey(),
1336        )
1337    } else {
1338        Message::new(&ixs, Some(&fee_payer.pubkey()))
1339    };
1340    simulate_and_update_compute_unit_limit(&compute_unit_limit, rpc_client, &mut message).await?;
1341    let mut tx = Transaction::new_unsigned(message);
1342
1343    if sign_only {
1344        tx.try_partial_sign(&config.signers, recent_blockhash)?;
1345        return_signers_with_config(
1346            &tx,
1347            &config.output_format,
1348            &ReturnSignersConfig {
1349                dump_transaction_message,
1350            },
1351        )
1352    } else {
1353        tx.try_sign(&config.signers, recent_blockhash)?;
1354        if let Some(nonce_account) = &nonce_account {
1355            let nonce_account =
1356                solana_rpc_client_nonce_utils::nonblocking::get_account_with_commitment(
1357                    rpc_client,
1358                    nonce_account,
1359                    config.commitment,
1360                )
1361                .await?;
1362            check_nonce_account(&nonce_account, &nonce_authority.pubkey(), &recent_blockhash)?;
1363        }
1364        check_account_for_fee_with_commitment(
1365            rpc_client,
1366            &config.signers[0].pubkey(),
1367            &tx.message,
1368            config.commitment,
1369        )
1370        .await?;
1371        let result = rpc_client
1372            .send_and_confirm_transaction_with_spinner_and_config(
1373                &tx,
1374                config.commitment,
1375                config.send_transaction_config,
1376            )
1377            .await;
1378        log_instruction_custom_error::<VoteError>(result, config)
1379    }
1380}
1381
1382#[allow(clippy::too_many_arguments)]
1383pub async fn process_vote_update_validator(
1384    rpc_client: &RpcClient,
1385    config: &CliConfig<'_>,
1386    vote_account_pubkey: &Pubkey,
1387    new_identity_account: SignerIndex,
1388    withdraw_authority: SignerIndex,
1389    sign_only: bool,
1390    dump_transaction_message: bool,
1391    blockhash_query: &BlockhashQuery,
1392    nonce_account: Option<Pubkey>,
1393    nonce_authority: SignerIndex,
1394    memo: Option<&String>,
1395    fee_payer: SignerIndex,
1396    compute_unit_price: Option<u64>,
1397) -> ProcessResult {
1398    let authorized_withdrawer = config.signers[withdraw_authority];
1399    let new_identity_account = config.signers[new_identity_account];
1400    let new_identity_pubkey = new_identity_account.pubkey();
1401    check_unique_pubkeys(
1402        (vote_account_pubkey, "vote_account_pubkey".to_string()),
1403        (&new_identity_pubkey, "new_identity_account".to_string()),
1404    )?;
1405    let recent_blockhash = blockhash_query
1406        .get_blockhash(rpc_client, config.commitment)
1407        .await?;
1408    let compute_unit_limit = match blockhash_query {
1409        BlockhashQuery::Static(_) | BlockhashQuery::Validated(_, _) => ComputeUnitLimit::Default,
1410        BlockhashQuery::Rpc(_) => ComputeUnitLimit::Simulated,
1411    };
1412    let ixs = vec![vote_instruction::update_validator_identity(
1413        vote_account_pubkey,
1414        &authorized_withdrawer.pubkey(),
1415        &new_identity_pubkey,
1416    )]
1417    .with_memo(memo)
1418    .with_compute_unit_config(&ComputeUnitConfig {
1419        compute_unit_price,
1420        compute_unit_limit,
1421    });
1422    let nonce_authority = config.signers[nonce_authority];
1423    let fee_payer = config.signers[fee_payer];
1424
1425    let mut message = if let Some(nonce_account) = &nonce_account {
1426        Message::new_with_nonce(
1427            ixs,
1428            Some(&fee_payer.pubkey()),
1429            nonce_account,
1430            &nonce_authority.pubkey(),
1431        )
1432    } else {
1433        Message::new(&ixs, Some(&fee_payer.pubkey()))
1434    };
1435    simulate_and_update_compute_unit_limit(&compute_unit_limit, rpc_client, &mut message).await?;
1436    let mut tx = Transaction::new_unsigned(message);
1437
1438    if sign_only {
1439        tx.try_partial_sign(&config.signers, recent_blockhash)?;
1440        return_signers_with_config(
1441            &tx,
1442            &config.output_format,
1443            &ReturnSignersConfig {
1444                dump_transaction_message,
1445            },
1446        )
1447    } else {
1448        tx.try_sign(&config.signers, recent_blockhash)?;
1449        if let Some(nonce_account) = &nonce_account {
1450            let nonce_account =
1451                solana_rpc_client_nonce_utils::nonblocking::get_account_with_commitment(
1452                    rpc_client,
1453                    nonce_account,
1454                    config.commitment,
1455                )
1456                .await?;
1457            check_nonce_account(&nonce_account, &nonce_authority.pubkey(), &recent_blockhash)?;
1458        }
1459        check_account_for_fee_with_commitment(
1460            rpc_client,
1461            &config.signers[0].pubkey(),
1462            &tx.message,
1463            config.commitment,
1464        )
1465        .await?;
1466        let result = rpc_client
1467            .send_and_confirm_transaction_with_spinner_and_config(
1468                &tx,
1469                config.commitment,
1470                config.send_transaction_config,
1471            )
1472            .await;
1473        log_instruction_custom_error::<VoteError>(result, config)
1474    }
1475}
1476
1477#[allow(clippy::too_many_arguments)]
1478pub async fn process_vote_update_commission(
1479    rpc_client: &RpcClient,
1480    config: &CliConfig<'_>,
1481    vote_account_pubkey: &Pubkey,
1482    commission: u8,
1483    withdraw_authority: SignerIndex,
1484    sign_only: bool,
1485    dump_transaction_message: bool,
1486    blockhash_query: &BlockhashQuery,
1487    nonce_account: Option<Pubkey>,
1488    nonce_authority: SignerIndex,
1489    memo: Option<&String>,
1490    fee_payer: SignerIndex,
1491    compute_unit_price: Option<u64>,
1492) -> ProcessResult {
1493    let authorized_withdrawer = config.signers[withdraw_authority];
1494    let recent_blockhash = blockhash_query
1495        .get_blockhash(rpc_client, config.commitment)
1496        .await?;
1497    let compute_unit_limit = match blockhash_query {
1498        BlockhashQuery::Static(_) | BlockhashQuery::Validated(_, _) => ComputeUnitLimit::Default,
1499        BlockhashQuery::Rpc(_) => ComputeUnitLimit::Simulated,
1500    };
1501    let ixs = vec![vote_instruction::update_commission(
1502        vote_account_pubkey,
1503        &authorized_withdrawer.pubkey(),
1504        commission,
1505    )]
1506    .with_memo(memo)
1507    .with_compute_unit_config(&ComputeUnitConfig {
1508        compute_unit_price,
1509        compute_unit_limit,
1510    });
1511    let nonce_authority = config.signers[nonce_authority];
1512    let fee_payer = config.signers[fee_payer];
1513
1514    let mut message = if let Some(nonce_account) = &nonce_account {
1515        Message::new_with_nonce(
1516            ixs,
1517            Some(&fee_payer.pubkey()),
1518            nonce_account,
1519            &nonce_authority.pubkey(),
1520        )
1521    } else {
1522        Message::new(&ixs, Some(&fee_payer.pubkey()))
1523    };
1524    simulate_and_update_compute_unit_limit(&compute_unit_limit, rpc_client, &mut message).await?;
1525    let mut tx = Transaction::new_unsigned(message);
1526    if sign_only {
1527        tx.try_partial_sign(&config.signers, recent_blockhash)?;
1528        return_signers_with_config(
1529            &tx,
1530            &config.output_format,
1531            &ReturnSignersConfig {
1532                dump_transaction_message,
1533            },
1534        )
1535    } else {
1536        tx.try_sign(&config.signers, recent_blockhash)?;
1537        if let Some(nonce_account) = &nonce_account {
1538            let nonce_account =
1539                solana_rpc_client_nonce_utils::nonblocking::get_account_with_commitment(
1540                    rpc_client,
1541                    nonce_account,
1542                    config.commitment,
1543                )
1544                .await?;
1545            check_nonce_account(&nonce_account, &nonce_authority.pubkey(), &recent_blockhash)?;
1546        }
1547        check_account_for_fee_with_commitment(
1548            rpc_client,
1549            &config.signers[0].pubkey(),
1550            &tx.message,
1551            config.commitment,
1552        )
1553        .await?;
1554        let result = rpc_client
1555            .send_and_confirm_transaction_with_spinner_and_config(
1556                &tx,
1557                config.commitment,
1558                config.send_transaction_config,
1559            )
1560            .await;
1561        log_instruction_custom_error::<VoteError>(result, config)
1562    }
1563}
1564
1565pub(crate) async fn get_vote_account(
1566    rpc_client: &RpcClient,
1567    vote_account_pubkey: &Pubkey,
1568    commitment_config: CommitmentConfig,
1569) -> Result<(Account, VoteStateV4), Box<dyn std::error::Error>> {
1570    let vote_account = rpc_client
1571        .get_account_with_commitment(vote_account_pubkey, commitment_config)
1572        .await?
1573        .value
1574        .ok_or_else(|| {
1575            CliError::RpcRequestError(format!("{vote_account_pubkey:?} account does not exist"))
1576        })?;
1577
1578    if vote_account.owner != solana_vote_program::id() {
1579        return Err(CliError::RpcRequestError(format!(
1580            "{vote_account_pubkey:?} is not a vote account"
1581        ))
1582        .into());
1583    }
1584    let vote_state =
1585        VoteStateV4::deserialize(&vote_account.data, vote_account_pubkey).map_err(|_| {
1586            CliError::RpcRequestError(
1587                "Account data could not be deserialized to vote state".to_string(),
1588            )
1589        })?;
1590
1591    Ok((vote_account, vote_state))
1592}
1593
1594pub async fn process_show_vote_account(
1595    rpc_client: &RpcClient,
1596    config: &CliConfig<'_>,
1597    vote_account_address: &Pubkey,
1598    use_lamports_unit: bool,
1599    use_csv: bool,
1600    with_rewards: Option<usize>,
1601    starting_epoch: Option<u64>,
1602) -> ProcessResult {
1603    let (vote_account, vote_state) =
1604        get_vote_account(rpc_client, vote_account_address, config.commitment).await?;
1605
1606    let epoch_schedule = rpc_client.get_epoch_schedule().await?;
1607    let tvc_activation_slot = rpc_client
1608        .get_account_with_commitment(
1609            &agave_feature_set::timely_vote_credits::id(),
1610            config.commitment,
1611        )
1612        .await
1613        .ok()
1614        .and_then(|response| response.value)
1615        .and_then(|account| from_account(&account))
1616        .and_then(|feature| feature.activated_at);
1617    let tvc_activation_epoch = tvc_activation_slot.map(|s| epoch_schedule.get_epoch(s));
1618
1619    let mut votes: Vec<CliLandedVote> = vec![];
1620    let mut epoch_voting_history: Vec<CliEpochVotingHistory> = vec![];
1621    if !vote_state.votes.is_empty() {
1622        for vote in &vote_state.votes {
1623            votes.push(vote.into());
1624        }
1625        for (epoch, credits, prev_credits) in vote_state.epoch_credits.iter().copied() {
1626            let credits_earned = credits.saturating_sub(prev_credits);
1627            let slots_in_epoch = epoch_schedule.get_slots_in_epoch(epoch);
1628            let is_tvc_active = tvc_activation_epoch.map(|e| epoch >= e).unwrap_or_default();
1629            let max_credits_per_slot = if is_tvc_active {
1630                VOTE_CREDITS_MAXIMUM_PER_SLOT
1631            } else {
1632                1
1633            };
1634            epoch_voting_history.push(CliEpochVotingHistory {
1635                epoch,
1636                slots_in_epoch,
1637                credits_earned,
1638                credits,
1639                prev_credits,
1640                max_credits_per_slot,
1641            });
1642        }
1643    }
1644
1645    let epoch_rewards = if let Some(num_epochs) = with_rewards {
1646        match crate::stake::fetch_epoch_rewards(
1647            rpc_client,
1648            vote_account_address,
1649            num_epochs,
1650            starting_epoch,
1651        )
1652        .await
1653        {
1654            Ok(rewards) => Some(rewards),
1655            Err(error) => {
1656                eprintln!("Failed to fetch epoch rewards: {error:?}");
1657                None
1658            }
1659        }
1660    } else {
1661        None
1662    };
1663
1664    let vote_account_data = CliVoteAccount {
1665        account_balance: vote_account.lamports,
1666        validator_identity: vote_state.node_pubkey.to_string(),
1667        authorized_voters: (&vote_state.authorized_voters).into(),
1668        authorized_withdrawer: vote_state.authorized_withdrawer.to_string(),
1669        credits: vote_state.credits(),
1670        commission: vote_state
1671            .inflation_rewards_commission_bps
1672            .div_ceil(100)
1673            .min(u8::MAX as u16) as u8,
1674        root_slot: vote_state.root_slot,
1675        recent_timestamp: vote_state.last_timestamp.clone(),
1676        votes,
1677        epoch_voting_history,
1678        use_lamports_unit,
1679        use_csv,
1680        epoch_rewards,
1681        inflation_rewards_commission_bps: vote_state.inflation_rewards_commission_bps,
1682        inflation_rewards_collector: vote_state.inflation_rewards_collector.to_string(),
1683        block_revenue_collector: vote_state.block_revenue_collector.to_string(),
1684        block_revenue_commission_bps: vote_state.block_revenue_commission_bps,
1685        pending_delegator_rewards: vote_state.pending_delegator_rewards,
1686        bls_pubkey_compressed: vote_state
1687            .bls_pubkey_compressed
1688            .map(|bytes| bs58::encode(bytes).into_string()),
1689    };
1690
1691    Ok(config.output_format.formatted_string(&vote_account_data))
1692}
1693
1694#[allow(clippy::too_many_arguments)]
1695pub async fn process_withdraw_from_vote_account(
1696    rpc_client: &RpcClient,
1697    config: &CliConfig<'_>,
1698    vote_account_pubkey: &Pubkey,
1699    withdraw_authority: SignerIndex,
1700    withdraw_amount: SpendAmount,
1701    destination_account_pubkey: &Pubkey,
1702    sign_only: bool,
1703    dump_transaction_message: bool,
1704    blockhash_query: &BlockhashQuery,
1705    nonce_account: Option<&Pubkey>,
1706    nonce_authority: SignerIndex,
1707    memo: Option<&String>,
1708    fee_payer: SignerIndex,
1709    compute_unit_price: Option<u64>,
1710) -> ProcessResult {
1711    let withdraw_authority = config.signers[withdraw_authority];
1712    let recent_blockhash = blockhash_query
1713        .get_blockhash(rpc_client, config.commitment)
1714        .await?;
1715
1716    let fee_payer = config.signers[fee_payer];
1717    let nonce_authority = config.signers[nonce_authority];
1718
1719    let compute_unit_limit = match blockhash_query {
1720        BlockhashQuery::Static(_) | BlockhashQuery::Validated(_, _) => ComputeUnitLimit::Default,
1721        BlockhashQuery::Rpc(_) => ComputeUnitLimit::Simulated,
1722    };
1723    let build_message = |lamports| {
1724        let ixs = vec![withdraw(
1725            vote_account_pubkey,
1726            &withdraw_authority.pubkey(),
1727            lamports,
1728            destination_account_pubkey,
1729        )]
1730        .with_memo(memo)
1731        .with_compute_unit_config(&ComputeUnitConfig {
1732            compute_unit_price,
1733            compute_unit_limit,
1734        });
1735
1736        if let Some(nonce_account) = &nonce_account {
1737            Message::new_with_nonce(
1738                ixs,
1739                Some(&fee_payer.pubkey()),
1740                nonce_account,
1741                &nonce_authority.pubkey(),
1742            )
1743        } else {
1744            Message::new(&ixs, Some(&fee_payer.pubkey()))
1745        }
1746    };
1747
1748    let (message, _) = resolve_spend_tx_and_check_account_balances(
1749        rpc_client,
1750        sign_only,
1751        withdraw_amount,
1752        &recent_blockhash,
1753        vote_account_pubkey,
1754        &fee_payer.pubkey(),
1755        compute_unit_limit,
1756        build_message,
1757        config.commitment,
1758    )
1759    .await?;
1760
1761    if !sign_only {
1762        let current_balance = rpc_client.get_balance(vote_account_pubkey).await?;
1763        let minimum_balance = rpc_client
1764            .get_minimum_balance_for_rent_exemption(VoteStateV4::size_of())
1765            .await?;
1766        if let SpendAmount::Some(withdraw_amount) = withdraw_amount {
1767            let balance_remaining = current_balance.saturating_sub(withdraw_amount);
1768            if balance_remaining < minimum_balance && balance_remaining != 0 {
1769                return Err(CliError::BadParameter(format!(
1770                    "Withdraw amount too large. The vote account balance must be at least {} SOL \
1771                     to remain rent exempt",
1772                    build_balance_message(minimum_balance, false, false)
1773                ))
1774                .into());
1775            }
1776        }
1777    }
1778
1779    let mut tx = Transaction::new_unsigned(message);
1780
1781    if sign_only {
1782        tx.try_partial_sign(&config.signers, recent_blockhash)?;
1783        return_signers_with_config(
1784            &tx,
1785            &config.output_format,
1786            &ReturnSignersConfig {
1787                dump_transaction_message,
1788            },
1789        )
1790    } else {
1791        tx.try_sign(&config.signers, recent_blockhash)?;
1792        if let Some(nonce_account) = &nonce_account {
1793            let nonce_account =
1794                solana_rpc_client_nonce_utils::nonblocking::get_account_with_commitment(
1795                    rpc_client,
1796                    nonce_account,
1797                    config.commitment,
1798                )
1799                .await?;
1800            check_nonce_account(&nonce_account, &nonce_authority.pubkey(), &recent_blockhash)?;
1801        }
1802        check_account_for_fee_with_commitment(
1803            rpc_client,
1804            &tx.message.account_keys[0],
1805            &tx.message,
1806            config.commitment,
1807        )
1808        .await?;
1809        let result = rpc_client
1810            .send_and_confirm_transaction_with_spinner_and_config(
1811                &tx,
1812                config.commitment,
1813                config.send_transaction_config,
1814            )
1815            .await;
1816        log_instruction_custom_error::<VoteError>(result, config)
1817    }
1818}
1819
1820pub async fn process_close_vote_account(
1821    rpc_client: &RpcClient,
1822    config: &CliConfig<'_>,
1823    vote_account_pubkey: &Pubkey,
1824    withdraw_authority: SignerIndex,
1825    destination_account_pubkey: &Pubkey,
1826    memo: Option<&String>,
1827    fee_payer: SignerIndex,
1828    compute_unit_price: Option<u64>,
1829) -> ProcessResult {
1830    let vote_account_status = rpc_client
1831        .get_vote_accounts_with_config(RpcGetVoteAccountsConfig {
1832            vote_pubkey: Some(vote_account_pubkey.to_string()),
1833            ..RpcGetVoteAccountsConfig::default()
1834        })
1835        .await?;
1836
1837    if let Some(vote_account) = vote_account_status
1838        .current
1839        .into_iter()
1840        .chain(vote_account_status.delinquent)
1841        .next()
1842    {
1843        if vote_account.activated_stake != 0 {
1844            return Err(format!(
1845                "Cannot close a vote account with active stake: {vote_account_pubkey}"
1846            )
1847            .into());
1848        }
1849    }
1850
1851    let latest_blockhash = rpc_client.get_latest_blockhash().await?;
1852    let withdraw_authority = config.signers[withdraw_authority];
1853    let fee_payer = config.signers[fee_payer];
1854
1855    let current_balance = rpc_client.get_balance(vote_account_pubkey).await?;
1856
1857    let compute_unit_limit = ComputeUnitLimit::Simulated;
1858    let ixs = vec![withdraw(
1859        vote_account_pubkey,
1860        &withdraw_authority.pubkey(),
1861        current_balance,
1862        destination_account_pubkey,
1863    )]
1864    .with_memo(memo)
1865    .with_compute_unit_config(&ComputeUnitConfig {
1866        compute_unit_price,
1867        compute_unit_limit,
1868    });
1869
1870    let mut message = Message::new(&ixs, Some(&fee_payer.pubkey()));
1871    simulate_and_update_compute_unit_limit(&compute_unit_limit, rpc_client, &mut message).await?;
1872    let mut tx = Transaction::new_unsigned(message);
1873    tx.try_sign(&config.signers, latest_blockhash)?;
1874    check_account_for_fee_with_commitment(
1875        rpc_client,
1876        &tx.message.account_keys[0],
1877        &tx.message,
1878        config.commitment,
1879    )
1880    .await?;
1881    let result = rpc_client
1882        .send_and_confirm_transaction_with_spinner_and_config(
1883            &tx,
1884            config.commitment,
1885            config.send_transaction_config,
1886        )
1887        .await;
1888    log_instruction_custom_error::<VoteError>(result, config)
1889}
1890
1891#[cfg(test)]
1892mod tests {
1893    use {
1894        super::*,
1895        crate::{clap_app::get_clap_app, cli::parse_command},
1896        solana_hash::Hash,
1897        solana_keypair::{Keypair, read_keypair_file, write_keypair},
1898        solana_presigner::Presigner,
1899        solana_rpc_client_nonce_utils::nonblocking::blockhash_query::Source,
1900        solana_signer::Signer,
1901        tempfile::NamedTempFile,
1902    };
1903
1904    fn make_tmp_file() -> (String, NamedTempFile) {
1905        let tmp_file = NamedTempFile::new().unwrap();
1906        (String::from(tmp_file.path().to_str().unwrap()), tmp_file)
1907    }
1908
1909    #[test]
1910    fn test_parse_command() {
1911        let test_commands = get_clap_app("test", "desc", "version");
1912        let keypair = Keypair::new();
1913        let pubkey = keypair.pubkey();
1914        let pubkey_string = pubkey.to_string();
1915        let keypair2 = Keypair::new();
1916        let pubkey2 = keypair2.pubkey();
1917        let pubkey2_string = pubkey2.to_string();
1918        let sig2 = keypair2.sign_message(&[0u8]);
1919        let signer2 = format!("{}={}", keypair2.pubkey(), sig2);
1920
1921        let default_keypair = Keypair::new();
1922        let (default_keypair_file, mut tmp_file) = make_tmp_file();
1923        write_keypair(&default_keypair, tmp_file.as_file_mut()).unwrap();
1924        let default_signer = DefaultSigner::new("", &default_keypair_file);
1925
1926        let blockhash = Hash::default();
1927        let blockhash_string = format!("{blockhash}");
1928        let nonce_account = Pubkey::new_unique();
1929
1930        // Test VoteAuthorize SubCommand
1931        let test_authorize_voter = test_commands.clone().get_matches_from(vec![
1932            "test",
1933            "vote-authorize-voter",
1934            &pubkey_string,
1935            &default_keypair_file,
1936            &pubkey2_string,
1937        ]);
1938        assert_eq!(
1939            parse_command(&test_authorize_voter, &default_signer, &mut None).unwrap(),
1940            CliCommandInfo {
1941                command: CliCommand::VoteAuthorize {
1942                    vote_account_pubkey: pubkey,
1943                    new_authorized_pubkey: pubkey2,
1944                    vote_authorize: VoteAuthorize::Voter,
1945                    use_v2_instruction: false,
1946                    sign_only: false,
1947                    dump_transaction_message: false,
1948                    blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
1949                    nonce_account: None,
1950                    nonce_authority: 0,
1951                    memo: None,
1952                    fee_payer: 0,
1953                    authorized: 0,
1954                    new_authorized: None,
1955                    compute_unit_price: None,
1956                },
1957                signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap())],
1958            }
1959        );
1960
1961        let authorized_keypair = Keypair::new();
1962        let (authorized_keypair_file, mut tmp_file) = make_tmp_file();
1963        write_keypair(&authorized_keypair, tmp_file.as_file_mut()).unwrap();
1964
1965        let test_authorize_voter = test_commands.clone().get_matches_from(vec![
1966            "test",
1967            "vote-authorize-voter",
1968            &pubkey_string,
1969            &authorized_keypair_file,
1970            &pubkey2_string,
1971        ]);
1972        assert_eq!(
1973            parse_command(&test_authorize_voter, &default_signer, &mut None).unwrap(),
1974            CliCommandInfo {
1975                command: CliCommand::VoteAuthorize {
1976                    vote_account_pubkey: pubkey,
1977                    new_authorized_pubkey: pubkey2,
1978                    vote_authorize: VoteAuthorize::Voter,
1979                    use_v2_instruction: false,
1980                    sign_only: false,
1981                    dump_transaction_message: false,
1982                    blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
1983                    nonce_account: None,
1984                    nonce_authority: 0,
1985                    memo: None,
1986                    fee_payer: 0,
1987                    authorized: 1,
1988                    new_authorized: None,
1989                    compute_unit_price: None,
1990                },
1991                signers: vec![
1992                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
1993                    Box::new(read_keypair_file(&authorized_keypair_file).unwrap()),
1994                ],
1995            }
1996        );
1997
1998        let test_authorize_voter = test_commands.clone().get_matches_from(vec![
1999            "test",
2000            "vote-authorize-voter",
2001            &pubkey_string,
2002            &authorized_keypair_file,
2003            &pubkey2_string,
2004            "--blockhash",
2005            &blockhash_string,
2006            "--sign-only",
2007        ]);
2008        assert_eq!(
2009            parse_command(&test_authorize_voter, &default_signer, &mut None).unwrap(),
2010            CliCommandInfo {
2011                command: CliCommand::VoteAuthorize {
2012                    vote_account_pubkey: pubkey,
2013                    new_authorized_pubkey: pubkey2,
2014                    vote_authorize: VoteAuthorize::Voter,
2015                    use_v2_instruction: false,
2016                    sign_only: true,
2017                    dump_transaction_message: false,
2018                    blockhash_query: BlockhashQuery::Static(blockhash),
2019                    nonce_account: None,
2020                    nonce_authority: 0,
2021                    memo: None,
2022                    fee_payer: 0,
2023                    authorized: 1,
2024                    new_authorized: None,
2025                    compute_unit_price: None,
2026                },
2027                signers: vec![
2028                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
2029                    Box::new(read_keypair_file(&authorized_keypair_file).unwrap()),
2030                ],
2031            }
2032        );
2033
2034        let authorized_sig = authorized_keypair.sign_message(&[0u8]);
2035        let authorized_signer = format!("{}={}", authorized_keypair.pubkey(), authorized_sig);
2036        let test_authorize_voter = test_commands.clone().get_matches_from(vec![
2037            "test",
2038            "vote-authorize-voter",
2039            &pubkey_string,
2040            &authorized_keypair.pubkey().to_string(),
2041            &pubkey2_string,
2042            "--blockhash",
2043            &blockhash_string,
2044            "--signer",
2045            &authorized_signer,
2046            "--signer",
2047            &signer2,
2048            "--fee-payer",
2049            &pubkey2_string,
2050            "--nonce",
2051            &nonce_account.to_string(),
2052            "--nonce-authority",
2053            &pubkey2_string,
2054        ]);
2055        assert_eq!(
2056            parse_command(&test_authorize_voter, &default_signer, &mut None).unwrap(),
2057            CliCommandInfo {
2058                command: CliCommand::VoteAuthorize {
2059                    vote_account_pubkey: pubkey,
2060                    new_authorized_pubkey: pubkey2,
2061                    vote_authorize: VoteAuthorize::Voter,
2062                    use_v2_instruction: false,
2063                    sign_only: false,
2064                    dump_transaction_message: false,
2065                    blockhash_query: BlockhashQuery::Validated(
2066                        Source::NonceAccount(nonce_account),
2067                        blockhash
2068                    ),
2069                    nonce_account: Some(nonce_account),
2070                    nonce_authority: 0,
2071                    memo: None,
2072                    fee_payer: 0,
2073                    authorized: 1,
2074                    new_authorized: None,
2075                    compute_unit_price: None,
2076                },
2077                signers: vec![
2078                    Box::new(Presigner::new(&pubkey2, &sig2)),
2079                    Box::new(Presigner::new(
2080                        &authorized_keypair.pubkey(),
2081                        &authorized_sig
2082                    )),
2083                ],
2084            }
2085        );
2086
2087        // Test checked VoteAuthorize SubCommand
2088        let (voter_keypair_file, mut tmp_file) = make_tmp_file();
2089        let voter_keypair = Keypair::new();
2090        write_keypair(&voter_keypair, tmp_file.as_file_mut()).unwrap();
2091
2092        let test_authorize_voter = test_commands.clone().get_matches_from(vec![
2093            "test",
2094            "vote-authorize-voter-checked",
2095            &pubkey_string,
2096            &default_keypair_file,
2097            &voter_keypair_file,
2098        ]);
2099        assert_eq!(
2100            parse_command(&test_authorize_voter, &default_signer, &mut None).unwrap(),
2101            CliCommandInfo {
2102                command: CliCommand::VoteAuthorize {
2103                    vote_account_pubkey: pubkey,
2104                    new_authorized_pubkey: voter_keypair.pubkey(),
2105                    vote_authorize: VoteAuthorize::Voter,
2106                    use_v2_instruction: false,
2107                    sign_only: false,
2108                    dump_transaction_message: false,
2109                    blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
2110                    nonce_account: None,
2111                    nonce_authority: 0,
2112                    memo: None,
2113                    fee_payer: 0,
2114                    authorized: 0,
2115                    new_authorized: Some(1),
2116                    compute_unit_price: None,
2117                },
2118                signers: vec![
2119                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
2120                    Box::new(read_keypair_file(&voter_keypair_file).unwrap())
2121                ],
2122            }
2123        );
2124
2125        let test_authorize_voter = test_commands.clone().get_matches_from(vec![
2126            "test",
2127            "vote-authorize-voter-checked",
2128            &pubkey_string,
2129            &authorized_keypair_file,
2130            &voter_keypair_file,
2131        ]);
2132        assert_eq!(
2133            parse_command(&test_authorize_voter, &default_signer, &mut None).unwrap(),
2134            CliCommandInfo {
2135                command: CliCommand::VoteAuthorize {
2136                    vote_account_pubkey: pubkey,
2137                    new_authorized_pubkey: voter_keypair.pubkey(),
2138                    vote_authorize: VoteAuthorize::Voter,
2139                    use_v2_instruction: false,
2140                    sign_only: false,
2141                    dump_transaction_message: false,
2142                    blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
2143                    nonce_account: None,
2144                    nonce_authority: 0,
2145                    memo: None,
2146                    fee_payer: 0,
2147                    authorized: 1,
2148                    new_authorized: Some(2),
2149                    compute_unit_price: None,
2150                },
2151                signers: vec![
2152                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
2153                    Box::new(read_keypair_file(&authorized_keypair_file).unwrap()),
2154                    Box::new(read_keypair_file(&voter_keypair_file).unwrap()),
2155                ],
2156            }
2157        );
2158
2159        let test_authorize_voter = test_commands.clone().get_matches_from(vec![
2160            "test",
2161            "vote-authorize-voter-checked",
2162            &pubkey_string,
2163            &authorized_keypair_file,
2164            &pubkey2_string,
2165        ]);
2166        assert!(parse_command(&test_authorize_voter, &default_signer, &mut None).is_err());
2167
2168        // Test vote-authorize-voter-checked with --use-v2-instruction flag.
2169        let (new_voter_keypair_file, mut tmp_file) = make_tmp_file();
2170        let new_voter_keypair = Keypair::new();
2171        write_keypair(&new_voter_keypair, tmp_file.as_file_mut()).unwrap();
2172
2173        let test_authorize_voter_checked_with_bls = test_commands.clone().get_matches_from(vec![
2174            "test",
2175            "vote-authorize-voter-checked",
2176            &pubkey_string,
2177            &authorized_keypair_file,
2178            &new_voter_keypair_file,
2179            "--use-v2-instruction",
2180        ]);
2181        assert_eq!(
2182            parse_command(
2183                &test_authorize_voter_checked_with_bls,
2184                &default_signer,
2185                &mut None
2186            )
2187            .unwrap(),
2188            CliCommandInfo {
2189                command: CliCommand::VoteAuthorize {
2190                    vote_account_pubkey: pubkey,
2191                    new_authorized_pubkey: new_voter_keypair.pubkey(),
2192                    vote_authorize: VoteAuthorize::Voter,
2193                    use_v2_instruction: true,
2194                    sign_only: false,
2195                    dump_transaction_message: false,
2196                    blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
2197                    nonce_account: None,
2198                    nonce_authority: 0,
2199                    memo: None,
2200                    fee_payer: 0,
2201                    authorized: 1,
2202                    new_authorized: Some(2),
2203                    compute_unit_price: None,
2204                },
2205                signers: vec![
2206                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
2207                    Box::new(read_keypair_file(&authorized_keypair_file).unwrap()),
2208                    Box::new(read_keypair_file(&new_voter_keypair_file).unwrap()),
2209                ],
2210            }
2211        );
2212
2213        // Test CreateVoteAccount SubCommand
2214        let (identity_keypair_file, mut tmp_file) = make_tmp_file();
2215        let identity_keypair = Keypair::new();
2216        let authorized_withdrawer = Keypair::new().pubkey();
2217        write_keypair(&identity_keypair, tmp_file.as_file_mut()).unwrap();
2218        let (keypair_file, mut tmp_file) = make_tmp_file();
2219        let keypair = Keypair::new();
2220        write_keypair(&keypair, tmp_file.as_file_mut()).unwrap();
2221
2222        let test_create_vote_account = test_commands.clone().get_matches_from(vec![
2223            "test",
2224            "create-vote-account",
2225            &keypair_file,
2226            &identity_keypair_file,
2227            &authorized_withdrawer.to_string(),
2228            "--commission",
2229            "10",
2230        ]);
2231        assert_eq!(
2232            parse_command(&test_create_vote_account, &default_signer, &mut None).unwrap(),
2233            CliCommandInfo {
2234                command: CliCommand::CreateVoteAccount {
2235                    vote_account: 1,
2236                    seed: None,
2237                    identity_account: 2,
2238                    authorized_voter: None,
2239                    authorized_withdrawer,
2240                    commission: Some(10),
2241                    use_v2_instruction: false,
2242
2243                    inflation_rewards_commission_bps: None,
2244                    inflation_rewards_collector: None,
2245                    block_revenue_commission_bps: None,
2246                    block_revenue_collector: None,
2247                    sign_only: false,
2248                    dump_transaction_message: false,
2249                    blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
2250                    nonce_account: None,
2251                    nonce_authority: 0,
2252                    memo: None,
2253                    fee_payer: 0,
2254                    compute_unit_price: None,
2255                },
2256                signers: vec![
2257                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
2258                    Box::new(read_keypair_file(&keypair_file).unwrap()),
2259                    Box::new(read_keypair_file(&identity_keypair_file).unwrap()),
2260                ],
2261            }
2262        );
2263
2264        let test_create_vote_account2 = test_commands.clone().get_matches_from(vec![
2265            "test",
2266            "create-vote-account",
2267            &keypair_file,
2268            &identity_keypair_file,
2269            &authorized_withdrawer.to_string(),
2270        ]);
2271        assert_eq!(
2272            parse_command(&test_create_vote_account2, &default_signer, &mut None).unwrap(),
2273            CliCommandInfo {
2274                command: CliCommand::CreateVoteAccount {
2275                    vote_account: 1,
2276                    seed: None,
2277                    identity_account: 2,
2278                    authorized_voter: None,
2279                    authorized_withdrawer,
2280                    commission: None, // No --commission; uses default at runtime.
2281                    use_v2_instruction: false,
2282
2283                    inflation_rewards_commission_bps: None,
2284                    inflation_rewards_collector: None,
2285                    block_revenue_commission_bps: None,
2286                    block_revenue_collector: None,
2287                    sign_only: false,
2288                    dump_transaction_message: false,
2289                    blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
2290                    nonce_account: None,
2291                    nonce_authority: 0,
2292                    memo: None,
2293                    fee_payer: 0,
2294                    compute_unit_price: None,
2295                },
2296                signers: vec![
2297                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
2298                    Box::new(read_keypair_file(&keypair_file).unwrap()),
2299                    Box::new(read_keypair_file(&identity_keypair_file).unwrap()),
2300                ],
2301            }
2302        );
2303
2304        let test_create_vote_account = test_commands.clone().get_matches_from(vec![
2305            "test",
2306            "create-vote-account",
2307            &keypair_file,
2308            &identity_keypair_file,
2309            &authorized_withdrawer.to_string(),
2310            "--commission",
2311            "10",
2312            "--blockhash",
2313            &blockhash_string,
2314            "--sign-only",
2315            "--fee-payer",
2316            &default_keypair.pubkey().to_string(),
2317        ]);
2318        assert_eq!(
2319            parse_command(&test_create_vote_account, &default_signer, &mut None).unwrap(),
2320            CliCommandInfo {
2321                command: CliCommand::CreateVoteAccount {
2322                    vote_account: 1,
2323                    seed: None,
2324                    identity_account: 2,
2325                    authorized_voter: None,
2326                    authorized_withdrawer,
2327                    commission: Some(10), // Explicitly set.
2328                    use_v2_instruction: false,
2329
2330                    inflation_rewards_commission_bps: None,
2331                    inflation_rewards_collector: None,
2332                    block_revenue_commission_bps: None,
2333                    block_revenue_collector: None,
2334                    sign_only: true,
2335                    dump_transaction_message: false,
2336                    blockhash_query: BlockhashQuery::Static(blockhash),
2337                    nonce_account: None,
2338                    nonce_authority: 0,
2339                    memo: None,
2340                    fee_payer: 0,
2341                    compute_unit_price: None,
2342                },
2343                signers: vec![
2344                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
2345                    Box::new(read_keypair_file(&keypair_file).unwrap()),
2346                    Box::new(read_keypair_file(&identity_keypair_file).unwrap()),
2347                ],
2348            }
2349        );
2350
2351        let identity_sig = identity_keypair.sign_message(&[0u8]);
2352        let identity_signer = format!("{}={}", identity_keypair.pubkey(), identity_sig);
2353        let test_create_vote_account = test_commands.clone().get_matches_from(vec![
2354            "test",
2355            "create-vote-account",
2356            &keypair_file,
2357            &identity_keypair.pubkey().to_string(),
2358            &authorized_withdrawer.to_string(),
2359            "--commission",
2360            "10",
2361            "--blockhash",
2362            &blockhash_string,
2363            "--signer",
2364            &identity_signer,
2365            "--signer",
2366            &signer2,
2367            "--fee-payer",
2368            &default_keypair_file,
2369            "--nonce",
2370            &nonce_account.to_string(),
2371            "--nonce-authority",
2372            &pubkey2_string,
2373        ]);
2374        assert_eq!(
2375            parse_command(&test_create_vote_account, &default_signer, &mut None).unwrap(),
2376            CliCommandInfo {
2377                command: CliCommand::CreateVoteAccount {
2378                    vote_account: 1,
2379                    seed: None,
2380                    identity_account: 2,
2381                    authorized_voter: None,
2382                    authorized_withdrawer,
2383                    commission: Some(10),
2384                    use_v2_instruction: false,
2385
2386                    inflation_rewards_commission_bps: None,
2387                    inflation_rewards_collector: None,
2388                    block_revenue_commission_bps: None,
2389                    block_revenue_collector: None,
2390                    sign_only: false,
2391                    dump_transaction_message: false,
2392                    blockhash_query: BlockhashQuery::Validated(
2393                        Source::NonceAccount(nonce_account),
2394                        blockhash
2395                    ),
2396                    nonce_account: Some(nonce_account),
2397                    nonce_authority: 3,
2398                    memo: None,
2399                    fee_payer: 0,
2400                    compute_unit_price: None,
2401                },
2402                signers: vec![
2403                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
2404                    Box::new(read_keypair_file(&keypair_file).unwrap()),
2405                    Box::new(Presigner::new(&identity_keypair.pubkey(), &identity_sig)),
2406                    Box::new(Presigner::new(&pubkey2, &sig2)),
2407                ],
2408            }
2409        );
2410
2411        // test init with an authed voter
2412        let authed = solana_pubkey::new_rand();
2413        let (keypair_file, mut tmp_file) = make_tmp_file();
2414        let keypair = Keypair::new();
2415        write_keypair(&keypair, tmp_file.as_file_mut()).unwrap();
2416
2417        let test_create_vote_account3 = test_commands.clone().get_matches_from(vec![
2418            "test",
2419            "create-vote-account",
2420            &keypair_file,
2421            &identity_keypair_file,
2422            &authorized_withdrawer.to_string(),
2423            "--authorized-voter",
2424            &authed.to_string(),
2425        ]);
2426        assert_eq!(
2427            parse_command(&test_create_vote_account3, &default_signer, &mut None).unwrap(),
2428            CliCommandInfo {
2429                command: CliCommand::CreateVoteAccount {
2430                    vote_account: 1,
2431                    seed: None,
2432                    identity_account: 2,
2433                    authorized_voter: Some(authed),
2434                    authorized_withdrawer,
2435                    commission: None, // No --commission specified.
2436                    use_v2_instruction: false,
2437
2438                    inflation_rewards_commission_bps: None,
2439                    inflation_rewards_collector: None,
2440                    block_revenue_commission_bps: None,
2441                    block_revenue_collector: None,
2442                    sign_only: false,
2443                    dump_transaction_message: false,
2444                    blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
2445                    nonce_account: None,
2446                    nonce_authority: 0,
2447                    memo: None,
2448                    fee_payer: 0,
2449                    compute_unit_price: None,
2450                },
2451                signers: vec![
2452                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
2453                    Box::new(keypair),
2454                    Box::new(read_keypair_file(&identity_keypair_file).unwrap()),
2455                ],
2456            }
2457        );
2458
2459        let (keypair_file, mut tmp_file) = make_tmp_file();
2460        let keypair = Keypair::new();
2461        write_keypair(&keypair, tmp_file.as_file_mut()).unwrap();
2462        // succeed even though withdrawer unsafe (because forcefully allowed)
2463        let test_create_vote_account4 = test_commands.clone().get_matches_from(vec![
2464            "test",
2465            "create-vote-account",
2466            &keypair_file,
2467            &identity_keypair_file,
2468            &identity_keypair_file,
2469            "--allow-unsafe-authorized-withdrawer",
2470        ]);
2471        assert_eq!(
2472            parse_command(&test_create_vote_account4, &default_signer, &mut None).unwrap(),
2473            CliCommandInfo {
2474                command: CliCommand::CreateVoteAccount {
2475                    vote_account: 1,
2476                    seed: None,
2477                    identity_account: 2,
2478                    authorized_voter: None,
2479                    authorized_withdrawer: identity_keypair.pubkey(),
2480                    commission: None, // No --commission specified.
2481                    use_v2_instruction: false,
2482
2483                    inflation_rewards_commission_bps: None,
2484                    inflation_rewards_collector: None,
2485                    block_revenue_commission_bps: None,
2486                    block_revenue_collector: None,
2487                    sign_only: false,
2488                    dump_transaction_message: false,
2489                    blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
2490                    nonce_account: None,
2491                    nonce_authority: 0,
2492                    memo: None,
2493                    fee_payer: 0,
2494                    compute_unit_price: None,
2495                },
2496                signers: vec![
2497                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
2498                    Box::new(read_keypair_file(&keypair_file).unwrap()),
2499                    Box::new(read_keypair_file(&identity_keypair_file).unwrap()),
2500                ],
2501            }
2502        );
2503
2504        // Test create-vote-account with --use-v2-instruction flag (VoteInitV2).
2505        let (keypair_file, mut tmp_file) = make_tmp_file();
2506        let keypair = Keypair::new();
2507        write_keypair(&keypair, tmp_file.as_file_mut()).unwrap();
2508        let inflation_rewards_collector = Keypair::new().pubkey();
2509        let block_revenue_collector = Keypair::new().pubkey();
2510
2511        // Test with VoteInitV2 and all optional arguments specified.
2512        let test_create_vote_account_v2 = test_commands.clone().get_matches_from(vec![
2513            "test",
2514            "create-vote-account",
2515            &keypair_file,
2516            &identity_keypair_file,
2517            &authorized_withdrawer.to_string(),
2518            "--use-v2-instruction",
2519            "--authorized-voter",
2520            &authed.to_string(),
2521            "--inflation-rewards-commission-bps",
2522            "500",
2523            "--inflation-rewards-collector",
2524            &inflation_rewards_collector.to_string(),
2525            "--block-revenue-commission-bps",
2526            "1000",
2527            "--block-revenue-collector",
2528            &block_revenue_collector.to_string(),
2529        ]);
2530        assert_eq!(
2531            parse_command(&test_create_vote_account_v2, &default_signer, &mut None).unwrap(),
2532            CliCommandInfo {
2533                command: CliCommand::CreateVoteAccount {
2534                    vote_account: 1,
2535                    seed: None,
2536                    identity_account: 2,
2537                    authorized_voter: Some(authed),
2538                    authorized_withdrawer,
2539                    commission: None,
2540                    use_v2_instruction: true,
2541                    inflation_rewards_commission_bps: Some(500),
2542                    inflation_rewards_collector: Some(inflation_rewards_collector),
2543                    block_revenue_commission_bps: Some(1000),
2544                    block_revenue_collector: Some(block_revenue_collector),
2545                    sign_only: false,
2546                    dump_transaction_message: false,
2547                    blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
2548                    nonce_account: None,
2549                    nonce_authority: 0,
2550                    memo: None,
2551                    fee_payer: 0,
2552                    compute_unit_price: None,
2553                },
2554                signers: vec![
2555                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
2556                    Box::new(read_keypair_file(&keypair_file).unwrap()),
2557                    Box::new(read_keypair_file(&identity_keypair_file).unwrap()),
2558                ],
2559            }
2560        );
2561
2562        // Test with VoteInitV2 in sign-only mode.
2563        let (keypair_file, mut tmp_file) = make_tmp_file();
2564        let sign_only_vote_keypair = Keypair::new();
2565        write_keypair(&sign_only_vote_keypair, tmp_file.as_file_mut()).unwrap();
2566
2567        let test_create_vote_account_v2_sign_only = test_commands.clone().get_matches_from(vec![
2568            "test",
2569            "create-vote-account",
2570            &keypair_file,
2571            &identity_keypair_file,
2572            &authorized_withdrawer.to_string(),
2573            "--use-v2-instruction",
2574            "--blockhash",
2575            &blockhash_string,
2576            "--sign-only",
2577        ]);
2578        assert_eq!(
2579            parse_command(
2580                &test_create_vote_account_v2_sign_only,
2581                &default_signer,
2582                &mut None
2583            )
2584            .unwrap(),
2585            CliCommandInfo {
2586                command: CliCommand::CreateVoteAccount {
2587                    vote_account: 1,
2588                    seed: None,
2589                    identity_account: 2,
2590                    authorized_voter: None,
2591                    authorized_withdrawer,
2592                    commission: None,
2593                    use_v2_instruction: true,
2594
2595                    inflation_rewards_commission_bps: None,
2596                    inflation_rewards_collector: None,
2597                    block_revenue_commission_bps: None,
2598                    block_revenue_collector: None,
2599                    sign_only: true,
2600                    dump_transaction_message: false,
2601                    blockhash_query: BlockhashQuery::Static(blockhash),
2602                    nonce_account: None,
2603                    nonce_authority: 0,
2604                    memo: None,
2605                    fee_payer: 0,
2606                    compute_unit_price: None,
2607                },
2608                signers: vec![
2609                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
2610                    Box::new(read_keypair_file(&keypair_file).unwrap()),
2611                    Box::new(read_keypair_file(&identity_keypair_file).unwrap()),
2612                ],
2613            }
2614        );
2615
2616        // Test that --commission conflicts with any VoteInitV2 args.
2617        let (keypair_file, mut tmp_file) = make_tmp_file();
2618        let conflict_vote_keypair = Keypair::new();
2619        write_keypair(&conflict_vote_keypair, tmp_file.as_file_mut()).unwrap();
2620
2621        // --commission with --use-v2-instruction should error.
2622        let test_conflict = test_commands.clone().get_matches_from(vec![
2623            "test",
2624            "create-vote-account",
2625            &keypair_file,
2626            &identity_keypair_file,
2627            &authorized_withdrawer.to_string(),
2628            "--commission",
2629            "10",
2630            "--use-v2-instruction",
2631        ]);
2632        assert!(parse_command(&test_conflict, &default_signer, &mut None).is_err());
2633
2634        // --commission with --inflation-rewards-commission-bps should error.
2635        let test_conflict = test_commands.clone().get_matches_from(vec![
2636            "test",
2637            "create-vote-account",
2638            &keypair_file,
2639            &identity_keypair_file,
2640            &authorized_withdrawer.to_string(),
2641            "--commission",
2642            "10",
2643            "--inflation-rewards-commission-bps",
2644            "1000",
2645        ]);
2646        assert!(parse_command(&test_conflict, &default_signer, &mut None).is_err());
2647
2648        // --commission with --block-revenue-commission-bps should error.
2649        let test_conflict = test_commands.clone().get_matches_from(vec![
2650            "test",
2651            "create-vote-account",
2652            &keypair_file,
2653            &identity_keypair_file,
2654            &authorized_withdrawer.to_string(),
2655            "--commission",
2656            "10",
2657            "--block-revenue-commission-bps",
2658            "500",
2659        ]);
2660        assert!(parse_command(&test_conflict, &default_signer, &mut None).is_err());
2661
2662        let test_update_validator = test_commands.clone().get_matches_from(vec![
2663            "test",
2664            "vote-update-validator",
2665            &pubkey_string,
2666            &identity_keypair_file,
2667            &keypair_file,
2668        ]);
2669        assert_eq!(
2670            parse_command(&test_update_validator, &default_signer, &mut None).unwrap(),
2671            CliCommandInfo {
2672                command: CliCommand::VoteUpdateValidator {
2673                    vote_account_pubkey: pubkey,
2674                    new_identity_account: 2,
2675                    withdraw_authority: 1,
2676                    sign_only: false,
2677                    dump_transaction_message: false,
2678                    blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
2679                    nonce_account: None,
2680                    nonce_authority: 0,
2681                    memo: None,
2682                    fee_payer: 0,
2683                    compute_unit_price: None,
2684                },
2685                signers: vec![
2686                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
2687                    Box::new(read_keypair_file(&keypair_file).unwrap()),
2688                    Box::new(read_keypair_file(&identity_keypair_file).unwrap()),
2689                ],
2690            }
2691        );
2692
2693        let test_update_commission = test_commands.clone().get_matches_from(vec![
2694            "test",
2695            "vote-update-commission",
2696            &pubkey_string,
2697            "42",
2698            &keypair_file,
2699        ]);
2700        assert_eq!(
2701            parse_command(&test_update_commission, &default_signer, &mut None).unwrap(),
2702            CliCommandInfo {
2703                command: CliCommand::VoteUpdateCommission {
2704                    vote_account_pubkey: pubkey,
2705                    commission: 42,
2706                    withdraw_authority: 1,
2707                    sign_only: false,
2708                    dump_transaction_message: false,
2709                    blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
2710                    nonce_account: None,
2711                    nonce_authority: 0,
2712                    memo: None,
2713                    fee_payer: 0,
2714                    compute_unit_price: None,
2715                },
2716                signers: vec![
2717                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
2718                    Box::new(read_keypair_file(&keypair_file).unwrap()),
2719                ],
2720            }
2721        );
2722
2723        // Test WithdrawFromVoteAccount subcommand
2724        let test_withdraw_from_vote_account = test_commands.clone().get_matches_from(vec![
2725            "test",
2726            "withdraw-from-vote-account",
2727            &keypair_file,
2728            &pubkey_string,
2729            "42",
2730        ]);
2731        assert_eq!(
2732            parse_command(&test_withdraw_from_vote_account, &default_signer, &mut None).unwrap(),
2733            CliCommandInfo {
2734                command: CliCommand::WithdrawFromVoteAccount {
2735                    vote_account_pubkey: read_keypair_file(&keypair_file).unwrap().pubkey(),
2736                    destination_account_pubkey: pubkey,
2737                    withdraw_authority: 0,
2738                    withdraw_amount: SpendAmount::Some(42_000_000_000),
2739                    sign_only: false,
2740                    dump_transaction_message: false,
2741                    blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
2742                    nonce_account: None,
2743                    nonce_authority: 0,
2744                    memo: None,
2745                    fee_payer: 0,
2746                    compute_unit_price: None,
2747                },
2748                signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap())],
2749            }
2750        );
2751
2752        // Test WithdrawFromVoteAccount subcommand
2753        let test_withdraw_from_vote_account = test_commands.clone().get_matches_from(vec![
2754            "test",
2755            "withdraw-from-vote-account",
2756            &keypair_file,
2757            &pubkey_string,
2758            "ALL",
2759        ]);
2760        assert_eq!(
2761            parse_command(&test_withdraw_from_vote_account, &default_signer, &mut None).unwrap(),
2762            CliCommandInfo {
2763                command: CliCommand::WithdrawFromVoteAccount {
2764                    vote_account_pubkey: read_keypair_file(&keypair_file).unwrap().pubkey(),
2765                    destination_account_pubkey: pubkey,
2766                    withdraw_authority: 0,
2767                    withdraw_amount: SpendAmount::RentExempt,
2768                    sign_only: false,
2769                    dump_transaction_message: false,
2770                    blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
2771                    nonce_account: None,
2772                    nonce_authority: 0,
2773                    memo: None,
2774                    fee_payer: 0,
2775                    compute_unit_price: None,
2776                },
2777                signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap())],
2778            }
2779        );
2780
2781        // Test WithdrawFromVoteAccount subcommand with authority
2782        let withdraw_authority = Keypair::new();
2783        let (withdraw_authority_file, mut tmp_file) = make_tmp_file();
2784        write_keypair(&withdraw_authority, tmp_file.as_file_mut()).unwrap();
2785        let test_withdraw_from_vote_account = test_commands.clone().get_matches_from(vec![
2786            "test",
2787            "withdraw-from-vote-account",
2788            &keypair_file,
2789            &pubkey_string,
2790            "42",
2791            "--authorized-withdrawer",
2792            &withdraw_authority_file,
2793        ]);
2794        assert_eq!(
2795            parse_command(&test_withdraw_from_vote_account, &default_signer, &mut None).unwrap(),
2796            CliCommandInfo {
2797                command: CliCommand::WithdrawFromVoteAccount {
2798                    vote_account_pubkey: read_keypair_file(&keypair_file).unwrap().pubkey(),
2799                    destination_account_pubkey: pubkey,
2800                    withdraw_authority: 1,
2801                    withdraw_amount: SpendAmount::Some(42_000_000_000),
2802                    sign_only: false,
2803                    dump_transaction_message: false,
2804                    blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
2805                    nonce_account: None,
2806                    nonce_authority: 0,
2807                    memo: None,
2808                    fee_payer: 0,
2809                    compute_unit_price: None,
2810                },
2811                signers: vec![
2812                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
2813                    Box::new(read_keypair_file(&withdraw_authority_file).unwrap())
2814                ],
2815            }
2816        );
2817
2818        // Test WithdrawFromVoteAccount subcommand with offline authority
2819        let test_withdraw_from_vote_account = test_commands.clone().get_matches_from(vec![
2820            "test",
2821            "withdraw-from-vote-account",
2822            &keypair.pubkey().to_string(),
2823            &pubkey_string,
2824            "42",
2825            "--authorized-withdrawer",
2826            &withdraw_authority_file,
2827            "--blockhash",
2828            &blockhash_string,
2829            "--sign-only",
2830            "--fee-payer",
2831            &withdraw_authority_file,
2832        ]);
2833        assert_eq!(
2834            parse_command(&test_withdraw_from_vote_account, &default_signer, &mut None).unwrap(),
2835            CliCommandInfo {
2836                command: CliCommand::WithdrawFromVoteAccount {
2837                    vote_account_pubkey: keypair.pubkey(),
2838                    destination_account_pubkey: pubkey,
2839                    withdraw_authority: 0,
2840                    withdraw_amount: SpendAmount::Some(42_000_000_000),
2841                    sign_only: true,
2842                    dump_transaction_message: false,
2843                    blockhash_query: BlockhashQuery::Static(blockhash),
2844                    nonce_account: None,
2845                    nonce_authority: 0,
2846                    memo: None,
2847                    fee_payer: 0,
2848                    compute_unit_price: None,
2849                },
2850                signers: vec![Box::new(
2851                    read_keypair_file(&withdraw_authority_file).unwrap()
2852                )],
2853            }
2854        );
2855
2856        let authorized_sig = withdraw_authority.sign_message(&[0u8]);
2857        let authorized_signer = format!("{}={}", withdraw_authority.pubkey(), authorized_sig);
2858        let test_withdraw_from_vote_account = test_commands.clone().get_matches_from(vec![
2859            "test",
2860            "withdraw-from-vote-account",
2861            &keypair.pubkey().to_string(),
2862            &pubkey_string,
2863            "42",
2864            "--authorized-withdrawer",
2865            &withdraw_authority.pubkey().to_string(),
2866            "--blockhash",
2867            &blockhash_string,
2868            "--signer",
2869            &authorized_signer,
2870            "--fee-payer",
2871            &withdraw_authority.pubkey().to_string(),
2872        ]);
2873        assert_eq!(
2874            parse_command(&test_withdraw_from_vote_account, &default_signer, &mut None).unwrap(),
2875            CliCommandInfo {
2876                command: CliCommand::WithdrawFromVoteAccount {
2877                    vote_account_pubkey: keypair.pubkey(),
2878                    destination_account_pubkey: pubkey,
2879                    withdraw_authority: 0,
2880                    withdraw_amount: SpendAmount::Some(42_000_000_000),
2881                    sign_only: false,
2882                    dump_transaction_message: false,
2883                    blockhash_query: BlockhashQuery::Validated(Source::Cluster, blockhash),
2884                    nonce_account: None,
2885                    nonce_authority: 0,
2886                    memo: None,
2887                    fee_payer: 0,
2888                    compute_unit_price: None,
2889                },
2890                signers: vec![Box::new(Presigner::new(
2891                    &withdraw_authority.pubkey(),
2892                    &authorized_sig
2893                )),],
2894            }
2895        );
2896
2897        // Test CloseVoteAccount subcommand
2898        let test_close_vote_account = test_commands.clone().get_matches_from(vec![
2899            "test",
2900            "close-vote-account",
2901            &keypair_file,
2902            &pubkey_string,
2903        ]);
2904        assert_eq!(
2905            parse_command(&test_close_vote_account, &default_signer, &mut None).unwrap(),
2906            CliCommandInfo {
2907                command: CliCommand::CloseVoteAccount {
2908                    vote_account_pubkey: read_keypair_file(&keypair_file).unwrap().pubkey(),
2909                    destination_account_pubkey: pubkey,
2910                    withdraw_authority: 0,
2911                    memo: None,
2912                    fee_payer: 0,
2913                    compute_unit_price: None,
2914                },
2915                signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap())],
2916            }
2917        );
2918
2919        // Test CloseVoteAccount subcommand with authority
2920        let withdraw_authority = Keypair::new();
2921        let (withdraw_authority_file, mut tmp_file) = make_tmp_file();
2922        write_keypair(&withdraw_authority, tmp_file.as_file_mut()).unwrap();
2923        let test_close_vote_account = test_commands.clone().get_matches_from(vec![
2924            "test",
2925            "close-vote-account",
2926            &keypair_file,
2927            &pubkey_string,
2928            "--authorized-withdrawer",
2929            &withdraw_authority_file,
2930        ]);
2931        assert_eq!(
2932            parse_command(&test_close_vote_account, &default_signer, &mut None).unwrap(),
2933            CliCommandInfo {
2934                command: CliCommand::CloseVoteAccount {
2935                    vote_account_pubkey: read_keypair_file(&keypair_file).unwrap().pubkey(),
2936                    destination_account_pubkey: pubkey,
2937                    withdraw_authority: 1,
2938                    memo: None,
2939                    fee_payer: 0,
2940                    compute_unit_price: None,
2941                },
2942                signers: vec![
2943                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
2944                    Box::new(read_keypair_file(&withdraw_authority_file).unwrap())
2945                ],
2946            }
2947        );
2948
2949        // Test CloseVoteAccount subcommand with authority w/ ComputeUnitPrice
2950        let withdraw_authority = Keypair::new();
2951        let (withdraw_authority_file, mut tmp_file) = make_tmp_file();
2952        write_keypair(&withdraw_authority, tmp_file.as_file_mut()).unwrap();
2953        let test_close_vote_account = test_commands.clone().get_matches_from(vec![
2954            "test",
2955            "close-vote-account",
2956            &keypair_file,
2957            &pubkey_string,
2958            "--authorized-withdrawer",
2959            &withdraw_authority_file,
2960            "--with-compute-unit-price",
2961            "99",
2962        ]);
2963        assert_eq!(
2964            parse_command(&test_close_vote_account, &default_signer, &mut None).unwrap(),
2965            CliCommandInfo {
2966                command: CliCommand::CloseVoteAccount {
2967                    vote_account_pubkey: read_keypair_file(&keypair_file).unwrap().pubkey(),
2968                    destination_account_pubkey: pubkey,
2969                    withdraw_authority: 1,
2970                    memo: None,
2971                    fee_payer: 0,
2972                    compute_unit_price: Some(99),
2973                },
2974                signers: vec![
2975                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
2976                    Box::new(read_keypair_file(&withdraw_authority_file).unwrap())
2977                ],
2978            }
2979        );
2980    }
2981}