solana_cli/
program_v4.rs

1use {
2    crate::{
3        checks::*,
4        cli::{CliCommand, CliCommandInfo, CliConfig, CliError, ProcessResult},
5        compute_budget::{
6            simulate_and_update_compute_unit_limit, ComputeUnitConfig, WithComputeUnitConfig,
7        },
8        feature::{status_from_account, CliFeatureStatus},
9        program::calculate_max_chunk_size,
10    },
11    agave_feature_set::{raise_cpi_nesting_limit_to_8, FeatureSet, FEATURE_NAMES},
12    clap::{value_t, App, AppSettings, Arg, ArgMatches, SubCommand},
13    log::*,
14    solana_account::Account,
15    solana_account_decoder::{UiAccountEncoding, UiDataSliceConfig},
16    solana_clap_utils::{
17        compute_budget::{compute_unit_price_arg, ComputeUnitLimit},
18        input_parsers::{pubkey_of, pubkey_of_signer, signer_of},
19        input_validators::{is_valid_pubkey, is_valid_signer},
20        keypair::{DefaultSigner, SignerIndex},
21        offline::{OfflineArgs, DUMP_TRANSACTION_MESSAGE, SIGN_ONLY_ARG},
22    },
23    solana_cli_output::{
24        return_signers_with_config, CliProgramId, CliProgramV4, CliProgramsV4, ReturnSignersConfig,
25    },
26    solana_client::{
27        connection_cache::ConnectionCache,
28        send_and_confirm_transactions_in_parallel::{
29            send_and_confirm_transactions_in_parallel_blocking_v2, SendAndConfirmConfigV2,
30        },
31        tpu_client::{TpuClient, TpuClientConfig},
32    },
33    solana_instruction::Instruction,
34    solana_loader_v4_interface::{
35        instruction,
36        state::{
37            LoaderV4State,
38            LoaderV4Status::{self, Retracted},
39        },
40    },
41    solana_message::Message,
42    solana_program_runtime::{
43        execution_budget::SVMTransactionExecutionBudget, invoke_context::InvokeContext,
44    },
45    solana_pubkey::Pubkey,
46    solana_remote_wallet::remote_wallet::RemoteWalletManager,
47    solana_rpc_client::rpc_client::RpcClient,
48    solana_rpc_client_api::{
49        config::{RpcAccountInfoConfig, RpcProgramAccountsConfig},
50        filter::{Memcmp, RpcFilterType},
51        request::MAX_MULTIPLE_ACCOUNTS,
52    },
53    solana_rpc_client_nonce_utils::blockhash_query::BlockhashQuery,
54    solana_sbpf::{elf::Executable, verifier::RequisiteVerifier},
55    solana_sdk_ids::{loader_v4, system_program},
56    solana_signer::Signer,
57    solana_system_interface::{instruction as system_instruction, MAX_PERMITTED_DATA_LENGTH},
58    solana_transaction::Transaction,
59    std::{
60        cmp::Ordering,
61        fs::File,
62        io::{Read, Write},
63        mem::size_of,
64        num::Saturating,
65        ops::Range,
66        rc::Rc,
67        sync::Arc,
68    },
69};
70
71#[derive(Debug, PartialEq, Eq, Default)]
72pub struct AdditionalCliConfig {
73    pub use_rpc: bool,
74    pub sign_only: bool,
75    pub dump_transaction_message: bool,
76    pub blockhash_query: BlockhashQuery,
77    pub compute_unit_price: Option<u64>,
78}
79
80impl AdditionalCliConfig {
81    fn from_matches(matches: &ArgMatches<'_>) -> Self {
82        Self {
83            use_rpc: matches.is_present("use-rpc"),
84            sign_only: matches.is_present(SIGN_ONLY_ARG.name),
85            dump_transaction_message: matches.is_present(DUMP_TRANSACTION_MESSAGE.name),
86            blockhash_query: BlockhashQuery::new_from_matches(matches),
87            compute_unit_price: value_t!(matches, "compute_unit_price", u64).ok(),
88        }
89    }
90}
91
92#[derive(Debug, PartialEq, Eq)]
93pub enum ProgramV4CliCommand {
94    Deploy {
95        additional_cli_config: AdditionalCliConfig,
96        program_address: Pubkey,
97        buffer_address: Option<Pubkey>,
98        upload_signer_index: Option<SignerIndex>,
99        authority_signer_index: SignerIndex,
100        path_to_elf: Option<String>,
101        upload_range: Range<Option<usize>>,
102    },
103    Retract {
104        additional_cli_config: AdditionalCliConfig,
105        program_address: Pubkey,
106        authority_signer_index: SignerIndex,
107        close_program_entirely: bool,
108    },
109    TransferAuthority {
110        additional_cli_config: AdditionalCliConfig,
111        program_address: Pubkey,
112        authority_signer_index: SignerIndex,
113        new_authority_signer_index: SignerIndex,
114    },
115    Finalize {
116        additional_cli_config: AdditionalCliConfig,
117        program_address: Pubkey,
118        authority_signer_index: SignerIndex,
119        next_version_signer_index: SignerIndex,
120    },
121    Show {
122        account_pubkey: Option<Pubkey>,
123        authority: Pubkey,
124        all: bool,
125    },
126    Dump {
127        account_pubkey: Option<Pubkey>,
128        output_location: String,
129    },
130}
131
132pub trait ProgramV4SubCommands {
133    fn program_v4_subcommands(self) -> Self;
134}
135
136impl ProgramV4SubCommands for App<'_, '_> {
137    fn program_v4_subcommands(self) -> Self {
138        self.subcommand(
139            SubCommand::with_name("program-v4")
140                .about("Program V4 management")
141                .setting(AppSettings::SubcommandRequiredElseHelp)
142                .subcommand(
143                    SubCommand::with_name("deploy")
144                        .about("Deploy a new or redeploy an existing program")
145                        .arg(
146                            Arg::with_name("path-to-elf")
147                                .index(1)
148                                .value_name("PATH-TO-ELF")
149                                .takes_value(true)
150                                .help("./target/deploy/program.so"),
151                        )
152                        .arg(
153                            Arg::with_name("start-offset")
154                                .long("start-offset")
155                                .value_name("START_OFFSET")
156                                .takes_value(true)
157                                .help("Optionally starts writing at this byte offset"),
158                        )
159                        .arg(
160                            Arg::with_name("end-offset")
161                                .long("end-offset")
162                                .value_name("END_OFFSET")
163                                .takes_value(true)
164                                .help("Optionally stops writing after this byte offset"),
165                        )
166                        .arg(
167                            Arg::with_name("program-keypair")
168                                .long("program-keypair")
169                                .value_name("PROGRAM_SIGNER")
170                                .takes_value(true)
171                                .validator(is_valid_signer)
172                                .help(
173                                    "Program account signer for deploying a new program",
174                                ),
175                        )
176                        .arg(
177                            Arg::with_name("program-id")
178                                .long("program-id")
179                                .value_name("PROGRAM_ID")
180                                .takes_value(true)
181                                .help("Program address for redeploying an existing program"),
182                        )
183                        .arg(
184                            Arg::with_name("buffer")
185                                .long("buffer")
186                                .value_name("BUFFER_SIGNER")
187                                .takes_value(true)
188                                .validator(is_valid_signer)
189                                .help(
190                                    "Optional intermediate buffer account to write data to",
191                                ),
192                        )
193                        .arg(
194                            Arg::with_name("authority")
195                                .long("authority")
196                                .value_name("AUTHORITY_SIGNER")
197                                .takes_value(true)
198                                .validator(is_valid_signer)
199                                .help(
200                                    "Program authority [default: the default configured keypair]",
201                                ),
202                        )
203                        .arg(Arg::with_name("use-rpc").long("use-rpc").help(
204                            "Send transactions to the configured RPC instead of validator TPUs",
205                        ))
206                        .offline_args()
207                        .arg(compute_unit_price_arg()),
208                )
209                .subcommand(
210                    SubCommand::with_name("retract")
211                        .about("Reverse deployment or close a program entirely")
212                        .arg(
213                            Arg::with_name("program-id")
214                                .long("program-id")
215                                .value_name("PROGRAM_ID")
216                                .takes_value(true)
217                                .required(true)
218                                .help("Executable program's address"),
219                        )
220                        .arg(
221                            Arg::with_name("authority")
222                                .long("authority")
223                                .value_name("AUTHORITY_SIGNER")
224                                .takes_value(true)
225                                .validator(is_valid_signer)
226                                .help(
227                                    "Program authority [default: the default configured keypair]",
228                                ),
229                        )
230                        .arg(
231                            Arg::with_name("close-program-entirely")
232                                .long("close-program-entirely")
233                                .help("Reset the program account and retrieve its funds"),
234                        )
235                        .offline_args()
236                        .arg(compute_unit_price_arg()),
237                )
238                .subcommand(
239                    SubCommand::with_name("transfer-authority")
240                        .about("Transfer the authority of a program to a different address")
241                        .arg(
242                            Arg::with_name("program-id")
243                                .long("program-id")
244                                .value_name("PROGRAM_ID")
245                                .takes_value(true)
246                                .required(true)
247                                .help("Executable program's address"),
248                        )
249                        .arg(
250                            Arg::with_name("authority")
251                                .long("authority")
252                                .value_name("AUTHORITY_SIGNER")
253                                .takes_value(true)
254                                .validator(is_valid_signer)
255                                .help(
256                                    "Current program authority [default: the default configured keypair]",
257                                ),
258                        )
259                        .arg(
260                            Arg::with_name("new-authority")
261                                .long("new-authority")
262                                .value_name("NEW_AUTHORITY_SIGNER")
263                                .takes_value(true)
264                                .required(true)
265                                .validator(is_valid_signer)
266                                .help(
267                                    "New program authority",
268                                ),
269                        )
270                        .offline_args()
271                        .arg(compute_unit_price_arg()),
272                )
273                .subcommand(
274                    SubCommand::with_name("finalize")
275                        .about("Finalize a program to make it immutable")
276                        .arg(
277                            Arg::with_name("program-id")
278                                .long("program-id")
279                                .value_name("PROGRAM_ID")
280                                .takes_value(true)
281                                .required(true)
282                                .help("Executable program's address"),
283                        )
284                        .arg(
285                            Arg::with_name("authority")
286                                .long("authority")
287                                .value_name("AUTHORITY_SIGNER")
288                                .takes_value(true)
289                                .validator(is_valid_signer)
290                                .help(
291                                    "Program authority [default: the default configured keypair]",
292                                ),
293                        )
294                        .arg(
295                            Arg::with_name("next-version")
296                                .long("next-version")
297                                .value_name("NEXT_VERSION")
298                                .takes_value(true)
299                                .validator(is_valid_signer)
300                                .help(
301                                    "Reserves the address and links it as the programs next-version, which is a hint that frontends can show to users",
302                                ),
303                        )
304                        .offline_args()
305                        .arg(compute_unit_price_arg()),
306                )
307                .subcommand(
308                    SubCommand::with_name("show")
309                        .about("Display information about a buffer or program")
310                        .arg(
311                            Arg::with_name("program-id")
312                                .long("program-id")
313                                .value_name("PROGRAM_ID")
314                                .takes_value(true)
315                                .help("Executable program's address"),
316                        )
317                        .arg(
318                            Arg::with_name("all")
319                                .long("all")
320                                .conflicts_with("program-id")
321                                .conflicts_with("authority")
322                                .help("Show accounts for all authorities"),
323                        )
324                        .arg(pubkey!(
325                            Arg::with_name("authority")
326                                .long("authority")
327                                .value_name("AUTHORITY")
328                                .conflicts_with("all"),
329                            "Authority [default: the default configured keypair]."
330                        )),
331                )
332                .subcommand(
333                    SubCommand::with_name("download")
334                        .about("Download the executable of a program to a file")
335                        .arg(
336                            Arg::with_name("path-to-elf")
337                                .index(1)
338                                .value_name("PATH-TO-ELF")
339                                .takes_value(true)
340                                .help("./target/deploy/program.so"),
341                        )
342                        .arg(
343                            Arg::with_name("program-id")
344                                .long("program-id")
345                                .value_name("PROGRAM_ID")
346                                .takes_value(true)
347                                .help("Executable program's address"),
348                        ),
349                ),
350        )
351    }
352}
353
354pub fn parse_program_v4_subcommand(
355    matches: &ArgMatches<'_>,
356    default_signer: &DefaultSigner,
357    wallet_manager: &mut Option<Rc<RemoteWalletManager>>,
358) -> Result<CliCommandInfo, CliError> {
359    let (subcommand, sub_matches) = matches.subcommand();
360    let response = match (subcommand, sub_matches) {
361        ("deploy", Some(matches)) => {
362            let mut bulk_signers = vec![Some(
363                default_signer.signer_from_path(matches, wallet_manager)?,
364            )];
365
366            let path_to_elf = matches
367                .value_of("path-to-elf")
368                .map(|location| location.to_string());
369
370            let program_address = pubkey_of(matches, "program-id");
371            let mut program_pubkey = if let Ok((program_signer, Some(program_pubkey))) =
372                signer_of(matches, "program-keypair", wallet_manager)
373            {
374                bulk_signers.push(program_signer);
375                Some(program_pubkey)
376            } else {
377                pubkey_of_signer(matches, "program-keypair", wallet_manager)?
378            };
379
380            let buffer_pubkey = if let Ok((buffer_signer, Some(buffer_pubkey))) =
381                signer_of(matches, "buffer", wallet_manager)
382            {
383                if program_address.is_none() && program_pubkey.is_none() {
384                    program_pubkey = Some(buffer_pubkey);
385                }
386                bulk_signers.push(buffer_signer);
387                Some(buffer_pubkey)
388            } else {
389                pubkey_of_signer(matches, "buffer", wallet_manager)?
390            };
391
392            let (authority, authority_pubkey) = signer_of(matches, "authority", wallet_manager)?;
393            bulk_signers.push(authority);
394
395            let signer_info =
396                default_signer.generate_unique_signers(bulk_signers, matches, wallet_manager)?;
397            let program_signer_index = signer_info.index_of_or_none(program_pubkey);
398            let buffer_signer_index = signer_info.index_of_or_none(buffer_pubkey);
399            let upload_signer_index = buffer_signer_index.or(program_signer_index);
400            let authority_signer_index = signer_info
401                .index_of(authority_pubkey)
402                .expect("Authority signer is missing");
403            assert!(
404                program_address.is_some() != program_signer_index.is_some(),
405                "Requires either --program-keypair or --program-id",
406            );
407
408            CliCommandInfo {
409                command: CliCommand::ProgramV4(ProgramV4CliCommand::Deploy {
410                    additional_cli_config: AdditionalCliConfig::from_matches(matches),
411                    program_address: program_address.or(program_pubkey).unwrap(),
412                    buffer_address: buffer_pubkey,
413                    upload_signer_index: path_to_elf.as_ref().and(upload_signer_index),
414                    authority_signer_index,
415                    path_to_elf,
416                    upload_range: value_t!(matches, "start-offset", usize).ok()
417                        ..value_t!(matches, "end-offset", usize).ok(),
418                }),
419                signers: signer_info.signers,
420            }
421        }
422        ("retract", Some(matches)) => {
423            let mut bulk_signers = vec![Some(
424                default_signer.signer_from_path(matches, wallet_manager)?,
425            )];
426
427            let (authority, authority_pubkey) = signer_of(matches, "authority", wallet_manager)?;
428            bulk_signers.push(authority);
429
430            let signer_info =
431                default_signer.generate_unique_signers(bulk_signers, matches, wallet_manager)?;
432
433            CliCommandInfo {
434                command: CliCommand::ProgramV4(ProgramV4CliCommand::Retract {
435                    additional_cli_config: AdditionalCliConfig::from_matches(matches),
436                    program_address: pubkey_of(matches, "program-id")
437                        .expect("Program address is missing"),
438                    authority_signer_index: signer_info
439                        .index_of(authority_pubkey)
440                        .expect("Authority signer is missing"),
441                    close_program_entirely: matches.is_present("close-program-entirely"),
442                }),
443                signers: signer_info.signers,
444            }
445        }
446        ("transfer-authority", Some(matches)) => {
447            let mut bulk_signers = vec![Some(
448                default_signer.signer_from_path(matches, wallet_manager)?,
449            )];
450
451            let (authority, authority_pubkey) = signer_of(matches, "authority", wallet_manager)?;
452            bulk_signers.push(authority);
453
454            let (new_authority, new_authority_pubkey) =
455                signer_of(matches, "new-authority", wallet_manager)?;
456            bulk_signers.push(new_authority);
457
458            let signer_info =
459                default_signer.generate_unique_signers(bulk_signers, matches, wallet_manager)?;
460
461            CliCommandInfo {
462                command: CliCommand::ProgramV4(ProgramV4CliCommand::TransferAuthority {
463                    additional_cli_config: AdditionalCliConfig::from_matches(matches),
464                    program_address: pubkey_of(matches, "program-id")
465                        .expect("Program address is missing"),
466                    authority_signer_index: signer_info
467                        .index_of(authority_pubkey)
468                        .expect("Authority signer is missing"),
469                    new_authority_signer_index: signer_info
470                        .index_of(new_authority_pubkey)
471                        .expect("New authority signer is missing"),
472                }),
473                signers: signer_info.signers,
474            }
475        }
476        ("finalize", Some(matches)) => {
477            let mut bulk_signers = vec![Some(
478                default_signer.signer_from_path(matches, wallet_manager)?,
479            )];
480
481            let (authority, authority_pubkey) = signer_of(matches, "authority", wallet_manager)?;
482            bulk_signers.push(authority);
483
484            if let Ok((next_version, _next_version_pubkey)) =
485                signer_of(matches, "next-version", wallet_manager)
486            {
487                bulk_signers.push(next_version);
488            }
489
490            let signer_info =
491                default_signer.generate_unique_signers(bulk_signers, matches, wallet_manager)?;
492            let authority_signer_index = signer_info
493                .index_of(authority_pubkey)
494                .expect("Authority signer is missing");
495
496            CliCommandInfo {
497                command: CliCommand::ProgramV4(ProgramV4CliCommand::Finalize {
498                    additional_cli_config: AdditionalCliConfig::from_matches(matches),
499                    program_address: pubkey_of(matches, "program-id")
500                        .expect("Program address is missing"),
501                    authority_signer_index,
502                    next_version_signer_index: pubkey_of(matches, "next-version")
503                        .and_then(|pubkey| signer_info.index_of(Some(pubkey)))
504                        .unwrap_or(authority_signer_index),
505                }),
506                signers: signer_info.signers,
507            }
508        }
509        ("show", Some(matches)) => {
510            let authority =
511                if let Some(authority) = pubkey_of_signer(matches, "authority", wallet_manager)? {
512                    authority
513                } else {
514                    default_signer
515                        .signer_from_path(matches, wallet_manager)?
516                        .pubkey()
517                };
518
519            CliCommandInfo::without_signers(CliCommand::ProgramV4(ProgramV4CliCommand::Show {
520                account_pubkey: pubkey_of(matches, "program-id"),
521                authority,
522                all: matches.is_present("all"),
523            }))
524        }
525        ("download", Some(matches)) => {
526            CliCommandInfo::without_signers(CliCommand::ProgramV4(ProgramV4CliCommand::Dump {
527                account_pubkey: pubkey_of(matches, "program-id"),
528                output_location: matches.value_of("path-to-elf").unwrap().to_string(),
529            }))
530        }
531        _ => unreachable!(),
532    };
533    Ok(response)
534}
535
536pub fn process_program_v4_subcommand(
537    rpc_client: Arc<RpcClient>,
538    config: &CliConfig,
539    program_subcommand: &ProgramV4CliCommand,
540) -> ProcessResult {
541    match program_subcommand {
542        ProgramV4CliCommand::Deploy {
543            additional_cli_config,
544            program_address,
545            buffer_address,
546            upload_signer_index,
547            authority_signer_index,
548            path_to_elf,
549            upload_range,
550        } => {
551            let mut program_data = Vec::new();
552            if let Some(path_to_elf) = path_to_elf {
553                let mut file = File::open(path_to_elf)
554                    .map_err(|err| format!("Unable to open program file: {err}"))?;
555                file.read_to_end(&mut program_data)
556                    .map_err(|err| format!("Unable to read program file: {err}"))?;
557            }
558            process_deploy_program(
559                rpc_client,
560                config,
561                additional_cli_config,
562                program_address,
563                buffer_address.as_ref(),
564                upload_signer_index.as_ref(),
565                authority_signer_index,
566                &program_data,
567                upload_range.clone(),
568            )
569        }
570        ProgramV4CliCommand::Retract {
571            additional_cli_config,
572            program_address,
573            authority_signer_index,
574            close_program_entirely,
575        } => process_retract_program(
576            rpc_client,
577            config,
578            additional_cli_config,
579            authority_signer_index,
580            program_address,
581            *close_program_entirely,
582        ),
583        ProgramV4CliCommand::TransferAuthority {
584            additional_cli_config,
585            program_address,
586            authority_signer_index,
587            new_authority_signer_index,
588        } => process_transfer_authority_of_program(
589            rpc_client,
590            config,
591            additional_cli_config,
592            authority_signer_index,
593            new_authority_signer_index,
594            program_address,
595        ),
596        ProgramV4CliCommand::Finalize {
597            additional_cli_config,
598            program_address,
599            authority_signer_index,
600            next_version_signer_index,
601        } => process_finalize_program(
602            rpc_client,
603            config,
604            additional_cli_config,
605            authority_signer_index,
606            next_version_signer_index,
607            program_address,
608        ),
609        ProgramV4CliCommand::Show {
610            account_pubkey,
611            authority,
612            all,
613        } => process_show(rpc_client, config, *account_pubkey, *authority, *all),
614        ProgramV4CliCommand::Dump {
615            account_pubkey,
616            output_location,
617        } => process_dump(rpc_client, config, *account_pubkey, output_location),
618    }
619}
620
621// This function can be used for the following use-cases
622// * Upload a new buffer account without deploying it (preparation for two-step redeployment)
623//   - buffer_address must be `Some(program_signer.pubkey())`
624//   - upload_signer_index must be `Some(program_signer_index)`
625// * Upload a new program account and deploy it
626//   - buffer_address must be `None`
627//   - upload_signer_index must be `Some(program_signer_index)`
628// * Single-step redeploy an existing program using the original program account
629//   - buffer_address must be `None`
630//   - upload_signer_index must be `None`
631// * Single-step redeploy an existing program using a buffer account
632//   - buffer_address must be `Some(buffer_signer.pubkey())`
633//   - upload_signer_index must be `Some(buffer_signer_index)`
634// * Two-step redeploy an existing program using a buffer account
635//   - buffer_address must be `Some(buffer_signer.pubkey())`
636//   - upload_signer_index must be None
637pub fn process_deploy_program(
638    rpc_client: Arc<RpcClient>,
639    config: &CliConfig,
640    additional_cli_config: &AdditionalCliConfig,
641    program_address: &Pubkey,
642    buffer_address: Option<&Pubkey>,
643    upload_signer_index: Option<&SignerIndex>,
644    auth_signer_index: &SignerIndex,
645    program_data: &[u8],
646    upload_range: Range<Option<usize>>,
647) -> ProcessResult {
648    let payer_pubkey = config.signers[0].pubkey();
649    let authority_pubkey = config.signers[*auth_signer_index].pubkey();
650
651    // Check requested command makes sense given the on-chain state
652    let program_account = rpc_client
653        .get_account_with_commitment(program_address, config.commitment)?
654        .value;
655    let buffer_account = if let Some(buffer_address) = buffer_address {
656        rpc_client
657            .get_account_with_commitment(buffer_address, config.commitment)?
658            .value
659    } else {
660        None
661    };
662    let lamports_required = rpc_client.get_minimum_balance_for_rent_exemption(
663        LoaderV4State::program_data_offset().saturating_add(program_data.len()),
664    )?;
665    let program_account_exists = program_account
666        .as_ref()
667        .map(|account| loader_v4::check_id(&account.owner))
668        .unwrap_or(false);
669    if upload_signer_index
670        .map(|index| &config.signers[*index].pubkey() == program_address)
671        .unwrap_or(false)
672    {
673        // Deploy new program
674        if program_account_exists {
675            return Err("Program account does exist already. Did you perhaps intent to redeploy an existing program instead? Then use --program-id instead of --program-keypair.".into());
676        }
677    } else {
678        // Redeploy an existing program
679        if !program_account_exists {
680            return Err("Program account does not exist. Did you perhaps intent to deploy a new program instead? Then use --program-keypair instead of --program-id.".into());
681        }
682    }
683    if let Some(program_account) = program_account.as_ref() {
684        if !system_program::check_id(&program_account.owner)
685            && !loader_v4::check_id(&program_account.owner)
686        {
687            return Err(format!("{program_address} is not owned by loader-v4").into());
688        }
689    }
690    if let Some(buffer_account) = buffer_account.as_ref() {
691        if !system_program::check_id(&buffer_account.owner)
692            && !loader_v4::check_id(&buffer_account.owner)
693        {
694            return Err(format!("{} is not owned by loader-v4", buffer_address.unwrap()).into());
695        }
696    }
697
698    // Download feature set
699    let mut feature_set = FeatureSet::default();
700    for feature_ids in FEATURE_NAMES
701        .keys()
702        .cloned()
703        .collect::<Vec<Pubkey>>()
704        .chunks(MAX_MULTIPLE_ACCOUNTS)
705    {
706        rpc_client
707            .get_multiple_accounts(feature_ids)?
708            .into_iter()
709            .zip(feature_ids)
710            .for_each(|(account, feature_id)| {
711                let activation_slot = account.and_then(status_from_account);
712
713                if let Some(CliFeatureStatus::Active(slot)) = activation_slot {
714                    feature_set.activate(feature_id, slot);
715                }
716            });
717    }
718    let program_runtime_environment = agave_syscalls::create_program_runtime_environment_v1(
719        &feature_set.runtime_features(),
720        &SVMTransactionExecutionBudget::new_with_defaults(
721            feature_set.is_active(&raise_cpi_nesting_limit_to_8::id()),
722        ),
723        true,
724        false,
725    )
726    .unwrap();
727
728    // Verify the program
729    let upload_range =
730        upload_range.start.unwrap_or(0)..upload_range.end.unwrap_or(program_data.len());
731    const MAX_LEN: usize =
732        (MAX_PERMITTED_DATA_LENGTH as usize).saturating_sub(LoaderV4State::program_data_offset());
733    if program_data.len() > MAX_LEN {
734        return Err(format!(
735            "Program length {} exceeds maximum length {}",
736            program_data.len(),
737            MAX_LEN,
738        )
739        .into());
740    }
741    if upload_range.end > program_data.len() {
742        return Err(format!(
743            "Range end {} exceeds program length {}",
744            upload_range.end,
745            program_data.len(),
746        )
747        .into());
748    }
749    let executable =
750        Executable::<InvokeContext>::from_elf(program_data, Arc::new(program_runtime_environment))
751            .map_err(|err| format!("ELF error: {err}"))?;
752    executable
753        .verify::<RequisiteVerifier>()
754        .map_err(|err| format!("ELF error: {err}"))?;
755
756    // Create and add retract and set_program_length instructions
757    let mut initial_instructions = Vec::default();
758    if let Some(program_account) = program_account.as_ref() {
759        if let Some(retract_instruction) =
760            build_retract_instruction(program_account, program_address, &authority_pubkey)?
761        {
762            initial_instructions.insert(0, retract_instruction);
763        }
764        let (mut set_program_length_instructions, _lamports_required) =
765            build_set_program_length_instructions(
766                rpc_client.clone(),
767                config,
768                auth_signer_index,
769                program_account,
770                program_address,
771                program_data.len() as u32,
772            )?;
773        if !set_program_length_instructions.is_empty() {
774            initial_instructions.append(&mut set_program_length_instructions);
775        }
776    }
777
778    let upload_address = buffer_address.unwrap_or(program_address);
779    let (upload_account, upload_account_length) = if buffer_address.is_some() {
780        (buffer_account, upload_range.len())
781    } else {
782        (program_account, program_data.len())
783    };
784    let existing_lamports = upload_account
785        .as_ref()
786        .map(|account| account.lamports)
787        .unwrap_or(0);
788    // Create and add create_buffer message
789    if let Some(upload_account) = upload_account.as_ref() {
790        if system_program::check_id(&upload_account.owner) {
791            initial_instructions.append(&mut vec![
792                system_instruction::transfer(&payer_pubkey, upload_address, lamports_required),
793                system_instruction::assign(upload_address, &loader_v4::id()),
794                instruction::set_program_length(
795                    upload_address,
796                    &authority_pubkey,
797                    upload_account_length as u32,
798                    &payer_pubkey,
799                ),
800            ]);
801        }
802    } else {
803        initial_instructions.append(&mut instruction::create_buffer(
804            &payer_pubkey,
805            upload_address,
806            lamports_required,
807            &authority_pubkey,
808            upload_account_length as u32,
809            &payer_pubkey,
810        ));
811    }
812
813    // Create and add write messages
814    let mut write_messages = vec![];
815    if upload_signer_index.is_none() {
816        if upload_account.is_none() {
817            return Err(format!(
818                "No ELF was provided or uploaded to the account {:?}",
819                upload_address,
820            )
821            .into());
822        }
823    } else {
824        if upload_range.is_empty() {
825            return Err(format!("Attempting to upload empty range {:?}", upload_range).into());
826        }
827        let first_write_message = Message::new(
828            &[instruction::write(
829                upload_address,
830                &authority_pubkey,
831                0,
832                Vec::new(),
833            )],
834            Some(&payer_pubkey),
835        );
836        let chunk_size = calculate_max_chunk_size(first_write_message);
837        for (chunk, i) in program_data[upload_range.clone()]
838            .chunks(chunk_size)
839            .zip(0usize..)
840        {
841            write_messages.push(vec![instruction::write(
842                upload_address,
843                &authority_pubkey,
844                (upload_range.start as u32).saturating_add(i.saturating_mul(chunk_size) as u32),
845                chunk.to_vec(),
846            )]);
847        }
848    }
849
850    let final_instructions = if buffer_address == Some(program_address) {
851        // Upload to buffer only and skip actual deployment
852        Vec::default()
853    } else if let Some(buffer_address) = buffer_address {
854        // Redeployment with a buffer
855        vec![
856            instruction::copy(
857                program_address,
858                &authority_pubkey,
859                buffer_address,
860                upload_range.start as u32,
861                0,
862                upload_range.len() as u32,
863            ),
864            instruction::deploy(program_address, &authority_pubkey),
865            instruction::set_program_length(buffer_address, &authority_pubkey, 0, &payer_pubkey),
866        ]
867    } else {
868        // Initial deployment or redeployment without a buffer
869        vec![instruction::deploy(program_address, &authority_pubkey)]
870    };
871
872    send_messages(
873        rpc_client,
874        config,
875        additional_cli_config,
876        auth_signer_index,
877        if initial_instructions.is_empty() {
878            Vec::default()
879        } else {
880            vec![initial_instructions]
881        },
882        write_messages,
883        if final_instructions.is_empty() {
884            Vec::default()
885        } else {
886            vec![final_instructions]
887        },
888        lamports_required.saturating_sub(existing_lamports),
889        config.output_format.formatted_string(&CliProgramId {
890            program_id: program_address.to_string(),
891            signature: None,
892        }),
893    )
894}
895
896fn process_retract_program(
897    rpc_client: Arc<RpcClient>,
898    config: &CliConfig,
899    additional_cli_config: &AdditionalCliConfig,
900    auth_signer_index: &SignerIndex,
901    program_address: &Pubkey,
902    close_program_entirely: bool,
903) -> ProcessResult {
904    let payer_pubkey = config.signers[0].pubkey();
905    let authority_pubkey = config.signers[*auth_signer_index].pubkey();
906
907    let Some(program_account) = rpc_client
908        .get_account_with_commitment(program_address, config.commitment)?
909        .value
910    else {
911        return Err("Program account does not exist".into());
912    };
913    if !loader_v4::check_id(&program_account.owner) {
914        return Err(format!("{program_address} is not owned by loader-v4").into());
915    }
916
917    let mut instructions = Vec::default();
918    let retract_instruction =
919        build_retract_instruction(&program_account, program_address, &authority_pubkey)?;
920    if let Some(retract_instruction) = retract_instruction {
921        instructions.push(retract_instruction);
922    }
923    if close_program_entirely {
924        let set_program_length_instruction =
925            instruction::set_program_length(program_address, &authority_pubkey, 0, &payer_pubkey);
926        instructions.push(set_program_length_instruction);
927    } else if instructions.is_empty() {
928        return Err("Program is retracted already".into());
929    }
930
931    send_messages(
932        rpc_client,
933        config,
934        additional_cli_config,
935        auth_signer_index,
936        vec![instructions],
937        Vec::default(),
938        Vec::default(),
939        0,
940        config.output_format.formatted_string(&CliProgramId {
941            program_id: program_address.to_string(),
942            signature: None,
943        }),
944    )
945}
946
947fn process_transfer_authority_of_program(
948    rpc_client: Arc<RpcClient>,
949    config: &CliConfig,
950    additional_cli_config: &AdditionalCliConfig,
951    auth_signer_index: &SignerIndex,
952    new_auth_signer_index: &SignerIndex,
953    program_address: &Pubkey,
954) -> ProcessResult {
955    if let Some(program_account) = rpc_client
956        .get_account_with_commitment(program_address, config.commitment)?
957        .value
958    {
959        if !loader_v4::check_id(&program_account.owner) {
960            return Err(format!("{program_address} is not owned by loader-v4").into());
961        }
962    } else {
963        return Err(format!("Unable to find the account {program_address}").into());
964    }
965
966    let authority_pubkey = config.signers[*auth_signer_index].pubkey();
967    let new_authority_pubkey = config.signers[*new_auth_signer_index].pubkey();
968
969    let messages = vec![vec![instruction::transfer_authority(
970        program_address,
971        &authority_pubkey,
972        &new_authority_pubkey,
973    )]];
974
975    send_messages(
976        rpc_client,
977        config,
978        additional_cli_config,
979        auth_signer_index,
980        messages,
981        Vec::default(),
982        Vec::default(),
983        0,
984        config.output_format.formatted_string(&CliProgramId {
985            program_id: program_address.to_string(),
986            signature: None,
987        }),
988    )
989}
990
991fn process_finalize_program(
992    rpc_client: Arc<RpcClient>,
993    config: &CliConfig,
994    additional_cli_config: &AdditionalCliConfig,
995    auth_signer_index: &SignerIndex,
996    next_version_signer_index: &SignerIndex,
997    program_address: &Pubkey,
998) -> ProcessResult {
999    if let Some(program_account) = rpc_client
1000        .get_account_with_commitment(program_address, config.commitment)?
1001        .value
1002    {
1003        if !loader_v4::check_id(&program_account.owner) {
1004            return Err(format!("{program_address} is not owned by loader-v4").into());
1005        }
1006    } else {
1007        return Err(format!("Unable to find the account {program_address}").into());
1008    }
1009
1010    let authority_pubkey = config.signers[*auth_signer_index].pubkey();
1011    let next_version_pubkey = config.signers[*next_version_signer_index].pubkey();
1012
1013    let messages = vec![vec![instruction::finalize(
1014        program_address,
1015        &authority_pubkey,
1016        &next_version_pubkey,
1017    )]];
1018
1019    send_messages(
1020        rpc_client,
1021        config,
1022        additional_cli_config,
1023        auth_signer_index,
1024        messages,
1025        Vec::default(),
1026        Vec::default(),
1027        0,
1028        config.output_format.formatted_string(&CliProgramId {
1029            program_id: program_address.to_string(),
1030            signature: None,
1031        }),
1032    )
1033}
1034
1035fn process_show(
1036    rpc_client: Arc<RpcClient>,
1037    config: &CliConfig,
1038    program_address: Option<Pubkey>,
1039    authority: Pubkey,
1040    all: bool,
1041) -> ProcessResult {
1042    if let Some(program_address) = program_address {
1043        if let Some(account) = rpc_client
1044            .get_account_with_commitment(&program_address, config.commitment)?
1045            .value
1046        {
1047            if loader_v4::check_id(&account.owner) {
1048                if let Ok(state) = solana_loader_v4_program::get_state(&account.data) {
1049                    let status = match state.status {
1050                        LoaderV4Status::Retracted => "retracted",
1051                        LoaderV4Status::Deployed => "deployed",
1052                        LoaderV4Status::Finalized => "finalized",
1053                    };
1054                    Ok(config.output_format.formatted_string(&CliProgramV4 {
1055                        program_id: program_address.to_string(),
1056                        owner: account.owner.to_string(),
1057                        authority: state.authority_address_or_next_version.to_string(),
1058                        last_deploy_slot: state.slot,
1059                        data_len: account
1060                            .data
1061                            .len()
1062                            .saturating_sub(LoaderV4State::program_data_offset()),
1063                        status: status.to_string(),
1064                    }))
1065                } else {
1066                    Err(format!("{program_address} program state is invalid").into())
1067                }
1068            } else {
1069                Err(format!("{program_address} is not owned by loader-v4").into())
1070            }
1071        } else {
1072            Err(format!("Unable to find the account {program_address}").into())
1073        }
1074    } else {
1075        let authority_pubkey = if all { None } else { Some(authority) };
1076        let programs = get_programs(rpc_client, config, authority_pubkey)?;
1077        Ok(config.output_format.formatted_string(&programs))
1078    }
1079}
1080
1081pub fn process_dump(
1082    rpc_client: Arc<RpcClient>,
1083    config: &CliConfig,
1084    account_pubkey: Option<Pubkey>,
1085    output_location: &str,
1086) -> ProcessResult {
1087    if let Some(account_pubkey) = account_pubkey {
1088        if let Some(account) = rpc_client
1089            .get_account_with_commitment(&account_pubkey, config.commitment)?
1090            .value
1091        {
1092            if loader_v4::check_id(&account.owner) {
1093                let mut f = File::create(output_location)?;
1094                f.write_all(&account.data[LoaderV4State::program_data_offset()..])?;
1095                Ok(format!("Wrote program to {output_location}"))
1096            } else {
1097                Err(format!("{account_pubkey} is not owned by loader-v4").into())
1098            }
1099        } else {
1100            Err(format!("Unable to find the account {account_pubkey}").into())
1101        }
1102    } else {
1103        Err("No account specified".into())
1104    }
1105}
1106
1107#[allow(clippy::too_many_arguments)]
1108fn send_messages(
1109    rpc_client: Arc<RpcClient>,
1110    config: &CliConfig,
1111    additional_cli_config: &AdditionalCliConfig,
1112    auth_signer_index: &SignerIndex,
1113    initial_messages: Vec<Vec<Instruction>>,
1114    write_messages: Vec<Vec<Instruction>>,
1115    final_messages: Vec<Vec<Instruction>>,
1116    balance_needed: u64,
1117    ok_result: String,
1118) -> ProcessResult {
1119    let payer_pubkey = config.signers[0].pubkey();
1120    let blockhash = additional_cli_config
1121        .blockhash_query
1122        .get_blockhash(&rpc_client, config.commitment)?;
1123    let compute_unit_config = ComputeUnitConfig {
1124        compute_unit_price: additional_cli_config.compute_unit_price,
1125        compute_unit_limit: ComputeUnitLimit::Simulated,
1126    };
1127    let simulate_messages = |message_prototypes: Vec<Vec<Instruction>>| {
1128        let mut messages = Vec::with_capacity(message_prototypes.len());
1129        for instructions in message_prototypes.into_iter() {
1130            let mut message = Message::new_with_blockhash(
1131                &instructions.with_compute_unit_config(&compute_unit_config),
1132                Some(&payer_pubkey),
1133                &blockhash,
1134            );
1135            simulate_and_update_compute_unit_limit(
1136                &ComputeUnitLimit::Simulated,
1137                &rpc_client,
1138                &mut message,
1139            )?;
1140            messages.push(message);
1141        }
1142        Ok::<Vec<solana_message::Message>, Box<dyn std::error::Error>>(messages)
1143    };
1144    let initial_messages = simulate_messages(initial_messages)?;
1145    let write_messages = simulate_messages(write_messages)?;
1146    let final_messages = simulate_messages(final_messages)?;
1147
1148    let mut fee = Saturating(0);
1149    for message in initial_messages.iter() {
1150        fee += rpc_client.get_fee_for_message(message)?;
1151    }
1152    for message in final_messages.iter() {
1153        fee += rpc_client.get_fee_for_message(message)?;
1154    }
1155    // Assume all write messages cost the same
1156    if let Some(message) = write_messages.first() {
1157        fee += rpc_client
1158            .get_fee_for_message(message)?
1159            .saturating_mul(write_messages.len() as u64);
1160    }
1161    check_account_for_spend_and_fee_with_commitment(
1162        &rpc_client,
1163        &payer_pubkey,
1164        balance_needed,
1165        fee.0,
1166        config.commitment,
1167    )?;
1168
1169    let send_or_return_message = |message: Message| {
1170        let signers = (0..message.header.num_required_signatures)
1171            .map(|signer_index| {
1172                let key = message.account_keys[signer_index as usize];
1173                config
1174                    .signers
1175                    .iter()
1176                    .find(|signer| signer.pubkey() == key)
1177                    .unwrap()
1178            })
1179            .collect::<Vec<_>>();
1180        let mut tx = Transaction::new_unsigned(message);
1181        tx.try_sign(&signers, blockhash)?;
1182        if additional_cli_config.sign_only {
1183            return_signers_with_config(
1184                &tx,
1185                &config.output_format,
1186                &ReturnSignersConfig {
1187                    dump_transaction_message: additional_cli_config.dump_transaction_message,
1188                },
1189            )
1190        } else {
1191            rpc_client
1192                .send_and_confirm_transaction_with_spinner_and_config(
1193                    &tx,
1194                    config.commitment,
1195                    config.send_transaction_config,
1196                )
1197                .map_err(|err| format!("Failed to send message: {err}").into())
1198                .map(|_| String::new())
1199        }
1200    };
1201
1202    for message in initial_messages.into_iter() {
1203        let result = send_or_return_message(message)?;
1204        if additional_cli_config.sign_only {
1205            return Ok(result);
1206        }
1207    }
1208
1209    if !write_messages.is_empty() {
1210        let connection_cache = if config.use_quic {
1211            ConnectionCache::new_quic("connection_cache_cli_program_v4_quic", 1)
1212        } else {
1213            ConnectionCache::with_udp("connection_cache_cli_program_v4_udp", 1)
1214        };
1215        let transaction_errors = match connection_cache {
1216            ConnectionCache::Udp(cache) => TpuClient::new_with_connection_cache(
1217                rpc_client.clone(),
1218                &config.websocket_url,
1219                TpuClientConfig::default(),
1220                cache,
1221            )?
1222            .send_and_confirm_messages_with_spinner(
1223                &write_messages,
1224                &[config.signers[0], config.signers[*auth_signer_index]],
1225            ),
1226            ConnectionCache::Quic(cache) => {
1227                let tpu_client_fut =
1228                    solana_client::nonblocking::tpu_client::TpuClient::new_with_connection_cache(
1229                        rpc_client.get_inner_client().clone(),
1230                        &config.websocket_url,
1231                        solana_client::tpu_client::TpuClientConfig::default(),
1232                        cache,
1233                    );
1234                let tpu_client = (!additional_cli_config.use_rpc).then(|| {
1235                    rpc_client
1236                        .runtime()
1237                        .block_on(tpu_client_fut)
1238                        .expect("Should return a valid tpu client")
1239                });
1240
1241                send_and_confirm_transactions_in_parallel_blocking_v2(
1242                    rpc_client.clone(),
1243                    tpu_client,
1244                    &write_messages,
1245                    &[config.signers[0], config.signers[*auth_signer_index]],
1246                    SendAndConfirmConfigV2 {
1247                        resign_txs_count: Some(5),
1248                        with_spinner: true,
1249                        rpc_send_transaction_config: config.send_transaction_config,
1250                    },
1251                )
1252            }
1253        }
1254        .map_err(|err| format!("Data writes to account failed: {err}"))?
1255        .into_iter()
1256        .flatten()
1257        .collect::<Vec<_>>();
1258
1259        if !transaction_errors.is_empty() {
1260            for transaction_error in &transaction_errors {
1261                error!("{:?}", transaction_error);
1262            }
1263            return Err(format!("{} write transactions failed", transaction_errors.len()).into());
1264        }
1265    }
1266
1267    for message in final_messages.into_iter() {
1268        let result = send_or_return_message(message)?;
1269        if additional_cli_config.sign_only {
1270            return Ok(result);
1271        }
1272    }
1273
1274    Ok(ok_result)
1275}
1276
1277fn build_retract_instruction(
1278    account: &Account,
1279    buffer_address: &Pubkey,
1280    authority: &Pubkey,
1281) -> Result<Option<Instruction>, Box<dyn std::error::Error>> {
1282    if !loader_v4::check_id(&account.owner) {
1283        return Ok(None);
1284    }
1285
1286    if let Ok(LoaderV4State {
1287        slot: _,
1288        authority_address_or_next_version,
1289        status,
1290    }) = solana_loader_v4_program::get_state(&account.data)
1291    {
1292        if authority != authority_address_or_next_version {
1293            return Err(
1294                "Program authority does not match with the provided authority address".into(),
1295            );
1296        }
1297
1298        match status {
1299            Retracted => Ok(None),
1300            LoaderV4Status::Deployed => Ok(Some(instruction::retract(buffer_address, authority))),
1301            LoaderV4Status::Finalized => Err("Program is immutable".into()),
1302        }
1303    } else {
1304        Err("Program account's state could not be deserialized".into())
1305    }
1306}
1307
1308fn build_set_program_length_instructions(
1309    rpc_client: Arc<RpcClient>,
1310    config: &CliConfig,
1311    auth_signer_index: &SignerIndex,
1312    account: &Account,
1313    buffer_address: &Pubkey,
1314    program_data_length: u32,
1315) -> Result<(Vec<Instruction>, u64), Box<dyn std::error::Error>> {
1316    let expected_account_data_len =
1317        LoaderV4State::program_data_offset().saturating_add(program_data_length as usize);
1318
1319    let lamports_required =
1320        rpc_client.get_minimum_balance_for_rent_exemption(expected_account_data_len)?;
1321
1322    if !loader_v4::check_id(&account.owner) {
1323        return Ok((Vec::default(), lamports_required));
1324    }
1325
1326    let payer_pubkey = config.signers[0].pubkey();
1327    let authority_pubkey = config.signers[*auth_signer_index].pubkey();
1328    let expected_account_data_len =
1329        LoaderV4State::program_data_offset().saturating_add(program_data_length as usize);
1330
1331    let lamports_required =
1332        rpc_client.get_minimum_balance_for_rent_exemption(expected_account_data_len)?;
1333
1334    if !loader_v4::check_id(&account.owner) {
1335        return Ok((Vec::default(), lamports_required));
1336    }
1337
1338    if !account.data.is_empty() {
1339        if let Ok(LoaderV4State {
1340            slot: _,
1341            authority_address_or_next_version,
1342            status,
1343        }) = solana_loader_v4_program::get_state(&account.data)
1344        {
1345            if &authority_pubkey != authority_address_or_next_version {
1346                return Err(
1347                    "Program authority does not match with the provided authority address".into(),
1348                );
1349            }
1350
1351            if matches!(status, LoaderV4Status::Finalized) {
1352                return Err("Program is immutable".into());
1353            }
1354        } else {
1355            return Err("Program account's state could not be deserialized".into());
1356        }
1357    }
1358
1359    let set_program_length_instruction = instruction::set_program_length(
1360        buffer_address,
1361        &authority_pubkey,
1362        program_data_length,
1363        &payer_pubkey,
1364    );
1365
1366    match account.data.len().cmp(&expected_account_data_len) {
1367        Ordering::Less => {
1368            if account.lamports < lamports_required {
1369                let extra_lamports_required = lamports_required.saturating_sub(account.lamports);
1370                Ok((
1371                    vec![
1372                        system_instruction::transfer(
1373                            &payer_pubkey,
1374                            buffer_address,
1375                            extra_lamports_required,
1376                        ),
1377                        set_program_length_instruction,
1378                    ],
1379                    extra_lamports_required,
1380                ))
1381            } else {
1382                Ok((vec![set_program_length_instruction], 0))
1383            }
1384        }
1385        Ordering::Equal => {
1386            if account.lamports < lamports_required {
1387                return Err("Program account has less lamports than required for its size".into());
1388            }
1389            Ok((vec![], 0))
1390        }
1391        Ordering::Greater => {
1392            if account.lamports < lamports_required {
1393                return Err("Program account has less lamports than required for its size".into());
1394            }
1395            Ok((vec![set_program_length_instruction], 0))
1396        }
1397    }
1398}
1399
1400fn get_accounts_with_filter(
1401    rpc_client: Arc<RpcClient>,
1402    _config: &CliConfig,
1403    filters: Vec<RpcFilterType>,
1404    length: usize,
1405) -> Result<Vec<(Pubkey, Account)>, Box<dyn std::error::Error>> {
1406    let results = rpc_client.get_program_accounts_with_config(
1407        &loader_v4::id(),
1408        RpcProgramAccountsConfig {
1409            filters: Some(filters),
1410            account_config: RpcAccountInfoConfig {
1411                encoding: Some(UiAccountEncoding::Base64),
1412                data_slice: Some(UiDataSliceConfig { offset: 0, length }),
1413                ..RpcAccountInfoConfig::default()
1414            },
1415            ..RpcProgramAccountsConfig::default()
1416        },
1417    )?;
1418    Ok(results)
1419}
1420
1421fn get_programs(
1422    rpc_client: Arc<RpcClient>,
1423    config: &CliConfig,
1424    authority_pubkey: Option<Pubkey>,
1425) -> Result<CliProgramsV4, Box<dyn std::error::Error>> {
1426    let filters = if let Some(authority_pubkey) = authority_pubkey {
1427        vec![
1428            (RpcFilterType::Memcmp(Memcmp::new_base58_encoded(
1429                size_of::<u64>(),
1430                authority_pubkey.as_ref(),
1431            ))),
1432        ]
1433    } else {
1434        vec![]
1435    };
1436
1437    let results = get_accounts_with_filter(
1438        rpc_client,
1439        config,
1440        filters,
1441        LoaderV4State::program_data_offset(),
1442    )?;
1443
1444    let mut programs = vec![];
1445    for (program, account) in results.iter() {
1446        if let Ok(state) = solana_loader_v4_program::get_state(&account.data) {
1447            let status = match state.status {
1448                LoaderV4Status::Retracted => "retracted",
1449                LoaderV4Status::Deployed => "deployed",
1450                LoaderV4Status::Finalized => "finalized",
1451            };
1452            programs.push(CliProgramV4 {
1453                program_id: program.to_string(),
1454                owner: account.owner.to_string(),
1455                authority: state.authority_address_or_next_version.to_string(),
1456                last_deploy_slot: state.slot,
1457                status: status.to_string(),
1458                data_len: account
1459                    .data
1460                    .len()
1461                    .saturating_sub(LoaderV4State::program_data_offset()),
1462            });
1463        } else {
1464            return Err(format!("Error parsing Program account {program}").into());
1465        }
1466    }
1467    Ok(CliProgramsV4 { programs })
1468}
1469
1470#[cfg(test)]
1471mod tests {
1472    use {
1473        super::*,
1474        crate::{clap_app::get_clap_app, cli::parse_command},
1475        serde_json::json,
1476        solana_keypair::{keypair_from_seed, read_keypair_file, write_keypair_file, Keypair},
1477        solana_rpc_client_api::{
1478            request::RpcRequest,
1479            response::{Response, RpcResponseContext},
1480        },
1481        std::collections::HashMap,
1482    };
1483
1484    fn program_authority() -> solana_keypair::Keypair {
1485        keypair_from_seed(&[3u8; 32]).unwrap()
1486    }
1487
1488    fn rpc_client_no_existing_program() -> RpcClient {
1489        RpcClient::new_mock("succeeds".to_string())
1490    }
1491
1492    fn rpc_client_with_program_data(data: &str, loader_is_owner: bool) -> RpcClient {
1493        let owner = if loader_is_owner {
1494            "LoaderV411111111111111111111111111111111111"
1495        } else {
1496            "Vote111111111111111111111111111111111111111"
1497        };
1498        let account_info_response = json!(Response {
1499            context: RpcResponseContext {
1500                slot: 1,
1501                api_version: None
1502            },
1503            value: json!({
1504                "data": [data, "base64"],
1505                "lamports": 42,
1506                "owner": owner,
1507                "executable": true,
1508                "rentEpoch": 1,
1509            }),
1510        });
1511        let mut mocks = HashMap::new();
1512        mocks.insert(RpcRequest::GetAccountInfo, account_info_response);
1513        RpcClient::new_mock_with_mocks("".to_string(), mocks)
1514    }
1515
1516    fn rpc_client_wrong_account_owner() -> RpcClient {
1517        rpc_client_with_program_data(
1518            "AAAAAAAAAADtSSjGKNHCxurpAziQWZVhKVknOlxj+TY2wUYUrIc30QAAAAAAAAAA",
1519            false,
1520        )
1521    }
1522
1523    fn rpc_client_wrong_authority() -> RpcClient {
1524        rpc_client_with_program_data(
1525            "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
1526            true,
1527        )
1528    }
1529
1530    fn rpc_client_with_program_retracted() -> RpcClient {
1531        rpc_client_with_program_data(
1532            "AAAAAAAAAADtSSjGKNHCxurpAziQWZVhKVknOlxj+TY2wUYUrIc30QAAAAAAAAAA",
1533            true,
1534        )
1535    }
1536
1537    fn rpc_client_with_program_deployed() -> RpcClient {
1538        rpc_client_with_program_data(
1539            "AAAAAAAAAADtSSjGKNHCxurpAziQWZVhKVknOlxj+TY2wUYUrIc30QEAAAAAAAAA",
1540            true,
1541        )
1542    }
1543
1544    fn rpc_client_with_program_finalized() -> RpcClient {
1545        rpc_client_with_program_data(
1546            "AAAAAAAAAADtSSjGKNHCxurpAziQWZVhKVknOlxj+TY2wUYUrIc30QIAAAAAAAAA",
1547            true,
1548        )
1549    }
1550
1551    #[test]
1552    fn test_deploy() {
1553        let mut config = CliConfig::default();
1554        let mut program_data = Vec::new();
1555        let mut file = File::open("tests/fixtures/noop.so").unwrap();
1556        file.read_to_end(&mut program_data).unwrap();
1557
1558        let payer = keypair_from_seed(&[1u8; 32]).unwrap();
1559        let program_signer = keypair_from_seed(&[2u8; 32]).unwrap();
1560        let authority_signer = program_authority();
1561
1562        config.signers.push(&payer);
1563        config.signers.push(&program_signer);
1564        config.signers.push(&authority_signer);
1565
1566        assert!(process_deploy_program(
1567            Arc::new(rpc_client_no_existing_program()),
1568            &config,
1569            &AdditionalCliConfig::default(),
1570            &program_signer.pubkey(),
1571            None,
1572            Some(&1),
1573            &2,
1574            &program_data,
1575            None..None,
1576        )
1577        .is_ok());
1578
1579        assert!(process_deploy_program(
1580            Arc::new(rpc_client_no_existing_program()),
1581            &config,
1582            &AdditionalCliConfig::default(),
1583            &program_signer.pubkey(),
1584            Some(&program_signer.pubkey()),
1585            Some(&1),
1586            &2,
1587            &program_data,
1588            None..None,
1589        )
1590        .is_ok());
1591
1592        assert!(process_deploy_program(
1593            Arc::new(rpc_client_wrong_account_owner()),
1594            &config,
1595            &AdditionalCliConfig::default(),
1596            &program_signer.pubkey(),
1597            None,
1598            Some(&1),
1599            &2,
1600            &program_data,
1601            None..None,
1602        )
1603        .is_err());
1604
1605        assert!(process_deploy_program(
1606            Arc::new(rpc_client_with_program_deployed()),
1607            &config,
1608            &AdditionalCliConfig::default(),
1609            &program_signer.pubkey(),
1610            None,
1611            Some(&1),
1612            &2,
1613            &program_data,
1614            None..None,
1615        )
1616        .is_err());
1617    }
1618
1619    #[test]
1620    fn test_redeploy() {
1621        let mut config = CliConfig::default();
1622        let mut program_data = Vec::new();
1623        let mut file = File::open("tests/fixtures/noop.so").unwrap();
1624        file.read_to_end(&mut program_data).unwrap();
1625
1626        let payer = keypair_from_seed(&[1u8; 32]).unwrap();
1627        let program_address = Pubkey::new_unique();
1628        let authority_signer = program_authority();
1629
1630        config.signers.push(&payer);
1631        config.signers.push(&authority_signer);
1632
1633        // Redeploying a non-existent program should fail
1634        assert!(process_deploy_program(
1635            Arc::new(rpc_client_no_existing_program()),
1636            &config,
1637            &AdditionalCliConfig::default(),
1638            &program_address,
1639            None,
1640            None,
1641            &1,
1642            &program_data,
1643            None..None,
1644        )
1645        .is_err());
1646
1647        assert!(process_deploy_program(
1648            Arc::new(rpc_client_with_program_retracted()),
1649            &config,
1650            &AdditionalCliConfig::default(),
1651            &program_address,
1652            None,
1653            None,
1654            &1,
1655            &program_data,
1656            None..None,
1657        )
1658        .is_ok());
1659
1660        assert!(process_deploy_program(
1661            Arc::new(rpc_client_with_program_deployed()),
1662            &config,
1663            &AdditionalCliConfig::default(),
1664            &program_address,
1665            None,
1666            None,
1667            &1,
1668            &program_data,
1669            None..None,
1670        )
1671        .is_ok());
1672
1673        assert!(process_deploy_program(
1674            Arc::new(rpc_client_with_program_finalized()),
1675            &config,
1676            &AdditionalCliConfig::default(),
1677            &program_address,
1678            None,
1679            None,
1680            &1,
1681            &program_data,
1682            None..None,
1683        )
1684        .is_err());
1685
1686        assert!(process_deploy_program(
1687            Arc::new(rpc_client_wrong_account_owner()),
1688            &config,
1689            &AdditionalCliConfig::default(),
1690            &program_address,
1691            None,
1692            None,
1693            &1,
1694            &program_data,
1695            None..None,
1696        )
1697        .is_err());
1698
1699        assert!(process_deploy_program(
1700            Arc::new(rpc_client_wrong_authority()),
1701            &config,
1702            &AdditionalCliConfig::default(),
1703            &program_address,
1704            None,
1705            None,
1706            &1,
1707            &program_data,
1708            None..None,
1709        )
1710        .is_err());
1711    }
1712
1713    #[test]
1714    fn test_redeploy_from_source() {
1715        let mut config = CliConfig::default();
1716        let mut program_data = Vec::new();
1717        let mut file = File::open("tests/fixtures/noop.so").unwrap();
1718        file.read_to_end(&mut program_data).unwrap();
1719
1720        let payer = keypair_from_seed(&[1u8; 32]).unwrap();
1721        let buffer_signer = keypair_from_seed(&[2u8; 32]).unwrap();
1722        let program_address = Pubkey::new_unique();
1723        let authority_signer = program_authority();
1724
1725        config.signers.push(&payer);
1726        config.signers.push(&buffer_signer);
1727        config.signers.push(&authority_signer);
1728
1729        // Redeploying a non-existent program should fail
1730        assert!(process_deploy_program(
1731            Arc::new(rpc_client_no_existing_program()),
1732            &config,
1733            &AdditionalCliConfig::default(),
1734            &program_address,
1735            Some(&buffer_signer.pubkey()),
1736            Some(&1),
1737            &2,
1738            &program_data,
1739            None..None,
1740        )
1741        .is_err());
1742
1743        assert!(process_deploy_program(
1744            Arc::new(rpc_client_wrong_account_owner()),
1745            &config,
1746            &AdditionalCliConfig::default(),
1747            &program_address,
1748            Some(&buffer_signer.pubkey()),
1749            Some(&1),
1750            &2,
1751            &program_data,
1752            None..None,
1753        )
1754        .is_err());
1755
1756        assert!(process_deploy_program(
1757            Arc::new(rpc_client_wrong_authority()),
1758            &config,
1759            &AdditionalCliConfig::default(),
1760            &program_address,
1761            Some(&buffer_signer.pubkey()),
1762            Some(&1),
1763            &2,
1764            &program_data,
1765            None..None,
1766        )
1767        .is_err());
1768    }
1769
1770    #[test]
1771    fn test_retract() {
1772        let mut config = CliConfig::default();
1773
1774        let payer = keypair_from_seed(&[1u8; 32]).unwrap();
1775        let program_signer = keypair_from_seed(&[2u8; 32]).unwrap();
1776        let authority_signer = program_authority();
1777
1778        config.signers.push(&payer);
1779        config.signers.push(&authority_signer);
1780
1781        for close_program_entirely in [false, true] {
1782            assert!(process_retract_program(
1783                Arc::new(rpc_client_no_existing_program()),
1784                &config,
1785                &AdditionalCliConfig::default(),
1786                &1,
1787                &program_signer.pubkey(),
1788                close_program_entirely,
1789            )
1790            .is_err());
1791
1792            assert!(
1793                process_retract_program(
1794                    Arc::new(rpc_client_with_program_retracted()),
1795                    &config,
1796                    &AdditionalCliConfig::default(),
1797                    &1,
1798                    &program_signer.pubkey(),
1799                    close_program_entirely,
1800                )
1801                .is_ok()
1802                    == close_program_entirely
1803            );
1804
1805            assert!(process_retract_program(
1806                Arc::new(rpc_client_with_program_deployed()),
1807                &config,
1808                &AdditionalCliConfig::default(),
1809                &1,
1810                &program_signer.pubkey(),
1811                close_program_entirely,
1812            )
1813            .is_ok());
1814
1815            assert!(process_retract_program(
1816                Arc::new(rpc_client_with_program_finalized()),
1817                &config,
1818                &AdditionalCliConfig::default(),
1819                &1,
1820                &program_signer.pubkey(),
1821                close_program_entirely,
1822            )
1823            .is_err());
1824
1825            assert!(process_retract_program(
1826                Arc::new(rpc_client_wrong_account_owner()),
1827                &config,
1828                &AdditionalCliConfig::default(),
1829                &1,
1830                &program_signer.pubkey(),
1831                close_program_entirely,
1832            )
1833            .is_err());
1834
1835            assert!(process_retract_program(
1836                Arc::new(rpc_client_wrong_authority()),
1837                &config,
1838                &AdditionalCliConfig::default(),
1839                &1,
1840                &program_signer.pubkey(),
1841                close_program_entirely,
1842            )
1843            .is_err());
1844        }
1845    }
1846
1847    #[test]
1848    fn test_transfer_authority() {
1849        let mut config = CliConfig::default();
1850
1851        let payer = keypair_from_seed(&[1u8; 32]).unwrap();
1852        let program_signer = keypair_from_seed(&[2u8; 32]).unwrap();
1853        let authority_signer = program_authority();
1854        let new_authority_signer = program_authority();
1855
1856        config.signers.push(&payer);
1857        config.signers.push(&authority_signer);
1858        config.signers.push(&new_authority_signer);
1859
1860        assert!(process_transfer_authority_of_program(
1861            Arc::new(rpc_client_with_program_deployed()),
1862            &config,
1863            &AdditionalCliConfig::default(),
1864            &1,
1865            &2,
1866            &program_signer.pubkey(),
1867        )
1868        .is_ok());
1869    }
1870
1871    #[test]
1872    fn test_finalize() {
1873        let mut config = CliConfig::default();
1874
1875        let payer = keypair_from_seed(&[1u8; 32]).unwrap();
1876        let program_signer = keypair_from_seed(&[2u8; 32]).unwrap();
1877        let authority_signer = program_authority();
1878        let next_version_signer = keypair_from_seed(&[4u8; 32]).unwrap();
1879
1880        config.signers.push(&payer);
1881        config.signers.push(&authority_signer);
1882        config.signers.push(&next_version_signer);
1883
1884        assert!(process_finalize_program(
1885            Arc::new(rpc_client_with_program_deployed()),
1886            &config,
1887            &AdditionalCliConfig::default(),
1888            &1,
1889            &2,
1890            &program_signer.pubkey(),
1891        )
1892        .is_ok());
1893    }
1894
1895    fn make_tmp_path(name: &str) -> String {
1896        let out_dir = std::env::var("FARF_DIR").unwrap_or_else(|_| "farf".to_string());
1897        let keypair = Keypair::new();
1898
1899        let path = format!("{}/tmp/{}-{}", out_dir, name, keypair.pubkey());
1900
1901        // whack any possible collision
1902        let _ignored = std::fs::remove_dir_all(&path);
1903        // whack any possible collision
1904        let _ignored = std::fs::remove_file(&path);
1905
1906        path
1907    }
1908
1909    #[test]
1910    #[allow(clippy::cognitive_complexity)]
1911    fn test_cli_parse_deploy() {
1912        let test_commands = get_clap_app("test", "desc", "version");
1913
1914        let default_keypair = Keypair::new();
1915        let keypair_file = make_tmp_path("keypair_file");
1916        write_keypair_file(&default_keypair, &keypair_file).unwrap();
1917        let default_signer = DefaultSigner::new("", &keypair_file);
1918
1919        let program_keypair = Keypair::new();
1920        let program_keypair_file = make_tmp_path("program_keypair_file");
1921        write_keypair_file(&program_keypair, &program_keypair_file).unwrap();
1922
1923        let buffer_keypair = Keypair::new();
1924        let buffer_keypair_file = make_tmp_path("buffer_keypair_file");
1925        write_keypair_file(&buffer_keypair, &buffer_keypair_file).unwrap();
1926
1927        let authority_keypair = Keypair::new();
1928        let authority_keypair_file = make_tmp_path("authority_keypair_file");
1929        write_keypair_file(&authority_keypair, &authority_keypair_file).unwrap();
1930
1931        let test_command = test_commands.clone().get_matches_from(vec![
1932            "test",
1933            "program-v4",
1934            "deploy",
1935            "/Users/test/program.so",
1936            "--program-keypair",
1937            &program_keypair_file,
1938        ]);
1939        assert_eq!(
1940            parse_command(&test_command, &default_signer, &mut None).unwrap(),
1941            CliCommandInfo {
1942                command: CliCommand::ProgramV4(ProgramV4CliCommand::Deploy {
1943                    additional_cli_config: AdditionalCliConfig::default(),
1944                    program_address: program_keypair.pubkey(),
1945                    buffer_address: None,
1946                    upload_signer_index: Some(1),
1947                    authority_signer_index: 0,
1948                    path_to_elf: Some("/Users/test/program.so".to_string()),
1949                    upload_range: None..None,
1950                }),
1951                signers: vec![
1952                    Box::new(read_keypair_file(&keypair_file).unwrap()),
1953                    Box::new(read_keypair_file(&program_keypair_file).unwrap()),
1954                ],
1955            }
1956        );
1957
1958        let test_command = test_commands.clone().get_matches_from(vec![
1959            "test",
1960            "program-v4",
1961            "deploy",
1962            "/Users/test/program.so",
1963            "--buffer",
1964            &program_keypair_file,
1965        ]);
1966        assert_eq!(
1967            parse_command(&test_command, &default_signer, &mut None).unwrap(),
1968            CliCommandInfo {
1969                command: CliCommand::ProgramV4(ProgramV4CliCommand::Deploy {
1970                    additional_cli_config: AdditionalCliConfig::default(),
1971                    program_address: program_keypair.pubkey(),
1972                    buffer_address: Some(program_keypair.pubkey()),
1973                    upload_signer_index: Some(1),
1974                    authority_signer_index: 0,
1975                    path_to_elf: Some("/Users/test/program.so".to_string()),
1976                    upload_range: None..None,
1977                }),
1978                signers: vec![
1979                    Box::new(read_keypair_file(&keypair_file).unwrap()),
1980                    Box::new(read_keypair_file(&program_keypair_file).unwrap()),
1981                ],
1982            }
1983        );
1984
1985        let test_command = test_commands.clone().get_matches_from(vec![
1986            "test",
1987            "program-v4",
1988            "deploy",
1989            "/Users/test/program.so",
1990            "--program-id",
1991            &program_keypair_file,
1992            "--buffer",
1993            &buffer_keypair_file,
1994        ]);
1995        assert_eq!(
1996            parse_command(&test_command, &default_signer, &mut None).unwrap(),
1997            CliCommandInfo {
1998                command: CliCommand::ProgramV4(ProgramV4CliCommand::Deploy {
1999                    additional_cli_config: AdditionalCliConfig::default(),
2000                    program_address: program_keypair.pubkey(),
2001                    buffer_address: Some(buffer_keypair.pubkey()),
2002                    upload_signer_index: Some(1),
2003                    authority_signer_index: 0,
2004                    path_to_elf: Some("/Users/test/program.so".to_string()),
2005                    upload_range: None..None,
2006                }),
2007                signers: vec![
2008                    Box::new(read_keypair_file(&keypair_file).unwrap()),
2009                    Box::new(read_keypair_file(&buffer_keypair_file).unwrap()),
2010                ],
2011            }
2012        );
2013
2014        let test_command = test_commands.clone().get_matches_from(vec![
2015            "test",
2016            "program-v4",
2017            "deploy",
2018            "/Users/test/program.so",
2019            "--program-keypair",
2020            &program_keypair_file,
2021            "--authority",
2022            &authority_keypair_file,
2023        ]);
2024        assert_eq!(
2025            parse_command(&test_command, &default_signer, &mut None).unwrap(),
2026            CliCommandInfo {
2027                command: CliCommand::ProgramV4(ProgramV4CliCommand::Deploy {
2028                    additional_cli_config: AdditionalCliConfig::default(),
2029                    program_address: program_keypair.pubkey(),
2030                    buffer_address: None,
2031                    upload_signer_index: Some(1),
2032                    authority_signer_index: 2,
2033                    path_to_elf: Some("/Users/test/program.so".to_string()),
2034                    upload_range: None..None,
2035                }),
2036                signers: vec![
2037                    Box::new(read_keypair_file(&keypair_file).unwrap()),
2038                    Box::new(read_keypair_file(&program_keypair_file).unwrap()),
2039                    Box::new(read_keypair_file(&authority_keypair_file).unwrap()),
2040                ],
2041            }
2042        );
2043
2044        let test_command = test_commands.clone().get_matches_from(vec![
2045            "test",
2046            "program-v4",
2047            "deploy",
2048            "/Users/test/program.so",
2049            "--program-id",
2050            &program_keypair_file,
2051            "--authority",
2052            &authority_keypair_file,
2053        ]);
2054        assert_eq!(
2055            parse_command(&test_command, &default_signer, &mut None).unwrap(),
2056            CliCommandInfo {
2057                command: CliCommand::ProgramV4(ProgramV4CliCommand::Deploy {
2058                    additional_cli_config: AdditionalCliConfig::default(),
2059                    program_address: program_keypair.pubkey(),
2060                    buffer_address: None,
2061                    upload_signer_index: None,
2062                    authority_signer_index: 1,
2063                    path_to_elf: Some("/Users/test/program.so".to_string()),
2064                    upload_range: None..None,
2065                }),
2066                signers: vec![
2067                    Box::new(read_keypair_file(&keypair_file).unwrap()),
2068                    Box::new(read_keypair_file(&authority_keypair_file).unwrap()),
2069                ],
2070            }
2071        );
2072
2073        let test_command = test_commands.clone().get_matches_from(vec![
2074            "test",
2075            "program-v4",
2076            "deploy",
2077            "/Users/test/program.so",
2078            "--program-id",
2079            &program_keypair_file,
2080            "--buffer",
2081            &buffer_keypair_file,
2082            "--authority",
2083            &authority_keypair_file,
2084        ]);
2085        assert_eq!(
2086            parse_command(&test_command, &default_signer, &mut None).unwrap(),
2087            CliCommandInfo {
2088                command: CliCommand::ProgramV4(ProgramV4CliCommand::Deploy {
2089                    additional_cli_config: AdditionalCliConfig::default(),
2090                    program_address: program_keypair.pubkey(),
2091                    buffer_address: Some(buffer_keypair.pubkey()),
2092                    upload_signer_index: Some(1),
2093                    authority_signer_index: 2,
2094                    path_to_elf: Some("/Users/test/program.so".to_string()),
2095                    upload_range: None..None,
2096                }),
2097                signers: vec![
2098                    Box::new(read_keypair_file(&keypair_file).unwrap()),
2099                    Box::new(read_keypair_file(&buffer_keypair_file).unwrap()),
2100                    Box::new(read_keypair_file(&authority_keypair_file).unwrap()),
2101                ],
2102            }
2103        );
2104
2105        let test_command = test_commands.clone().get_matches_from(vec![
2106            "test",
2107            "program-v4",
2108            "deploy",
2109            "--program-id",
2110            &program_keypair_file,
2111            "--buffer",
2112            &buffer_keypair_file,
2113            "--authority",
2114            &authority_keypair_file,
2115        ]);
2116        assert_eq!(
2117            parse_command(&test_command, &default_signer, &mut None).unwrap(),
2118            CliCommandInfo {
2119                command: CliCommand::ProgramV4(ProgramV4CliCommand::Deploy {
2120                    additional_cli_config: AdditionalCliConfig::default(),
2121                    program_address: program_keypair.pubkey(),
2122                    buffer_address: Some(buffer_keypair.pubkey()),
2123                    upload_signer_index: None,
2124                    authority_signer_index: 2,
2125                    path_to_elf: None,
2126                    upload_range: None..None,
2127                }),
2128                signers: vec![
2129                    Box::new(read_keypair_file(&keypair_file).unwrap()),
2130                    Box::new(read_keypair_file(&buffer_keypair_file).unwrap()),
2131                    Box::new(read_keypair_file(&authority_keypair_file).unwrap()),
2132                ],
2133            }
2134        );
2135
2136        let test_command = test_commands.clone().get_matches_from(vec![
2137            "test",
2138            "program-v4",
2139            "deploy",
2140            "/Users/test/program.so",
2141            "--start-offset",
2142            "16",
2143            "--end-offset",
2144            "32",
2145            "--program-id",
2146            &program_keypair_file,
2147            "--use-rpc",
2148            "--with-compute-unit-price",
2149            "1",
2150        ]);
2151        assert_eq!(
2152            parse_command(&test_command, &default_signer, &mut None).unwrap(),
2153            CliCommandInfo {
2154                command: CliCommand::ProgramV4(ProgramV4CliCommand::Deploy {
2155                    additional_cli_config: AdditionalCliConfig {
2156                        use_rpc: true,
2157                        sign_only: false,
2158                        dump_transaction_message: false,
2159                        blockhash_query: BlockhashQuery::default(),
2160                        compute_unit_price: Some(1),
2161                    },
2162                    program_address: program_keypair.pubkey(),
2163                    buffer_address: None,
2164                    upload_signer_index: None,
2165                    authority_signer_index: 0,
2166                    path_to_elf: Some("/Users/test/program.so".to_string()),
2167                    upload_range: Some(16)..Some(32),
2168                }),
2169                signers: vec![Box::new(read_keypair_file(&keypair_file).unwrap()),],
2170            }
2171        );
2172    }
2173
2174    #[test]
2175    #[allow(clippy::cognitive_complexity)]
2176    fn test_cli_parse_retract() {
2177        let test_commands = get_clap_app("test", "desc", "version");
2178
2179        let default_keypair = Keypair::new();
2180        let keypair_file = make_tmp_path("keypair_file");
2181        write_keypair_file(&default_keypair, &keypair_file).unwrap();
2182        let default_signer = DefaultSigner::new("", &keypair_file);
2183
2184        let program_keypair = Keypair::new();
2185        let program_keypair_file = make_tmp_path("program_keypair_file");
2186        write_keypair_file(&program_keypair, &program_keypair_file).unwrap();
2187
2188        let authority_keypair = Keypair::new();
2189        let authority_keypair_file = make_tmp_path("authority_keypair_file");
2190        write_keypair_file(&authority_keypair, &authority_keypair_file).unwrap();
2191
2192        let test_command = test_commands.clone().get_matches_from(vec![
2193            "test",
2194            "program-v4",
2195            "retract",
2196            "--program-id",
2197            &program_keypair_file,
2198        ]);
2199        assert_eq!(
2200            parse_command(&test_command, &default_signer, &mut None).unwrap(),
2201            CliCommandInfo {
2202                command: CliCommand::ProgramV4(ProgramV4CliCommand::Retract {
2203                    additional_cli_config: AdditionalCliConfig::default(),
2204                    program_address: program_keypair.pubkey(),
2205                    authority_signer_index: 0,
2206                    close_program_entirely: false,
2207                }),
2208                signers: vec![Box::new(read_keypair_file(&keypair_file).unwrap()),],
2209            }
2210        );
2211
2212        let test_command = test_commands.clone().get_matches_from(vec![
2213            "test",
2214            "program-v4",
2215            "retract",
2216            "--program-id",
2217            &program_keypair_file,
2218            "--authority",
2219            &authority_keypair_file,
2220            "--close-program-entirely",
2221        ]);
2222        assert_eq!(
2223            parse_command(&test_command, &default_signer, &mut None).unwrap(),
2224            CliCommandInfo {
2225                command: CliCommand::ProgramV4(ProgramV4CliCommand::Retract {
2226                    additional_cli_config: AdditionalCliConfig::default(),
2227                    program_address: program_keypair.pubkey(),
2228                    authority_signer_index: 1,
2229                    close_program_entirely: true,
2230                }),
2231                signers: vec![
2232                    Box::new(read_keypair_file(&keypair_file).unwrap()),
2233                    Box::new(read_keypair_file(&authority_keypair_file).unwrap())
2234                ],
2235            }
2236        );
2237    }
2238
2239    #[test]
2240    #[allow(clippy::cognitive_complexity)]
2241    fn test_cli_parse_transfer_authority() {
2242        let test_commands = get_clap_app("test", "desc", "version");
2243
2244        let default_keypair = Keypair::new();
2245        let keypair_file = make_tmp_path("keypair_file");
2246        write_keypair_file(&default_keypair, &keypair_file).unwrap();
2247        let default_signer = DefaultSigner::new("", &keypair_file);
2248
2249        let program_keypair = Keypair::new();
2250        let program_keypair_file = make_tmp_path("program_keypair_file");
2251        write_keypair_file(&program_keypair, &program_keypair_file).unwrap();
2252
2253        let authority_keypair = Keypair::new();
2254        let authority_keypair_file = make_tmp_path("authority_keypair_file");
2255        write_keypair_file(&authority_keypair, &authority_keypair_file).unwrap();
2256
2257        let new_authority_keypair = Keypair::new();
2258        let new_authority_keypair_file = make_tmp_path("new_authority_keypair_file");
2259        write_keypair_file(&new_authority_keypair, &new_authority_keypair_file).unwrap();
2260
2261        let test_command = test_commands.clone().get_matches_from(vec![
2262            "test",
2263            "program-v4",
2264            "transfer-authority",
2265            "--program-id",
2266            &program_keypair_file,
2267            "--authority",
2268            &authority_keypair_file,
2269            "--new-authority",
2270            &new_authority_keypair_file,
2271        ]);
2272        assert_eq!(
2273            parse_command(&test_command, &default_signer, &mut None).unwrap(),
2274            CliCommandInfo {
2275                command: CliCommand::ProgramV4(ProgramV4CliCommand::TransferAuthority {
2276                    additional_cli_config: AdditionalCliConfig::default(),
2277                    program_address: program_keypair.pubkey(),
2278                    authority_signer_index: 1,
2279                    new_authority_signer_index: 2,
2280                }),
2281                signers: vec![
2282                    Box::new(read_keypair_file(&keypair_file).unwrap()),
2283                    Box::new(read_keypair_file(&authority_keypair_file).unwrap()),
2284                    Box::new(read_keypair_file(&new_authority_keypair_file).unwrap()),
2285                ],
2286            }
2287        );
2288    }
2289
2290    #[test]
2291    #[allow(clippy::cognitive_complexity)]
2292    fn test_cli_parse_finalize() {
2293        let test_commands = get_clap_app("test", "desc", "version");
2294
2295        let default_keypair = Keypair::new();
2296        let keypair_file = make_tmp_path("keypair_file");
2297        write_keypair_file(&default_keypair, &keypair_file).unwrap();
2298        let default_signer = DefaultSigner::new("", &keypair_file);
2299
2300        let program_keypair = Keypair::new();
2301        let program_keypair_file = make_tmp_path("program_keypair_file");
2302        write_keypair_file(&program_keypair, &program_keypair_file).unwrap();
2303
2304        let authority_keypair = Keypair::new();
2305        let authority_keypair_file = make_tmp_path("authority_keypair_file");
2306        write_keypair_file(&authority_keypair, &authority_keypair_file).unwrap();
2307
2308        let next_version_keypair = Keypair::new();
2309        let next_version_keypair_file = make_tmp_path("next_version_keypair_file");
2310        write_keypair_file(&next_version_keypair, &next_version_keypair_file).unwrap();
2311
2312        let test_command = test_commands.clone().get_matches_from(vec![
2313            "test",
2314            "program-v4",
2315            "finalize",
2316            "--program-id",
2317            &program_keypair_file,
2318            "--authority",
2319            &authority_keypair_file,
2320            "--next-version",
2321            &next_version_keypair_file,
2322        ]);
2323        assert_eq!(
2324            parse_command(&test_command, &default_signer, &mut None).unwrap(),
2325            CliCommandInfo {
2326                command: CliCommand::ProgramV4(ProgramV4CliCommand::Finalize {
2327                    additional_cli_config: AdditionalCliConfig::default(),
2328                    program_address: program_keypair.pubkey(),
2329                    authority_signer_index: 1,
2330                    next_version_signer_index: 2,
2331                }),
2332                signers: vec![
2333                    Box::new(read_keypair_file(&keypair_file).unwrap()),
2334                    Box::new(read_keypair_file(&authority_keypair_file).unwrap()),
2335                    Box::new(read_keypair_file(&next_version_keypair_file).unwrap()),
2336                ],
2337            }
2338        );
2339    }
2340}