Skip to main content

solana_cli/
program_v4.rs

1use {
2    crate::{
3        checks::*,
4        cli::{CliCommand, CliCommandInfo, CliConfig, CliError, ProcessResult},
5        compute_budget::{
6            ComputeUnitConfig, WithComputeUnitConfig, simulate_and_update_compute_unit_limit,
7        },
8        feature::{CliFeatureStatus, status_from_account},
9        program::calculate_max_chunk_size,
10    },
11    agave_feature_set::{FEATURE_NAMES, FeatureSet, raise_cpi_nesting_limit_to_8},
12    clap::{App, AppSettings, Arg, ArgMatches, SubCommand, value_t},
13    log::*,
14    solana_account::Account,
15    solana_account_decoder::{UiAccount, UiAccountEncoding, UiDataSliceConfig},
16    solana_clap_utils::{
17        compute_budget::{ComputeUnitLimit, compute_unit_price_arg},
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::{DUMP_TRANSACTION_MESSAGE, OfflineArgs, SIGN_ONLY_ARG},
22    },
23    solana_cli_output::{
24        CliProgramId, CliProgramV4, CliProgramsV4, ReturnSignersConfig, return_signers_with_config,
25    },
26    solana_client::{
27        connection_cache::ConnectionCache,
28        send_and_confirm_transactions_in_parallel::{
29            SendAndConfirmConfigV2, send_and_confirm_transactions_in_parallel_v2,
30        },
31    },
32    solana_instruction::Instruction,
33    solana_loader_v4_interface::{
34        instruction,
35        state::{
36            LoaderV4State,
37            LoaderV4Status::{self, Retracted},
38        },
39    },
40    solana_message::Message,
41    solana_program_runtime::{
42        execution_budget::SVMTransactionExecutionBudget, invoke_context::InvokeContext,
43    },
44    solana_pubkey::Pubkey,
45    solana_remote_wallet::remote_wallet::RemoteWalletManager,
46    solana_rpc_client::nonblocking::rpc_client::RpcClient,
47    solana_rpc_client_api::{
48        config::{RpcAccountInfoConfig, RpcProgramAccountsConfig},
49        filter::{Memcmp, RpcFilterType},
50        request::MAX_MULTIPLE_ACCOUNTS,
51    },
52    solana_rpc_client_nonce_utils::nonblocking::blockhash_query::BlockhashQuery,
53    solana_sbpf::{elf::Executable, verifier::RequisiteVerifier},
54    solana_sdk_ids::{loader_v4, system_program},
55    solana_signer::Signer,
56    solana_system_interface::{MAX_PERMITTED_DATA_LENGTH, instruction as system_instruction},
57    solana_tpu_client::tpu_client::TpuClientConfig,
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 async 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            .await
567        }
568        ProgramV4CliCommand::Retract {
569            additional_cli_config,
570            program_address,
571            authority_signer_index,
572            close_program_entirely,
573        } => {
574            process_retract_program(
575                rpc_client,
576                config,
577                additional_cli_config,
578                authority_signer_index,
579                program_address,
580                *close_program_entirely,
581            )
582            .await
583        }
584        ProgramV4CliCommand::TransferAuthority {
585            additional_cli_config,
586            program_address,
587            authority_signer_index,
588            new_authority_signer_index,
589        } => {
590            process_transfer_authority_of_program(
591                rpc_client,
592                config,
593                additional_cli_config,
594                authority_signer_index,
595                new_authority_signer_index,
596                program_address,
597            )
598            .await
599        }
600        ProgramV4CliCommand::Finalize {
601            additional_cli_config,
602            program_address,
603            authority_signer_index,
604            next_version_signer_index,
605        } => {
606            process_finalize_program(
607                rpc_client,
608                config,
609                additional_cli_config,
610                authority_signer_index,
611                next_version_signer_index,
612                program_address,
613            )
614            .await
615        }
616        ProgramV4CliCommand::Show {
617            account_pubkey,
618            authority,
619            all,
620        } => process_show(rpc_client, config, *account_pubkey, *authority, *all).await,
621        ProgramV4CliCommand::Dump {
622            account_pubkey,
623            output_location,
624        } => process_dump(rpc_client, config, *account_pubkey, output_location).await,
625    }
626}
627
628// This function can be used for the following use-cases
629// * Upload a new buffer account without deploying it (preparation for two-step redeployment)
630//   - buffer_address must be `Some(program_signer.pubkey())`
631//   - upload_signer_index must be `Some(program_signer_index)`
632// * Upload a new program account and deploy it
633//   - buffer_address must be `None`
634//   - upload_signer_index must be `Some(program_signer_index)`
635// * Single-step redeploy an existing program using the original program account
636//   - buffer_address must be `None`
637//   - upload_signer_index must be `None`
638// * Single-step redeploy an existing program using a buffer account
639//   - buffer_address must be `Some(buffer_signer.pubkey())`
640//   - upload_signer_index must be `Some(buffer_signer_index)`
641// * Two-step redeploy an existing program using a buffer account
642//   - buffer_address must be `Some(buffer_signer.pubkey())`
643//   - upload_signer_index must be None
644pub async fn process_deploy_program(
645    rpc_client: Arc<RpcClient>,
646    config: &CliConfig<'_>,
647    additional_cli_config: &AdditionalCliConfig,
648    program_address: &Pubkey,
649    buffer_address: Option<&Pubkey>,
650    upload_signer_index: Option<&SignerIndex>,
651    auth_signer_index: &SignerIndex,
652    program_data: &[u8],
653    upload_range: Range<Option<usize>>,
654) -> ProcessResult {
655    let payer_pubkey = config.signers[0].pubkey();
656    let authority_pubkey = config.signers[*auth_signer_index].pubkey();
657
658    // Check requested command makes sense given the on-chain state
659    let program_account = rpc_client
660        .get_account_with_commitment(program_address, config.commitment)
661        .await?
662        .value;
663    let buffer_account = if let Some(buffer_address) = buffer_address {
664        rpc_client
665            .get_account_with_commitment(buffer_address, config.commitment)
666            .await?
667            .value
668    } else {
669        None
670    };
671    let lamports_required = rpc_client
672        .get_minimum_balance_for_rent_exemption(
673            LoaderV4State::program_data_offset().saturating_add(program_data.len()),
674        )
675        .await?;
676    let program_account_exists = program_account
677        .as_ref()
678        .map(|account| loader_v4::check_id(&account.owner))
679        .unwrap_or(false);
680    if upload_signer_index
681        .map(|index| &config.signers[*index].pubkey() == program_address)
682        .unwrap_or(false)
683    {
684        // Deploy new program
685        if program_account_exists {
686            return Err(
687                "Program account does exist already. Did you perhaps intent to redeploy an \
688                 existing program instead? Then use --program-id instead of --program-keypair."
689                    .into(),
690            );
691        }
692    } else {
693        // Redeploy an existing program
694        if !program_account_exists {
695            return Err(
696                "Program account does not exist. Did you perhaps intent to deploy a new program \
697                 instead? Then use --program-keypair instead of --program-id."
698                    .into(),
699            );
700        }
701    }
702    if let Some(program_account) = program_account.as_ref() {
703        if !system_program::check_id(&program_account.owner)
704            && !loader_v4::check_id(&program_account.owner)
705        {
706            return Err(format!("{program_address} is not owned by loader-v4").into());
707        }
708    }
709    if let Some(buffer_account) = buffer_account.as_ref() {
710        if !system_program::check_id(&buffer_account.owner)
711            && !loader_v4::check_id(&buffer_account.owner)
712        {
713            return Err(format!("{} is not owned by loader-v4", buffer_address.unwrap()).into());
714        }
715    }
716
717    // Download feature set
718    let mut feature_set = FeatureSet::default();
719    for feature_ids in FEATURE_NAMES
720        .keys()
721        .cloned()
722        .collect::<Vec<Pubkey>>()
723        .chunks(MAX_MULTIPLE_ACCOUNTS)
724    {
725        rpc_client
726            .get_multiple_accounts(feature_ids)
727            .await?
728            .into_iter()
729            .zip(feature_ids)
730            .for_each(|(account, feature_id)| {
731                let activation_slot = account.and_then(status_from_account);
732
733                if let Some(CliFeatureStatus::Active(slot)) = activation_slot {
734                    feature_set.activate(feature_id, slot);
735                }
736            });
737    }
738    let program_runtime_environment = agave_syscalls::create_program_runtime_environment_v1(
739        &feature_set.runtime_features(),
740        &SVMTransactionExecutionBudget::new_with_defaults(
741            feature_set.is_active(&raise_cpi_nesting_limit_to_8::id()),
742        ),
743        true,
744        false,
745    )
746    .unwrap();
747
748    // Verify the program
749    let upload_range =
750        upload_range.start.unwrap_or(0)..upload_range.end.unwrap_or(program_data.len());
751    const MAX_LEN: usize =
752        (MAX_PERMITTED_DATA_LENGTH as usize).saturating_sub(LoaderV4State::program_data_offset());
753    if program_data.len() > MAX_LEN {
754        return Err(format!(
755            "Program length {} exceeds maximum length {}",
756            program_data.len(),
757            MAX_LEN,
758        )
759        .into());
760    }
761    if upload_range.end > program_data.len() {
762        return Err(format!(
763            "Range end {} exceeds program length {}",
764            upload_range.end,
765            program_data.len(),
766        )
767        .into());
768    }
769    let executable =
770        Executable::<InvokeContext>::from_elf(program_data, Arc::new(program_runtime_environment))
771            .map_err(|err| format!("ELF error: {err}"))?;
772    executable
773        .verify::<RequisiteVerifier>()
774        .map_err(|err| format!("ELF error: {err}"))?;
775
776    // Create and add retract and set_program_length instructions
777    let mut initial_instructions = Vec::default();
778    if let Some(program_account) = program_account.as_ref() {
779        if let Some(retract_instruction) =
780            build_retract_instruction(program_account, program_address, &authority_pubkey)?
781        {
782            initial_instructions.insert(0, retract_instruction);
783        }
784        let (mut set_program_length_instructions, _lamports_required) =
785            build_set_program_length_instructions(
786                rpc_client.clone(),
787                config,
788                auth_signer_index,
789                program_account,
790                program_address,
791                program_data.len() as u32,
792            )
793            .await?;
794        if !set_program_length_instructions.is_empty() {
795            initial_instructions.append(&mut set_program_length_instructions);
796        }
797    }
798
799    let upload_address = buffer_address.unwrap_or(program_address);
800    let (upload_account, upload_account_length) = if buffer_address.is_some() {
801        (buffer_account, upload_range.len())
802    } else {
803        (program_account, program_data.len())
804    };
805    let existing_lamports = upload_account
806        .as_ref()
807        .map(|account| account.lamports)
808        .unwrap_or(0);
809    // Create and add create_buffer message
810    if let Some(upload_account) = upload_account.as_ref() {
811        if system_program::check_id(&upload_account.owner) {
812            initial_instructions.append(&mut vec![
813                system_instruction::transfer(&payer_pubkey, upload_address, lamports_required),
814                system_instruction::assign(upload_address, &loader_v4::id()),
815                instruction::set_program_length(
816                    upload_address,
817                    &authority_pubkey,
818                    upload_account_length as u32,
819                    &payer_pubkey,
820                ),
821            ]);
822        }
823    } else {
824        initial_instructions.append(&mut instruction::create_buffer(
825            &payer_pubkey,
826            upload_address,
827            lamports_required,
828            &authority_pubkey,
829            upload_account_length as u32,
830            &payer_pubkey,
831        ));
832    }
833
834    // Create and add write messages
835    let mut write_messages = vec![];
836    if upload_signer_index.is_none() {
837        if upload_account.is_none() {
838            return Err(format!(
839                "No ELF was provided or uploaded to the account {upload_address:?}",
840            )
841            .into());
842        }
843    } else {
844        if upload_range.is_empty() {
845            return Err(format!("Attempting to upload empty range {upload_range:?}").into());
846        }
847        let first_write_message = Message::new(
848            &[instruction::write(
849                upload_address,
850                &authority_pubkey,
851                0,
852                Vec::new(),
853            )],
854            Some(&payer_pubkey),
855        );
856        let chunk_size = calculate_max_chunk_size(first_write_message);
857        for (chunk, i) in program_data[upload_range.clone()]
858            .chunks(chunk_size)
859            .zip(0usize..)
860        {
861            write_messages.push(vec![instruction::write(
862                upload_address,
863                &authority_pubkey,
864                (upload_range.start as u32).saturating_add(i.saturating_mul(chunk_size) as u32),
865                chunk.to_vec(),
866            )]);
867        }
868    }
869
870    let final_instructions = if buffer_address == Some(program_address) {
871        // Upload to buffer only and skip actual deployment
872        Vec::default()
873    } else if let Some(buffer_address) = buffer_address {
874        // Redeployment with a buffer
875        vec![
876            instruction::copy(
877                program_address,
878                &authority_pubkey,
879                buffer_address,
880                upload_range.start as u32,
881                0,
882                upload_range.len() as u32,
883            ),
884            instruction::deploy(program_address, &authority_pubkey),
885            instruction::set_program_length(buffer_address, &authority_pubkey, 0, &payer_pubkey),
886        ]
887    } else {
888        // Initial deployment or redeployment without a buffer
889        vec![instruction::deploy(program_address, &authority_pubkey)]
890    };
891
892    send_messages(
893        rpc_client,
894        config,
895        additional_cli_config,
896        auth_signer_index,
897        if initial_instructions.is_empty() {
898            Vec::default()
899        } else {
900            vec![initial_instructions]
901        },
902        write_messages,
903        if final_instructions.is_empty() {
904            Vec::default()
905        } else {
906            vec![final_instructions]
907        },
908        lamports_required.saturating_sub(existing_lamports),
909        config.output_format.formatted_string(&CliProgramId {
910            program_id: program_address.to_string(),
911            signature: None,
912        }),
913    )
914    .await
915}
916
917async fn process_retract_program(
918    rpc_client: Arc<RpcClient>,
919    config: &CliConfig<'_>,
920    additional_cli_config: &AdditionalCliConfig,
921    auth_signer_index: &SignerIndex,
922    program_address: &Pubkey,
923    close_program_entirely: bool,
924) -> ProcessResult {
925    let payer_pubkey = config.signers[0].pubkey();
926    let authority_pubkey = config.signers[*auth_signer_index].pubkey();
927
928    let Some(program_account) = rpc_client
929        .get_account_with_commitment(program_address, config.commitment)
930        .await?
931        .value
932    else {
933        return Err("Program account does not exist".into());
934    };
935    if !loader_v4::check_id(&program_account.owner) {
936        return Err(format!("{program_address} is not owned by loader-v4").into());
937    }
938
939    let mut instructions = Vec::default();
940    let retract_instruction =
941        build_retract_instruction(&program_account, program_address, &authority_pubkey)?;
942    if let Some(retract_instruction) = retract_instruction {
943        instructions.push(retract_instruction);
944    }
945    if close_program_entirely {
946        let set_program_length_instruction =
947            instruction::set_program_length(program_address, &authority_pubkey, 0, &payer_pubkey);
948        instructions.push(set_program_length_instruction);
949    } else if instructions.is_empty() {
950        return Err("Program is retracted already".into());
951    }
952
953    send_messages(
954        rpc_client,
955        config,
956        additional_cli_config,
957        auth_signer_index,
958        vec![instructions],
959        Vec::default(),
960        Vec::default(),
961        0,
962        config.output_format.formatted_string(&CliProgramId {
963            program_id: program_address.to_string(),
964            signature: None,
965        }),
966    )
967    .await
968}
969
970async fn process_transfer_authority_of_program(
971    rpc_client: Arc<RpcClient>,
972    config: &CliConfig<'_>,
973    additional_cli_config: &AdditionalCliConfig,
974    auth_signer_index: &SignerIndex,
975    new_auth_signer_index: &SignerIndex,
976    program_address: &Pubkey,
977) -> ProcessResult {
978    if let Some(program_account) = rpc_client
979        .get_account_with_commitment(program_address, config.commitment)
980        .await?
981        .value
982    {
983        if !loader_v4::check_id(&program_account.owner) {
984            return Err(format!("{program_address} is not owned by loader-v4").into());
985        }
986    } else {
987        return Err(format!("Unable to find the account {program_address}").into());
988    }
989
990    let authority_pubkey = config.signers[*auth_signer_index].pubkey();
991    let new_authority_pubkey = config.signers[*new_auth_signer_index].pubkey();
992
993    let messages = vec![vec![instruction::transfer_authority(
994        program_address,
995        &authority_pubkey,
996        &new_authority_pubkey,
997    )]];
998
999    send_messages(
1000        rpc_client,
1001        config,
1002        additional_cli_config,
1003        auth_signer_index,
1004        messages,
1005        Vec::default(),
1006        Vec::default(),
1007        0,
1008        config.output_format.formatted_string(&CliProgramId {
1009            program_id: program_address.to_string(),
1010            signature: None,
1011        }),
1012    )
1013    .await
1014}
1015
1016async fn process_finalize_program(
1017    rpc_client: Arc<RpcClient>,
1018    config: &CliConfig<'_>,
1019    additional_cli_config: &AdditionalCliConfig,
1020    auth_signer_index: &SignerIndex,
1021    next_version_signer_index: &SignerIndex,
1022    program_address: &Pubkey,
1023) -> ProcessResult {
1024    if let Some(program_account) = rpc_client
1025        .get_account_with_commitment(program_address, config.commitment)
1026        .await?
1027        .value
1028    {
1029        if !loader_v4::check_id(&program_account.owner) {
1030            return Err(format!("{program_address} is not owned by loader-v4").into());
1031        }
1032    } else {
1033        return Err(format!("Unable to find the account {program_address}").into());
1034    }
1035
1036    let authority_pubkey = config.signers[*auth_signer_index].pubkey();
1037    let next_version_pubkey = config.signers[*next_version_signer_index].pubkey();
1038
1039    let messages = vec![vec![instruction::finalize(
1040        program_address,
1041        &authority_pubkey,
1042        &next_version_pubkey,
1043    )]];
1044
1045    send_messages(
1046        rpc_client,
1047        config,
1048        additional_cli_config,
1049        auth_signer_index,
1050        messages,
1051        Vec::default(),
1052        Vec::default(),
1053        0,
1054        config.output_format.formatted_string(&CliProgramId {
1055            program_id: program_address.to_string(),
1056            signature: None,
1057        }),
1058    )
1059    .await
1060}
1061
1062async fn process_show(
1063    rpc_client: Arc<RpcClient>,
1064    config: &CliConfig<'_>,
1065    program_address: Option<Pubkey>,
1066    authority: Pubkey,
1067    all: bool,
1068) -> ProcessResult {
1069    if let Some(program_address) = program_address {
1070        if let Some(account) = rpc_client
1071            .get_account_with_commitment(&program_address, config.commitment)
1072            .await?
1073            .value
1074        {
1075            if loader_v4::check_id(&account.owner) {
1076                if let Ok(state) = solana_loader_v4_program::get_state(&account.data) {
1077                    let status = match state.status {
1078                        LoaderV4Status::Retracted => "retracted",
1079                        LoaderV4Status::Deployed => "deployed",
1080                        LoaderV4Status::Finalized => "finalized",
1081                    };
1082                    Ok(config.output_format.formatted_string(&CliProgramV4 {
1083                        program_id: program_address.to_string(),
1084                        owner: account.owner.to_string(),
1085                        authority: state.authority_address_or_next_version.to_string(),
1086                        last_deploy_slot: state.slot,
1087                        data_len: account
1088                            .data
1089                            .len()
1090                            .saturating_sub(LoaderV4State::program_data_offset()),
1091                        status: status.to_string(),
1092                    }))
1093                } else {
1094                    Err(format!("{program_address} program state is invalid").into())
1095                }
1096            } else {
1097                Err(format!("{program_address} is not owned by loader-v4").into())
1098            }
1099        } else {
1100            Err(format!("Unable to find the account {program_address}").into())
1101        }
1102    } else {
1103        let authority_pubkey = if all { None } else { Some(authority) };
1104        let programs = get_programs(rpc_client, config, authority_pubkey).await?;
1105        Ok(config.output_format.formatted_string(&programs))
1106    }
1107}
1108
1109pub async fn process_dump(
1110    rpc_client: Arc<RpcClient>,
1111    config: &CliConfig<'_>,
1112    account_pubkey: Option<Pubkey>,
1113    output_location: &str,
1114) -> ProcessResult {
1115    if let Some(account_pubkey) = account_pubkey {
1116        if let Some(account) = rpc_client
1117            .get_account_with_commitment(&account_pubkey, config.commitment)
1118            .await?
1119            .value
1120        {
1121            if loader_v4::check_id(&account.owner) {
1122                let mut f = File::create(output_location)?;
1123                f.write_all(&account.data[LoaderV4State::program_data_offset()..])?;
1124                Ok(format!("Wrote program to {output_location}"))
1125            } else {
1126                Err(format!("{account_pubkey} is not owned by loader-v4").into())
1127            }
1128        } else {
1129            Err(format!("Unable to find the account {account_pubkey}").into())
1130        }
1131    } else {
1132        Err("No account specified".into())
1133    }
1134}
1135
1136/// Synchronous wrapper for process_deploy_program
1137/// Use this when calling from a blocking context within an async runtime
1138/// Accepts a blocking RpcClient and converts it internally to nonblocking
1139///
1140/// # Performance Note
1141/// This function creates a new RpcClient on each call and has overhead from
1142/// sync-async-sync bridging. For better performance, consider using the async
1143/// `process_deploy_program()` directly with a nonblocking RpcClient.
1144#[deprecated(
1145    note = "Consider using async process_deploy_program() with nonblocking RpcClient for better \
1146            performance and resource usage"
1147)]
1148pub fn process_deploy_program_sync(
1149    rpc_client_blocking: Arc<solana_rpc_client::rpc_client::RpcClient>,
1150    config: &CliConfig<'_>,
1151    additional_cli_config: &AdditionalCliConfig,
1152    program_address: &Pubkey,
1153    buffer_address: Option<&Pubkey>,
1154    upload_signer_index: Option<&SignerIndex>,
1155    auth_signer_index: &SignerIndex,
1156    program_data: &[u8],
1157    upload_range: Range<Option<usize>>,
1158) -> ProcessResult {
1159    tokio::task::block_in_place(|| {
1160        tokio::runtime::Handle::current().block_on(async {
1161            // Convert blocking RpcClient to nonblocking
1162            let rpc_client_nonblocking = Arc::new(RpcClient::new_with_commitment(
1163                rpc_client_blocking.url(),
1164                rpc_client_blocking.commitment(),
1165            ));
1166
1167            process_deploy_program(
1168                rpc_client_nonblocking,
1169                config,
1170                additional_cli_config,
1171                program_address,
1172                buffer_address,
1173                upload_signer_index,
1174                auth_signer_index,
1175                program_data,
1176                upload_range,
1177            )
1178            .await
1179        })
1180    })
1181}
1182
1183/// Synchronous wrapper for process_dump
1184/// Use this when calling from a blocking context within an async runtime
1185/// Accepts a blocking RpcClient and converts it internally to nonblocking
1186///
1187/// # Performance Note
1188/// This function creates a new RpcClient on each call and has overhead from
1189/// sync-async-sync bridging. For better performance, consider using the async
1190/// `process_dump()` directly with a nonblocking RpcClient.
1191#[deprecated(
1192    note = "Consider using async process_dump() with nonblocking RpcClient for better performance \
1193            and resource usage"
1194)]
1195pub fn process_dump_sync(
1196    rpc_client_blocking: Arc<solana_rpc_client::rpc_client::RpcClient>,
1197    config: &CliConfig<'_>,
1198    account_pubkey: Option<Pubkey>,
1199    output_location: &str,
1200) -> ProcessResult {
1201    tokio::task::block_in_place(|| {
1202        tokio::runtime::Handle::current().block_on(async {
1203            // Convert blocking RpcClient to nonblocking
1204            let rpc_client_nonblocking = Arc::new(RpcClient::new_with_commitment(
1205                rpc_client_blocking.url(),
1206                rpc_client_blocking.commitment(),
1207            ));
1208
1209            process_dump(
1210                rpc_client_nonblocking,
1211                config,
1212                account_pubkey,
1213                output_location,
1214            )
1215            .await
1216        })
1217    })
1218}
1219
1220#[allow(clippy::too_many_arguments)]
1221async fn send_messages(
1222    rpc_client: Arc<RpcClient>,
1223    config: &CliConfig<'_>,
1224    additional_cli_config: &AdditionalCliConfig,
1225    auth_signer_index: &SignerIndex,
1226    initial_messages: Vec<Vec<Instruction>>,
1227    write_messages: Vec<Vec<Instruction>>,
1228    final_messages: Vec<Vec<Instruction>>,
1229    balance_needed: u64,
1230    ok_result: String,
1231) -> ProcessResult {
1232    let payer_pubkey = config.signers[0].pubkey();
1233    let blockhash = additional_cli_config
1234        .blockhash_query
1235        .get_blockhash(&rpc_client, config.commitment)
1236        .await?;
1237    let compute_unit_config = ComputeUnitConfig {
1238        compute_unit_price: additional_cli_config.compute_unit_price,
1239        compute_unit_limit: ComputeUnitLimit::Simulated,
1240    };
1241    let simulate_messages = |message_prototypes: Vec<Vec<Instruction>>| async {
1242        let mut messages = Vec::with_capacity(message_prototypes.len());
1243        for instructions in message_prototypes.into_iter() {
1244            let mut message = Message::new_with_blockhash(
1245                &instructions.with_compute_unit_config(&compute_unit_config),
1246                Some(&payer_pubkey),
1247                &blockhash,
1248            );
1249            simulate_and_update_compute_unit_limit(
1250                &ComputeUnitLimit::Simulated,
1251                &rpc_client,
1252                &mut message,
1253            )
1254            .await?;
1255            messages.push(message);
1256        }
1257        Ok::<Vec<solana_message::Message>, Box<dyn std::error::Error>>(messages)
1258    };
1259    let initial_messages = simulate_messages(initial_messages).await?;
1260    let write_messages = simulate_messages(write_messages).await?;
1261    let final_messages = simulate_messages(final_messages).await?;
1262
1263    let mut fee = Saturating(0);
1264    for message in initial_messages.iter() {
1265        fee += rpc_client.get_fee_for_message(message).await?;
1266    }
1267    for message in final_messages.iter() {
1268        fee += rpc_client.get_fee_for_message(message).await?;
1269    }
1270    // Assume all write messages cost the same
1271    if let Some(message) = write_messages.first() {
1272        fee += rpc_client
1273            .get_fee_for_message(message)
1274            .await?
1275            .saturating_mul(write_messages.len() as u64);
1276    }
1277    check_account_for_spend_and_fee_with_commitment(
1278        &rpc_client,
1279        &payer_pubkey,
1280        balance_needed,
1281        fee.0,
1282        config.commitment,
1283    )
1284    .await?;
1285
1286    let send_or_return_message = |message: Message| async {
1287        let signers = (0..message.header.num_required_signatures)
1288            .map(|signer_index| {
1289                let key = message.account_keys[signer_index as usize];
1290                config
1291                    .signers
1292                    .iter()
1293                    .find(|signer| signer.pubkey() == key)
1294                    .unwrap()
1295            })
1296            .collect::<Vec<_>>();
1297        let mut tx = Transaction::new_unsigned(message);
1298        tx.try_sign(&signers, blockhash)?;
1299        if additional_cli_config.sign_only {
1300            return_signers_with_config(
1301                &tx,
1302                &config.output_format,
1303                &ReturnSignersConfig {
1304                    dump_transaction_message: additional_cli_config.dump_transaction_message,
1305                },
1306            )
1307        } else {
1308            rpc_client
1309                .send_and_confirm_transaction_with_spinner_and_config(
1310                    &tx,
1311                    config.commitment,
1312                    config.send_transaction_config,
1313                )
1314                .await
1315                .map_err(|err| format!("Failed to send message: {err}").into())
1316                .map(|_| String::new())
1317        }
1318    };
1319
1320    for message in initial_messages.into_iter() {
1321        let result = send_or_return_message(message).await?;
1322        if additional_cli_config.sign_only {
1323            return Ok(result);
1324        }
1325    }
1326
1327    if !write_messages.is_empty() {
1328        let connection_cache = {
1329            #[cfg(feature = "dev-context-only-utils")]
1330            let cache =
1331                ConnectionCache::new_quic_for_tests("connection_cache_cli_program_v4_quic", 1);
1332            #[cfg(not(feature = "dev-context-only-utils"))]
1333            let cache = ConnectionCache::new_quic("connection_cache_cli_program_v4_quic", 1);
1334            cache
1335        };
1336
1337        let transaction_errors = match connection_cache {
1338            ConnectionCache::Udp(cache) => {
1339                solana_tpu_client::nonblocking::tpu_client::TpuClient::new_with_connection_cache(
1340                    rpc_client.clone(),
1341                    &config.websocket_url,
1342                    TpuClientConfig::default(),
1343                    cache,
1344                )
1345                .await?
1346                .send_and_confirm_messages_with_spinner(
1347                    &write_messages,
1348                    &[config.signers[0], config.signers[*auth_signer_index]],
1349                )
1350                .await
1351            }
1352            ConnectionCache::Quic(cache) => {
1353                let tpu_client = if additional_cli_config.use_rpc {
1354                    None
1355                } else {
1356                    // `solana_client` type currently required by `send_and_confirm_transactions_in_parallel_v2`
1357                    Some(
1358                        solana_client::nonblocking::tpu_client::TpuClient::new_with_connection_cache(
1359                            rpc_client.clone(),
1360                            &config.websocket_url,
1361                            TpuClientConfig::default(),
1362                            cache,
1363                        )
1364                        .await
1365                        .expect("Should return a valid tpu client"),
1366                    )
1367                };
1368
1369                send_and_confirm_transactions_in_parallel_v2(
1370                    rpc_client.clone(),
1371                    tpu_client,
1372                    &write_messages,
1373                    &[config.signers[0], config.signers[*auth_signer_index]],
1374                    SendAndConfirmConfigV2 {
1375                        resign_txs_count: Some(5),
1376                        with_spinner: true,
1377                        rpc_send_transaction_config: config.send_transaction_config,
1378                    },
1379                )
1380                .await
1381            }
1382        }
1383        .map_err(|err| format!("Data writes to account failed: {err}"))?
1384        .into_iter()
1385        .flatten()
1386        .collect::<Vec<_>>();
1387
1388        if !transaction_errors.is_empty() {
1389            for transaction_error in &transaction_errors {
1390                error!("{transaction_error:?}");
1391            }
1392            return Err(format!("{} write transactions failed", transaction_errors.len()).into());
1393        }
1394    }
1395
1396    for message in final_messages.into_iter() {
1397        let result = send_or_return_message(message).await?;
1398        if additional_cli_config.sign_only {
1399            return Ok(result);
1400        }
1401    }
1402
1403    Ok(ok_result)
1404}
1405
1406fn build_retract_instruction(
1407    account: &Account,
1408    buffer_address: &Pubkey,
1409    authority: &Pubkey,
1410) -> Result<Option<Instruction>, Box<dyn std::error::Error>> {
1411    if !loader_v4::check_id(&account.owner) {
1412        return Ok(None);
1413    }
1414
1415    if let Ok(LoaderV4State {
1416        slot: _,
1417        authority_address_or_next_version,
1418        status,
1419    }) = solana_loader_v4_program::get_state(&account.data)
1420    {
1421        if authority != authority_address_or_next_version {
1422            return Err(
1423                "Program authority does not match with the provided authority address".into(),
1424            );
1425        }
1426
1427        match status {
1428            Retracted => Ok(None),
1429            LoaderV4Status::Deployed => Ok(Some(instruction::retract(buffer_address, authority))),
1430            LoaderV4Status::Finalized => Err("Program is immutable".into()),
1431        }
1432    } else {
1433        Err("Program account's state could not be deserialized".into())
1434    }
1435}
1436
1437async fn build_set_program_length_instructions(
1438    rpc_client: Arc<RpcClient>,
1439    config: &CliConfig<'_>,
1440    auth_signer_index: &SignerIndex,
1441    account: &Account,
1442    buffer_address: &Pubkey,
1443    program_data_length: u32,
1444) -> Result<(Vec<Instruction>, u64), Box<dyn std::error::Error>> {
1445    let expected_account_data_len =
1446        LoaderV4State::program_data_offset().saturating_add(program_data_length as usize);
1447
1448    let lamports_required = rpc_client
1449        .get_minimum_balance_for_rent_exemption(expected_account_data_len)
1450        .await?;
1451
1452    if !loader_v4::check_id(&account.owner) {
1453        return Ok((Vec::default(), lamports_required));
1454    }
1455
1456    let payer_pubkey = config.signers[0].pubkey();
1457    let authority_pubkey = config.signers[*auth_signer_index].pubkey();
1458    let expected_account_data_len =
1459        LoaderV4State::program_data_offset().saturating_add(program_data_length as usize);
1460
1461    let lamports_required = rpc_client
1462        .get_minimum_balance_for_rent_exemption(expected_account_data_len)
1463        .await?;
1464
1465    if !loader_v4::check_id(&account.owner) {
1466        return Ok((Vec::default(), lamports_required));
1467    }
1468
1469    if !account.data.is_empty() {
1470        if let Ok(LoaderV4State {
1471            slot: _,
1472            authority_address_or_next_version,
1473            status,
1474        }) = solana_loader_v4_program::get_state(&account.data)
1475        {
1476            if &authority_pubkey != authority_address_or_next_version {
1477                return Err(
1478                    "Program authority does not match with the provided authority address".into(),
1479                );
1480            }
1481
1482            if matches!(status, LoaderV4Status::Finalized) {
1483                return Err("Program is immutable".into());
1484            }
1485        } else {
1486            return Err("Program account's state could not be deserialized".into());
1487        }
1488    }
1489
1490    let set_program_length_instruction = instruction::set_program_length(
1491        buffer_address,
1492        &authority_pubkey,
1493        program_data_length,
1494        &payer_pubkey,
1495    );
1496
1497    match account.data.len().cmp(&expected_account_data_len) {
1498        Ordering::Less => {
1499            if account.lamports < lamports_required {
1500                let extra_lamports_required = lamports_required.saturating_sub(account.lamports);
1501                Ok((
1502                    vec![
1503                        system_instruction::transfer(
1504                            &payer_pubkey,
1505                            buffer_address,
1506                            extra_lamports_required,
1507                        ),
1508                        set_program_length_instruction,
1509                    ],
1510                    extra_lamports_required,
1511                ))
1512            } else {
1513                Ok((vec![set_program_length_instruction], 0))
1514            }
1515        }
1516        Ordering::Equal => {
1517            if account.lamports < lamports_required {
1518                return Err("Program account has less lamports than required for its size".into());
1519            }
1520            Ok((vec![], 0))
1521        }
1522        Ordering::Greater => {
1523            if account.lamports < lamports_required {
1524                return Err("Program account has less lamports than required for its size".into());
1525            }
1526            Ok((vec![set_program_length_instruction], 0))
1527        }
1528    }
1529}
1530
1531async fn get_accounts_with_filter(
1532    rpc_client: Arc<RpcClient>,
1533    _config: &CliConfig<'_>,
1534    filters: Vec<RpcFilterType>,
1535    length: usize,
1536) -> Result<Vec<(Pubkey, UiAccount)>, Box<dyn std::error::Error>> {
1537    let results = rpc_client
1538        .get_program_ui_accounts_with_config(
1539            &loader_v4::id(),
1540            RpcProgramAccountsConfig {
1541                filters: Some(filters),
1542                account_config: RpcAccountInfoConfig {
1543                    encoding: Some(UiAccountEncoding::Base64),
1544                    data_slice: Some(UiDataSliceConfig { offset: 0, length }),
1545                    ..RpcAccountInfoConfig::default()
1546                },
1547                ..RpcProgramAccountsConfig::default()
1548            },
1549        )
1550        .await?;
1551    Ok(results)
1552}
1553
1554async fn get_programs(
1555    rpc_client: Arc<RpcClient>,
1556    config: &CliConfig<'_>,
1557    authority_pubkey: Option<Pubkey>,
1558) -> Result<CliProgramsV4, Box<dyn std::error::Error>> {
1559    let filters = if let Some(authority_pubkey) = authority_pubkey {
1560        vec![
1561            (RpcFilterType::Memcmp(Memcmp::new_base58_encoded(
1562                size_of::<u64>(),
1563                authority_pubkey.as_ref(),
1564            ))),
1565        ]
1566    } else {
1567        vec![]
1568    };
1569
1570    let results = get_accounts_with_filter(
1571        rpc_client,
1572        config,
1573        filters,
1574        LoaderV4State::program_data_offset(),
1575    )
1576    .await?;
1577
1578    let mut programs = vec![];
1579    for (program, account) in results.iter() {
1580        let data_bytes = account.data.decode().expect(
1581            "It should be impossible at this point for the account data not to be decodable. \
1582             Ensure that the account was fetched using a binary encoding.",
1583        );
1584        if let Ok(state) = solana_loader_v4_program::get_state(data_bytes.as_slice()) {
1585            let status = match state.status {
1586                LoaderV4Status::Retracted => "retracted",
1587                LoaderV4Status::Deployed => "deployed",
1588                LoaderV4Status::Finalized => "finalized",
1589            };
1590            programs.push(CliProgramV4 {
1591                program_id: program.to_string(),
1592                owner: account.owner.to_string(),
1593                authority: state.authority_address_or_next_version.to_string(),
1594                last_deploy_slot: state.slot,
1595                status: status.to_string(),
1596                data_len: data_bytes
1597                    .len()
1598                    .saturating_sub(LoaderV4State::program_data_offset()),
1599            });
1600        } else {
1601            return Err(format!("Error parsing Program account {program}").into());
1602        }
1603    }
1604    Ok(CliProgramsV4 { programs })
1605}
1606
1607#[cfg(test)]
1608mod tests {
1609    use {
1610        super::*,
1611        crate::{clap_app::get_clap_app, cli::parse_command},
1612        serde_json::json,
1613        solana_keypair::{Keypair, keypair_from_seed, read_keypair_file, write_keypair_file},
1614        solana_rpc_client_api::{
1615            request::RpcRequest,
1616            response::{Response, RpcResponseContext},
1617        },
1618        std::collections::HashMap,
1619    };
1620
1621    fn program_authority() -> solana_keypair::Keypair {
1622        keypair_from_seed(&[3u8; 32]).unwrap()
1623    }
1624
1625    fn rpc_client_no_existing_program() -> RpcClient {
1626        RpcClient::new_mock("succeeds".to_string())
1627    }
1628
1629    fn rpc_client_with_program_data(data: &str, loader_is_owner: bool) -> RpcClient {
1630        let owner = if loader_is_owner {
1631            "LoaderV411111111111111111111111111111111111"
1632        } else {
1633            "Vote111111111111111111111111111111111111111"
1634        };
1635        let account_info_response = json!(Response {
1636            context: RpcResponseContext {
1637                slot: 1,
1638                api_version: None
1639            },
1640            value: json!({
1641                "data": [data, "base64"],
1642                "lamports": 42,
1643                "owner": owner,
1644                "executable": true,
1645                "rentEpoch": 1,
1646            }),
1647        });
1648        let mut mocks = HashMap::new();
1649        mocks.insert(RpcRequest::GetAccountInfo, account_info_response);
1650        RpcClient::new_mock_with_mocks("".to_string(), mocks)
1651    }
1652
1653    fn rpc_client_wrong_account_owner() -> RpcClient {
1654        rpc_client_with_program_data(
1655            "AAAAAAAAAADtSSjGKNHCxurpAziQWZVhKVknOlxj+TY2wUYUrIc30QAAAAAAAAAA",
1656            false,
1657        )
1658    }
1659
1660    fn rpc_client_wrong_authority() -> RpcClient {
1661        rpc_client_with_program_data(
1662            "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
1663            true,
1664        )
1665    }
1666
1667    fn rpc_client_with_program_retracted() -> RpcClient {
1668        rpc_client_with_program_data(
1669            "AAAAAAAAAADtSSjGKNHCxurpAziQWZVhKVknOlxj+TY2wUYUrIc30QAAAAAAAAAA",
1670            true,
1671        )
1672    }
1673
1674    fn rpc_client_with_program_deployed() -> RpcClient {
1675        rpc_client_with_program_data(
1676            "AAAAAAAAAADtSSjGKNHCxurpAziQWZVhKVknOlxj+TY2wUYUrIc30QEAAAAAAAAA",
1677            true,
1678        )
1679    }
1680
1681    fn rpc_client_with_program_finalized() -> RpcClient {
1682        rpc_client_with_program_data(
1683            "AAAAAAAAAADtSSjGKNHCxurpAziQWZVhKVknOlxj+TY2wUYUrIc30QIAAAAAAAAA",
1684            true,
1685        )
1686    }
1687
1688    #[tokio::test]
1689    async fn test_deploy() {
1690        let mut config = CliConfig::default();
1691        let mut program_data = Vec::new();
1692        let mut file = File::open("tests/fixtures/noop.so").unwrap();
1693        file.read_to_end(&mut program_data).unwrap();
1694
1695        let payer = keypair_from_seed(&[1u8; 32]).unwrap();
1696        let program_signer = keypair_from_seed(&[2u8; 32]).unwrap();
1697        let authority_signer = program_authority();
1698
1699        config.signers.push(&payer);
1700        config.signers.push(&program_signer);
1701        config.signers.push(&authority_signer);
1702
1703        assert!(
1704            process_deploy_program(
1705                Arc::new(rpc_client_no_existing_program()),
1706                &config,
1707                &AdditionalCliConfig::default(),
1708                &program_signer.pubkey(),
1709                None,
1710                Some(&1),
1711                &2,
1712                &program_data,
1713                None..None,
1714            )
1715            .await
1716            .is_ok()
1717        );
1718
1719        assert!(
1720            process_deploy_program(
1721                Arc::new(rpc_client_no_existing_program()),
1722                &config,
1723                &AdditionalCliConfig::default(),
1724                &program_signer.pubkey(),
1725                Some(&program_signer.pubkey()),
1726                Some(&1),
1727                &2,
1728                &program_data,
1729                None..None,
1730            )
1731            .await
1732            .is_ok()
1733        );
1734
1735        assert!(
1736            process_deploy_program(
1737                Arc::new(rpc_client_wrong_account_owner()),
1738                &config,
1739                &AdditionalCliConfig::default(),
1740                &program_signer.pubkey(),
1741                None,
1742                Some(&1),
1743                &2,
1744                &program_data,
1745                None..None,
1746            )
1747            .await
1748            .is_err()
1749        );
1750
1751        assert!(
1752            process_deploy_program(
1753                Arc::new(rpc_client_with_program_deployed()),
1754                &config,
1755                &AdditionalCliConfig::default(),
1756                &program_signer.pubkey(),
1757                None,
1758                Some(&1),
1759                &2,
1760                &program_data,
1761                None..None,
1762            )
1763            .await
1764            .is_err()
1765        );
1766    }
1767
1768    #[tokio::test]
1769    async fn test_redeploy() {
1770        let mut config = CliConfig::default();
1771        let mut program_data = Vec::new();
1772        let mut file = File::open("tests/fixtures/noop.so").unwrap();
1773        file.read_to_end(&mut program_data).unwrap();
1774
1775        let payer = keypair_from_seed(&[1u8; 32]).unwrap();
1776        let program_address = Pubkey::new_unique();
1777        let authority_signer = program_authority();
1778
1779        config.signers.push(&payer);
1780        config.signers.push(&authority_signer);
1781
1782        // Redeploying a non-existent program should fail
1783        assert!(
1784            process_deploy_program(
1785                Arc::new(rpc_client_no_existing_program()),
1786                &config,
1787                &AdditionalCliConfig::default(),
1788                &program_address,
1789                None,
1790                None,
1791                &1,
1792                &program_data,
1793                None..None,
1794            )
1795            .await
1796            .is_err()
1797        );
1798
1799        assert!(
1800            process_deploy_program(
1801                Arc::new(rpc_client_with_program_retracted()),
1802                &config,
1803                &AdditionalCliConfig::default(),
1804                &program_address,
1805                None,
1806                None,
1807                &1,
1808                &program_data,
1809                None..None,
1810            )
1811            .await
1812            .is_ok()
1813        );
1814
1815        assert!(
1816            process_deploy_program(
1817                Arc::new(rpc_client_with_program_deployed()),
1818                &config,
1819                &AdditionalCliConfig::default(),
1820                &program_address,
1821                None,
1822                None,
1823                &1,
1824                &program_data,
1825                None..None,
1826            )
1827            .await
1828            .is_ok()
1829        );
1830
1831        assert!(
1832            process_deploy_program(
1833                Arc::new(rpc_client_with_program_finalized()),
1834                &config,
1835                &AdditionalCliConfig::default(),
1836                &program_address,
1837                None,
1838                None,
1839                &1,
1840                &program_data,
1841                None..None,
1842            )
1843            .await
1844            .is_err()
1845        );
1846
1847        assert!(
1848            process_deploy_program(
1849                Arc::new(rpc_client_wrong_account_owner()),
1850                &config,
1851                &AdditionalCliConfig::default(),
1852                &program_address,
1853                None,
1854                None,
1855                &1,
1856                &program_data,
1857                None..None,
1858            )
1859            .await
1860            .is_err()
1861        );
1862
1863        assert!(
1864            process_deploy_program(
1865                Arc::new(rpc_client_wrong_authority()),
1866                &config,
1867                &AdditionalCliConfig::default(),
1868                &program_address,
1869                None,
1870                None,
1871                &1,
1872                &program_data,
1873                None..None,
1874            )
1875            .await
1876            .is_err()
1877        );
1878    }
1879
1880    #[tokio::test]
1881    async fn test_redeploy_from_source() {
1882        let mut config = CliConfig::default();
1883        let mut program_data = Vec::new();
1884        let mut file = File::open("tests/fixtures/noop.so").unwrap();
1885        file.read_to_end(&mut program_data).unwrap();
1886
1887        let payer = keypair_from_seed(&[1u8; 32]).unwrap();
1888        let buffer_signer = keypair_from_seed(&[2u8; 32]).unwrap();
1889        let program_address = Pubkey::new_unique();
1890        let authority_signer = program_authority();
1891
1892        config.signers.push(&payer);
1893        config.signers.push(&buffer_signer);
1894        config.signers.push(&authority_signer);
1895
1896        // Redeploying a non-existent program should fail
1897        assert!(
1898            process_deploy_program(
1899                Arc::new(rpc_client_no_existing_program()),
1900                &config,
1901                &AdditionalCliConfig::default(),
1902                &program_address,
1903                Some(&buffer_signer.pubkey()),
1904                Some(&1),
1905                &2,
1906                &program_data,
1907                None..None,
1908            )
1909            .await
1910            .is_err()
1911        );
1912
1913        assert!(
1914            process_deploy_program(
1915                Arc::new(rpc_client_wrong_account_owner()),
1916                &config,
1917                &AdditionalCliConfig::default(),
1918                &program_address,
1919                Some(&buffer_signer.pubkey()),
1920                Some(&1),
1921                &2,
1922                &program_data,
1923                None..None,
1924            )
1925            .await
1926            .is_err()
1927        );
1928
1929        assert!(
1930            process_deploy_program(
1931                Arc::new(rpc_client_wrong_authority()),
1932                &config,
1933                &AdditionalCliConfig::default(),
1934                &program_address,
1935                Some(&buffer_signer.pubkey()),
1936                Some(&1),
1937                &2,
1938                &program_data,
1939                None..None,
1940            )
1941            .await
1942            .is_err()
1943        );
1944    }
1945
1946    #[tokio::test]
1947    async fn test_retract() {
1948        let mut config = CliConfig::default();
1949
1950        let payer = keypair_from_seed(&[1u8; 32]).unwrap();
1951        let program_signer = keypair_from_seed(&[2u8; 32]).unwrap();
1952        let authority_signer = program_authority();
1953
1954        config.signers.push(&payer);
1955        config.signers.push(&authority_signer);
1956
1957        for close_program_entirely in [false, true] {
1958            assert!(
1959                process_retract_program(
1960                    Arc::new(rpc_client_no_existing_program()),
1961                    &config,
1962                    &AdditionalCliConfig::default(),
1963                    &1,
1964                    &program_signer.pubkey(),
1965                    close_program_entirely,
1966                )
1967                .await
1968                .is_err()
1969            );
1970
1971            assert!(
1972                process_retract_program(
1973                    Arc::new(rpc_client_with_program_retracted()),
1974                    &config,
1975                    &AdditionalCliConfig::default(),
1976                    &1,
1977                    &program_signer.pubkey(),
1978                    close_program_entirely,
1979                )
1980                .await
1981                .is_ok()
1982                    == close_program_entirely
1983            );
1984
1985            assert!(
1986                process_retract_program(
1987                    Arc::new(rpc_client_with_program_deployed()),
1988                    &config,
1989                    &AdditionalCliConfig::default(),
1990                    &1,
1991                    &program_signer.pubkey(),
1992                    close_program_entirely,
1993                )
1994                .await
1995                .is_ok()
1996            );
1997
1998            assert!(
1999                process_retract_program(
2000                    Arc::new(rpc_client_with_program_finalized()),
2001                    &config,
2002                    &AdditionalCliConfig::default(),
2003                    &1,
2004                    &program_signer.pubkey(),
2005                    close_program_entirely,
2006                )
2007                .await
2008                .is_err()
2009            );
2010
2011            assert!(
2012                process_retract_program(
2013                    Arc::new(rpc_client_wrong_account_owner()),
2014                    &config,
2015                    &AdditionalCliConfig::default(),
2016                    &1,
2017                    &program_signer.pubkey(),
2018                    close_program_entirely,
2019                )
2020                .await
2021                .is_err()
2022            );
2023
2024            assert!(
2025                process_retract_program(
2026                    Arc::new(rpc_client_wrong_authority()),
2027                    &config,
2028                    &AdditionalCliConfig::default(),
2029                    &1,
2030                    &program_signer.pubkey(),
2031                    close_program_entirely,
2032                )
2033                .await
2034                .is_err()
2035            );
2036        }
2037    }
2038
2039    #[tokio::test]
2040    async fn test_transfer_authority() {
2041        let mut config = CliConfig::default();
2042
2043        let payer = keypair_from_seed(&[1u8; 32]).unwrap();
2044        let program_signer = keypair_from_seed(&[2u8; 32]).unwrap();
2045        let authority_signer = program_authority();
2046        let new_authority_signer = program_authority();
2047
2048        config.signers.push(&payer);
2049        config.signers.push(&authority_signer);
2050        config.signers.push(&new_authority_signer);
2051
2052        assert!(
2053            process_transfer_authority_of_program(
2054                Arc::new(rpc_client_with_program_deployed()),
2055                &config,
2056                &AdditionalCliConfig::default(),
2057                &1,
2058                &2,
2059                &program_signer.pubkey(),
2060            )
2061            .await
2062            .is_ok()
2063        );
2064    }
2065
2066    #[tokio::test]
2067    async fn test_finalize() {
2068        let mut config = CliConfig::default();
2069
2070        let payer = keypair_from_seed(&[1u8; 32]).unwrap();
2071        let program_signer = keypair_from_seed(&[2u8; 32]).unwrap();
2072        let authority_signer = program_authority();
2073        let next_version_signer = keypair_from_seed(&[4u8; 32]).unwrap();
2074
2075        config.signers.push(&payer);
2076        config.signers.push(&authority_signer);
2077        config.signers.push(&next_version_signer);
2078
2079        assert!(
2080            process_finalize_program(
2081                Arc::new(rpc_client_with_program_deployed()),
2082                &config,
2083                &AdditionalCliConfig::default(),
2084                &1,
2085                &2,
2086                &program_signer.pubkey(),
2087            )
2088            .await
2089            .is_ok()
2090        );
2091    }
2092
2093    fn make_tmp_path(name: &str) -> String {
2094        let out_dir = std::env::var("FARF_DIR").unwrap_or_else(|_| "farf".to_string());
2095        let keypair = Keypair::new();
2096
2097        let path = format!("{}/tmp/{}-{}", out_dir, name, keypair.pubkey());
2098
2099        // whack any possible collision
2100        let _ignored = std::fs::remove_dir_all(&path);
2101        // whack any possible collision
2102        let _ignored = std::fs::remove_file(&path);
2103
2104        path
2105    }
2106
2107    #[test]
2108    #[allow(clippy::cognitive_complexity)]
2109    fn test_cli_parse_deploy() {
2110        let test_commands = get_clap_app("test", "desc", "version");
2111
2112        let default_keypair = Keypair::new();
2113        let keypair_file = make_tmp_path("keypair_file");
2114        write_keypair_file(&default_keypair, &keypair_file).unwrap();
2115        let default_signer = DefaultSigner::new("", &keypair_file);
2116
2117        let program_keypair = Keypair::new();
2118        let program_keypair_file = make_tmp_path("program_keypair_file");
2119        write_keypair_file(&program_keypair, &program_keypair_file).unwrap();
2120
2121        let buffer_keypair = Keypair::new();
2122        let buffer_keypair_file = make_tmp_path("buffer_keypair_file");
2123        write_keypair_file(&buffer_keypair, &buffer_keypair_file).unwrap();
2124
2125        let authority_keypair = Keypair::new();
2126        let authority_keypair_file = make_tmp_path("authority_keypair_file");
2127        write_keypair_file(&authority_keypair, &authority_keypair_file).unwrap();
2128
2129        let test_command = test_commands.clone().get_matches_from(vec![
2130            "test",
2131            "program-v4",
2132            "deploy",
2133            "/Users/test/program.so",
2134            "--program-keypair",
2135            &program_keypair_file,
2136        ]);
2137        assert_eq!(
2138            parse_command(&test_command, &default_signer, &mut None).unwrap(),
2139            CliCommandInfo {
2140                command: CliCommand::ProgramV4(ProgramV4CliCommand::Deploy {
2141                    additional_cli_config: AdditionalCliConfig::default(),
2142                    program_address: program_keypair.pubkey(),
2143                    buffer_address: None,
2144                    upload_signer_index: Some(1),
2145                    authority_signer_index: 0,
2146                    path_to_elf: Some("/Users/test/program.so".to_string()),
2147                    upload_range: None..None,
2148                }),
2149                signers: vec![
2150                    Box::new(read_keypair_file(&keypair_file).unwrap()),
2151                    Box::new(read_keypair_file(&program_keypair_file).unwrap()),
2152                ],
2153            }
2154        );
2155
2156        let test_command = test_commands.clone().get_matches_from(vec![
2157            "test",
2158            "program-v4",
2159            "deploy",
2160            "/Users/test/program.so",
2161            "--buffer",
2162            &program_keypair_file,
2163        ]);
2164        assert_eq!(
2165            parse_command(&test_command, &default_signer, &mut None).unwrap(),
2166            CliCommandInfo {
2167                command: CliCommand::ProgramV4(ProgramV4CliCommand::Deploy {
2168                    additional_cli_config: AdditionalCliConfig::default(),
2169                    program_address: program_keypair.pubkey(),
2170                    buffer_address: Some(program_keypair.pubkey()),
2171                    upload_signer_index: Some(1),
2172                    authority_signer_index: 0,
2173                    path_to_elf: Some("/Users/test/program.so".to_string()),
2174                    upload_range: None..None,
2175                }),
2176                signers: vec![
2177                    Box::new(read_keypair_file(&keypair_file).unwrap()),
2178                    Box::new(read_keypair_file(&program_keypair_file).unwrap()),
2179                ],
2180            }
2181        );
2182
2183        let test_command = test_commands.clone().get_matches_from(vec![
2184            "test",
2185            "program-v4",
2186            "deploy",
2187            "/Users/test/program.so",
2188            "--program-id",
2189            &program_keypair_file,
2190            "--buffer",
2191            &buffer_keypair_file,
2192        ]);
2193        assert_eq!(
2194            parse_command(&test_command, &default_signer, &mut None).unwrap(),
2195            CliCommandInfo {
2196                command: CliCommand::ProgramV4(ProgramV4CliCommand::Deploy {
2197                    additional_cli_config: AdditionalCliConfig::default(),
2198                    program_address: program_keypair.pubkey(),
2199                    buffer_address: Some(buffer_keypair.pubkey()),
2200                    upload_signer_index: Some(1),
2201                    authority_signer_index: 0,
2202                    path_to_elf: Some("/Users/test/program.so".to_string()),
2203                    upload_range: None..None,
2204                }),
2205                signers: vec![
2206                    Box::new(read_keypair_file(&keypair_file).unwrap()),
2207                    Box::new(read_keypair_file(&buffer_keypair_file).unwrap()),
2208                ],
2209            }
2210        );
2211
2212        let test_command = test_commands.clone().get_matches_from(vec![
2213            "test",
2214            "program-v4",
2215            "deploy",
2216            "/Users/test/program.so",
2217            "--program-keypair",
2218            &program_keypair_file,
2219            "--authority",
2220            &authority_keypair_file,
2221        ]);
2222        assert_eq!(
2223            parse_command(&test_command, &default_signer, &mut None).unwrap(),
2224            CliCommandInfo {
2225                command: CliCommand::ProgramV4(ProgramV4CliCommand::Deploy {
2226                    additional_cli_config: AdditionalCliConfig::default(),
2227                    program_address: program_keypair.pubkey(),
2228                    buffer_address: None,
2229                    upload_signer_index: Some(1),
2230                    authority_signer_index: 2,
2231                    path_to_elf: Some("/Users/test/program.so".to_string()),
2232                    upload_range: None..None,
2233                }),
2234                signers: vec![
2235                    Box::new(read_keypair_file(&keypair_file).unwrap()),
2236                    Box::new(read_keypair_file(&program_keypair_file).unwrap()),
2237                    Box::new(read_keypair_file(&authority_keypair_file).unwrap()),
2238                ],
2239            }
2240        );
2241
2242        let test_command = test_commands.clone().get_matches_from(vec![
2243            "test",
2244            "program-v4",
2245            "deploy",
2246            "/Users/test/program.so",
2247            "--program-id",
2248            &program_keypair_file,
2249            "--authority",
2250            &authority_keypair_file,
2251        ]);
2252        assert_eq!(
2253            parse_command(&test_command, &default_signer, &mut None).unwrap(),
2254            CliCommandInfo {
2255                command: CliCommand::ProgramV4(ProgramV4CliCommand::Deploy {
2256                    additional_cli_config: AdditionalCliConfig::default(),
2257                    program_address: program_keypair.pubkey(),
2258                    buffer_address: None,
2259                    upload_signer_index: None,
2260                    authority_signer_index: 1,
2261                    path_to_elf: Some("/Users/test/program.so".to_string()),
2262                    upload_range: None..None,
2263                }),
2264                signers: vec![
2265                    Box::new(read_keypair_file(&keypair_file).unwrap()),
2266                    Box::new(read_keypair_file(&authority_keypair_file).unwrap()),
2267                ],
2268            }
2269        );
2270
2271        let test_command = test_commands.clone().get_matches_from(vec![
2272            "test",
2273            "program-v4",
2274            "deploy",
2275            "/Users/test/program.so",
2276            "--program-id",
2277            &program_keypair_file,
2278            "--buffer",
2279            &buffer_keypair_file,
2280            "--authority",
2281            &authority_keypair_file,
2282        ]);
2283        assert_eq!(
2284            parse_command(&test_command, &default_signer, &mut None).unwrap(),
2285            CliCommandInfo {
2286                command: CliCommand::ProgramV4(ProgramV4CliCommand::Deploy {
2287                    additional_cli_config: AdditionalCliConfig::default(),
2288                    program_address: program_keypair.pubkey(),
2289                    buffer_address: Some(buffer_keypair.pubkey()),
2290                    upload_signer_index: Some(1),
2291                    authority_signer_index: 2,
2292                    path_to_elf: Some("/Users/test/program.so".to_string()),
2293                    upload_range: None..None,
2294                }),
2295                signers: vec![
2296                    Box::new(read_keypair_file(&keypair_file).unwrap()),
2297                    Box::new(read_keypair_file(&buffer_keypair_file).unwrap()),
2298                    Box::new(read_keypair_file(&authority_keypair_file).unwrap()),
2299                ],
2300            }
2301        );
2302
2303        let test_command = test_commands.clone().get_matches_from(vec![
2304            "test",
2305            "program-v4",
2306            "deploy",
2307            "--program-id",
2308            &program_keypair_file,
2309            "--buffer",
2310            &buffer_keypair_file,
2311            "--authority",
2312            &authority_keypair_file,
2313        ]);
2314        assert_eq!(
2315            parse_command(&test_command, &default_signer, &mut None).unwrap(),
2316            CliCommandInfo {
2317                command: CliCommand::ProgramV4(ProgramV4CliCommand::Deploy {
2318                    additional_cli_config: AdditionalCliConfig::default(),
2319                    program_address: program_keypair.pubkey(),
2320                    buffer_address: Some(buffer_keypair.pubkey()),
2321                    upload_signer_index: None,
2322                    authority_signer_index: 2,
2323                    path_to_elf: None,
2324                    upload_range: None..None,
2325                }),
2326                signers: vec![
2327                    Box::new(read_keypair_file(&keypair_file).unwrap()),
2328                    Box::new(read_keypair_file(&buffer_keypair_file).unwrap()),
2329                    Box::new(read_keypair_file(&authority_keypair_file).unwrap()),
2330                ],
2331            }
2332        );
2333
2334        let test_command = test_commands.clone().get_matches_from(vec![
2335            "test",
2336            "program-v4",
2337            "deploy",
2338            "/Users/test/program.so",
2339            "--start-offset",
2340            "16",
2341            "--end-offset",
2342            "32",
2343            "--program-id",
2344            &program_keypair_file,
2345            "--use-rpc",
2346            "--with-compute-unit-price",
2347            "1",
2348        ]);
2349        assert_eq!(
2350            parse_command(&test_command, &default_signer, &mut None).unwrap(),
2351            CliCommandInfo {
2352                command: CliCommand::ProgramV4(ProgramV4CliCommand::Deploy {
2353                    additional_cli_config: AdditionalCliConfig {
2354                        use_rpc: true,
2355                        sign_only: false,
2356                        dump_transaction_message: false,
2357                        blockhash_query: BlockhashQuery::default(),
2358                        compute_unit_price: Some(1),
2359                    },
2360                    program_address: program_keypair.pubkey(),
2361                    buffer_address: None,
2362                    upload_signer_index: None,
2363                    authority_signer_index: 0,
2364                    path_to_elf: Some("/Users/test/program.so".to_string()),
2365                    upload_range: Some(16)..Some(32),
2366                }),
2367                signers: vec![Box::new(read_keypair_file(&keypair_file).unwrap()),],
2368            }
2369        );
2370    }
2371
2372    #[test]
2373    #[allow(clippy::cognitive_complexity)]
2374    fn test_cli_parse_retract() {
2375        let test_commands = get_clap_app("test", "desc", "version");
2376
2377        let default_keypair = Keypair::new();
2378        let keypair_file = make_tmp_path("keypair_file");
2379        write_keypair_file(&default_keypair, &keypair_file).unwrap();
2380        let default_signer = DefaultSigner::new("", &keypair_file);
2381
2382        let program_keypair = Keypair::new();
2383        let program_keypair_file = make_tmp_path("program_keypair_file");
2384        write_keypair_file(&program_keypair, &program_keypair_file).unwrap();
2385
2386        let authority_keypair = Keypair::new();
2387        let authority_keypair_file = make_tmp_path("authority_keypair_file");
2388        write_keypair_file(&authority_keypair, &authority_keypair_file).unwrap();
2389
2390        let test_command = test_commands.clone().get_matches_from(vec![
2391            "test",
2392            "program-v4",
2393            "retract",
2394            "--program-id",
2395            &program_keypair_file,
2396        ]);
2397        assert_eq!(
2398            parse_command(&test_command, &default_signer, &mut None).unwrap(),
2399            CliCommandInfo {
2400                command: CliCommand::ProgramV4(ProgramV4CliCommand::Retract {
2401                    additional_cli_config: AdditionalCliConfig::default(),
2402                    program_address: program_keypair.pubkey(),
2403                    authority_signer_index: 0,
2404                    close_program_entirely: false,
2405                }),
2406                signers: vec![Box::new(read_keypair_file(&keypair_file).unwrap()),],
2407            }
2408        );
2409
2410        let test_command = test_commands.clone().get_matches_from(vec![
2411            "test",
2412            "program-v4",
2413            "retract",
2414            "--program-id",
2415            &program_keypair_file,
2416            "--authority",
2417            &authority_keypair_file,
2418            "--close-program-entirely",
2419        ]);
2420        assert_eq!(
2421            parse_command(&test_command, &default_signer, &mut None).unwrap(),
2422            CliCommandInfo {
2423                command: CliCommand::ProgramV4(ProgramV4CliCommand::Retract {
2424                    additional_cli_config: AdditionalCliConfig::default(),
2425                    program_address: program_keypair.pubkey(),
2426                    authority_signer_index: 1,
2427                    close_program_entirely: true,
2428                }),
2429                signers: vec![
2430                    Box::new(read_keypair_file(&keypair_file).unwrap()),
2431                    Box::new(read_keypair_file(&authority_keypair_file).unwrap())
2432                ],
2433            }
2434        );
2435    }
2436
2437    #[test]
2438    #[allow(clippy::cognitive_complexity)]
2439    fn test_cli_parse_transfer_authority() {
2440        let test_commands = get_clap_app("test", "desc", "version");
2441
2442        let default_keypair = Keypair::new();
2443        let keypair_file = make_tmp_path("keypair_file");
2444        write_keypair_file(&default_keypair, &keypair_file).unwrap();
2445        let default_signer = DefaultSigner::new("", &keypair_file);
2446
2447        let program_keypair = Keypair::new();
2448        let program_keypair_file = make_tmp_path("program_keypair_file");
2449        write_keypair_file(&program_keypair, &program_keypair_file).unwrap();
2450
2451        let authority_keypair = Keypair::new();
2452        let authority_keypair_file = make_tmp_path("authority_keypair_file");
2453        write_keypair_file(&authority_keypair, &authority_keypair_file).unwrap();
2454
2455        let new_authority_keypair = Keypair::new();
2456        let new_authority_keypair_file = make_tmp_path("new_authority_keypair_file");
2457        write_keypair_file(&new_authority_keypair, &new_authority_keypair_file).unwrap();
2458
2459        let test_command = test_commands.clone().get_matches_from(vec![
2460            "test",
2461            "program-v4",
2462            "transfer-authority",
2463            "--program-id",
2464            &program_keypair_file,
2465            "--authority",
2466            &authority_keypair_file,
2467            "--new-authority",
2468            &new_authority_keypair_file,
2469        ]);
2470        assert_eq!(
2471            parse_command(&test_command, &default_signer, &mut None).unwrap(),
2472            CliCommandInfo {
2473                command: CliCommand::ProgramV4(ProgramV4CliCommand::TransferAuthority {
2474                    additional_cli_config: AdditionalCliConfig::default(),
2475                    program_address: program_keypair.pubkey(),
2476                    authority_signer_index: 1,
2477                    new_authority_signer_index: 2,
2478                }),
2479                signers: vec![
2480                    Box::new(read_keypair_file(&keypair_file).unwrap()),
2481                    Box::new(read_keypair_file(&authority_keypair_file).unwrap()),
2482                    Box::new(read_keypair_file(&new_authority_keypair_file).unwrap()),
2483                ],
2484            }
2485        );
2486    }
2487
2488    #[test]
2489    #[allow(clippy::cognitive_complexity)]
2490    fn test_cli_parse_finalize() {
2491        let test_commands = get_clap_app("test", "desc", "version");
2492
2493        let default_keypair = Keypair::new();
2494        let keypair_file = make_tmp_path("keypair_file");
2495        write_keypair_file(&default_keypair, &keypair_file).unwrap();
2496        let default_signer = DefaultSigner::new("", &keypair_file);
2497
2498        let program_keypair = Keypair::new();
2499        let program_keypair_file = make_tmp_path("program_keypair_file");
2500        write_keypair_file(&program_keypair, &program_keypair_file).unwrap();
2501
2502        let authority_keypair = Keypair::new();
2503        let authority_keypair_file = make_tmp_path("authority_keypair_file");
2504        write_keypair_file(&authority_keypair, &authority_keypair_file).unwrap();
2505
2506        let next_version_keypair = Keypair::new();
2507        let next_version_keypair_file = make_tmp_path("next_version_keypair_file");
2508        write_keypair_file(&next_version_keypair, &next_version_keypair_file).unwrap();
2509
2510        let test_command = test_commands.clone().get_matches_from(vec![
2511            "test",
2512            "program-v4",
2513            "finalize",
2514            "--program-id",
2515            &program_keypair_file,
2516            "--authority",
2517            &authority_keypair_file,
2518            "--next-version",
2519            &next_version_keypair_file,
2520        ]);
2521        assert_eq!(
2522            parse_command(&test_command, &default_signer, &mut None).unwrap(),
2523            CliCommandInfo {
2524                command: CliCommand::ProgramV4(ProgramV4CliCommand::Finalize {
2525                    additional_cli_config: AdditionalCliConfig::default(),
2526                    program_address: program_keypair.pubkey(),
2527                    authority_signer_index: 1,
2528                    next_version_signer_index: 2,
2529                }),
2530                signers: vec![
2531                    Box::new(read_keypair_file(&keypair_file).unwrap()),
2532                    Box::new(read_keypair_file(&authority_keypair_file).unwrap()),
2533                    Box::new(read_keypair_file(&next_version_keypair_file).unwrap()),
2534                ],
2535            }
2536        );
2537    }
2538}