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