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