solana_cli/
program_v4.rs

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