solana_cli/
program_v4.rs

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