1use {
2 crate::{
3 checks::{check_account_for_fee_with_commitment, check_unique_pubkeys},
4 cli::{
5 log_instruction_custom_error, CliCommand, CliCommandInfo, CliConfig, CliError,
6 ProcessResult,
7 },
8 compute_budget::{
9 simulate_and_update_compute_unit_limit, ComputeUnitConfig, WithComputeUnitConfig,
10 },
11 memo::WithMemo,
12 nonce::check_nonce_account,
13 spend_utils::{resolve_spend_tx_and_check_account_balances, SpendAmount},
14 stake::check_current_authority,
15 },
16 clap::{value_t_or_exit, App, Arg, ArgMatches, SubCommand},
17 solana_account::Account,
18 solana_clap_utils::{
19 compute_budget::{compute_unit_price_arg, ComputeUnitLimit, COMPUTE_UNIT_PRICE_ARG},
20 fee_payer::{fee_payer_arg, FEE_PAYER_ARG},
21 input_parsers::*,
22 input_validators::*,
23 keypair::{DefaultSigner, SignerIndex},
24 memo::{memo_arg, MEMO_ARG},
25 nonce::*,
26 offline::*,
27 },
28 solana_cli_output::{
29 return_signers_with_config, CliEpochVotingHistory, CliLandedVote, CliVoteAccount,
30 ReturnSignersConfig,
31 },
32 solana_commitment_config::CommitmentConfig,
33 solana_message::Message,
34 solana_native_token::lamports_to_sol,
35 solana_pubkey::Pubkey,
36 solana_remote_wallet::remote_wallet::RemoteWalletManager,
37 solana_rpc_client::rpc_client::RpcClient,
38 solana_rpc_client_api::config::RpcGetVoteAccountsConfig,
39 solana_rpc_client_nonce_utils::blockhash_query::BlockhashQuery,
40 solana_system_interface::error::SystemError,
41 solana_transaction::Transaction,
42 solana_vote_program::{
43 vote_error::VoteError,
44 vote_instruction::{self, withdraw, CreateVoteAccountConfig},
45 vote_state::{
46 VoteAuthorize, VoteInit, VoteState, VoteStateVersions, VOTE_CREDITS_MAXIMUM_PER_SLOT,
47 },
48 },
49 std::rc::Rc,
50};
51
52pub trait VoteSubCommands {
53 fn vote_subcommands(self) -> Self;
54}
55
56impl VoteSubCommands for App<'_, '_> {
57 fn vote_subcommands(self) -> Self {
58 self.subcommand(
59 SubCommand::with_name("create-vote-account")
60 .about("Create a vote account")
61 .arg(
62 Arg::with_name("vote_account")
63 .index(1)
64 .value_name("ACCOUNT_KEYPAIR")
65 .takes_value(true)
66 .required(true)
67 .validator(is_valid_signer)
68 .help("Vote account keypair to create"),
69 )
70 .arg(
71 Arg::with_name("identity_account")
72 .index(2)
73 .value_name("IDENTITY_KEYPAIR")
74 .takes_value(true)
75 .required(true)
76 .validator(is_valid_signer)
77 .help("Keypair of validator that will vote with this account"),
78 )
79 .arg(pubkey!(
80 Arg::with_name("authorized_withdrawer")
81 .index(3)
82 .value_name("WITHDRAWER_PUBKEY")
83 .takes_value(true)
84 .required(true)
85 .long("authorized-withdrawer"),
86 "Authorized withdrawer."
87 ))
88 .arg(
89 Arg::with_name("commission")
90 .long("commission")
91 .value_name("PERCENTAGE")
92 .takes_value(true)
93 .default_value("100")
94 .help("The commission taken on reward redemption (0-100)"),
95 )
96 .arg(pubkey!(
97 Arg::with_name("authorized_voter")
98 .long("authorized-voter")
99 .value_name("VOTER_PUBKEY"),
100 "Authorized voter [default: validator identity pubkey]."
101 ))
102 .arg(
103 Arg::with_name("allow_unsafe_authorized_withdrawer")
104 .long("allow-unsafe-authorized-withdrawer")
105 .takes_value(false)
106 .help(
107 "Allow an authorized withdrawer pubkey to be identical to the \
108 validator identity account pubkey or vote account pubkey, which is \
109 normally an unsafe configuration and should be avoided.",
110 ),
111 )
112 .arg(
113 Arg::with_name("seed")
114 .long("seed")
115 .value_name("STRING")
116 .takes_value(true)
117 .help(
118 "Seed for address generation; if specified, the resulting account \
119 will be at a derived address of the VOTE ACCOUNT pubkey",
120 ),
121 )
122 .offline_args()
123 .nonce_args(false)
124 .arg(fee_payer_arg())
125 .arg(memo_arg())
126 .arg(compute_unit_price_arg()),
127 )
128 .subcommand(
129 SubCommand::with_name("vote-authorize-voter")
130 .about("Authorize a new vote signing keypair for the given vote account")
131 .arg(pubkey!(
132 Arg::with_name("vote_account_pubkey")
133 .index(1)
134 .value_name("VOTE_ACCOUNT_ADDRESS")
135 .required(true),
136 "Vote account in which to set the authorized voter."
137 ))
138 .arg(
139 Arg::with_name("authorized")
140 .index(2)
141 .value_name("AUTHORIZED_KEYPAIR")
142 .required(true)
143 .validator(is_valid_signer)
144 .help("Current authorized vote signer."),
145 )
146 .arg(pubkey!(
147 Arg::with_name("new_authorized_pubkey")
148 .index(3)
149 .value_name("NEW_AUTHORIZED_PUBKEY")
150 .required(true),
151 "New authorized vote signer."
152 ))
153 .offline_args()
154 .nonce_args(false)
155 .arg(fee_payer_arg())
156 .arg(memo_arg())
157 .arg(compute_unit_price_arg()),
158 )
159 .subcommand(
160 SubCommand::with_name("vote-authorize-withdrawer")
161 .about("Authorize a new withdraw signing keypair for the given vote account")
162 .arg(pubkey!(
163 Arg::with_name("vote_account_pubkey")
164 .index(1)
165 .value_name("VOTE_ACCOUNT_ADDRESS")
166 .required(true),
167 "Vote account in which to set the authorized withdrawer."
168 ))
169 .arg(
170 Arg::with_name("authorized")
171 .index(2)
172 .value_name("AUTHORIZED_KEYPAIR")
173 .required(true)
174 .validator(is_valid_signer)
175 .help("Current authorized withdrawer."),
176 )
177 .arg(pubkey!(
178 Arg::with_name("new_authorized_pubkey")
179 .index(3)
180 .value_name("AUTHORIZED_PUBKEY")
181 .required(true),
182 "New authorized withdrawer."
183 ))
184 .offline_args()
185 .nonce_args(false)
186 .arg(fee_payer_arg())
187 .arg(memo_arg())
188 .arg(compute_unit_price_arg()),
189 )
190 .subcommand(
191 SubCommand::with_name("vote-authorize-voter-checked")
192 .about(
193 "Authorize a new vote signing keypair for the given vote account, checking \
194 the new authority as a signer",
195 )
196 .arg(pubkey!(
197 Arg::with_name("vote_account_pubkey")
198 .index(1)
199 .value_name("VOTE_ACCOUNT_ADDRESS")
200 .required(true),
201 "Vote account in which to set the authorized voter."
202 ))
203 .arg(
204 Arg::with_name("authorized")
205 .index(2)
206 .value_name("AUTHORIZED_KEYPAIR")
207 .required(true)
208 .validator(is_valid_signer)
209 .help("Current authorized vote signer."),
210 )
211 .arg(
212 Arg::with_name("new_authorized")
213 .index(3)
214 .value_name("NEW_AUTHORIZED_KEYPAIR")
215 .required(true)
216 .validator(is_valid_signer)
217 .help("New authorized vote signer."),
218 )
219 .offline_args()
220 .nonce_args(false)
221 .arg(fee_payer_arg())
222 .arg(memo_arg())
223 .arg(compute_unit_price_arg()),
224 )
225 .subcommand(
226 SubCommand::with_name("vote-authorize-withdrawer-checked")
227 .about(
228 "Authorize a new withdraw signing keypair for the given vote account, \
229 checking the new authority as a signer",
230 )
231 .arg(pubkey!(
232 Arg::with_name("vote_account_pubkey")
233 .index(1)
234 .value_name("VOTE_ACCOUNT_ADDRESS")
235 .required(true),
236 "Vote account in which to set the authorized withdrawer."
237 ))
238 .arg(
239 Arg::with_name("authorized")
240 .index(2)
241 .value_name("AUTHORIZED_KEYPAIR")
242 .required(true)
243 .validator(is_valid_signer)
244 .help("Current authorized withdrawer."),
245 )
246 .arg(
247 Arg::with_name("new_authorized")
248 .index(3)
249 .value_name("NEW_AUTHORIZED_KEYPAIR")
250 .required(true)
251 .validator(is_valid_signer)
252 .help("New authorized withdrawer."),
253 )
254 .offline_args()
255 .nonce_args(false)
256 .arg(fee_payer_arg())
257 .arg(memo_arg())
258 .arg(compute_unit_price_arg()),
259 )
260 .subcommand(
261 SubCommand::with_name("vote-update-validator")
262 .about("Update the vote account's validator identity")
263 .arg(pubkey!(
264 Arg::with_name("vote_account_pubkey")
265 .index(1)
266 .value_name("VOTE_ACCOUNT_ADDRESS")
267 .required(true),
268 "Vote account to update."
269 ))
270 .arg(
271 Arg::with_name("new_identity_account")
272 .index(2)
273 .value_name("IDENTITY_KEYPAIR")
274 .takes_value(true)
275 .required(true)
276 .validator(is_valid_signer)
277 .help("Keypair of new validator that will vote with this account"),
278 )
279 .arg(
280 Arg::with_name("authorized_withdrawer")
281 .index(3)
282 .value_name("AUTHORIZED_KEYPAIR")
283 .takes_value(true)
284 .required(true)
285 .validator(is_valid_signer)
286 .help("Authorized withdrawer keypair"),
287 )
288 .offline_args()
289 .nonce_args(false)
290 .arg(fee_payer_arg())
291 .arg(memo_arg())
292 .arg(compute_unit_price_arg()),
293 )
294 .subcommand(
295 SubCommand::with_name("vote-update-commission")
296 .about("Update the vote account's commission")
297 .arg(pubkey!(
298 Arg::with_name("vote_account_pubkey")
299 .index(1)
300 .value_name("VOTE_ACCOUNT_ADDRESS")
301 .required(true),
302 "Vote account to update."
303 ))
304 .arg(
305 Arg::with_name("commission")
306 .index(2)
307 .value_name("PERCENTAGE")
308 .takes_value(true)
309 .required(true)
310 .validator(is_valid_percentage)
311 .help("The new commission"),
312 )
313 .arg(
314 Arg::with_name("authorized_withdrawer")
315 .index(3)
316 .value_name("AUTHORIZED_KEYPAIR")
317 .takes_value(true)
318 .required(true)
319 .validator(is_valid_signer)
320 .help("Authorized withdrawer keypair"),
321 )
322 .offline_args()
323 .nonce_args(false)
324 .arg(fee_payer_arg())
325 .arg(memo_arg())
326 .arg(compute_unit_price_arg()),
327 )
328 .subcommand(
329 SubCommand::with_name("vote-account")
330 .about("Show the contents of a vote account")
331 .alias("show-vote-account")
332 .arg(pubkey!(
333 Arg::with_name("vote_account_pubkey")
334 .index(1)
335 .value_name("VOTE_ACCOUNT_ADDRESS")
336 .required(true),
337 "Vote account."
338 ))
339 .arg(
340 Arg::with_name("lamports")
341 .long("lamports")
342 .takes_value(false)
343 .help("Display balance in lamports instead of SOL"),
344 )
345 .arg(
346 Arg::with_name("with_rewards")
347 .long("with-rewards")
348 .takes_value(false)
349 .help("Display inflation rewards"),
350 )
351 .arg(
352 Arg::with_name("csv")
353 .long("csv")
354 .takes_value(false)
355 .help("Format rewards in a CSV table"),
356 )
357 .arg(
358 Arg::with_name("starting_epoch")
359 .long("starting-epoch")
360 .takes_value(true)
361 .value_name("NUM")
362 .requires("with_rewards")
363 .help("Start displaying from epoch NUM"),
364 )
365 .arg(
366 Arg::with_name("num_rewards_epochs")
367 .long("num-rewards-epochs")
368 .takes_value(true)
369 .value_name("NUM")
370 .validator(|s| is_within_range(s, 1..=50))
371 .default_value_if("with_rewards", None, "1")
372 .requires("with_rewards")
373 .help(
374 "Display rewards for NUM recent epochs, max 10 \
375 [default: latest epoch only]",
376 ),
377 ),
378 )
379 .subcommand(
380 SubCommand::with_name("withdraw-from-vote-account")
381 .about("Withdraw lamports from a vote account into a specified account")
382 .arg(pubkey!(
383 Arg::with_name("vote_account_pubkey")
384 .index(1)
385 .value_name("VOTE_ACCOUNT_ADDRESS")
386 .required(true),
387 "Vote account from which to withdraw."
388 ))
389 .arg(pubkey!(
390 Arg::with_name("destination_account_pubkey")
391 .index(2)
392 .value_name("RECIPIENT_ADDRESS")
393 .required(true),
394 "The recipient of withdrawn SOL."
395 ))
396 .arg(
397 Arg::with_name("amount")
398 .index(3)
399 .value_name("AMOUNT")
400 .takes_value(true)
401 .required(true)
402 .validator(is_amount_or_all)
403 .help(
404 "The amount to withdraw, in SOL; accepts keyword ALL, which for this \
405 command means account balance minus rent-exempt minimum",
406 ),
407 )
408 .arg(
409 Arg::with_name("authorized_withdrawer")
410 .long("authorized-withdrawer")
411 .value_name("AUTHORIZED_KEYPAIR")
412 .takes_value(true)
413 .validator(is_valid_signer)
414 .help("Authorized withdrawer [default: cli config keypair]"),
415 )
416 .offline_args()
417 .nonce_args(false)
418 .arg(fee_payer_arg())
419 .arg(memo_arg())
420 .arg(compute_unit_price_arg()),
421 )
422 .subcommand(
423 SubCommand::with_name("close-vote-account")
424 .about("Close a vote account and withdraw all funds remaining")
425 .arg(pubkey!(
426 Arg::with_name("vote_account_pubkey")
427 .index(1)
428 .value_name("VOTE_ACCOUNT_ADDRESS")
429 .required(true),
430 "Vote account to be closed."
431 ))
432 .arg(pubkey!(
433 Arg::with_name("destination_account_pubkey")
434 .index(2)
435 .value_name("RECIPIENT_ADDRESS")
436 .required(true),
437 "The recipient of all withdrawn SOL."
438 ))
439 .arg(
440 Arg::with_name("authorized_withdrawer")
441 .long("authorized-withdrawer")
442 .value_name("AUTHORIZED_KEYPAIR")
443 .takes_value(true)
444 .validator(is_valid_signer)
445 .help("Authorized withdrawer [default: cli config keypair]"),
446 )
447 .arg(fee_payer_arg())
448 .arg(memo_arg())
449 .arg(compute_unit_price_arg()),
450 )
451 }
452}
453
454pub fn parse_create_vote_account(
455 matches: &ArgMatches<'_>,
456 default_signer: &DefaultSigner,
457 wallet_manager: &mut Option<Rc<RemoteWalletManager>>,
458) -> Result<CliCommandInfo, CliError> {
459 let (vote_account, vote_account_pubkey) = signer_of(matches, "vote_account", wallet_manager)?;
460 let seed = matches.value_of("seed").map(|s| s.to_string());
461 let (identity_account, identity_pubkey) =
462 signer_of(matches, "identity_account", wallet_manager)?;
463 let commission = value_t_or_exit!(matches, "commission", u8);
464 let authorized_voter = pubkey_of_signer(matches, "authorized_voter", wallet_manager)?;
465 let authorized_withdrawer =
466 pubkey_of_signer(matches, "authorized_withdrawer", wallet_manager)?.unwrap();
467 let allow_unsafe = matches.is_present("allow_unsafe_authorized_withdrawer");
468 let sign_only = matches.is_present(SIGN_ONLY_ARG.name);
469 let dump_transaction_message = matches.is_present(DUMP_TRANSACTION_MESSAGE.name);
470 let blockhash_query = BlockhashQuery::new_from_matches(matches);
471 let nonce_account = pubkey_of_signer(matches, NONCE_ARG.name, wallet_manager)?;
472 let memo = matches.value_of(MEMO_ARG.name).map(String::from);
473 let (nonce_authority, nonce_authority_pubkey) =
474 signer_of(matches, NONCE_AUTHORITY_ARG.name, wallet_manager)?;
475 let (fee_payer, fee_payer_pubkey) = signer_of(matches, FEE_PAYER_ARG.name, wallet_manager)?;
476 let compute_unit_price = value_of(matches, COMPUTE_UNIT_PRICE_ARG.name);
477
478 if !allow_unsafe {
479 if authorized_withdrawer == vote_account_pubkey.unwrap() {
480 return Err(CliError::BadParameter(
481 "Authorized withdrawer pubkey is identical to vote account pubkey, an unsafe \
482 configuration"
483 .to_owned(),
484 ));
485 }
486 if authorized_withdrawer == identity_pubkey.unwrap() {
487 return Err(CliError::BadParameter(
488 "Authorized withdrawer pubkey is identical to identity account pubkey, an unsafe \
489 configuration"
490 .to_owned(),
491 ));
492 }
493 }
494
495 let mut bulk_signers = vec![fee_payer, vote_account, identity_account];
496 if nonce_account.is_some() {
497 bulk_signers.push(nonce_authority);
498 }
499 let signer_info =
500 default_signer.generate_unique_signers(bulk_signers, matches, wallet_manager)?;
501
502 Ok(CliCommandInfo {
503 command: CliCommand::CreateVoteAccount {
504 vote_account: signer_info.index_of(vote_account_pubkey).unwrap(),
505 seed,
506 identity_account: signer_info.index_of(identity_pubkey).unwrap(),
507 authorized_voter,
508 authorized_withdrawer,
509 commission,
510 sign_only,
511 dump_transaction_message,
512 blockhash_query,
513 nonce_account,
514 nonce_authority: signer_info.index_of(nonce_authority_pubkey).unwrap(),
515 memo,
516 fee_payer: signer_info.index_of(fee_payer_pubkey).unwrap(),
517 compute_unit_price,
518 },
519 signers: signer_info.signers,
520 })
521}
522
523pub fn parse_vote_authorize(
524 matches: &ArgMatches<'_>,
525 default_signer: &DefaultSigner,
526 wallet_manager: &mut Option<Rc<RemoteWalletManager>>,
527 vote_authorize: VoteAuthorize,
528 checked: bool,
529) -> Result<CliCommandInfo, CliError> {
530 let vote_account_pubkey =
531 pubkey_of_signer(matches, "vote_account_pubkey", wallet_manager)?.unwrap();
532 let (authorized, authorized_pubkey) = signer_of(matches, "authorized", wallet_manager)?;
533
534 let sign_only = matches.is_present(SIGN_ONLY_ARG.name);
535 let dump_transaction_message = matches.is_present(DUMP_TRANSACTION_MESSAGE.name);
536 let blockhash_query = BlockhashQuery::new_from_matches(matches);
537 let nonce_account = pubkey_of(matches, NONCE_ARG.name);
538 let memo = matches.value_of(MEMO_ARG.name).map(String::from);
539 let (nonce_authority, nonce_authority_pubkey) =
540 signer_of(matches, NONCE_AUTHORITY_ARG.name, wallet_manager)?;
541 let (fee_payer, fee_payer_pubkey) = signer_of(matches, FEE_PAYER_ARG.name, wallet_manager)?;
542 let compute_unit_price = value_of(matches, COMPUTE_UNIT_PRICE_ARG.name);
543
544 let mut bulk_signers = vec![fee_payer, authorized];
545
546 let new_authorized_pubkey = if checked {
547 let (new_authorized_signer, new_authorized_pubkey) =
548 signer_of(matches, "new_authorized", wallet_manager)?;
549 bulk_signers.push(new_authorized_signer);
550 new_authorized_pubkey.unwrap()
551 } else {
552 pubkey_of_signer(matches, "new_authorized_pubkey", wallet_manager)?.unwrap()
553 };
554 if nonce_account.is_some() {
555 bulk_signers.push(nonce_authority);
556 }
557 let signer_info =
558 default_signer.generate_unique_signers(bulk_signers, matches, wallet_manager)?;
559
560 Ok(CliCommandInfo {
561 command: CliCommand::VoteAuthorize {
562 vote_account_pubkey,
563 new_authorized_pubkey,
564 vote_authorize,
565 sign_only,
566 dump_transaction_message,
567 blockhash_query,
568 nonce_account,
569 nonce_authority: signer_info.index_of(nonce_authority_pubkey).unwrap(),
570 memo,
571 fee_payer: signer_info.index_of(fee_payer_pubkey).unwrap(),
572 authorized: signer_info.index_of(authorized_pubkey).unwrap(),
573 new_authorized: if checked {
574 signer_info.index_of(Some(new_authorized_pubkey))
575 } else {
576 None
577 },
578 compute_unit_price,
579 },
580 signers: signer_info.signers,
581 })
582}
583
584pub fn parse_vote_update_validator(
585 matches: &ArgMatches<'_>,
586 default_signer: &DefaultSigner,
587 wallet_manager: &mut Option<Rc<RemoteWalletManager>>,
588) -> Result<CliCommandInfo, CliError> {
589 let vote_account_pubkey =
590 pubkey_of_signer(matches, "vote_account_pubkey", wallet_manager)?.unwrap();
591 let (new_identity_account, new_identity_pubkey) =
592 signer_of(matches, "new_identity_account", wallet_manager)?;
593 let (authorized_withdrawer, authorized_withdrawer_pubkey) =
594 signer_of(matches, "authorized_withdrawer", wallet_manager)?;
595
596 let sign_only = matches.is_present(SIGN_ONLY_ARG.name);
597 let dump_transaction_message = matches.is_present(DUMP_TRANSACTION_MESSAGE.name);
598 let blockhash_query = BlockhashQuery::new_from_matches(matches);
599 let nonce_account = pubkey_of(matches, NONCE_ARG.name);
600 let memo = matches.value_of(MEMO_ARG.name).map(String::from);
601 let (nonce_authority, nonce_authority_pubkey) =
602 signer_of(matches, NONCE_AUTHORITY_ARG.name, wallet_manager)?;
603 let (fee_payer, fee_payer_pubkey) = signer_of(matches, FEE_PAYER_ARG.name, wallet_manager)?;
604 let compute_unit_price = value_of(matches, COMPUTE_UNIT_PRICE_ARG.name);
605
606 let mut bulk_signers = vec![fee_payer, authorized_withdrawer, new_identity_account];
607 if nonce_account.is_some() {
608 bulk_signers.push(nonce_authority);
609 }
610 let signer_info =
611 default_signer.generate_unique_signers(bulk_signers, matches, wallet_manager)?;
612
613 Ok(CliCommandInfo {
614 command: CliCommand::VoteUpdateValidator {
615 vote_account_pubkey,
616 new_identity_account: signer_info.index_of(new_identity_pubkey).unwrap(),
617 withdraw_authority: signer_info.index_of(authorized_withdrawer_pubkey).unwrap(),
618 sign_only,
619 dump_transaction_message,
620 blockhash_query,
621 nonce_account,
622 nonce_authority: signer_info.index_of(nonce_authority_pubkey).unwrap(),
623 memo,
624 fee_payer: signer_info.index_of(fee_payer_pubkey).unwrap(),
625 compute_unit_price,
626 },
627 signers: signer_info.signers,
628 })
629}
630
631pub fn parse_vote_update_commission(
632 matches: &ArgMatches<'_>,
633 default_signer: &DefaultSigner,
634 wallet_manager: &mut Option<Rc<RemoteWalletManager>>,
635) -> Result<CliCommandInfo, CliError> {
636 let vote_account_pubkey =
637 pubkey_of_signer(matches, "vote_account_pubkey", wallet_manager)?.unwrap();
638 let (authorized_withdrawer, authorized_withdrawer_pubkey) =
639 signer_of(matches, "authorized_withdrawer", wallet_manager)?;
640 let commission = value_t_or_exit!(matches, "commission", u8);
641
642 let sign_only = matches.is_present(SIGN_ONLY_ARG.name);
643 let dump_transaction_message = matches.is_present(DUMP_TRANSACTION_MESSAGE.name);
644 let blockhash_query = BlockhashQuery::new_from_matches(matches);
645 let nonce_account = pubkey_of(matches, NONCE_ARG.name);
646 let memo = matches.value_of(MEMO_ARG.name).map(String::from);
647 let (nonce_authority, nonce_authority_pubkey) =
648 signer_of(matches, NONCE_AUTHORITY_ARG.name, wallet_manager)?;
649 let (fee_payer, fee_payer_pubkey) = signer_of(matches, FEE_PAYER_ARG.name, wallet_manager)?;
650 let compute_unit_price = value_of(matches, COMPUTE_UNIT_PRICE_ARG.name);
651
652 let mut bulk_signers = vec![fee_payer, authorized_withdrawer];
653 if nonce_account.is_some() {
654 bulk_signers.push(nonce_authority);
655 }
656 let signer_info =
657 default_signer.generate_unique_signers(bulk_signers, matches, wallet_manager)?;
658
659 Ok(CliCommandInfo {
660 command: CliCommand::VoteUpdateCommission {
661 vote_account_pubkey,
662 commission,
663 withdraw_authority: signer_info.index_of(authorized_withdrawer_pubkey).unwrap(),
664 sign_only,
665 dump_transaction_message,
666 blockhash_query,
667 nonce_account,
668 nonce_authority: signer_info.index_of(nonce_authority_pubkey).unwrap(),
669 memo,
670 fee_payer: signer_info.index_of(fee_payer_pubkey).unwrap(),
671 compute_unit_price,
672 },
673 signers: signer_info.signers,
674 })
675}
676
677pub fn parse_vote_get_account_command(
678 matches: &ArgMatches<'_>,
679 wallet_manager: &mut Option<Rc<RemoteWalletManager>>,
680) -> Result<CliCommandInfo, CliError> {
681 let vote_account_pubkey =
682 pubkey_of_signer(matches, "vote_account_pubkey", wallet_manager)?.unwrap();
683 let use_lamports_unit = matches.is_present("lamports");
684 let use_csv = matches.is_present("csv");
685 let with_rewards = if matches.is_present("with_rewards") {
686 Some(value_of(matches, "num_rewards_epochs").unwrap())
687 } else {
688 None
689 };
690 let starting_epoch = value_of(matches, "starting_epoch");
691 Ok(CliCommandInfo::without_signers(
692 CliCommand::ShowVoteAccount {
693 pubkey: vote_account_pubkey,
694 use_lamports_unit,
695 use_csv,
696 with_rewards,
697 starting_epoch,
698 },
699 ))
700}
701
702pub fn parse_withdraw_from_vote_account(
703 matches: &ArgMatches<'_>,
704 default_signer: &DefaultSigner,
705 wallet_manager: &mut Option<Rc<RemoteWalletManager>>,
706) -> Result<CliCommandInfo, CliError> {
707 let vote_account_pubkey =
708 pubkey_of_signer(matches, "vote_account_pubkey", wallet_manager)?.unwrap();
709 let destination_account_pubkey =
710 pubkey_of_signer(matches, "destination_account_pubkey", wallet_manager)?.unwrap();
711 let mut withdraw_amount = SpendAmount::new_from_matches(matches, "amount");
712 if withdraw_amount == SpendAmount::All {
716 withdraw_amount = SpendAmount::RentExempt;
717 }
718
719 let (withdraw_authority, withdraw_authority_pubkey) =
720 signer_of(matches, "authorized_withdrawer", wallet_manager)?;
721
722 let sign_only = matches.is_present(SIGN_ONLY_ARG.name);
723 let dump_transaction_message = matches.is_present(DUMP_TRANSACTION_MESSAGE.name);
724 let blockhash_query = BlockhashQuery::new_from_matches(matches);
725 let nonce_account = pubkey_of(matches, NONCE_ARG.name);
726 let memo = matches.value_of(MEMO_ARG.name).map(String::from);
727 let (nonce_authority, nonce_authority_pubkey) =
728 signer_of(matches, NONCE_AUTHORITY_ARG.name, wallet_manager)?;
729 let (fee_payer, fee_payer_pubkey) = signer_of(matches, FEE_PAYER_ARG.name, wallet_manager)?;
730 let compute_unit_price = value_of(matches, COMPUTE_UNIT_PRICE_ARG.name);
731
732 let mut bulk_signers = vec![fee_payer, withdraw_authority];
733 if nonce_account.is_some() {
734 bulk_signers.push(nonce_authority);
735 }
736 let signer_info =
737 default_signer.generate_unique_signers(bulk_signers, matches, wallet_manager)?;
738
739 Ok(CliCommandInfo {
740 command: CliCommand::WithdrawFromVoteAccount {
741 vote_account_pubkey,
742 destination_account_pubkey,
743 withdraw_authority: signer_info.index_of(withdraw_authority_pubkey).unwrap(),
744 withdraw_amount,
745 sign_only,
746 dump_transaction_message,
747 blockhash_query,
748 nonce_account,
749 nonce_authority: signer_info.index_of(nonce_authority_pubkey).unwrap(),
750 memo,
751 fee_payer: signer_info.index_of(fee_payer_pubkey).unwrap(),
752 compute_unit_price,
753 },
754 signers: signer_info.signers,
755 })
756}
757
758pub fn parse_close_vote_account(
759 matches: &ArgMatches<'_>,
760 default_signer: &DefaultSigner,
761 wallet_manager: &mut Option<Rc<RemoteWalletManager>>,
762) -> Result<CliCommandInfo, CliError> {
763 let vote_account_pubkey =
764 pubkey_of_signer(matches, "vote_account_pubkey", wallet_manager)?.unwrap();
765 let destination_account_pubkey =
766 pubkey_of_signer(matches, "destination_account_pubkey", wallet_manager)?.unwrap();
767
768 let (withdraw_authority, withdraw_authority_pubkey) =
769 signer_of(matches, "authorized_withdrawer", wallet_manager)?;
770 let (fee_payer, fee_payer_pubkey) = signer_of(matches, FEE_PAYER_ARG.name, wallet_manager)?;
771
772 let signer_info = default_signer.generate_unique_signers(
773 vec![fee_payer, withdraw_authority],
774 matches,
775 wallet_manager,
776 )?;
777 let memo = matches.value_of(MEMO_ARG.name).map(String::from);
778 let compute_unit_price = value_of(matches, COMPUTE_UNIT_PRICE_ARG.name);
779
780 Ok(CliCommandInfo {
781 command: CliCommand::CloseVoteAccount {
782 vote_account_pubkey,
783 destination_account_pubkey,
784 withdraw_authority: signer_info.index_of(withdraw_authority_pubkey).unwrap(),
785 memo,
786 fee_payer: signer_info.index_of(fee_payer_pubkey).unwrap(),
787 compute_unit_price,
788 },
789 signers: signer_info.signers,
790 })
791}
792
793#[allow(clippy::too_many_arguments)]
794pub fn process_create_vote_account(
795 rpc_client: &RpcClient,
796 config: &CliConfig,
797 vote_account: SignerIndex,
798 seed: &Option<String>,
799 identity_account: SignerIndex,
800 authorized_voter: &Option<Pubkey>,
801 authorized_withdrawer: Pubkey,
802 commission: u8,
803 sign_only: bool,
804 dump_transaction_message: bool,
805 blockhash_query: &BlockhashQuery,
806 nonce_account: Option<&Pubkey>,
807 nonce_authority: SignerIndex,
808 memo: Option<&String>,
809 fee_payer: SignerIndex,
810 compute_unit_price: Option<u64>,
811) -> ProcessResult {
812 let vote_account = config.signers[vote_account];
813 let vote_account_pubkey = vote_account.pubkey();
814 let vote_account_address = if let Some(seed) = seed {
815 Pubkey::create_with_seed(&vote_account_pubkey, seed, &solana_vote_program::id())?
816 } else {
817 vote_account_pubkey
818 };
819 check_unique_pubkeys(
820 (&config.signers[0].pubkey(), "cli keypair".to_string()),
821 (&vote_account_address, "vote_account".to_string()),
822 )?;
823
824 let identity_account = config.signers[identity_account];
825 let identity_pubkey = identity_account.pubkey();
826 check_unique_pubkeys(
827 (&vote_account_address, "vote_account".to_string()),
828 (&identity_pubkey, "identity_pubkey".to_string()),
829 )?;
830
831 let required_balance = rpc_client
832 .get_minimum_balance_for_rent_exemption(VoteState::size_of())?
833 .max(1);
834 let amount = SpendAmount::Some(required_balance);
835
836 let fee_payer = config.signers[fee_payer];
837 let nonce_authority = config.signers[nonce_authority];
838 let space = VoteStateVersions::vote_state_size_of(true) as u64;
839
840 let compute_unit_limit = match blockhash_query {
841 BlockhashQuery::None(_) | BlockhashQuery::FeeCalculator(_, _) => ComputeUnitLimit::Default,
842 BlockhashQuery::All(_) => ComputeUnitLimit::Simulated,
843 };
844 let build_message = |lamports| {
845 let vote_init = VoteInit {
846 node_pubkey: identity_pubkey,
847 authorized_voter: authorized_voter.unwrap_or(identity_pubkey),
848 authorized_withdrawer,
849 commission,
850 };
851 let mut create_vote_account_config = CreateVoteAccountConfig {
852 space,
853 ..CreateVoteAccountConfig::default()
854 };
855 let to = if let Some(seed) = seed {
856 create_vote_account_config.with_seed = Some((&vote_account_pubkey, seed));
857 &vote_account_address
858 } else {
859 &vote_account_pubkey
860 };
861
862 let ixs = vote_instruction::create_account_with_config(
863 &config.signers[0].pubkey(),
864 to,
865 &vote_init,
866 lamports,
867 create_vote_account_config,
868 )
869 .with_memo(memo)
870 .with_compute_unit_config(&ComputeUnitConfig {
871 compute_unit_price,
872 compute_unit_limit,
873 });
874
875 if let Some(nonce_account) = &nonce_account {
876 Message::new_with_nonce(
877 ixs,
878 Some(&fee_payer.pubkey()),
879 nonce_account,
880 &nonce_authority.pubkey(),
881 )
882 } else {
883 Message::new(&ixs, Some(&fee_payer.pubkey()))
884 }
885 };
886
887 let recent_blockhash = blockhash_query.get_blockhash(rpc_client, config.commitment)?;
888
889 let (message, _) = resolve_spend_tx_and_check_account_balances(
890 rpc_client,
891 sign_only,
892 amount,
893 &recent_blockhash,
894 &config.signers[0].pubkey(),
895 &fee_payer.pubkey(),
896 compute_unit_limit,
897 build_message,
898 config.commitment,
899 )?;
900
901 if !sign_only {
902 if let Ok(response) =
903 rpc_client.get_account_with_commitment(&vote_account_address, config.commitment)
904 {
905 if let Some(vote_account) = response.value {
906 let err_msg = if vote_account.owner == solana_vote_program::id() {
907 format!("Vote account {vote_account_address} already exists")
908 } else {
909 format!(
910 "Account {vote_account_address} already exists and is not a vote account"
911 )
912 };
913 return Err(CliError::BadParameter(err_msg).into());
914 }
915 }
916
917 if let Some(nonce_account) = &nonce_account {
918 let nonce_account = solana_rpc_client_nonce_utils::get_account_with_commitment(
919 rpc_client,
920 nonce_account,
921 config.commitment,
922 )?;
923 check_nonce_account(&nonce_account, &nonce_authority.pubkey(), &recent_blockhash)?;
924 }
925 }
926
927 let mut tx = Transaction::new_unsigned(message);
928 if sign_only {
929 tx.try_partial_sign(&config.signers, recent_blockhash)?;
930 return_signers_with_config(
931 &tx,
932 &config.output_format,
933 &ReturnSignersConfig {
934 dump_transaction_message,
935 },
936 )
937 } else {
938 tx.try_sign(&config.signers, recent_blockhash)?;
939 let result = rpc_client.send_and_confirm_transaction_with_spinner_and_config(
940 &tx,
941 config.commitment,
942 config.send_transaction_config,
943 );
944 log_instruction_custom_error::<SystemError>(result, config)
945 }
946}
947
948#[allow(clippy::too_many_arguments)]
949pub fn process_vote_authorize(
950 rpc_client: &RpcClient,
951 config: &CliConfig,
952 vote_account_pubkey: &Pubkey,
953 new_authorized_pubkey: &Pubkey,
954 vote_authorize: VoteAuthorize,
955 authorized: SignerIndex,
956 new_authorized: Option<SignerIndex>,
957 sign_only: bool,
958 dump_transaction_message: bool,
959 blockhash_query: &BlockhashQuery,
960 nonce_account: Option<Pubkey>,
961 nonce_authority: SignerIndex,
962 memo: Option<&String>,
963 fee_payer: SignerIndex,
964 compute_unit_price: Option<u64>,
965) -> ProcessResult {
966 let authorized = config.signers[authorized];
967 let new_authorized_signer = new_authorized.map(|index| config.signers[index]);
968
969 let vote_state = if !sign_only {
970 Some(get_vote_account(rpc_client, vote_account_pubkey, config.commitment)?.1)
971 } else {
972 None
973 };
974 match vote_authorize {
975 VoteAuthorize::Voter => {
976 if let Some(vote_state) = vote_state {
977 let current_epoch = rpc_client.get_epoch_info()?.epoch;
978 let current_authorized_voter = vote_state
979 .authorized_voters()
980 .get_authorized_voter(current_epoch)
981 .ok_or_else(|| {
982 CliError::RpcRequestError(
983 "Invalid vote account state; no authorized voters found".to_string(),
984 )
985 })?;
986 check_current_authority(
987 &[current_authorized_voter, vote_state.authorized_withdrawer],
988 &authorized.pubkey(),
989 )?;
990 if let Some(signer) = new_authorized_signer {
991 if signer.is_interactive() {
992 return Err(CliError::BadParameter(format!(
993 "invalid new authorized vote signer {new_authorized_pubkey:?}. \
994 Interactive vote signers not supported"
995 ))
996 .into());
997 }
998 }
999 }
1000 }
1001 VoteAuthorize::Withdrawer => {
1002 check_unique_pubkeys(
1003 (&authorized.pubkey(), "authorized_account".to_string()),
1004 (new_authorized_pubkey, "new_authorized_pubkey".to_string()),
1005 )?;
1006 if let Some(vote_state) = vote_state {
1007 check_current_authority(&[vote_state.authorized_withdrawer], &authorized.pubkey())?
1008 }
1009 }
1010 }
1011
1012 let vote_ix = if new_authorized_signer.is_some() {
1013 vote_instruction::authorize_checked(
1014 vote_account_pubkey, &authorized.pubkey(), new_authorized_pubkey, vote_authorize, )
1019 } else {
1020 vote_instruction::authorize(
1021 vote_account_pubkey, &authorized.pubkey(), new_authorized_pubkey, vote_authorize, )
1026 };
1027
1028 let compute_unit_limit = match blockhash_query {
1029 BlockhashQuery::None(_) | BlockhashQuery::FeeCalculator(_, _) => ComputeUnitLimit::Default,
1030 BlockhashQuery::All(_) => ComputeUnitLimit::Simulated,
1031 };
1032 let ixs = vec![vote_ix]
1033 .with_memo(memo)
1034 .with_compute_unit_config(&ComputeUnitConfig {
1035 compute_unit_price,
1036 compute_unit_limit,
1037 });
1038
1039 let recent_blockhash = blockhash_query.get_blockhash(rpc_client, config.commitment)?;
1040
1041 let nonce_authority = config.signers[nonce_authority];
1042 let fee_payer = config.signers[fee_payer];
1043
1044 let mut message = if let Some(nonce_account) = &nonce_account {
1045 Message::new_with_nonce(
1046 ixs,
1047 Some(&fee_payer.pubkey()),
1048 nonce_account,
1049 &nonce_authority.pubkey(),
1050 )
1051 } else {
1052 Message::new(&ixs, Some(&fee_payer.pubkey()))
1053 };
1054 simulate_and_update_compute_unit_limit(&compute_unit_limit, rpc_client, &mut message)?;
1055 let mut tx = Transaction::new_unsigned(message);
1056
1057 if sign_only {
1058 tx.try_partial_sign(&config.signers, recent_blockhash)?;
1059 return_signers_with_config(
1060 &tx,
1061 &config.output_format,
1062 &ReturnSignersConfig {
1063 dump_transaction_message,
1064 },
1065 )
1066 } else {
1067 tx.try_sign(&config.signers, recent_blockhash)?;
1068 if let Some(nonce_account) = &nonce_account {
1069 let nonce_account = solana_rpc_client_nonce_utils::get_account_with_commitment(
1070 rpc_client,
1071 nonce_account,
1072 config.commitment,
1073 )?;
1074 check_nonce_account(&nonce_account, &nonce_authority.pubkey(), &recent_blockhash)?;
1075 }
1076 check_account_for_fee_with_commitment(
1077 rpc_client,
1078 &config.signers[0].pubkey(),
1079 &tx.message,
1080 config.commitment,
1081 )?;
1082 let result = rpc_client.send_and_confirm_transaction_with_spinner_and_config(
1083 &tx,
1084 config.commitment,
1085 config.send_transaction_config,
1086 );
1087 log_instruction_custom_error::<VoteError>(result, config)
1088 }
1089}
1090
1091#[allow(clippy::too_many_arguments)]
1092pub fn process_vote_update_validator(
1093 rpc_client: &RpcClient,
1094 config: &CliConfig,
1095 vote_account_pubkey: &Pubkey,
1096 new_identity_account: SignerIndex,
1097 withdraw_authority: SignerIndex,
1098 sign_only: bool,
1099 dump_transaction_message: bool,
1100 blockhash_query: &BlockhashQuery,
1101 nonce_account: Option<Pubkey>,
1102 nonce_authority: SignerIndex,
1103 memo: Option<&String>,
1104 fee_payer: SignerIndex,
1105 compute_unit_price: Option<u64>,
1106) -> ProcessResult {
1107 let authorized_withdrawer = config.signers[withdraw_authority];
1108 let new_identity_account = config.signers[new_identity_account];
1109 let new_identity_pubkey = new_identity_account.pubkey();
1110 check_unique_pubkeys(
1111 (vote_account_pubkey, "vote_account_pubkey".to_string()),
1112 (&new_identity_pubkey, "new_identity_account".to_string()),
1113 )?;
1114 let recent_blockhash = blockhash_query.get_blockhash(rpc_client, config.commitment)?;
1115 let compute_unit_limit = match blockhash_query {
1116 BlockhashQuery::None(_) | BlockhashQuery::FeeCalculator(_, _) => ComputeUnitLimit::Default,
1117 BlockhashQuery::All(_) => ComputeUnitLimit::Simulated,
1118 };
1119 let ixs = vec![vote_instruction::update_validator_identity(
1120 vote_account_pubkey,
1121 &authorized_withdrawer.pubkey(),
1122 &new_identity_pubkey,
1123 )]
1124 .with_memo(memo)
1125 .with_compute_unit_config(&ComputeUnitConfig {
1126 compute_unit_price,
1127 compute_unit_limit,
1128 });
1129 let nonce_authority = config.signers[nonce_authority];
1130 let fee_payer = config.signers[fee_payer];
1131
1132 let mut message = if let Some(nonce_account) = &nonce_account {
1133 Message::new_with_nonce(
1134 ixs,
1135 Some(&fee_payer.pubkey()),
1136 nonce_account,
1137 &nonce_authority.pubkey(),
1138 )
1139 } else {
1140 Message::new(&ixs, Some(&fee_payer.pubkey()))
1141 };
1142 simulate_and_update_compute_unit_limit(&compute_unit_limit, rpc_client, &mut message)?;
1143 let mut tx = Transaction::new_unsigned(message);
1144
1145 if sign_only {
1146 tx.try_partial_sign(&config.signers, recent_blockhash)?;
1147 return_signers_with_config(
1148 &tx,
1149 &config.output_format,
1150 &ReturnSignersConfig {
1151 dump_transaction_message,
1152 },
1153 )
1154 } else {
1155 tx.try_sign(&config.signers, recent_blockhash)?;
1156 if let Some(nonce_account) = &nonce_account {
1157 let nonce_account = solana_rpc_client_nonce_utils::get_account_with_commitment(
1158 rpc_client,
1159 nonce_account,
1160 config.commitment,
1161 )?;
1162 check_nonce_account(&nonce_account, &nonce_authority.pubkey(), &recent_blockhash)?;
1163 }
1164 check_account_for_fee_with_commitment(
1165 rpc_client,
1166 &config.signers[0].pubkey(),
1167 &tx.message,
1168 config.commitment,
1169 )?;
1170 let result = rpc_client.send_and_confirm_transaction_with_spinner_and_config(
1171 &tx,
1172 config.commitment,
1173 config.send_transaction_config,
1174 );
1175 log_instruction_custom_error::<VoteError>(result, config)
1176 }
1177}
1178
1179#[allow(clippy::too_many_arguments)]
1180pub fn process_vote_update_commission(
1181 rpc_client: &RpcClient,
1182 config: &CliConfig,
1183 vote_account_pubkey: &Pubkey,
1184 commission: u8,
1185 withdraw_authority: SignerIndex,
1186 sign_only: bool,
1187 dump_transaction_message: bool,
1188 blockhash_query: &BlockhashQuery,
1189 nonce_account: Option<Pubkey>,
1190 nonce_authority: SignerIndex,
1191 memo: Option<&String>,
1192 fee_payer: SignerIndex,
1193 compute_unit_price: Option<u64>,
1194) -> ProcessResult {
1195 let authorized_withdrawer = config.signers[withdraw_authority];
1196 let recent_blockhash = blockhash_query.get_blockhash(rpc_client, config.commitment)?;
1197 let compute_unit_limit = match blockhash_query {
1198 BlockhashQuery::None(_) | BlockhashQuery::FeeCalculator(_, _) => ComputeUnitLimit::Default,
1199 BlockhashQuery::All(_) => ComputeUnitLimit::Simulated,
1200 };
1201 let ixs = vec![vote_instruction::update_commission(
1202 vote_account_pubkey,
1203 &authorized_withdrawer.pubkey(),
1204 commission,
1205 )]
1206 .with_memo(memo)
1207 .with_compute_unit_config(&ComputeUnitConfig {
1208 compute_unit_price,
1209 compute_unit_limit,
1210 });
1211 let nonce_authority = config.signers[nonce_authority];
1212 let fee_payer = config.signers[fee_payer];
1213
1214 let mut message = if let Some(nonce_account) = &nonce_account {
1215 Message::new_with_nonce(
1216 ixs,
1217 Some(&fee_payer.pubkey()),
1218 nonce_account,
1219 &nonce_authority.pubkey(),
1220 )
1221 } else {
1222 Message::new(&ixs, Some(&fee_payer.pubkey()))
1223 };
1224 simulate_and_update_compute_unit_limit(&compute_unit_limit, rpc_client, &mut message)?;
1225 let mut tx = Transaction::new_unsigned(message);
1226 if sign_only {
1227 tx.try_partial_sign(&config.signers, recent_blockhash)?;
1228 return_signers_with_config(
1229 &tx,
1230 &config.output_format,
1231 &ReturnSignersConfig {
1232 dump_transaction_message,
1233 },
1234 )
1235 } else {
1236 tx.try_sign(&config.signers, recent_blockhash)?;
1237 if let Some(nonce_account) = &nonce_account {
1238 let nonce_account = solana_rpc_client_nonce_utils::get_account_with_commitment(
1239 rpc_client,
1240 nonce_account,
1241 config.commitment,
1242 )?;
1243 check_nonce_account(&nonce_account, &nonce_authority.pubkey(), &recent_blockhash)?;
1244 }
1245 check_account_for_fee_with_commitment(
1246 rpc_client,
1247 &config.signers[0].pubkey(),
1248 &tx.message,
1249 config.commitment,
1250 )?;
1251 let result = rpc_client.send_and_confirm_transaction_with_spinner_and_config(
1252 &tx,
1253 config.commitment,
1254 config.send_transaction_config,
1255 );
1256 log_instruction_custom_error::<VoteError>(result, config)
1257 }
1258}
1259
1260pub(crate) fn get_vote_account(
1261 rpc_client: &RpcClient,
1262 vote_account_pubkey: &Pubkey,
1263 commitment_config: CommitmentConfig,
1264) -> Result<(Account, VoteState), Box<dyn std::error::Error>> {
1265 let vote_account = rpc_client
1266 .get_account_with_commitment(vote_account_pubkey, commitment_config)?
1267 .value
1268 .ok_or_else(|| {
1269 CliError::RpcRequestError(format!("{vote_account_pubkey:?} account does not exist"))
1270 })?;
1271
1272 if vote_account.owner != solana_vote_program::id() {
1273 return Err(CliError::RpcRequestError(format!(
1274 "{vote_account_pubkey:?} is not a vote account"
1275 ))
1276 .into());
1277 }
1278 let vote_state = VoteState::deserialize(&vote_account.data).map_err(|_| {
1279 CliError::RpcRequestError(
1280 "Account data could not be deserialized to vote state".to_string(),
1281 )
1282 })?;
1283
1284 Ok((vote_account, vote_state))
1285}
1286
1287pub fn process_show_vote_account(
1288 rpc_client: &RpcClient,
1289 config: &CliConfig,
1290 vote_account_address: &Pubkey,
1291 use_lamports_unit: bool,
1292 use_csv: bool,
1293 with_rewards: Option<usize>,
1294 starting_epoch: Option<u64>,
1295) -> ProcessResult {
1296 let (vote_account, vote_state) =
1297 get_vote_account(rpc_client, vote_account_address, config.commitment)?;
1298
1299 let epoch_schedule = rpc_client.get_epoch_schedule()?;
1300 let tvc_activation_slot =
1301 rpc_client.get_feature_activation_slot(&solana_feature_set::timely_vote_credits::id())?;
1302 let tvc_activation_epoch = tvc_activation_slot.map(|s| epoch_schedule.get_epoch(s));
1303
1304 let mut votes: Vec<CliLandedVote> = vec![];
1305 let mut epoch_voting_history: Vec<CliEpochVotingHistory> = vec![];
1306 if !vote_state.votes.is_empty() {
1307 for vote in &vote_state.votes {
1308 votes.push(vote.into());
1309 }
1310 for (epoch, credits, prev_credits) in vote_state.epoch_credits().iter().copied() {
1311 let credits_earned = credits.saturating_sub(prev_credits);
1312 let slots_in_epoch = epoch_schedule.get_slots_in_epoch(epoch);
1313 let is_tvc_active = tvc_activation_epoch.map(|e| epoch >= e).unwrap_or_default();
1314 let max_credits_per_slot = if is_tvc_active {
1315 VOTE_CREDITS_MAXIMUM_PER_SLOT
1316 } else {
1317 1
1318 };
1319 epoch_voting_history.push(CliEpochVotingHistory {
1320 epoch,
1321 slots_in_epoch,
1322 credits_earned,
1323 credits,
1324 prev_credits,
1325 max_credits_per_slot,
1326 });
1327 }
1328 }
1329
1330 let epoch_rewards =
1331 with_rewards.and_then(|num_epochs| {
1332 match crate::stake::fetch_epoch_rewards(
1333 rpc_client,
1334 vote_account_address,
1335 num_epochs,
1336 starting_epoch,
1337 ) {
1338 Ok(rewards) => Some(rewards),
1339 Err(error) => {
1340 eprintln!("Failed to fetch epoch rewards: {error:?}");
1341 None
1342 }
1343 }
1344 });
1345
1346 let vote_account_data = CliVoteAccount {
1347 account_balance: vote_account.lamports,
1348 validator_identity: vote_state.node_pubkey.to_string(),
1349 authorized_voters: vote_state.authorized_voters().into(),
1350 authorized_withdrawer: vote_state.authorized_withdrawer.to_string(),
1351 credits: vote_state.credits(),
1352 commission: vote_state.commission,
1353 root_slot: vote_state.root_slot,
1354 recent_timestamp: vote_state.last_timestamp.clone(),
1355 votes,
1356 epoch_voting_history,
1357 use_lamports_unit,
1358 use_csv,
1359 epoch_rewards,
1360 };
1361
1362 Ok(config.output_format.formatted_string(&vote_account_data))
1363}
1364
1365#[allow(clippy::too_many_arguments)]
1366pub fn process_withdraw_from_vote_account(
1367 rpc_client: &RpcClient,
1368 config: &CliConfig,
1369 vote_account_pubkey: &Pubkey,
1370 withdraw_authority: SignerIndex,
1371 withdraw_amount: SpendAmount,
1372 destination_account_pubkey: &Pubkey,
1373 sign_only: bool,
1374 dump_transaction_message: bool,
1375 blockhash_query: &BlockhashQuery,
1376 nonce_account: Option<&Pubkey>,
1377 nonce_authority: SignerIndex,
1378 memo: Option<&String>,
1379 fee_payer: SignerIndex,
1380 compute_unit_price: Option<u64>,
1381) -> ProcessResult {
1382 let withdraw_authority = config.signers[withdraw_authority];
1383 let recent_blockhash = blockhash_query.get_blockhash(rpc_client, config.commitment)?;
1384
1385 let fee_payer = config.signers[fee_payer];
1386 let nonce_authority = config.signers[nonce_authority];
1387
1388 let compute_unit_limit = match blockhash_query {
1389 BlockhashQuery::None(_) | BlockhashQuery::FeeCalculator(_, _) => ComputeUnitLimit::Default,
1390 BlockhashQuery::All(_) => ComputeUnitLimit::Simulated,
1391 };
1392 let build_message = |lamports| {
1393 let ixs = vec![withdraw(
1394 vote_account_pubkey,
1395 &withdraw_authority.pubkey(),
1396 lamports,
1397 destination_account_pubkey,
1398 )]
1399 .with_memo(memo)
1400 .with_compute_unit_config(&ComputeUnitConfig {
1401 compute_unit_price,
1402 compute_unit_limit,
1403 });
1404
1405 if let Some(nonce_account) = &nonce_account {
1406 Message::new_with_nonce(
1407 ixs,
1408 Some(&fee_payer.pubkey()),
1409 nonce_account,
1410 &nonce_authority.pubkey(),
1411 )
1412 } else {
1413 Message::new(&ixs, Some(&fee_payer.pubkey()))
1414 }
1415 };
1416
1417 let (message, _) = resolve_spend_tx_and_check_account_balances(
1418 rpc_client,
1419 sign_only,
1420 withdraw_amount,
1421 &recent_blockhash,
1422 vote_account_pubkey,
1423 &fee_payer.pubkey(),
1424 compute_unit_limit,
1425 build_message,
1426 config.commitment,
1427 )?;
1428
1429 if !sign_only {
1430 let current_balance = rpc_client.get_balance(vote_account_pubkey)?;
1431 let minimum_balance =
1432 rpc_client.get_minimum_balance_for_rent_exemption(VoteState::size_of())?;
1433 if let SpendAmount::Some(withdraw_amount) = withdraw_amount {
1434 let balance_remaining = current_balance.saturating_sub(withdraw_amount);
1435 if balance_remaining < minimum_balance && balance_remaining != 0 {
1436 return Err(CliError::BadParameter(format!(
1437 "Withdraw amount too large. The vote account balance must be at least {} SOL \
1438 to remain rent exempt",
1439 lamports_to_sol(minimum_balance)
1440 ))
1441 .into());
1442 }
1443 }
1444 }
1445
1446 let mut tx = Transaction::new_unsigned(message);
1447
1448 if sign_only {
1449 tx.try_partial_sign(&config.signers, recent_blockhash)?;
1450 return_signers_with_config(
1451 &tx,
1452 &config.output_format,
1453 &ReturnSignersConfig {
1454 dump_transaction_message,
1455 },
1456 )
1457 } else {
1458 tx.try_sign(&config.signers, recent_blockhash)?;
1459 if let Some(nonce_account) = &nonce_account {
1460 let nonce_account = solana_rpc_client_nonce_utils::get_account_with_commitment(
1461 rpc_client,
1462 nonce_account,
1463 config.commitment,
1464 )?;
1465 check_nonce_account(&nonce_account, &nonce_authority.pubkey(), &recent_blockhash)?;
1466 }
1467 check_account_for_fee_with_commitment(
1468 rpc_client,
1469 &tx.message.account_keys[0],
1470 &tx.message,
1471 config.commitment,
1472 )?;
1473 let result = rpc_client.send_and_confirm_transaction_with_spinner_and_config(
1474 &tx,
1475 config.commitment,
1476 config.send_transaction_config,
1477 );
1478 log_instruction_custom_error::<VoteError>(result, config)
1479 }
1480}
1481
1482pub fn process_close_vote_account(
1483 rpc_client: &RpcClient,
1484 config: &CliConfig,
1485 vote_account_pubkey: &Pubkey,
1486 withdraw_authority: SignerIndex,
1487 destination_account_pubkey: &Pubkey,
1488 memo: Option<&String>,
1489 fee_payer: SignerIndex,
1490 compute_unit_price: Option<u64>,
1491) -> ProcessResult {
1492 let vote_account_status =
1493 rpc_client.get_vote_accounts_with_config(RpcGetVoteAccountsConfig {
1494 vote_pubkey: Some(vote_account_pubkey.to_string()),
1495 ..RpcGetVoteAccountsConfig::default()
1496 })?;
1497
1498 if let Some(vote_account) = vote_account_status
1499 .current
1500 .into_iter()
1501 .chain(vote_account_status.delinquent)
1502 .next()
1503 {
1504 if vote_account.activated_stake != 0 {
1505 return Err(format!(
1506 "Cannot close a vote account with active stake: {vote_account_pubkey}"
1507 )
1508 .into());
1509 }
1510 }
1511
1512 let latest_blockhash = rpc_client.get_latest_blockhash()?;
1513 let withdraw_authority = config.signers[withdraw_authority];
1514 let fee_payer = config.signers[fee_payer];
1515
1516 let current_balance = rpc_client.get_balance(vote_account_pubkey)?;
1517
1518 let compute_unit_limit = ComputeUnitLimit::Simulated;
1519 let ixs = vec![withdraw(
1520 vote_account_pubkey,
1521 &withdraw_authority.pubkey(),
1522 current_balance,
1523 destination_account_pubkey,
1524 )]
1525 .with_memo(memo)
1526 .with_compute_unit_config(&ComputeUnitConfig {
1527 compute_unit_price,
1528 compute_unit_limit,
1529 });
1530
1531 let mut message = Message::new(&ixs, Some(&fee_payer.pubkey()));
1532 simulate_and_update_compute_unit_limit(&compute_unit_limit, rpc_client, &mut message)?;
1533 let mut tx = Transaction::new_unsigned(message);
1534 tx.try_sign(&config.signers, latest_blockhash)?;
1535 check_account_for_fee_with_commitment(
1536 rpc_client,
1537 &tx.message.account_keys[0],
1538 &tx.message,
1539 config.commitment,
1540 )?;
1541 let result = rpc_client.send_and_confirm_transaction_with_spinner_and_config(
1542 &tx,
1543 config.commitment,
1544 config.send_transaction_config,
1545 );
1546 log_instruction_custom_error::<VoteError>(result, config)
1547}
1548
1549#[cfg(test)]
1550mod tests {
1551 use {
1552 super::*,
1553 crate::{clap_app::get_clap_app, cli::parse_command},
1554 solana_hash::Hash,
1555 solana_keypair::{read_keypair_file, write_keypair, Keypair},
1556 solana_presigner::Presigner,
1557 solana_rpc_client_nonce_utils::blockhash_query,
1558 solana_signer::Signer,
1559 tempfile::NamedTempFile,
1560 };
1561
1562 fn make_tmp_file() -> (String, NamedTempFile) {
1563 let tmp_file = NamedTempFile::new().unwrap();
1564 (String::from(tmp_file.path().to_str().unwrap()), tmp_file)
1565 }
1566
1567 #[test]
1568 fn test_parse_command() {
1569 let test_commands = get_clap_app("test", "desc", "version");
1570 let keypair = Keypair::new();
1571 let pubkey = keypair.pubkey();
1572 let pubkey_string = pubkey.to_string();
1573 let keypair2 = Keypair::new();
1574 let pubkey2 = keypair2.pubkey();
1575 let pubkey2_string = pubkey2.to_string();
1576 let sig2 = keypair2.sign_message(&[0u8]);
1577 let signer2 = format!("{}={}", keypair2.pubkey(), sig2);
1578
1579 let default_keypair = Keypair::new();
1580 let (default_keypair_file, mut tmp_file) = make_tmp_file();
1581 write_keypair(&default_keypair, tmp_file.as_file_mut()).unwrap();
1582 let default_signer = DefaultSigner::new("", &default_keypair_file);
1583
1584 let blockhash = Hash::default();
1585 let blockhash_string = format!("{blockhash}");
1586 let nonce_account = Pubkey::new_unique();
1587
1588 let test_authorize_voter = test_commands.clone().get_matches_from(vec![
1590 "test",
1591 "vote-authorize-voter",
1592 &pubkey_string,
1593 &default_keypair_file,
1594 &pubkey2_string,
1595 ]);
1596 assert_eq!(
1597 parse_command(&test_authorize_voter, &default_signer, &mut None).unwrap(),
1598 CliCommandInfo {
1599 command: CliCommand::VoteAuthorize {
1600 vote_account_pubkey: pubkey,
1601 new_authorized_pubkey: pubkey2,
1602 vote_authorize: VoteAuthorize::Voter,
1603 sign_only: false,
1604 dump_transaction_message: false,
1605 blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
1606 nonce_account: None,
1607 nonce_authority: 0,
1608 memo: None,
1609 fee_payer: 0,
1610 authorized: 0,
1611 new_authorized: None,
1612 compute_unit_price: None,
1613 },
1614 signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap())],
1615 }
1616 );
1617
1618 let authorized_keypair = Keypair::new();
1619 let (authorized_keypair_file, mut tmp_file) = make_tmp_file();
1620 write_keypair(&authorized_keypair, tmp_file.as_file_mut()).unwrap();
1621
1622 let test_authorize_voter = test_commands.clone().get_matches_from(vec![
1623 "test",
1624 "vote-authorize-voter",
1625 &pubkey_string,
1626 &authorized_keypair_file,
1627 &pubkey2_string,
1628 ]);
1629 assert_eq!(
1630 parse_command(&test_authorize_voter, &default_signer, &mut None).unwrap(),
1631 CliCommandInfo {
1632 command: CliCommand::VoteAuthorize {
1633 vote_account_pubkey: pubkey,
1634 new_authorized_pubkey: pubkey2,
1635 vote_authorize: VoteAuthorize::Voter,
1636 sign_only: false,
1637 dump_transaction_message: false,
1638 blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
1639 nonce_account: None,
1640 nonce_authority: 0,
1641 memo: None,
1642 fee_payer: 0,
1643 authorized: 1,
1644 new_authorized: None,
1645 compute_unit_price: None,
1646 },
1647 signers: vec![
1648 Box::new(read_keypair_file(&default_keypair_file).unwrap()),
1649 Box::new(read_keypair_file(&authorized_keypair_file).unwrap()),
1650 ],
1651 }
1652 );
1653
1654 let test_authorize_voter = test_commands.clone().get_matches_from(vec![
1655 "test",
1656 "vote-authorize-voter",
1657 &pubkey_string,
1658 &authorized_keypair_file,
1659 &pubkey2_string,
1660 "--blockhash",
1661 &blockhash_string,
1662 "--sign-only",
1663 ]);
1664 assert_eq!(
1665 parse_command(&test_authorize_voter, &default_signer, &mut None).unwrap(),
1666 CliCommandInfo {
1667 command: CliCommand::VoteAuthorize {
1668 vote_account_pubkey: pubkey,
1669 new_authorized_pubkey: pubkey2,
1670 vote_authorize: VoteAuthorize::Voter,
1671 sign_only: true,
1672 dump_transaction_message: false,
1673 blockhash_query: BlockhashQuery::None(blockhash),
1674 nonce_account: None,
1675 nonce_authority: 0,
1676 memo: None,
1677 fee_payer: 0,
1678 authorized: 1,
1679 new_authorized: None,
1680 compute_unit_price: None,
1681 },
1682 signers: vec![
1683 Box::new(read_keypair_file(&default_keypair_file).unwrap()),
1684 Box::new(read_keypair_file(&authorized_keypair_file).unwrap()),
1685 ],
1686 }
1687 );
1688
1689 let authorized_sig = authorized_keypair.sign_message(&[0u8]);
1690 let authorized_signer = format!("{}={}", authorized_keypair.pubkey(), authorized_sig);
1691 let test_authorize_voter = test_commands.clone().get_matches_from(vec![
1692 "test",
1693 "vote-authorize-voter",
1694 &pubkey_string,
1695 &authorized_keypair.pubkey().to_string(),
1696 &pubkey2_string,
1697 "--blockhash",
1698 &blockhash_string,
1699 "--signer",
1700 &authorized_signer,
1701 "--signer",
1702 &signer2,
1703 "--fee-payer",
1704 &pubkey2_string,
1705 "--nonce",
1706 &nonce_account.to_string(),
1707 "--nonce-authority",
1708 &pubkey2_string,
1709 ]);
1710 assert_eq!(
1711 parse_command(&test_authorize_voter, &default_signer, &mut None).unwrap(),
1712 CliCommandInfo {
1713 command: CliCommand::VoteAuthorize {
1714 vote_account_pubkey: pubkey,
1715 new_authorized_pubkey: pubkey2,
1716 vote_authorize: VoteAuthorize::Voter,
1717 sign_only: false,
1718 dump_transaction_message: false,
1719 blockhash_query: BlockhashQuery::FeeCalculator(
1720 blockhash_query::Source::NonceAccount(nonce_account),
1721 blockhash
1722 ),
1723 nonce_account: Some(nonce_account),
1724 nonce_authority: 0,
1725 memo: None,
1726 fee_payer: 0,
1727 authorized: 1,
1728 new_authorized: None,
1729 compute_unit_price: None,
1730 },
1731 signers: vec![
1732 Box::new(Presigner::new(&pubkey2, &sig2)),
1733 Box::new(Presigner::new(
1734 &authorized_keypair.pubkey(),
1735 &authorized_sig
1736 )),
1737 ],
1738 }
1739 );
1740
1741 let (voter_keypair_file, mut tmp_file) = make_tmp_file();
1743 let voter_keypair = Keypair::new();
1744 write_keypair(&voter_keypair, tmp_file.as_file_mut()).unwrap();
1745
1746 let test_authorize_voter = test_commands.clone().get_matches_from(vec![
1747 "test",
1748 "vote-authorize-voter-checked",
1749 &pubkey_string,
1750 &default_keypair_file,
1751 &voter_keypair_file,
1752 ]);
1753 assert_eq!(
1754 parse_command(&test_authorize_voter, &default_signer, &mut None).unwrap(),
1755 CliCommandInfo {
1756 command: CliCommand::VoteAuthorize {
1757 vote_account_pubkey: pubkey,
1758 new_authorized_pubkey: voter_keypair.pubkey(),
1759 vote_authorize: VoteAuthorize::Voter,
1760 sign_only: false,
1761 dump_transaction_message: false,
1762 blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
1763 nonce_account: None,
1764 nonce_authority: 0,
1765 memo: None,
1766 fee_payer: 0,
1767 authorized: 0,
1768 new_authorized: Some(1),
1769 compute_unit_price: None,
1770 },
1771 signers: vec![
1772 Box::new(read_keypair_file(&default_keypair_file).unwrap()),
1773 Box::new(read_keypair_file(&voter_keypair_file).unwrap())
1774 ],
1775 }
1776 );
1777
1778 let test_authorize_voter = test_commands.clone().get_matches_from(vec![
1779 "test",
1780 "vote-authorize-voter-checked",
1781 &pubkey_string,
1782 &authorized_keypair_file,
1783 &voter_keypair_file,
1784 ]);
1785 assert_eq!(
1786 parse_command(&test_authorize_voter, &default_signer, &mut None).unwrap(),
1787 CliCommandInfo {
1788 command: CliCommand::VoteAuthorize {
1789 vote_account_pubkey: pubkey,
1790 new_authorized_pubkey: voter_keypair.pubkey(),
1791 vote_authorize: VoteAuthorize::Voter,
1792 sign_only: false,
1793 dump_transaction_message: false,
1794 blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
1795 nonce_account: None,
1796 nonce_authority: 0,
1797 memo: None,
1798 fee_payer: 0,
1799 authorized: 1,
1800 new_authorized: Some(2),
1801 compute_unit_price: None,
1802 },
1803 signers: vec![
1804 Box::new(read_keypair_file(&default_keypair_file).unwrap()),
1805 Box::new(read_keypair_file(&authorized_keypair_file).unwrap()),
1806 Box::new(read_keypair_file(&voter_keypair_file).unwrap()),
1807 ],
1808 }
1809 );
1810
1811 let test_authorize_voter = test_commands.clone().get_matches_from(vec![
1812 "test",
1813 "vote-authorize-voter-checked",
1814 &pubkey_string,
1815 &authorized_keypair_file,
1816 &pubkey2_string,
1817 ]);
1818 assert!(parse_command(&test_authorize_voter, &default_signer, &mut None).is_err());
1819
1820 let (identity_keypair_file, mut tmp_file) = make_tmp_file();
1822 let identity_keypair = Keypair::new();
1823 let authorized_withdrawer = Keypair::new().pubkey();
1824 write_keypair(&identity_keypair, tmp_file.as_file_mut()).unwrap();
1825 let (keypair_file, mut tmp_file) = make_tmp_file();
1826 let keypair = Keypair::new();
1827 write_keypair(&keypair, tmp_file.as_file_mut()).unwrap();
1828
1829 let test_create_vote_account = test_commands.clone().get_matches_from(vec![
1830 "test",
1831 "create-vote-account",
1832 &keypair_file,
1833 &identity_keypair_file,
1834 &authorized_withdrawer.to_string(),
1835 "--commission",
1836 "10",
1837 ]);
1838 assert_eq!(
1839 parse_command(&test_create_vote_account, &default_signer, &mut None).unwrap(),
1840 CliCommandInfo {
1841 command: CliCommand::CreateVoteAccount {
1842 vote_account: 1,
1843 seed: None,
1844 identity_account: 2,
1845 authorized_voter: None,
1846 authorized_withdrawer,
1847 commission: 10,
1848 sign_only: false,
1849 dump_transaction_message: false,
1850 blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
1851 nonce_account: None,
1852 nonce_authority: 0,
1853 memo: None,
1854 fee_payer: 0,
1855 compute_unit_price: None,
1856 },
1857 signers: vec![
1858 Box::new(read_keypair_file(&default_keypair_file).unwrap()),
1859 Box::new(read_keypair_file(&keypair_file).unwrap()),
1860 Box::new(read_keypair_file(&identity_keypair_file).unwrap()),
1861 ],
1862 }
1863 );
1864
1865 let test_create_vote_account2 = test_commands.clone().get_matches_from(vec![
1866 "test",
1867 "create-vote-account",
1868 &keypair_file,
1869 &identity_keypair_file,
1870 &authorized_withdrawer.to_string(),
1871 ]);
1872 assert_eq!(
1873 parse_command(&test_create_vote_account2, &default_signer, &mut None).unwrap(),
1874 CliCommandInfo {
1875 command: CliCommand::CreateVoteAccount {
1876 vote_account: 1,
1877 seed: None,
1878 identity_account: 2,
1879 authorized_voter: None,
1880 authorized_withdrawer,
1881 commission: 100,
1882 sign_only: false,
1883 dump_transaction_message: false,
1884 blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
1885 nonce_account: None,
1886 nonce_authority: 0,
1887 memo: None,
1888 fee_payer: 0,
1889 compute_unit_price: None,
1890 },
1891 signers: vec![
1892 Box::new(read_keypair_file(&default_keypair_file).unwrap()),
1893 Box::new(read_keypair_file(&keypair_file).unwrap()),
1894 Box::new(read_keypair_file(&identity_keypair_file).unwrap()),
1895 ],
1896 }
1897 );
1898
1899 let test_create_vote_account = test_commands.clone().get_matches_from(vec![
1900 "test",
1901 "create-vote-account",
1902 &keypair_file,
1903 &identity_keypair_file,
1904 &authorized_withdrawer.to_string(),
1905 "--commission",
1906 "10",
1907 "--blockhash",
1908 &blockhash_string,
1909 "--sign-only",
1910 "--fee-payer",
1911 &default_keypair.pubkey().to_string(),
1912 ]);
1913 assert_eq!(
1914 parse_command(&test_create_vote_account, &default_signer, &mut None).unwrap(),
1915 CliCommandInfo {
1916 command: CliCommand::CreateVoteAccount {
1917 vote_account: 1,
1918 seed: None,
1919 identity_account: 2,
1920 authorized_voter: None,
1921 authorized_withdrawer,
1922 commission: 10,
1923 sign_only: true,
1924 dump_transaction_message: false,
1925 blockhash_query: BlockhashQuery::None(blockhash),
1926 nonce_account: None,
1927 nonce_authority: 0,
1928 memo: None,
1929 fee_payer: 0,
1930 compute_unit_price: None,
1931 },
1932 signers: vec![
1933 Box::new(read_keypair_file(&default_keypair_file).unwrap()),
1934 Box::new(read_keypair_file(&keypair_file).unwrap()),
1935 Box::new(read_keypair_file(&identity_keypair_file).unwrap()),
1936 ],
1937 }
1938 );
1939
1940 let identity_sig = identity_keypair.sign_message(&[0u8]);
1941 let identity_signer = format!("{}={}", identity_keypair.pubkey(), identity_sig);
1942 let test_create_vote_account = test_commands.clone().get_matches_from(vec![
1943 "test",
1944 "create-vote-account",
1945 &keypair_file,
1946 &identity_keypair.pubkey().to_string(),
1947 &authorized_withdrawer.to_string(),
1948 "--commission",
1949 "10",
1950 "--blockhash",
1951 &blockhash_string,
1952 "--signer",
1953 &identity_signer,
1954 "--signer",
1955 &signer2,
1956 "--fee-payer",
1957 &default_keypair_file,
1958 "--nonce",
1959 &nonce_account.to_string(),
1960 "--nonce-authority",
1961 &pubkey2_string,
1962 ]);
1963 assert_eq!(
1964 parse_command(&test_create_vote_account, &default_signer, &mut None).unwrap(),
1965 CliCommandInfo {
1966 command: CliCommand::CreateVoteAccount {
1967 vote_account: 1,
1968 seed: None,
1969 identity_account: 2,
1970 authorized_voter: None,
1971 authorized_withdrawer,
1972 commission: 10,
1973 sign_only: false,
1974 dump_transaction_message: false,
1975 blockhash_query: BlockhashQuery::FeeCalculator(
1976 blockhash_query::Source::NonceAccount(nonce_account),
1977 blockhash
1978 ),
1979 nonce_account: Some(nonce_account),
1980 nonce_authority: 3,
1981 memo: None,
1982 fee_payer: 0,
1983 compute_unit_price: None,
1984 },
1985 signers: vec![
1986 Box::new(read_keypair_file(&default_keypair_file).unwrap()),
1987 Box::new(read_keypair_file(&keypair_file).unwrap()),
1988 Box::new(Presigner::new(&identity_keypair.pubkey(), &identity_sig)),
1989 Box::new(Presigner::new(&pubkey2, &sig2)),
1990 ],
1991 }
1992 );
1993
1994 let authed = solana_pubkey::new_rand();
1996 let (keypair_file, mut tmp_file) = make_tmp_file();
1997 let keypair = Keypair::new();
1998 write_keypair(&keypair, tmp_file.as_file_mut()).unwrap();
1999
2000 let test_create_vote_account3 = test_commands.clone().get_matches_from(vec![
2001 "test",
2002 "create-vote-account",
2003 &keypair_file,
2004 &identity_keypair_file,
2005 &authorized_withdrawer.to_string(),
2006 "--authorized-voter",
2007 &authed.to_string(),
2008 ]);
2009 assert_eq!(
2010 parse_command(&test_create_vote_account3, &default_signer, &mut None).unwrap(),
2011 CliCommandInfo {
2012 command: CliCommand::CreateVoteAccount {
2013 vote_account: 1,
2014 seed: None,
2015 identity_account: 2,
2016 authorized_voter: Some(authed),
2017 authorized_withdrawer,
2018 commission: 100,
2019 sign_only: false,
2020 dump_transaction_message: false,
2021 blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
2022 nonce_account: None,
2023 nonce_authority: 0,
2024 memo: None,
2025 fee_payer: 0,
2026 compute_unit_price: None,
2027 },
2028 signers: vec![
2029 Box::new(read_keypair_file(&default_keypair_file).unwrap()),
2030 Box::new(keypair),
2031 Box::new(read_keypair_file(&identity_keypair_file).unwrap()),
2032 ],
2033 }
2034 );
2035
2036 let (keypair_file, mut tmp_file) = make_tmp_file();
2037 let keypair = Keypair::new();
2038 write_keypair(&keypair, tmp_file.as_file_mut()).unwrap();
2039 let test_create_vote_account4 = test_commands.clone().get_matches_from(vec![
2041 "test",
2042 "create-vote-account",
2043 &keypair_file,
2044 &identity_keypair_file,
2045 &identity_keypair_file,
2046 "--allow-unsafe-authorized-withdrawer",
2047 ]);
2048 assert_eq!(
2049 parse_command(&test_create_vote_account4, &default_signer, &mut None).unwrap(),
2050 CliCommandInfo {
2051 command: CliCommand::CreateVoteAccount {
2052 vote_account: 1,
2053 seed: None,
2054 identity_account: 2,
2055 authorized_voter: None,
2056 authorized_withdrawer: identity_keypair.pubkey(),
2057 commission: 100,
2058 sign_only: false,
2059 dump_transaction_message: false,
2060 blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
2061 nonce_account: None,
2062 nonce_authority: 0,
2063 memo: None,
2064 fee_payer: 0,
2065 compute_unit_price: None,
2066 },
2067 signers: vec![
2068 Box::new(read_keypair_file(&default_keypair_file).unwrap()),
2069 Box::new(read_keypair_file(&keypair_file).unwrap()),
2070 Box::new(read_keypair_file(&identity_keypair_file).unwrap()),
2071 ],
2072 }
2073 );
2074
2075 let test_update_validator = test_commands.clone().get_matches_from(vec![
2076 "test",
2077 "vote-update-validator",
2078 &pubkey_string,
2079 &identity_keypair_file,
2080 &keypair_file,
2081 ]);
2082 assert_eq!(
2083 parse_command(&test_update_validator, &default_signer, &mut None).unwrap(),
2084 CliCommandInfo {
2085 command: CliCommand::VoteUpdateValidator {
2086 vote_account_pubkey: pubkey,
2087 new_identity_account: 2,
2088 withdraw_authority: 1,
2089 sign_only: false,
2090 dump_transaction_message: false,
2091 blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
2092 nonce_account: None,
2093 nonce_authority: 0,
2094 memo: None,
2095 fee_payer: 0,
2096 compute_unit_price: None,
2097 },
2098 signers: vec![
2099 Box::new(read_keypair_file(&default_keypair_file).unwrap()),
2100 Box::new(read_keypair_file(&keypair_file).unwrap()),
2101 Box::new(read_keypair_file(&identity_keypair_file).unwrap()),
2102 ],
2103 }
2104 );
2105
2106 let test_update_commission = test_commands.clone().get_matches_from(vec![
2107 "test",
2108 "vote-update-commission",
2109 &pubkey_string,
2110 "42",
2111 &keypair_file,
2112 ]);
2113 assert_eq!(
2114 parse_command(&test_update_commission, &default_signer, &mut None).unwrap(),
2115 CliCommandInfo {
2116 command: CliCommand::VoteUpdateCommission {
2117 vote_account_pubkey: pubkey,
2118 commission: 42,
2119 withdraw_authority: 1,
2120 sign_only: false,
2121 dump_transaction_message: false,
2122 blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
2123 nonce_account: None,
2124 nonce_authority: 0,
2125 memo: None,
2126 fee_payer: 0,
2127 compute_unit_price: None,
2128 },
2129 signers: vec![
2130 Box::new(read_keypair_file(&default_keypair_file).unwrap()),
2131 Box::new(read_keypair_file(&keypair_file).unwrap()),
2132 ],
2133 }
2134 );
2135
2136 let test_withdraw_from_vote_account = test_commands.clone().get_matches_from(vec![
2138 "test",
2139 "withdraw-from-vote-account",
2140 &keypair_file,
2141 &pubkey_string,
2142 "42",
2143 ]);
2144 assert_eq!(
2145 parse_command(&test_withdraw_from_vote_account, &default_signer, &mut None).unwrap(),
2146 CliCommandInfo {
2147 command: CliCommand::WithdrawFromVoteAccount {
2148 vote_account_pubkey: read_keypair_file(&keypair_file).unwrap().pubkey(),
2149 destination_account_pubkey: pubkey,
2150 withdraw_authority: 0,
2151 withdraw_amount: SpendAmount::Some(42_000_000_000),
2152 sign_only: false,
2153 dump_transaction_message: false,
2154 blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
2155 nonce_account: None,
2156 nonce_authority: 0,
2157 memo: None,
2158 fee_payer: 0,
2159 compute_unit_price: None,
2160 },
2161 signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap())],
2162 }
2163 );
2164
2165 let test_withdraw_from_vote_account = test_commands.clone().get_matches_from(vec![
2167 "test",
2168 "withdraw-from-vote-account",
2169 &keypair_file,
2170 &pubkey_string,
2171 "ALL",
2172 ]);
2173 assert_eq!(
2174 parse_command(&test_withdraw_from_vote_account, &default_signer, &mut None).unwrap(),
2175 CliCommandInfo {
2176 command: CliCommand::WithdrawFromVoteAccount {
2177 vote_account_pubkey: read_keypair_file(&keypair_file).unwrap().pubkey(),
2178 destination_account_pubkey: pubkey,
2179 withdraw_authority: 0,
2180 withdraw_amount: SpendAmount::RentExempt,
2181 sign_only: false,
2182 dump_transaction_message: false,
2183 blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
2184 nonce_account: None,
2185 nonce_authority: 0,
2186 memo: None,
2187 fee_payer: 0,
2188 compute_unit_price: None,
2189 },
2190 signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap())],
2191 }
2192 );
2193
2194 let withdraw_authority = Keypair::new();
2196 let (withdraw_authority_file, mut tmp_file) = make_tmp_file();
2197 write_keypair(&withdraw_authority, tmp_file.as_file_mut()).unwrap();
2198 let test_withdraw_from_vote_account = test_commands.clone().get_matches_from(vec![
2199 "test",
2200 "withdraw-from-vote-account",
2201 &keypair_file,
2202 &pubkey_string,
2203 "42",
2204 "--authorized-withdrawer",
2205 &withdraw_authority_file,
2206 ]);
2207 assert_eq!(
2208 parse_command(&test_withdraw_from_vote_account, &default_signer, &mut None).unwrap(),
2209 CliCommandInfo {
2210 command: CliCommand::WithdrawFromVoteAccount {
2211 vote_account_pubkey: read_keypair_file(&keypair_file).unwrap().pubkey(),
2212 destination_account_pubkey: pubkey,
2213 withdraw_authority: 1,
2214 withdraw_amount: SpendAmount::Some(42_000_000_000),
2215 sign_only: false,
2216 dump_transaction_message: false,
2217 blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
2218 nonce_account: None,
2219 nonce_authority: 0,
2220 memo: None,
2221 fee_payer: 0,
2222 compute_unit_price: None,
2223 },
2224 signers: vec![
2225 Box::new(read_keypair_file(&default_keypair_file).unwrap()),
2226 Box::new(read_keypair_file(&withdraw_authority_file).unwrap())
2227 ],
2228 }
2229 );
2230
2231 let test_withdraw_from_vote_account = test_commands.clone().get_matches_from(vec![
2233 "test",
2234 "withdraw-from-vote-account",
2235 &keypair.pubkey().to_string(),
2236 &pubkey_string,
2237 "42",
2238 "--authorized-withdrawer",
2239 &withdraw_authority_file,
2240 "--blockhash",
2241 &blockhash_string,
2242 "--sign-only",
2243 "--fee-payer",
2244 &withdraw_authority_file,
2245 ]);
2246 assert_eq!(
2247 parse_command(&test_withdraw_from_vote_account, &default_signer, &mut None).unwrap(),
2248 CliCommandInfo {
2249 command: CliCommand::WithdrawFromVoteAccount {
2250 vote_account_pubkey: keypair.pubkey(),
2251 destination_account_pubkey: pubkey,
2252 withdraw_authority: 0,
2253 withdraw_amount: SpendAmount::Some(42_000_000_000),
2254 sign_only: true,
2255 dump_transaction_message: false,
2256 blockhash_query: BlockhashQuery::None(blockhash),
2257 nonce_account: None,
2258 nonce_authority: 0,
2259 memo: None,
2260 fee_payer: 0,
2261 compute_unit_price: None,
2262 },
2263 signers: vec![Box::new(
2264 read_keypair_file(&withdraw_authority_file).unwrap()
2265 )],
2266 }
2267 );
2268
2269 let authorized_sig = withdraw_authority.sign_message(&[0u8]);
2270 let authorized_signer = format!("{}={}", withdraw_authority.pubkey(), authorized_sig);
2271 let test_withdraw_from_vote_account = test_commands.clone().get_matches_from(vec![
2272 "test",
2273 "withdraw-from-vote-account",
2274 &keypair.pubkey().to_string(),
2275 &pubkey_string,
2276 "42",
2277 "--authorized-withdrawer",
2278 &withdraw_authority.pubkey().to_string(),
2279 "--blockhash",
2280 &blockhash_string,
2281 "--signer",
2282 &authorized_signer,
2283 "--fee-payer",
2284 &withdraw_authority.pubkey().to_string(),
2285 ]);
2286 assert_eq!(
2287 parse_command(&test_withdraw_from_vote_account, &default_signer, &mut None).unwrap(),
2288 CliCommandInfo {
2289 command: CliCommand::WithdrawFromVoteAccount {
2290 vote_account_pubkey: keypair.pubkey(),
2291 destination_account_pubkey: pubkey,
2292 withdraw_authority: 0,
2293 withdraw_amount: SpendAmount::Some(42_000_000_000),
2294 sign_only: false,
2295 dump_transaction_message: false,
2296 blockhash_query: BlockhashQuery::FeeCalculator(
2297 blockhash_query::Source::Cluster,
2298 blockhash
2299 ),
2300 nonce_account: None,
2301 nonce_authority: 0,
2302 memo: None,
2303 fee_payer: 0,
2304 compute_unit_price: None,
2305 },
2306 signers: vec![Box::new(Presigner::new(
2307 &withdraw_authority.pubkey(),
2308 &authorized_sig
2309 )),],
2310 }
2311 );
2312
2313 let test_close_vote_account = test_commands.clone().get_matches_from(vec![
2315 "test",
2316 "close-vote-account",
2317 &keypair_file,
2318 &pubkey_string,
2319 ]);
2320 assert_eq!(
2321 parse_command(&test_close_vote_account, &default_signer, &mut None).unwrap(),
2322 CliCommandInfo {
2323 command: CliCommand::CloseVoteAccount {
2324 vote_account_pubkey: read_keypair_file(&keypair_file).unwrap().pubkey(),
2325 destination_account_pubkey: pubkey,
2326 withdraw_authority: 0,
2327 memo: None,
2328 fee_payer: 0,
2329 compute_unit_price: None,
2330 },
2331 signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap())],
2332 }
2333 );
2334
2335 let withdraw_authority = Keypair::new();
2337 let (withdraw_authority_file, mut tmp_file) = make_tmp_file();
2338 write_keypair(&withdraw_authority, tmp_file.as_file_mut()).unwrap();
2339 let test_close_vote_account = test_commands.clone().get_matches_from(vec![
2340 "test",
2341 "close-vote-account",
2342 &keypair_file,
2343 &pubkey_string,
2344 "--authorized-withdrawer",
2345 &withdraw_authority_file,
2346 ]);
2347 assert_eq!(
2348 parse_command(&test_close_vote_account, &default_signer, &mut None).unwrap(),
2349 CliCommandInfo {
2350 command: CliCommand::CloseVoteAccount {
2351 vote_account_pubkey: read_keypair_file(&keypair_file).unwrap().pubkey(),
2352 destination_account_pubkey: pubkey,
2353 withdraw_authority: 1,
2354 memo: None,
2355 fee_payer: 0,
2356 compute_unit_price: None,
2357 },
2358 signers: vec![
2359 Box::new(read_keypair_file(&default_keypair_file).unwrap()),
2360 Box::new(read_keypair_file(&withdraw_authority_file).unwrap())
2361 ],
2362 }
2363 );
2364
2365 let withdraw_authority = Keypair::new();
2367 let (withdraw_authority_file, mut tmp_file) = make_tmp_file();
2368 write_keypair(&withdraw_authority, tmp_file.as_file_mut()).unwrap();
2369 let test_close_vote_account = test_commands.clone().get_matches_from(vec![
2370 "test",
2371 "close-vote-account",
2372 &keypair_file,
2373 &pubkey_string,
2374 "--authorized-withdrawer",
2375 &withdraw_authority_file,
2376 "--with-compute-unit-price",
2377 "99",
2378 ]);
2379 assert_eq!(
2380 parse_command(&test_close_vote_account, &default_signer, &mut None).unwrap(),
2381 CliCommandInfo {
2382 command: CliCommand::CloseVoteAccount {
2383 vote_account_pubkey: read_keypair_file(&keypair_file).unwrap().pubkey(),
2384 destination_account_pubkey: pubkey,
2385 withdraw_authority: 1,
2386 memo: None,
2387 fee_payer: 0,
2388 compute_unit_price: Some(99),
2389 },
2390 signers: vec![
2391 Box::new(read_keypair_file(&default_keypair_file).unwrap()),
2392 Box::new(read_keypair_file(&withdraw_authority_file).unwrap())
2393 ],
2394 }
2395 );
2396 }
2397}