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