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