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