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