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 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
540pub 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 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 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 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 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 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 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 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 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 let _ignored = std::fs::remove_dir_all(&path);
1626 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}