Skip to main content

solana_cli/
nonce.rs

1use {
2    crate::{
3        checks::{check_account_for_fee_with_commitment, check_unique_pubkeys},
4        cli::{
5            CliCommand, CliCommandInfo, CliConfig, CliError, ProcessResult,
6            log_instruction_custom_error,
7        },
8        compute_budget::{
9            ComputeUnitConfig, WithComputeUnitConfig, simulate_and_update_compute_unit_limit,
10        },
11        memo::WithMemo,
12        spend_utils::{SpendAmount, resolve_spend_tx_and_check_account_balance},
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::state::State,
28    solana_pubkey::Pubkey,
29    solana_remote_wallet::remote_wallet::RemoteWalletManager,
30    solana_rpc_client::nonblocking::rpc_client::RpcClient,
31    solana_rpc_client_nonce_utils::nonblocking::*,
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
378/// Check if a nonce account is initialized with the given authority and hash
379pub 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 async 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().await?;
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).await?;
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    .await?;
441    let result = rpc_client
442        .send_and_confirm_transaction_with_spinner_and_config(
443            &tx,
444            config.commitment,
445            config.send_transaction_config,
446        )
447        .await;
448
449    log_instruction_custom_error::<SystemError>(result, config)
450}
451
452pub async fn process_create_nonce_account(
453    rpc_client: &RpcClient,
454    config: &CliConfig<'_>,
455    nonce_account: SignerIndex,
456    seed: Option<String>,
457    nonce_authority: Option<Pubkey>,
458    memo: Option<&String>,
459    mut amount: SpendAmount,
460    compute_unit_price: Option<u64>,
461) -> ProcessResult {
462    let nonce_account_pubkey = config.signers[nonce_account].pubkey();
463    let nonce_account_address = if let Some(ref seed) = seed {
464        Pubkey::create_with_seed(&nonce_account_pubkey, seed, &system_program::id())?
465    } else {
466        nonce_account_pubkey
467    };
468
469    check_unique_pubkeys(
470        (&config.signers[0].pubkey(), "cli keypair".to_string()),
471        (&nonce_account_address, "nonce_account".to_string()),
472    )?;
473
474    let minimum_balance = rpc_client
475        .get_minimum_balance_for_rent_exemption(State::size())
476        .await?;
477    if amount == SpendAmount::All {
478        amount = SpendAmount::AllForAccountCreation {
479            create_account_min_balance: minimum_balance,
480        };
481    }
482
483    let nonce_authority = nonce_authority.unwrap_or_else(|| config.signers[0].pubkey());
484
485    let compute_unit_limit = ComputeUnitLimit::Simulated;
486    let build_message = |lamports| {
487        let ixs = if let Some(seed) = seed.clone() {
488            create_nonce_account_with_seed(
489                &config.signers[0].pubkey(), // from
490                &nonce_account_address,      // to
491                &nonce_account_pubkey,       // base
492                &seed,                       // seed
493                &nonce_authority,
494                lamports,
495            )
496            .with_memo(memo)
497            .with_compute_unit_config(&ComputeUnitConfig {
498                compute_unit_price,
499                compute_unit_limit,
500            })
501        } else {
502            create_nonce_account(
503                &config.signers[0].pubkey(),
504                &nonce_account_pubkey,
505                &nonce_authority,
506                lamports,
507            )
508            .with_memo(memo)
509            .with_compute_unit_config(&ComputeUnitConfig {
510                compute_unit_price,
511                compute_unit_limit,
512            })
513        };
514        Message::new(&ixs, Some(&config.signers[0].pubkey()))
515    };
516
517    let latest_blockhash = rpc_client.get_latest_blockhash().await?;
518
519    let (message, lamports) = resolve_spend_tx_and_check_account_balance(
520        rpc_client,
521        false,
522        amount,
523        &latest_blockhash,
524        &config.signers[0].pubkey(),
525        compute_unit_limit,
526        build_message,
527        config.commitment,
528    )
529    .await?;
530
531    if let Ok(nonce_account) = get_account(rpc_client, &nonce_account_address).await {
532        let err_msg = if state_from_account(&nonce_account).is_ok() {
533            format!("Nonce account {nonce_account_address} already exists")
534        } else {
535            format!("Account {nonce_account_address} already exists and is not a nonce account")
536        };
537        return Err(CliError::BadParameter(err_msg).into());
538    }
539
540    if lamports < minimum_balance {
541        return Err(CliError::BadParameter(format!(
542            "need at least {minimum_balance} lamports for nonce account to be rent exempt, \
543             provided lamports: {lamports}"
544        ))
545        .into());
546    }
547
548    let mut tx = Transaction::new_unsigned(message);
549    tx.try_sign(&config.signers, latest_blockhash)?;
550    let result = rpc_client
551        .send_and_confirm_transaction_with_spinner_and_config(
552            &tx,
553            config.commitment,
554            config.send_transaction_config,
555        )
556        .await;
557
558    log_instruction_custom_error::<SystemError>(result, config)
559}
560
561pub async fn process_get_nonce(
562    rpc_client: &RpcClient,
563    config: &CliConfig<'_>,
564    nonce_account_pubkey: &Pubkey,
565) -> ProcessResult {
566    match get_account_with_commitment(rpc_client, nonce_account_pubkey, config.commitment)
567        .await
568        .and_then(|ref a| state_from_account(a))?
569    {
570        State::Uninitialized => Ok("Nonce account is uninitialized".to_string()),
571        State::Initialized(ref data) => Ok(format!("{:?}", data.blockhash())),
572    }
573}
574
575pub async fn process_new_nonce(
576    rpc_client: &RpcClient,
577    config: &CliConfig<'_>,
578    nonce_account: &Pubkey,
579    nonce_authority: SignerIndex,
580    memo: Option<&String>,
581    compute_unit_price: Option<u64>,
582) -> ProcessResult {
583    check_unique_pubkeys(
584        (&config.signers[0].pubkey(), "cli keypair".to_string()),
585        (nonce_account, "nonce_account_pubkey".to_string()),
586    )?;
587
588    if let Err(err) = rpc_client.get_account(nonce_account).await {
589        return Err(CliError::BadParameter(format!(
590            "Unable to advance nonce account {nonce_account}. error: {err}"
591        ))
592        .into());
593    }
594
595    let nonce_authority = config.signers[nonce_authority];
596    let compute_unit_limit = ComputeUnitLimit::Simulated;
597    let ixs = vec![advance_nonce_account(
598        nonce_account,
599        &nonce_authority.pubkey(),
600    )]
601    .with_memo(memo)
602    .with_compute_unit_config(&ComputeUnitConfig {
603        compute_unit_price,
604        compute_unit_limit,
605    });
606    let latest_blockhash = rpc_client.get_latest_blockhash().await?;
607    let mut message = Message::new(&ixs, Some(&config.signers[0].pubkey()));
608    simulate_and_update_compute_unit_limit(&compute_unit_limit, rpc_client, &mut message).await?;
609    let mut tx = Transaction::new_unsigned(message);
610    tx.try_sign(&config.signers, latest_blockhash)?;
611    check_account_for_fee_with_commitment(
612        rpc_client,
613        &config.signers[0].pubkey(),
614        &tx.message,
615        config.commitment,
616    )
617    .await?;
618    let result = rpc_client
619        .send_and_confirm_transaction_with_spinner_and_config(
620            &tx,
621            config.commitment,
622            config.send_transaction_config,
623        )
624        .await;
625
626    log_instruction_custom_error::<SystemError>(result, config)
627}
628
629pub async fn process_show_nonce_account(
630    rpc_client: &RpcClient,
631    config: &CliConfig<'_>,
632    nonce_account_pubkey: &Pubkey,
633    use_lamports_unit: bool,
634) -> ProcessResult {
635    let nonce_account =
636        get_account_with_commitment(rpc_client, nonce_account_pubkey, config.commitment).await?;
637    let minimum_balance_for_rent_exemption = rpc_client
638        .get_minimum_balance_for_rent_exemption(State::size())
639        .await?;
640
641    let mut cli_nonce_account = CliNonceAccount {
642        balance: nonce_account.lamports,
643        minimum_balance_for_rent_exemption,
644        use_lamports_unit,
645        ..CliNonceAccount::default()
646    };
647
648    match state_from_account(&nonce_account)? {
649        State::Uninitialized => {}
650        State::Initialized(ref data) => {
651            cli_nonce_account.nonce = Some(data.blockhash().to_string());
652            cli_nonce_account.lamports_per_signature =
653                Some(data.fee_calculator.lamports_per_signature);
654            cli_nonce_account.authority = Some(data.authority.to_string());
655        }
656    }
657
658    Ok(config.output_format.formatted_string(&cli_nonce_account))
659}
660
661pub async fn process_withdraw_from_nonce_account(
662    rpc_client: &RpcClient,
663    config: &CliConfig<'_>,
664    nonce_account: &Pubkey,
665    nonce_authority: SignerIndex,
666    memo: Option<&String>,
667    destination_account_pubkey: &Pubkey,
668    lamports: u64,
669    compute_unit_price: Option<u64>,
670) -> ProcessResult {
671    let latest_blockhash = rpc_client.get_latest_blockhash().await?;
672
673    let nonce_authority = config.signers[nonce_authority];
674    let compute_unit_limit = ComputeUnitLimit::Simulated;
675    let ixs = vec![withdraw_nonce_account(
676        nonce_account,
677        &nonce_authority.pubkey(),
678        destination_account_pubkey,
679        lamports,
680    )]
681    .with_memo(memo)
682    .with_compute_unit_config(&ComputeUnitConfig {
683        compute_unit_price,
684        compute_unit_limit,
685    });
686    let mut message = Message::new(&ixs, Some(&config.signers[0].pubkey()));
687    simulate_and_update_compute_unit_limit(&compute_unit_limit, rpc_client, &mut message).await?;
688    let mut tx = Transaction::new_unsigned(message);
689    tx.try_sign(&config.signers, latest_blockhash)?;
690    check_account_for_fee_with_commitment(
691        rpc_client,
692        &config.signers[0].pubkey(),
693        &tx.message,
694        config.commitment,
695    )
696    .await?;
697    let result = rpc_client
698        .send_and_confirm_transaction_with_spinner_and_config(
699            &tx,
700            config.commitment,
701            config.send_transaction_config,
702        )
703        .await;
704
705    log_instruction_custom_error::<SystemError>(result, config)
706}
707
708pub(crate) async fn process_upgrade_nonce_account(
709    rpc_client: &RpcClient,
710    config: &CliConfig<'_>,
711    nonce_account: Pubkey,
712    memo: Option<&String>,
713    compute_unit_price: Option<u64>,
714) -> ProcessResult {
715    let latest_blockhash = rpc_client.get_latest_blockhash().await?;
716    let compute_unit_limit = ComputeUnitLimit::Simulated;
717    let ixs = vec![upgrade_nonce_account(nonce_account)]
718        .with_memo(memo)
719        .with_compute_unit_config(&ComputeUnitConfig {
720            compute_unit_price,
721            compute_unit_limit,
722        });
723    let mut message = Message::new(&ixs, Some(&config.signers[0].pubkey()));
724    simulate_and_update_compute_unit_limit(&compute_unit_limit, rpc_client, &mut message).await?;
725    let mut tx = Transaction::new_unsigned(message);
726    tx.try_sign(&config.signers, latest_blockhash)?;
727    check_account_for_fee_with_commitment(
728        rpc_client,
729        &config.signers[0].pubkey(),
730        &tx.message,
731        config.commitment,
732    )
733    .await?;
734    let result = rpc_client
735        .send_and_confirm_transaction_with_spinner_and_config(
736            &tx,
737            config.commitment,
738            config.send_transaction_config,
739        )
740        .await;
741    log_instruction_custom_error::<SystemError>(result, config)
742}
743
744#[cfg(test)]
745mod tests {
746    use {
747        super::*,
748        crate::{clap_app::get_clap_app, cli::parse_command},
749        solana_account::{Account, state_traits::StateMut},
750        solana_keypair::{Keypair, read_keypair_file, write_keypair},
751        solana_nonce::{
752            self as nonce,
753            state::{DurableNonce, State},
754            versions::Versions,
755        },
756        solana_nonce_account as nonce_account,
757        solana_sdk_ids::system_program,
758        solana_sha256_hasher::hash,
759        solana_signer::Signer,
760        tempfile::NamedTempFile,
761    };
762
763    fn make_tmp_file() -> (String, NamedTempFile) {
764        let tmp_file = NamedTempFile::new().unwrap();
765        (String::from(tmp_file.path().to_str().unwrap()), tmp_file)
766    }
767
768    #[test]
769    fn test_parse_command() {
770        let test_commands = get_clap_app("test", "desc", "version");
771        let default_keypair = Keypair::new();
772        let (default_keypair_file, mut tmp_file) = make_tmp_file();
773        write_keypair(&default_keypair, tmp_file.as_file_mut()).unwrap();
774        let default_signer = DefaultSigner::new("", &default_keypair_file);
775        let (keypair_file, mut tmp_file) = make_tmp_file();
776        let nonce_account_keypair = Keypair::new();
777        write_keypair(&nonce_account_keypair, tmp_file.as_file_mut()).unwrap();
778        let nonce_account_pubkey = nonce_account_keypair.pubkey();
779        let nonce_account_string = nonce_account_pubkey.to_string();
780
781        let (authority_keypair_file, mut tmp_file2) = make_tmp_file();
782        let nonce_authority_keypair = Keypair::new();
783        write_keypair(&nonce_authority_keypair, tmp_file2.as_file_mut()).unwrap();
784
785        // Test AuthorizeNonceAccount Subcommand
786        let test_authorize_nonce_account = test_commands.clone().get_matches_from(vec![
787            "test",
788            "authorize-nonce-account",
789            &keypair_file,
790            &Pubkey::default().to_string(),
791        ]);
792        assert_eq!(
793            parse_command(&test_authorize_nonce_account, &default_signer, &mut None).unwrap(),
794            CliCommandInfo {
795                command: CliCommand::AuthorizeNonceAccount {
796                    nonce_account: nonce_account_pubkey,
797                    nonce_authority: 0,
798                    memo: None,
799                    new_authority: Pubkey::default(),
800                    compute_unit_price: None,
801                },
802                signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap())],
803            }
804        );
805
806        // Test AuthorizeNonceAccount Subcommand with authority
807        let test_authorize_nonce_account = test_commands.clone().get_matches_from(vec![
808            "test",
809            "authorize-nonce-account",
810            &keypair_file,
811            &Pubkey::default().to_string(),
812            "--nonce-authority",
813            &authority_keypair_file,
814        ]);
815        assert_eq!(
816            parse_command(&test_authorize_nonce_account, &default_signer, &mut None).unwrap(),
817            CliCommandInfo {
818                command: CliCommand::AuthorizeNonceAccount {
819                    nonce_account: read_keypair_file(&keypair_file).unwrap().pubkey(),
820                    nonce_authority: 1,
821                    memo: None,
822                    new_authority: Pubkey::default(),
823                    compute_unit_price: None,
824                },
825                signers: vec![
826                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
827                    Box::new(read_keypair_file(&authority_keypair_file).unwrap())
828                ],
829            }
830        );
831
832        // Test CreateNonceAccount SubCommand
833        let test_create_nonce_account = test_commands.clone().get_matches_from(vec![
834            "test",
835            "create-nonce-account",
836            &keypair_file,
837            "50",
838        ]);
839        assert_eq!(
840            parse_command(&test_create_nonce_account, &default_signer, &mut None).unwrap(),
841            CliCommandInfo {
842                command: CliCommand::CreateNonceAccount {
843                    nonce_account: 1,
844                    seed: None,
845                    nonce_authority: None,
846                    memo: None,
847                    amount: SpendAmount::Some(50_000_000_000),
848                    compute_unit_price: None,
849                },
850                signers: vec![
851                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
852                    Box::new(read_keypair_file(&keypair_file).unwrap())
853                ],
854            }
855        );
856
857        // Test CreateNonceAccount SubCommand with authority
858        let test_create_nonce_account = test_commands.clone().get_matches_from(vec![
859            "test",
860            "create-nonce-account",
861            &keypair_file,
862            "50",
863            "--nonce-authority",
864            &authority_keypair_file,
865        ]);
866        assert_eq!(
867            parse_command(&test_create_nonce_account, &default_signer, &mut None).unwrap(),
868            CliCommandInfo {
869                command: CliCommand::CreateNonceAccount {
870                    nonce_account: 1,
871                    seed: None,
872                    nonce_authority: Some(nonce_authority_keypair.pubkey()),
873                    memo: None,
874                    amount: SpendAmount::Some(50_000_000_000),
875                    compute_unit_price: None,
876                },
877                signers: vec![
878                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
879                    Box::new(read_keypair_file(&keypair_file).unwrap())
880                ],
881            }
882        );
883
884        // Test GetNonce Subcommand
885        let test_get_nonce = test_commands.clone().get_matches_from(vec![
886            "test",
887            "get-nonce",
888            &nonce_account_string,
889        ]);
890        assert_eq!(
891            parse_command(&test_get_nonce, &default_signer, &mut None).unwrap(),
892            CliCommandInfo::without_signers(CliCommand::GetNonce(nonce_account_keypair.pubkey()))
893        );
894
895        // Test NewNonce SubCommand
896        let test_new_nonce =
897            test_commands
898                .clone()
899                .get_matches_from(vec!["test", "new-nonce", &keypair_file]);
900        let nonce_account = read_keypair_file(&keypair_file).unwrap();
901        assert_eq!(
902            parse_command(&test_new_nonce, &default_signer, &mut None).unwrap(),
903            CliCommandInfo {
904                command: CliCommand::NewNonce {
905                    nonce_account: nonce_account.pubkey(),
906                    nonce_authority: 0,
907                    memo: None,
908                    compute_unit_price: None,
909                },
910                signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap())],
911            }
912        );
913
914        // Test NewNonce SubCommand with authority
915        let test_new_nonce = test_commands.clone().get_matches_from(vec![
916            "test",
917            "new-nonce",
918            &keypair_file,
919            "--nonce-authority",
920            &authority_keypair_file,
921        ]);
922        let nonce_account = read_keypair_file(&keypair_file).unwrap();
923        assert_eq!(
924            parse_command(&test_new_nonce, &default_signer, &mut None).unwrap(),
925            CliCommandInfo {
926                command: CliCommand::NewNonce {
927                    nonce_account: nonce_account.pubkey(),
928                    nonce_authority: 1,
929                    memo: None,
930                    compute_unit_price: None,
931                },
932                signers: vec![
933                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
934                    Box::new(read_keypair_file(&authority_keypair_file).unwrap())
935                ],
936            }
937        );
938
939        // Test ShowNonceAccount Subcommand
940        let test_show_nonce_account = test_commands.clone().get_matches_from(vec![
941            "test",
942            "nonce-account",
943            &nonce_account_string,
944        ]);
945        assert_eq!(
946            parse_command(&test_show_nonce_account, &default_signer, &mut None).unwrap(),
947            CliCommandInfo::without_signers(CliCommand::ShowNonceAccount {
948                nonce_account_pubkey: nonce_account_keypair.pubkey(),
949                use_lamports_unit: false,
950            })
951        );
952
953        // Test WithdrawFromNonceAccount Subcommand
954        let test_withdraw_from_nonce_account = test_commands.clone().get_matches_from(vec![
955            "test",
956            "withdraw-from-nonce-account",
957            &keypair_file,
958            &nonce_account_string,
959            "42",
960        ]);
961        assert_eq!(
962            parse_command(
963                &test_withdraw_from_nonce_account,
964                &default_signer,
965                &mut None
966            )
967            .unwrap(),
968            CliCommandInfo {
969                command: CliCommand::WithdrawFromNonceAccount {
970                    nonce_account: read_keypair_file(&keypair_file).unwrap().pubkey(),
971                    nonce_authority: 0,
972                    memo: None,
973                    destination_account_pubkey: nonce_account_pubkey,
974                    lamports: 42_000_000_000,
975                    compute_unit_price: None,
976                },
977                signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap())],
978            }
979        );
980
981        // Test WithdrawFromNonceAccount Subcommand with authority
982        let test_withdraw_from_nonce_account = test_commands.clone().get_matches_from(vec![
983            "test",
984            "withdraw-from-nonce-account",
985            &keypair_file,
986            &nonce_account_string,
987            "42",
988            "--nonce-authority",
989            &authority_keypair_file,
990        ]);
991        assert_eq!(
992            parse_command(
993                &test_withdraw_from_nonce_account,
994                &default_signer,
995                &mut None
996            )
997            .unwrap(),
998            CliCommandInfo {
999                command: CliCommand::WithdrawFromNonceAccount {
1000                    nonce_account: read_keypair_file(&keypair_file).unwrap().pubkey(),
1001                    nonce_authority: 1,
1002                    memo: None,
1003                    destination_account_pubkey: nonce_account_pubkey,
1004                    lamports: 42_000_000_000,
1005                    compute_unit_price: None,
1006                },
1007                signers: vec![
1008                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
1009                    Box::new(read_keypair_file(&authority_keypair_file).unwrap())
1010                ],
1011            }
1012        );
1013
1014        // Test UpgradeNonceAccount Subcommand.
1015        let test_upgrade_nonce_account = test_commands.clone().get_matches_from(vec![
1016            "test",
1017            "upgrade-nonce-account",
1018            &nonce_account_string,
1019        ]);
1020        assert_eq!(
1021            parse_command(&test_upgrade_nonce_account, &default_signer, &mut None).unwrap(),
1022            CliCommandInfo {
1023                command: CliCommand::UpgradeNonceAccount {
1024                    nonce_account: nonce_account_pubkey,
1025                    memo: None,
1026                    compute_unit_price: None,
1027                },
1028                signers: CliSigners::default(),
1029            }
1030        );
1031
1032        // Test ComputeUnitPrice Subcommand with authority
1033        let test_authorize_nonce_account = test_commands.clone().get_matches_from(vec![
1034            "test",
1035            "authorize-nonce-account",
1036            &keypair_file,
1037            &Pubkey::default().to_string(),
1038            "--nonce-authority",
1039            &authority_keypair_file,
1040            "--with-compute-unit-price",
1041            "99",
1042        ]);
1043        assert_eq!(
1044            parse_command(&test_authorize_nonce_account, &default_signer, &mut None).unwrap(),
1045            CliCommandInfo {
1046                command: CliCommand::AuthorizeNonceAccount {
1047                    nonce_account: read_keypair_file(&keypair_file).unwrap().pubkey(),
1048                    nonce_authority: 1,
1049                    memo: None,
1050                    new_authority: Pubkey::default(),
1051                    compute_unit_price: Some(99),
1052                },
1053                signers: vec![
1054                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
1055                    Box::new(read_keypair_file(&authority_keypair_file).unwrap())
1056                ],
1057            }
1058        );
1059    }
1060
1061    #[test]
1062    fn test_check_nonce_account() {
1063        let durable_nonce = DurableNonce::from_blockhash(&Hash::default());
1064        let blockhash = *durable_nonce.as_hash();
1065        let nonce_pubkey = solana_pubkey::new_rand();
1066        let data = Versions::new(State::Initialized(nonce::state::Data::new(
1067            nonce_pubkey,
1068            durable_nonce,
1069            0,
1070        )));
1071        let valid = Account::new_data(1, &data, &system_program::ID);
1072        assert!(check_nonce_account(&valid.unwrap(), &nonce_pubkey, &blockhash).is_ok());
1073
1074        let invalid_owner = Account::new_data(1, &data, &Pubkey::from([1u8; 32]));
1075        if let CliError::InvalidNonce(err) =
1076            check_nonce_account(&invalid_owner.unwrap(), &nonce_pubkey, &blockhash).unwrap_err()
1077        {
1078            assert_eq!(err, Error::InvalidAccountOwner,);
1079        }
1080
1081        let invalid_data = Account::new_data(1, &"invalid", &system_program::ID);
1082        if let CliError::InvalidNonce(err) =
1083            check_nonce_account(&invalid_data.unwrap(), &nonce_pubkey, &blockhash).unwrap_err()
1084        {
1085            assert_eq!(err, Error::InvalidAccountData,);
1086        }
1087
1088        let invalid_durable_nonce = DurableNonce::from_blockhash(&hash(b"invalid"));
1089        let data = Versions::new(State::Initialized(nonce::state::Data::new(
1090            nonce_pubkey,
1091            invalid_durable_nonce,
1092            0,
1093        )));
1094        let invalid_hash = Account::new_data(1, &data, &system_program::ID).unwrap();
1095        if let CliError::InvalidNonce(err) =
1096            check_nonce_account(&invalid_hash, &nonce_pubkey, &blockhash).unwrap_err()
1097        {
1098            assert_eq!(
1099                err,
1100                Error::InvalidHash {
1101                    provided: blockhash,
1102                    expected: *invalid_durable_nonce.as_hash(),
1103                }
1104            );
1105        }
1106
1107        let new_nonce_authority = solana_pubkey::new_rand();
1108        let data = Versions::new(State::Initialized(nonce::state::Data::new(
1109            new_nonce_authority,
1110            durable_nonce,
1111            0,
1112        )));
1113        let invalid_authority = Account::new_data(1, &data, &system_program::ID);
1114        if let CliError::InvalidNonce(err) =
1115            check_nonce_account(&invalid_authority.unwrap(), &nonce_pubkey, &blockhash).unwrap_err()
1116        {
1117            assert_eq!(
1118                err,
1119                Error::InvalidAuthority {
1120                    provided: nonce_pubkey,
1121                    expected: new_nonce_authority,
1122                }
1123            );
1124        }
1125
1126        let data = Versions::new(State::Uninitialized);
1127        let invalid_state = Account::new_data(1, &data, &system_program::ID);
1128        if let CliError::InvalidNonce(err) =
1129            check_nonce_account(&invalid_state.unwrap(), &nonce_pubkey, &blockhash).unwrap_err()
1130        {
1131            assert_eq!(err, Error::InvalidStateForOperation,);
1132        }
1133    }
1134
1135    #[test]
1136    fn test_account_identity_ok() {
1137        let nonce_account = nonce_account::create_account(1).into_inner();
1138        assert_eq!(account_identity_ok(&nonce_account), Ok(()));
1139
1140        let system_account = Account::new(1, 0, &system_program::id());
1141        assert_eq!(
1142            account_identity_ok(&system_account),
1143            Err(Error::UnexpectedDataSize),
1144        );
1145
1146        let other_program = Pubkey::from([1u8; 32]);
1147        let other_account_no_data = Account::new(1, 0, &other_program);
1148        assert_eq!(
1149            account_identity_ok(&other_account_no_data),
1150            Err(Error::InvalidAccountOwner),
1151        );
1152    }
1153
1154    #[test]
1155    fn test_state_from_account() {
1156        let mut nonce_account = nonce_account::create_account(1).into_inner();
1157        assert_eq!(state_from_account(&nonce_account), Ok(State::Uninitialized));
1158
1159        let durable_nonce = DurableNonce::from_blockhash(&Hash::new_from_array([42u8; 32]));
1160        let data = nonce::state::Data::new(Pubkey::from([1u8; 32]), durable_nonce, 42);
1161        nonce_account
1162            .set_state(&Versions::new(State::Initialized(data.clone())))
1163            .unwrap();
1164        assert_eq!(
1165            state_from_account(&nonce_account),
1166            Ok(State::Initialized(data))
1167        );
1168
1169        let wrong_data_size_account = Account::new(1, 1, &system_program::id());
1170        assert_eq!(
1171            state_from_account(&wrong_data_size_account),
1172            Err(Error::InvalidAccountData),
1173        );
1174    }
1175
1176    #[test]
1177    fn test_data_from_helpers() {
1178        let mut nonce_account = nonce_account::create_account(1).into_inner();
1179        let state = state_from_account(&nonce_account).unwrap();
1180        assert_eq!(
1181            data_from_state(&state),
1182            Err(Error::InvalidStateForOperation)
1183        );
1184        assert_eq!(
1185            data_from_account(&nonce_account),
1186            Err(Error::InvalidStateForOperation)
1187        );
1188
1189        let durable_nonce = DurableNonce::from_blockhash(&Hash::new_from_array([42u8; 32]));
1190        let data = nonce::state::Data::new(Pubkey::from([1u8; 32]), durable_nonce, 42);
1191        nonce_account
1192            .set_state(&Versions::new(State::Initialized(data.clone())))
1193            .unwrap();
1194        let state = state_from_account(&nonce_account).unwrap();
1195        assert_eq!(data_from_state(&state), Ok(&data));
1196        assert_eq!(data_from_account(&nonce_account), Ok(data));
1197    }
1198}