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 spend_utils::{resolve_spend_tx_and_check_account_balance, SpendAmount},
13 },
14 clap::{App, Arg, ArgMatches, SubCommand},
15 solana_account::Account,
16 solana_clap_utils::{
17 compute_budget::{compute_unit_price_arg, ComputeUnitLimit, COMPUTE_UNIT_PRICE_ARG},
18 input_parsers::*,
19 input_validators::*,
20 keypair::{CliSigners, DefaultSigner, SignerIndex},
21 memo::{memo_arg, MEMO_ARG},
22 nonce::*,
23 },
24 solana_cli_output::CliNonceAccount,
25 solana_hash::Hash,
26 solana_message::Message,
27 solana_nonce::{self as nonce, state::State},
28 solana_pubkey::Pubkey,
29 solana_remote_wallet::remote_wallet::RemoteWalletManager,
30 solana_rpc_client::rpc_client::RpcClient,
31 solana_rpc_client_nonce_utils::*,
32 solana_sdk_ids::system_program,
33 solana_system_interface::{
34 error::SystemError,
35 instruction::{
36 advance_nonce_account, authorize_nonce_account, create_nonce_account,
37 create_nonce_account_with_seed, upgrade_nonce_account, withdraw_nonce_account,
38 },
39 },
40 solana_transaction::Transaction,
41 std::rc::Rc,
42};
43
44pub trait NonceSubCommands {
45 fn nonce_subcommands(self) -> Self;
46}
47
48impl NonceSubCommands for App<'_, '_> {
49 fn nonce_subcommands(self) -> Self {
50 self.subcommand(
51 SubCommand::with_name("authorize-nonce-account")
52 .about("Assign account authority to a new entity")
53 .arg(pubkey!(
54 Arg::with_name("nonce_account_pubkey")
55 .index(1)
56 .value_name("NONCE_ACCOUNT_ADDRESS")
57 .required(true),
58 "Nonce account."
59 ))
60 .arg(pubkey!(
61 Arg::with_name("new_authority")
62 .index(2)
63 .value_name("AUTHORITY_PUBKEY")
64 .required(true),
65 "Account to be granted authority of the nonce account."
66 ))
67 .arg(nonce_authority_arg())
68 .arg(memo_arg())
69 .arg(compute_unit_price_arg()),
70 )
71 .subcommand(
72 SubCommand::with_name("create-nonce-account")
73 .about("Create a nonce account")
74 .arg(
75 Arg::with_name("nonce_account_keypair")
76 .index(1)
77 .value_name("ACCOUNT_KEYPAIR")
78 .takes_value(true)
79 .required(true)
80 .validator(is_valid_signer)
81 .help("Keypair of the nonce account to fund"),
82 )
83 .arg(
84 Arg::with_name("amount")
85 .index(2)
86 .value_name("AMOUNT")
87 .takes_value(true)
88 .required(true)
89 .validator(is_amount_or_all)
90 .help(
91 "The amount to load the nonce account with, in SOL; accepts keyword \
92 ALL",
93 ),
94 )
95 .arg(pubkey!(
96 Arg::with_name(NONCE_AUTHORITY_ARG.name)
97 .long(NONCE_AUTHORITY_ARG.long)
98 .value_name("PUBKEY"),
99 "Assign noncing authority to this other entity."
100 ))
101 .arg(
102 Arg::with_name("seed")
103 .long("seed")
104 .value_name("STRING")
105 .takes_value(true)
106 .help(
107 "Seed for address generation; if specified, the resulting account \
108 will be at a derived address of the NONCE_ACCOUNT pubkey",
109 ),
110 )
111 .arg(memo_arg())
112 .arg(compute_unit_price_arg()),
113 )
114 .subcommand(
115 SubCommand::with_name("nonce")
116 .about("Get the current nonce value")
117 .alias("get-nonce")
118 .arg(pubkey!(
119 Arg::with_name("nonce_account_pubkey")
120 .index(1)
121 .value_name("NONCE_ACCOUNT_ADDRESS")
122 .required(true),
123 "Nonce account to display."
124 )),
125 )
126 .subcommand(
127 SubCommand::with_name("new-nonce")
128 .about("Generate a new nonce, rendering the existing nonce useless")
129 .arg(pubkey!(
130 Arg::with_name("nonce_account_pubkey")
131 .index(1)
132 .value_name("NONCE_ACCOUNT_ADDRESS")
133 .required(true),
134 "Nonce account."
135 ))
136 .arg(nonce_authority_arg())
137 .arg(memo_arg())
138 .arg(compute_unit_price_arg()),
139 )
140 .subcommand(
141 SubCommand::with_name("nonce-account")
142 .about("Show the contents of a nonce account")
143 .alias("show-nonce-account")
144 .arg(pubkey!(
145 Arg::with_name("nonce_account_pubkey")
146 .index(1)
147 .value_name("NONCE_ACCOUNT_ADDRESS")
148 .required(true),
149 "Nonce account to display."
150 ))
151 .arg(
152 Arg::with_name("lamports")
153 .long("lamports")
154 .takes_value(false)
155 .help("Display balance in lamports instead of SOL"),
156 ),
157 )
158 .subcommand(
159 SubCommand::with_name("withdraw-from-nonce-account")
160 .about("Withdraw SOL from the nonce account")
161 .arg(pubkey!(
162 Arg::with_name("nonce_account_pubkey")
163 .index(1)
164 .value_name("NONCE_ACCOUNT_ADDRESS")
165 .required(true),
166 "Nonce account to withdraw from."
167 ))
168 .arg(pubkey!(
169 Arg::with_name("destination_account_pubkey")
170 .index(2)
171 .value_name("RECIPIENT_ADDRESS")
172 .required(true),
173 "Recipient of withdrawn SOL."
174 ))
175 .arg(
176 Arg::with_name("amount")
177 .index(3)
178 .value_name("AMOUNT")
179 .takes_value(true)
180 .required(true)
181 .validator(is_amount)
182 .help("The amount to withdraw from the nonce account, in SOL"),
183 )
184 .arg(nonce_authority_arg())
185 .arg(memo_arg())
186 .arg(compute_unit_price_arg()),
187 )
188 .subcommand(
189 SubCommand::with_name("upgrade-nonce-account")
190 .about(
191 "One-time idempotent upgrade of legacy nonce versions in order to bump them \
192 out of chain blockhash domain.",
193 )
194 .arg(pubkey!(
195 Arg::with_name("nonce_account_pubkey")
196 .index(1)
197 .value_name("NONCE_ACCOUNT_ADDRESS")
198 .required(true),
199 "Nonce account to upgrade."
200 ))
201 .arg(memo_arg())
202 .arg(compute_unit_price_arg()),
203 )
204 }
205}
206
207pub fn parse_authorize_nonce_account(
208 matches: &ArgMatches<'_>,
209 default_signer: &DefaultSigner,
210 wallet_manager: &mut Option<Rc<RemoteWalletManager>>,
211) -> Result<CliCommandInfo, CliError> {
212 let nonce_account = pubkey_of_signer(matches, "nonce_account_pubkey", wallet_manager)?.unwrap();
213 let new_authority = pubkey_of_signer(matches, "new_authority", wallet_manager)?.unwrap();
214 let memo = matches.value_of(MEMO_ARG.name).map(String::from);
215 let (nonce_authority, nonce_authority_pubkey) =
216 signer_of(matches, NONCE_AUTHORITY_ARG.name, wallet_manager)?;
217
218 let payer_provided = None;
219 let signer_info = default_signer.generate_unique_signers(
220 vec![payer_provided, nonce_authority],
221 matches,
222 wallet_manager,
223 )?;
224 let compute_unit_price = value_of(matches, COMPUTE_UNIT_PRICE_ARG.name);
225
226 Ok(CliCommandInfo {
227 command: CliCommand::AuthorizeNonceAccount {
228 nonce_account,
229 nonce_authority: signer_info.index_of(nonce_authority_pubkey).unwrap(),
230 memo,
231 new_authority,
232 compute_unit_price,
233 },
234 signers: signer_info.signers,
235 })
236}
237
238pub fn parse_nonce_create_account(
239 matches: &ArgMatches<'_>,
240 default_signer: &DefaultSigner,
241 wallet_manager: &mut Option<Rc<RemoteWalletManager>>,
242) -> Result<CliCommandInfo, CliError> {
243 let (nonce_account, nonce_account_pubkey) =
244 signer_of(matches, "nonce_account_keypair", wallet_manager)?;
245 let seed = matches.value_of("seed").map(|s| s.to_string());
246 let amount = SpendAmount::new_from_matches(matches, "amount");
247 let nonce_authority = pubkey_of_signer(matches, NONCE_AUTHORITY_ARG.name, wallet_manager)?;
248 let memo = matches.value_of(MEMO_ARG.name).map(String::from);
249
250 let payer_provided = None;
251 let signer_info = default_signer.generate_unique_signers(
252 vec![payer_provided, nonce_account],
253 matches,
254 wallet_manager,
255 )?;
256 let compute_unit_price = value_of(matches, COMPUTE_UNIT_PRICE_ARG.name);
257
258 Ok(CliCommandInfo {
259 command: CliCommand::CreateNonceAccount {
260 nonce_account: signer_info.index_of(nonce_account_pubkey).unwrap(),
261 seed,
262 nonce_authority,
263 memo,
264 amount,
265 compute_unit_price,
266 },
267 signers: signer_info.signers,
268 })
269}
270
271pub fn parse_get_nonce(
272 matches: &ArgMatches<'_>,
273 wallet_manager: &mut Option<Rc<RemoteWalletManager>>,
274) -> Result<CliCommandInfo, CliError> {
275 let nonce_account_pubkey =
276 pubkey_of_signer(matches, "nonce_account_pubkey", wallet_manager)?.unwrap();
277
278 Ok(CliCommandInfo::without_signers(CliCommand::GetNonce(
279 nonce_account_pubkey,
280 )))
281}
282
283pub fn parse_new_nonce(
284 matches: &ArgMatches<'_>,
285 default_signer: &DefaultSigner,
286 wallet_manager: &mut Option<Rc<RemoteWalletManager>>,
287) -> Result<CliCommandInfo, CliError> {
288 let nonce_account = pubkey_of_signer(matches, "nonce_account_pubkey", wallet_manager)?.unwrap();
289 let memo = matches.value_of(MEMO_ARG.name).map(String::from);
290 let (nonce_authority, nonce_authority_pubkey) =
291 signer_of(matches, NONCE_AUTHORITY_ARG.name, wallet_manager)?;
292
293 let payer_provided = None;
294 let signer_info = default_signer.generate_unique_signers(
295 vec![payer_provided, nonce_authority],
296 matches,
297 wallet_manager,
298 )?;
299 let compute_unit_price = value_of(matches, COMPUTE_UNIT_PRICE_ARG.name);
300
301 Ok(CliCommandInfo {
302 command: CliCommand::NewNonce {
303 nonce_account,
304 nonce_authority: signer_info.index_of(nonce_authority_pubkey).unwrap(),
305 memo,
306 compute_unit_price,
307 },
308 signers: signer_info.signers,
309 })
310}
311
312pub fn parse_show_nonce_account(
313 matches: &ArgMatches<'_>,
314 wallet_manager: &mut Option<Rc<RemoteWalletManager>>,
315) -> Result<CliCommandInfo, CliError> {
316 let nonce_account_pubkey =
317 pubkey_of_signer(matches, "nonce_account_pubkey", wallet_manager)?.unwrap();
318 let use_lamports_unit = matches.is_present("lamports");
319
320 Ok(CliCommandInfo::without_signers(
321 CliCommand::ShowNonceAccount {
322 nonce_account_pubkey,
323 use_lamports_unit,
324 },
325 ))
326}
327
328pub fn parse_withdraw_from_nonce_account(
329 matches: &ArgMatches<'_>,
330 default_signer: &DefaultSigner,
331 wallet_manager: &mut Option<Rc<RemoteWalletManager>>,
332) -> Result<CliCommandInfo, CliError> {
333 let nonce_account = pubkey_of_signer(matches, "nonce_account_pubkey", wallet_manager)?.unwrap();
334 let destination_account_pubkey =
335 pubkey_of_signer(matches, "destination_account_pubkey", wallet_manager)?.unwrap();
336 let lamports = lamports_of_sol(matches, "amount").unwrap();
337 let memo = matches.value_of(MEMO_ARG.name).map(String::from);
338 let (nonce_authority, nonce_authority_pubkey) =
339 signer_of(matches, NONCE_AUTHORITY_ARG.name, wallet_manager)?;
340
341 let payer_provided = None;
342 let signer_info = default_signer.generate_unique_signers(
343 vec![payer_provided, nonce_authority],
344 matches,
345 wallet_manager,
346 )?;
347 let compute_unit_price = value_of(matches, COMPUTE_UNIT_PRICE_ARG.name);
348
349 Ok(CliCommandInfo {
350 command: CliCommand::WithdrawFromNonceAccount {
351 nonce_account,
352 nonce_authority: signer_info.index_of(nonce_authority_pubkey).unwrap(),
353 memo,
354 destination_account_pubkey,
355 lamports,
356 compute_unit_price,
357 },
358 signers: signer_info.signers,
359 })
360}
361
362pub(crate) fn parse_upgrade_nonce_account(
363 matches: &ArgMatches<'_>,
364) -> Result<CliCommandInfo, CliError> {
365 let nonce_account = pubkey_of(matches, "nonce_account_pubkey").unwrap();
366 let memo = matches.value_of(MEMO_ARG.name).map(String::from);
367 let compute_unit_price = value_of(matches, COMPUTE_UNIT_PRICE_ARG.name);
368 Ok(CliCommandInfo {
369 command: CliCommand::UpgradeNonceAccount {
370 nonce_account,
371 memo,
372 compute_unit_price,
373 },
374 signers: CliSigners::default(),
375 })
376}
377
378pub fn check_nonce_account(
380 nonce_account: &Account,
381 nonce_authority: &Pubkey,
382 nonce_hash: &Hash,
383) -> Result<(), CliError> {
384 match state_from_account(nonce_account)? {
385 State::Initialized(ref data) => {
386 if &data.blockhash() != nonce_hash {
387 Err(Error::InvalidHash {
388 provided: *nonce_hash,
389 expected: data.blockhash(),
390 }
391 .into())
392 } else if nonce_authority != &data.authority {
393 Err(Error::InvalidAuthority {
394 provided: *nonce_authority,
395 expected: data.authority,
396 }
397 .into())
398 } else {
399 Ok(())
400 }
401 }
402 State::Uninitialized => Err(Error::InvalidStateForOperation.into()),
403 }
404}
405
406pub fn process_authorize_nonce_account(
407 rpc_client: &RpcClient,
408 config: &CliConfig,
409 nonce_account: &Pubkey,
410 nonce_authority: SignerIndex,
411 memo: Option<&String>,
412 new_authority: &Pubkey,
413 compute_unit_price: Option<u64>,
414) -> ProcessResult {
415 let latest_blockhash = rpc_client.get_latest_blockhash()?;
416
417 let nonce_authority = config.signers[nonce_authority];
418 let compute_unit_limit = ComputeUnitLimit::Simulated;
419 let ixs = vec![authorize_nonce_account(
420 nonce_account,
421 &nonce_authority.pubkey(),
422 new_authority,
423 )]
424 .with_memo(memo)
425 .with_compute_unit_config(&ComputeUnitConfig {
426 compute_unit_price,
427 compute_unit_limit,
428 });
429 let mut message = Message::new(&ixs, Some(&config.signers[0].pubkey()));
430 simulate_and_update_compute_unit_limit(&compute_unit_limit, rpc_client, &mut message)?;
431 let mut tx = Transaction::new_unsigned(message);
432 tx.try_sign(&config.signers, latest_blockhash)?;
433
434 check_account_for_fee_with_commitment(
435 rpc_client,
436 &config.signers[0].pubkey(),
437 &tx.message,
438 config.commitment,
439 )?;
440 let result = rpc_client.send_and_confirm_transaction_with_spinner_and_config(
441 &tx,
442 config.commitment,
443 config.send_transaction_config,
444 );
445
446 log_instruction_custom_error::<SystemError>(result, config)
447}
448
449pub fn process_create_nonce_account(
450 rpc_client: &RpcClient,
451 config: &CliConfig,
452 nonce_account: SignerIndex,
453 seed: Option<String>,
454 nonce_authority: Option<Pubkey>,
455 memo: Option<&String>,
456 mut amount: SpendAmount,
457 compute_unit_price: Option<u64>,
458) -> ProcessResult {
459 let nonce_account_pubkey = config.signers[nonce_account].pubkey();
460 let nonce_account_address = if let Some(ref seed) = seed {
461 Pubkey::create_with_seed(&nonce_account_pubkey, seed, &system_program::id())?
462 } else {
463 nonce_account_pubkey
464 };
465
466 check_unique_pubkeys(
467 (&config.signers[0].pubkey(), "cli keypair".to_string()),
468 (&nonce_account_address, "nonce_account".to_string()),
469 )?;
470
471 let minimum_balance = rpc_client.get_minimum_balance_for_rent_exemption(State::size())?;
472 if amount == SpendAmount::All {
473 amount = SpendAmount::AllForAccountCreation {
474 create_account_min_balance: minimum_balance,
475 };
476 }
477
478 let nonce_authority = nonce_authority.unwrap_or_else(|| config.signers[0].pubkey());
479
480 let compute_unit_limit = ComputeUnitLimit::Simulated;
481 let build_message = |lamports| {
482 let ixs = if let Some(seed) = seed.clone() {
483 create_nonce_account_with_seed(
484 &config.signers[0].pubkey(), &nonce_account_address, &nonce_account_pubkey, &seed, &nonce_authority,
489 lamports,
490 )
491 .with_memo(memo)
492 .with_compute_unit_config(&ComputeUnitConfig {
493 compute_unit_price,
494 compute_unit_limit,
495 })
496 } else {
497 create_nonce_account(
498 &config.signers[0].pubkey(),
499 &nonce_account_pubkey,
500 &nonce_authority,
501 lamports,
502 )
503 .with_memo(memo)
504 .with_compute_unit_config(&ComputeUnitConfig {
505 compute_unit_price,
506 compute_unit_limit,
507 })
508 };
509 Message::new(&ixs, Some(&config.signers[0].pubkey()))
510 };
511
512 let latest_blockhash = rpc_client.get_latest_blockhash()?;
513
514 let (message, lamports) = resolve_spend_tx_and_check_account_balance(
515 rpc_client,
516 false,
517 amount,
518 &latest_blockhash,
519 &config.signers[0].pubkey(),
520 compute_unit_limit,
521 build_message,
522 config.commitment,
523 )?;
524
525 if let Ok(nonce_account) = get_account(rpc_client, &nonce_account_address) {
526 let err_msg = if state_from_account(&nonce_account).is_ok() {
527 format!("Nonce account {nonce_account_address} already exists")
528 } else {
529 format!("Account {nonce_account_address} already exists and is not a nonce account")
530 };
531 return Err(CliError::BadParameter(err_msg).into());
532 }
533
534 if lamports < minimum_balance {
535 return Err(CliError::BadParameter(format!(
536 "need at least {minimum_balance} lamports for nonce account to be rent exempt, \
537 provided lamports: {lamports}"
538 ))
539 .into());
540 }
541
542 let mut tx = Transaction::new_unsigned(message);
543 tx.try_sign(&config.signers, latest_blockhash)?;
544 let result = rpc_client.send_and_confirm_transaction_with_spinner_and_config(
545 &tx,
546 config.commitment,
547 config.send_transaction_config,
548 );
549
550 log_instruction_custom_error::<SystemError>(result, config)
551}
552
553pub fn process_get_nonce(
554 rpc_client: &RpcClient,
555 config: &CliConfig,
556 nonce_account_pubkey: &Pubkey,
557) -> ProcessResult {
558 match get_account_with_commitment(rpc_client, nonce_account_pubkey, config.commitment)
559 .and_then(|ref a| state_from_account(a))?
560 {
561 State::Uninitialized => Ok("Nonce account is uninitialized".to_string()),
562 State::Initialized(ref data) => Ok(format!("{:?}", data.blockhash())),
563 }
564}
565
566pub fn process_new_nonce(
567 rpc_client: &RpcClient,
568 config: &CliConfig,
569 nonce_account: &Pubkey,
570 nonce_authority: SignerIndex,
571 memo: Option<&String>,
572 compute_unit_price: Option<u64>,
573) -> ProcessResult {
574 check_unique_pubkeys(
575 (&config.signers[0].pubkey(), "cli keypair".to_string()),
576 (nonce_account, "nonce_account_pubkey".to_string()),
577 )?;
578
579 if let Err(err) = rpc_client.get_account(nonce_account) {
580 return Err(CliError::BadParameter(format!(
581 "Unable to advance nonce account {nonce_account}. error: {err}"
582 ))
583 .into());
584 }
585
586 let nonce_authority = config.signers[nonce_authority];
587 let compute_unit_limit = ComputeUnitLimit::Simulated;
588 let ixs = vec![advance_nonce_account(
589 nonce_account,
590 &nonce_authority.pubkey(),
591 )]
592 .with_memo(memo)
593 .with_compute_unit_config(&ComputeUnitConfig {
594 compute_unit_price,
595 compute_unit_limit,
596 });
597 let latest_blockhash = rpc_client.get_latest_blockhash()?;
598 let mut message = Message::new(&ixs, Some(&config.signers[0].pubkey()));
599 simulate_and_update_compute_unit_limit(&compute_unit_limit, rpc_client, &mut message)?;
600 let mut tx = Transaction::new_unsigned(message);
601 tx.try_sign(&config.signers, latest_blockhash)?;
602 check_account_for_fee_with_commitment(
603 rpc_client,
604 &config.signers[0].pubkey(),
605 &tx.message,
606 config.commitment,
607 )?;
608 let result = rpc_client.send_and_confirm_transaction_with_spinner_and_config(
609 &tx,
610 config.commitment,
611 config.send_transaction_config,
612 );
613
614 log_instruction_custom_error::<SystemError>(result, config)
615}
616
617pub fn process_show_nonce_account(
618 rpc_client: &RpcClient,
619 config: &CliConfig,
620 nonce_account_pubkey: &Pubkey,
621 use_lamports_unit: bool,
622) -> ProcessResult {
623 let nonce_account =
624 get_account_with_commitment(rpc_client, nonce_account_pubkey, config.commitment)?;
625 let print_account = |data: Option<&nonce::state::Data>| {
626 let mut nonce_account = CliNonceAccount {
627 balance: nonce_account.lamports,
628 minimum_balance_for_rent_exemption: rpc_client
629 .get_minimum_balance_for_rent_exemption(State::size())?,
630 use_lamports_unit,
631 ..CliNonceAccount::default()
632 };
633 if let Some(data) = data {
634 nonce_account.nonce = Some(data.blockhash().to_string());
635 nonce_account.lamports_per_signature = Some(data.fee_calculator.lamports_per_signature);
636 nonce_account.authority = Some(data.authority.to_string());
637 }
638
639 Ok(config.output_format.formatted_string(&nonce_account))
640 };
641 match state_from_account(&nonce_account)? {
642 State::Uninitialized => print_account(None),
643 State::Initialized(ref data) => print_account(Some(data)),
644 }
645}
646
647pub fn process_withdraw_from_nonce_account(
648 rpc_client: &RpcClient,
649 config: &CliConfig,
650 nonce_account: &Pubkey,
651 nonce_authority: SignerIndex,
652 memo: Option<&String>,
653 destination_account_pubkey: &Pubkey,
654 lamports: u64,
655 compute_unit_price: Option<u64>,
656) -> ProcessResult {
657 let latest_blockhash = rpc_client.get_latest_blockhash()?;
658
659 let nonce_authority = config.signers[nonce_authority];
660 let compute_unit_limit = ComputeUnitLimit::Simulated;
661 let ixs = vec![withdraw_nonce_account(
662 nonce_account,
663 &nonce_authority.pubkey(),
664 destination_account_pubkey,
665 lamports,
666 )]
667 .with_memo(memo)
668 .with_compute_unit_config(&ComputeUnitConfig {
669 compute_unit_price,
670 compute_unit_limit,
671 });
672 let mut message = Message::new(&ixs, Some(&config.signers[0].pubkey()));
673 simulate_and_update_compute_unit_limit(&compute_unit_limit, rpc_client, &mut message)?;
674 let mut tx = Transaction::new_unsigned(message);
675 tx.try_sign(&config.signers, latest_blockhash)?;
676 check_account_for_fee_with_commitment(
677 rpc_client,
678 &config.signers[0].pubkey(),
679 &tx.message,
680 config.commitment,
681 )?;
682 let result = rpc_client.send_and_confirm_transaction_with_spinner_and_config(
683 &tx,
684 config.commitment,
685 config.send_transaction_config,
686 );
687
688 log_instruction_custom_error::<SystemError>(result, config)
689}
690
691pub(crate) fn process_upgrade_nonce_account(
692 rpc_client: &RpcClient,
693 config: &CliConfig,
694 nonce_account: Pubkey,
695 memo: Option<&String>,
696 compute_unit_price: Option<u64>,
697) -> ProcessResult {
698 let latest_blockhash = rpc_client.get_latest_blockhash()?;
699 let compute_unit_limit = ComputeUnitLimit::Simulated;
700 let ixs = vec![upgrade_nonce_account(nonce_account)]
701 .with_memo(memo)
702 .with_compute_unit_config(&ComputeUnitConfig {
703 compute_unit_price,
704 compute_unit_limit,
705 });
706 let mut message = Message::new(&ixs, Some(&config.signers[0].pubkey()));
707 simulate_and_update_compute_unit_limit(&compute_unit_limit, rpc_client, &mut message)?;
708 let mut tx = Transaction::new_unsigned(message);
709 tx.try_sign(&config.signers, latest_blockhash)?;
710 check_account_for_fee_with_commitment(
711 rpc_client,
712 &config.signers[0].pubkey(),
713 &tx.message,
714 config.commitment,
715 )?;
716 let result = rpc_client.send_and_confirm_transaction_with_spinner_and_config(
717 &tx,
718 config.commitment,
719 config.send_transaction_config,
720 );
721 log_instruction_custom_error::<SystemError>(result, config)
722}
723
724#[cfg(test)]
725mod tests {
726 use {
727 super::*,
728 crate::{clap_app::get_clap_app, cli::parse_command},
729 solana_account::{state_traits::StateMut, Account},
730 solana_keypair::{read_keypair_file, write_keypair, Keypair},
731 solana_nonce::{
732 self as nonce,
733 state::{DurableNonce, State},
734 versions::Versions,
735 },
736 solana_nonce_account as nonce_account,
737 solana_sdk_ids::system_program,
738 solana_sha256_hasher::hash,
739 solana_signer::Signer,
740 tempfile::NamedTempFile,
741 };
742
743 fn make_tmp_file() -> (String, NamedTempFile) {
744 let tmp_file = NamedTempFile::new().unwrap();
745 (String::from(tmp_file.path().to_str().unwrap()), tmp_file)
746 }
747
748 #[test]
749 fn test_parse_command() {
750 let test_commands = get_clap_app("test", "desc", "version");
751 let default_keypair = Keypair::new();
752 let (default_keypair_file, mut tmp_file) = make_tmp_file();
753 write_keypair(&default_keypair, tmp_file.as_file_mut()).unwrap();
754 let default_signer = DefaultSigner::new("", &default_keypair_file);
755 let (keypair_file, mut tmp_file) = make_tmp_file();
756 let nonce_account_keypair = Keypair::new();
757 write_keypair(&nonce_account_keypair, tmp_file.as_file_mut()).unwrap();
758 let nonce_account_pubkey = nonce_account_keypair.pubkey();
759 let nonce_account_string = nonce_account_pubkey.to_string();
760
761 let (authority_keypair_file, mut tmp_file2) = make_tmp_file();
762 let nonce_authority_keypair = Keypair::new();
763 write_keypair(&nonce_authority_keypair, tmp_file2.as_file_mut()).unwrap();
764
765 let test_authorize_nonce_account = test_commands.clone().get_matches_from(vec![
767 "test",
768 "authorize-nonce-account",
769 &keypair_file,
770 &Pubkey::default().to_string(),
771 ]);
772 assert_eq!(
773 parse_command(&test_authorize_nonce_account, &default_signer, &mut None).unwrap(),
774 CliCommandInfo {
775 command: CliCommand::AuthorizeNonceAccount {
776 nonce_account: nonce_account_pubkey,
777 nonce_authority: 0,
778 memo: None,
779 new_authority: Pubkey::default(),
780 compute_unit_price: None,
781 },
782 signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap())],
783 }
784 );
785
786 let test_authorize_nonce_account = test_commands.clone().get_matches_from(vec![
788 "test",
789 "authorize-nonce-account",
790 &keypair_file,
791 &Pubkey::default().to_string(),
792 "--nonce-authority",
793 &authority_keypair_file,
794 ]);
795 assert_eq!(
796 parse_command(&test_authorize_nonce_account, &default_signer, &mut None).unwrap(),
797 CliCommandInfo {
798 command: CliCommand::AuthorizeNonceAccount {
799 nonce_account: read_keypair_file(&keypair_file).unwrap().pubkey(),
800 nonce_authority: 1,
801 memo: None,
802 new_authority: Pubkey::default(),
803 compute_unit_price: None,
804 },
805 signers: vec![
806 Box::new(read_keypair_file(&default_keypair_file).unwrap()),
807 Box::new(read_keypair_file(&authority_keypair_file).unwrap())
808 ],
809 }
810 );
811
812 let test_create_nonce_account = test_commands.clone().get_matches_from(vec![
814 "test",
815 "create-nonce-account",
816 &keypair_file,
817 "50",
818 ]);
819 assert_eq!(
820 parse_command(&test_create_nonce_account, &default_signer, &mut None).unwrap(),
821 CliCommandInfo {
822 command: CliCommand::CreateNonceAccount {
823 nonce_account: 1,
824 seed: None,
825 nonce_authority: None,
826 memo: None,
827 amount: SpendAmount::Some(50_000_000_000),
828 compute_unit_price: None,
829 },
830 signers: vec![
831 Box::new(read_keypair_file(&default_keypair_file).unwrap()),
832 Box::new(read_keypair_file(&keypair_file).unwrap())
833 ],
834 }
835 );
836
837 let test_create_nonce_account = test_commands.clone().get_matches_from(vec![
839 "test",
840 "create-nonce-account",
841 &keypair_file,
842 "50",
843 "--nonce-authority",
844 &authority_keypair_file,
845 ]);
846 assert_eq!(
847 parse_command(&test_create_nonce_account, &default_signer, &mut None).unwrap(),
848 CliCommandInfo {
849 command: CliCommand::CreateNonceAccount {
850 nonce_account: 1,
851 seed: None,
852 nonce_authority: Some(nonce_authority_keypair.pubkey()),
853 memo: None,
854 amount: SpendAmount::Some(50_000_000_000),
855 compute_unit_price: None,
856 },
857 signers: vec![
858 Box::new(read_keypair_file(&default_keypair_file).unwrap()),
859 Box::new(read_keypair_file(&keypair_file).unwrap())
860 ],
861 }
862 );
863
864 let test_get_nonce = test_commands.clone().get_matches_from(vec![
866 "test",
867 "get-nonce",
868 &nonce_account_string,
869 ]);
870 assert_eq!(
871 parse_command(&test_get_nonce, &default_signer, &mut None).unwrap(),
872 CliCommandInfo::without_signers(CliCommand::GetNonce(nonce_account_keypair.pubkey()))
873 );
874
875 let test_new_nonce =
877 test_commands
878 .clone()
879 .get_matches_from(vec!["test", "new-nonce", &keypair_file]);
880 let nonce_account = read_keypair_file(&keypair_file).unwrap();
881 assert_eq!(
882 parse_command(&test_new_nonce, &default_signer, &mut None).unwrap(),
883 CliCommandInfo {
884 command: CliCommand::NewNonce {
885 nonce_account: nonce_account.pubkey(),
886 nonce_authority: 0,
887 memo: None,
888 compute_unit_price: None,
889 },
890 signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap())],
891 }
892 );
893
894 let test_new_nonce = test_commands.clone().get_matches_from(vec![
896 "test",
897 "new-nonce",
898 &keypair_file,
899 "--nonce-authority",
900 &authority_keypair_file,
901 ]);
902 let nonce_account = read_keypair_file(&keypair_file).unwrap();
903 assert_eq!(
904 parse_command(&test_new_nonce, &default_signer, &mut None).unwrap(),
905 CliCommandInfo {
906 command: CliCommand::NewNonce {
907 nonce_account: nonce_account.pubkey(),
908 nonce_authority: 1,
909 memo: None,
910 compute_unit_price: None,
911 },
912 signers: vec![
913 Box::new(read_keypair_file(&default_keypair_file).unwrap()),
914 Box::new(read_keypair_file(&authority_keypair_file).unwrap())
915 ],
916 }
917 );
918
919 let test_show_nonce_account = test_commands.clone().get_matches_from(vec![
921 "test",
922 "nonce-account",
923 &nonce_account_string,
924 ]);
925 assert_eq!(
926 parse_command(&test_show_nonce_account, &default_signer, &mut None).unwrap(),
927 CliCommandInfo::without_signers(CliCommand::ShowNonceAccount {
928 nonce_account_pubkey: nonce_account_keypair.pubkey(),
929 use_lamports_unit: false,
930 })
931 );
932
933 let test_withdraw_from_nonce_account = test_commands.clone().get_matches_from(vec![
935 "test",
936 "withdraw-from-nonce-account",
937 &keypair_file,
938 &nonce_account_string,
939 "42",
940 ]);
941 assert_eq!(
942 parse_command(
943 &test_withdraw_from_nonce_account,
944 &default_signer,
945 &mut None
946 )
947 .unwrap(),
948 CliCommandInfo {
949 command: CliCommand::WithdrawFromNonceAccount {
950 nonce_account: read_keypair_file(&keypair_file).unwrap().pubkey(),
951 nonce_authority: 0,
952 memo: None,
953 destination_account_pubkey: nonce_account_pubkey,
954 lamports: 42_000_000_000,
955 compute_unit_price: None,
956 },
957 signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap())],
958 }
959 );
960
961 let test_withdraw_from_nonce_account = test_commands.clone().get_matches_from(vec![
963 "test",
964 "withdraw-from-nonce-account",
965 &keypair_file,
966 &nonce_account_string,
967 "42",
968 "--nonce-authority",
969 &authority_keypair_file,
970 ]);
971 assert_eq!(
972 parse_command(
973 &test_withdraw_from_nonce_account,
974 &default_signer,
975 &mut None
976 )
977 .unwrap(),
978 CliCommandInfo {
979 command: CliCommand::WithdrawFromNonceAccount {
980 nonce_account: read_keypair_file(&keypair_file).unwrap().pubkey(),
981 nonce_authority: 1,
982 memo: None,
983 destination_account_pubkey: nonce_account_pubkey,
984 lamports: 42_000_000_000,
985 compute_unit_price: None,
986 },
987 signers: vec![
988 Box::new(read_keypair_file(&default_keypair_file).unwrap()),
989 Box::new(read_keypair_file(&authority_keypair_file).unwrap())
990 ],
991 }
992 );
993
994 let test_upgrade_nonce_account = test_commands.clone().get_matches_from(vec![
996 "test",
997 "upgrade-nonce-account",
998 &nonce_account_string,
999 ]);
1000 assert_eq!(
1001 parse_command(&test_upgrade_nonce_account, &default_signer, &mut None).unwrap(),
1002 CliCommandInfo {
1003 command: CliCommand::UpgradeNonceAccount {
1004 nonce_account: nonce_account_pubkey,
1005 memo: None,
1006 compute_unit_price: None,
1007 },
1008 signers: CliSigners::default(),
1009 }
1010 );
1011
1012 let test_authorize_nonce_account = test_commands.clone().get_matches_from(vec![
1014 "test",
1015 "authorize-nonce-account",
1016 &keypair_file,
1017 &Pubkey::default().to_string(),
1018 "--nonce-authority",
1019 &authority_keypair_file,
1020 "--with-compute-unit-price",
1021 "99",
1022 ]);
1023 assert_eq!(
1024 parse_command(&test_authorize_nonce_account, &default_signer, &mut None).unwrap(),
1025 CliCommandInfo {
1026 command: CliCommand::AuthorizeNonceAccount {
1027 nonce_account: read_keypair_file(&keypair_file).unwrap().pubkey(),
1028 nonce_authority: 1,
1029 memo: None,
1030 new_authority: Pubkey::default(),
1031 compute_unit_price: Some(99),
1032 },
1033 signers: vec![
1034 Box::new(read_keypair_file(&default_keypair_file).unwrap()),
1035 Box::new(read_keypair_file(&authority_keypair_file).unwrap())
1036 ],
1037 }
1038 );
1039 }
1040
1041 #[test]
1042 fn test_check_nonce_account() {
1043 let durable_nonce = DurableNonce::from_blockhash(&Hash::default());
1044 let blockhash = *durable_nonce.as_hash();
1045 let nonce_pubkey = solana_pubkey::new_rand();
1046 let data = Versions::new(State::Initialized(nonce::state::Data::new(
1047 nonce_pubkey,
1048 durable_nonce,
1049 0,
1050 )));
1051 let valid = Account::new_data(1, &data, &system_program::ID);
1052 assert!(check_nonce_account(&valid.unwrap(), &nonce_pubkey, &blockhash).is_ok());
1053
1054 let invalid_owner = Account::new_data(1, &data, &Pubkey::from([1u8; 32]));
1055 if let CliError::InvalidNonce(err) =
1056 check_nonce_account(&invalid_owner.unwrap(), &nonce_pubkey, &blockhash).unwrap_err()
1057 {
1058 assert_eq!(err, Error::InvalidAccountOwner,);
1059 }
1060
1061 let invalid_data = Account::new_data(1, &"invalid", &system_program::ID);
1062 if let CliError::InvalidNonce(err) =
1063 check_nonce_account(&invalid_data.unwrap(), &nonce_pubkey, &blockhash).unwrap_err()
1064 {
1065 assert_eq!(err, Error::InvalidAccountData,);
1066 }
1067
1068 let invalid_durable_nonce = DurableNonce::from_blockhash(&hash(b"invalid"));
1069 let data = Versions::new(State::Initialized(nonce::state::Data::new(
1070 nonce_pubkey,
1071 invalid_durable_nonce,
1072 0,
1073 )));
1074 let invalid_hash = Account::new_data(1, &data, &system_program::ID).unwrap();
1075 if let CliError::InvalidNonce(err) =
1076 check_nonce_account(&invalid_hash, &nonce_pubkey, &blockhash).unwrap_err()
1077 {
1078 assert_eq!(
1079 err,
1080 Error::InvalidHash {
1081 provided: blockhash,
1082 expected: *invalid_durable_nonce.as_hash(),
1083 }
1084 );
1085 }
1086
1087 let new_nonce_authority = solana_pubkey::new_rand();
1088 let data = Versions::new(State::Initialized(nonce::state::Data::new(
1089 new_nonce_authority,
1090 durable_nonce,
1091 0,
1092 )));
1093 let invalid_authority = Account::new_data(1, &data, &system_program::ID);
1094 if let CliError::InvalidNonce(err) =
1095 check_nonce_account(&invalid_authority.unwrap(), &nonce_pubkey, &blockhash).unwrap_err()
1096 {
1097 assert_eq!(
1098 err,
1099 Error::InvalidAuthority {
1100 provided: nonce_pubkey,
1101 expected: new_nonce_authority,
1102 }
1103 );
1104 }
1105
1106 let data = Versions::new(State::Uninitialized);
1107 let invalid_state = Account::new_data(1, &data, &system_program::ID);
1108 if let CliError::InvalidNonce(err) =
1109 check_nonce_account(&invalid_state.unwrap(), &nonce_pubkey, &blockhash).unwrap_err()
1110 {
1111 assert_eq!(err, Error::InvalidStateForOperation,);
1112 }
1113 }
1114
1115 #[test]
1116 fn test_account_identity_ok() {
1117 let nonce_account = nonce_account::create_account(1).into_inner();
1118 assert_eq!(account_identity_ok(&nonce_account), Ok(()));
1119
1120 let system_account = Account::new(1, 0, &system_program::id());
1121 assert_eq!(
1122 account_identity_ok(&system_account),
1123 Err(Error::UnexpectedDataSize),
1124 );
1125
1126 let other_program = Pubkey::from([1u8; 32]);
1127 let other_account_no_data = Account::new(1, 0, &other_program);
1128 assert_eq!(
1129 account_identity_ok(&other_account_no_data),
1130 Err(Error::InvalidAccountOwner),
1131 );
1132 }
1133
1134 #[test]
1135 fn test_state_from_account() {
1136 let mut nonce_account = nonce_account::create_account(1).into_inner();
1137 assert_eq!(state_from_account(&nonce_account), Ok(State::Uninitialized));
1138
1139 let durable_nonce = DurableNonce::from_blockhash(&Hash::new_from_array([42u8; 32]));
1140 let data = nonce::state::Data::new(Pubkey::from([1u8; 32]), durable_nonce, 42);
1141 nonce_account
1142 .set_state(&Versions::new(State::Initialized(data.clone())))
1143 .unwrap();
1144 assert_eq!(
1145 state_from_account(&nonce_account),
1146 Ok(State::Initialized(data))
1147 );
1148
1149 let wrong_data_size_account = Account::new(1, 1, &system_program::id());
1150 assert_eq!(
1151 state_from_account(&wrong_data_size_account),
1152 Err(Error::InvalidAccountData),
1153 );
1154 }
1155
1156 #[test]
1157 fn test_data_from_helpers() {
1158 let mut nonce_account = nonce_account::create_account(1).into_inner();
1159 let state = state_from_account(&nonce_account).unwrap();
1160 assert_eq!(
1161 data_from_state(&state),
1162 Err(Error::InvalidStateForOperation)
1163 );
1164 assert_eq!(
1165 data_from_account(&nonce_account),
1166 Err(Error::InvalidStateForOperation)
1167 );
1168
1169 let durable_nonce = DurableNonce::from_blockhash(&Hash::new_from_array([42u8; 32]));
1170 let data = nonce::state::Data::new(Pubkey::from([1u8; 32]), durable_nonce, 42);
1171 nonce_account
1172 .set_state(&Versions::new(State::Initialized(data.clone())))
1173 .unwrap();
1174 let state = state_from_account(&nonce_account).unwrap();
1175 assert_eq!(data_from_state(&state), Ok(&data));
1176 assert_eq!(data_from_account(&nonce_account), Ok(data));
1177 }
1178}