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