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