1use {
2 crate::{
3 checks::*,
4 cli::{CliCommand, CliCommandInfo, CliConfig, CliError, ProcessResult},
5 compute_budget::{
6 ComputeUnitConfig, WithComputeUnitConfig, simulate_and_update_compute_unit_limit,
7 },
8 feature::{CliFeatureStatus, status_from_account},
9 program::calculate_max_chunk_size,
10 },
11 agave_feature_set::{FEATURE_NAMES, FeatureSet, raise_cpi_nesting_limit_to_8},
12 clap::{App, AppSettings, Arg, ArgMatches, SubCommand, value_t},
13 log::*,
14 solana_account::Account,
15 solana_account_decoder::{UiAccount, UiAccountEncoding, UiDataSliceConfig},
16 solana_clap_utils::{
17 compute_budget::{ComputeUnitLimit, compute_unit_price_arg},
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::{DUMP_TRANSACTION_MESSAGE, OfflineArgs, SIGN_ONLY_ARG},
22 },
23 solana_cli_output::{
24 CliProgramId, CliProgramV4, CliProgramsV4, ReturnSignersConfig, return_signers_with_config,
25 },
26 solana_client::{
27 connection_cache::ConnectionCache,
28 send_and_confirm_transactions_in_parallel::{
29 SendAndConfirmConfigV2, send_and_confirm_transactions_in_parallel_v2,
30 },
31 },
32 solana_instruction::Instruction,
33 solana_loader_v4_interface::{
34 instruction,
35 state::{
36 LoaderV4State,
37 LoaderV4Status::{self, Retracted},
38 },
39 },
40 solana_message::Message,
41 solana_program_runtime::{
42 execution_budget::SVMTransactionExecutionBudget, invoke_context::InvokeContext,
43 },
44 solana_pubkey::Pubkey,
45 solana_remote_wallet::remote_wallet::RemoteWalletManager,
46 solana_rpc_client::nonblocking::rpc_client::RpcClient,
47 solana_rpc_client_api::{
48 config::{RpcAccountInfoConfig, RpcProgramAccountsConfig},
49 filter::{Memcmp, RpcFilterType},
50 request::MAX_MULTIPLE_ACCOUNTS,
51 },
52 solana_rpc_client_nonce_utils::nonblocking::blockhash_query::BlockhashQuery,
53 solana_sbpf::{elf::Executable, verifier::RequisiteVerifier},
54 solana_sdk_ids::{loader_v4, system_program},
55 solana_signer::Signer,
56 solana_system_interface::{MAX_PERMITTED_DATA_LENGTH, instruction as system_instruction},
57 solana_tpu_client::tpu_client::TpuClientConfig,
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 async 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 .await
567 }
568 ProgramV4CliCommand::Retract {
569 additional_cli_config,
570 program_address,
571 authority_signer_index,
572 close_program_entirely,
573 } => {
574 process_retract_program(
575 rpc_client,
576 config,
577 additional_cli_config,
578 authority_signer_index,
579 program_address,
580 *close_program_entirely,
581 )
582 .await
583 }
584 ProgramV4CliCommand::TransferAuthority {
585 additional_cli_config,
586 program_address,
587 authority_signer_index,
588 new_authority_signer_index,
589 } => {
590 process_transfer_authority_of_program(
591 rpc_client,
592 config,
593 additional_cli_config,
594 authority_signer_index,
595 new_authority_signer_index,
596 program_address,
597 )
598 .await
599 }
600 ProgramV4CliCommand::Finalize {
601 additional_cli_config,
602 program_address,
603 authority_signer_index,
604 next_version_signer_index,
605 } => {
606 process_finalize_program(
607 rpc_client,
608 config,
609 additional_cli_config,
610 authority_signer_index,
611 next_version_signer_index,
612 program_address,
613 )
614 .await
615 }
616 ProgramV4CliCommand::Show {
617 account_pubkey,
618 authority,
619 all,
620 } => process_show(rpc_client, config, *account_pubkey, *authority, *all).await,
621 ProgramV4CliCommand::Dump {
622 account_pubkey,
623 output_location,
624 } => process_dump(rpc_client, config, *account_pubkey, output_location).await,
625 }
626}
627
628pub async fn process_deploy_program(
645 rpc_client: Arc<RpcClient>,
646 config: &CliConfig<'_>,
647 additional_cli_config: &AdditionalCliConfig,
648 program_address: &Pubkey,
649 buffer_address: Option<&Pubkey>,
650 upload_signer_index: Option<&SignerIndex>,
651 auth_signer_index: &SignerIndex,
652 program_data: &[u8],
653 upload_range: Range<Option<usize>>,
654) -> ProcessResult {
655 let payer_pubkey = config.signers[0].pubkey();
656 let authority_pubkey = config.signers[*auth_signer_index].pubkey();
657
658 let program_account = rpc_client
660 .get_account_with_commitment(program_address, config.commitment)
661 .await?
662 .value;
663 let buffer_account = if let Some(buffer_address) = buffer_address {
664 rpc_client
665 .get_account_with_commitment(buffer_address, config.commitment)
666 .await?
667 .value
668 } else {
669 None
670 };
671 let lamports_required = rpc_client
672 .get_minimum_balance_for_rent_exemption(
673 LoaderV4State::program_data_offset().saturating_add(program_data.len()),
674 )
675 .await?;
676 let program_account_exists = program_account
677 .as_ref()
678 .map(|account| loader_v4::check_id(&account.owner))
679 .unwrap_or(false);
680 if upload_signer_index
681 .map(|index| &config.signers[*index].pubkey() == program_address)
682 .unwrap_or(false)
683 {
684 if program_account_exists {
686 return Err(
687 "Program account does exist already. Did you perhaps intent to redeploy an \
688 existing program instead? Then use --program-id instead of --program-keypair."
689 .into(),
690 );
691 }
692 } else {
693 if !program_account_exists {
695 return Err(
696 "Program account does not exist. Did you perhaps intent to deploy a new program \
697 instead? Then use --program-keypair instead of --program-id."
698 .into(),
699 );
700 }
701 }
702 if let Some(program_account) = program_account.as_ref() {
703 if !system_program::check_id(&program_account.owner)
704 && !loader_v4::check_id(&program_account.owner)
705 {
706 return Err(format!("{program_address} is not owned by loader-v4").into());
707 }
708 }
709 if let Some(buffer_account) = buffer_account.as_ref() {
710 if !system_program::check_id(&buffer_account.owner)
711 && !loader_v4::check_id(&buffer_account.owner)
712 {
713 return Err(format!("{} is not owned by loader-v4", buffer_address.unwrap()).into());
714 }
715 }
716
717 let mut feature_set = FeatureSet::default();
719 for feature_ids in FEATURE_NAMES
720 .keys()
721 .cloned()
722 .collect::<Vec<Pubkey>>()
723 .chunks(MAX_MULTIPLE_ACCOUNTS)
724 {
725 rpc_client
726 .get_multiple_accounts(feature_ids)
727 .await?
728 .into_iter()
729 .zip(feature_ids)
730 .for_each(|(account, feature_id)| {
731 let activation_slot = account.and_then(status_from_account);
732
733 if let Some(CliFeatureStatus::Active(slot)) = activation_slot {
734 feature_set.activate(feature_id, slot);
735 }
736 });
737 }
738 let program_runtime_environment = agave_syscalls::create_program_runtime_environment_v1(
739 &feature_set.runtime_features(),
740 &SVMTransactionExecutionBudget::new_with_defaults(
741 feature_set.is_active(&raise_cpi_nesting_limit_to_8::id()),
742 ),
743 true,
744 false,
745 )
746 .unwrap();
747
748 let upload_range =
750 upload_range.start.unwrap_or(0)..upload_range.end.unwrap_or(program_data.len());
751 const MAX_LEN: usize =
752 (MAX_PERMITTED_DATA_LENGTH as usize).saturating_sub(LoaderV4State::program_data_offset());
753 if program_data.len() > MAX_LEN {
754 return Err(format!(
755 "Program length {} exceeds maximum length {}",
756 program_data.len(),
757 MAX_LEN,
758 )
759 .into());
760 }
761 if upload_range.end > program_data.len() {
762 return Err(format!(
763 "Range end {} exceeds program length {}",
764 upload_range.end,
765 program_data.len(),
766 )
767 .into());
768 }
769 let executable =
770 Executable::<InvokeContext>::from_elf(program_data, Arc::new(program_runtime_environment))
771 .map_err(|err| format!("ELF error: {err}"))?;
772 executable
773 .verify::<RequisiteVerifier>()
774 .map_err(|err| format!("ELF error: {err}"))?;
775
776 let mut initial_instructions = Vec::default();
778 if let Some(program_account) = program_account.as_ref() {
779 if let Some(retract_instruction) =
780 build_retract_instruction(program_account, program_address, &authority_pubkey)?
781 {
782 initial_instructions.insert(0, retract_instruction);
783 }
784 let (mut set_program_length_instructions, _lamports_required) =
785 build_set_program_length_instructions(
786 rpc_client.clone(),
787 config,
788 auth_signer_index,
789 program_account,
790 program_address,
791 program_data.len() as u32,
792 )
793 .await?;
794 if !set_program_length_instructions.is_empty() {
795 initial_instructions.append(&mut set_program_length_instructions);
796 }
797 }
798
799 let upload_address = buffer_address.unwrap_or(program_address);
800 let (upload_account, upload_account_length) = if buffer_address.is_some() {
801 (buffer_account, upload_range.len())
802 } else {
803 (program_account, program_data.len())
804 };
805 let existing_lamports = upload_account
806 .as_ref()
807 .map(|account| account.lamports)
808 .unwrap_or(0);
809 if let Some(upload_account) = upload_account.as_ref() {
811 if system_program::check_id(&upload_account.owner) {
812 initial_instructions.append(&mut vec![
813 system_instruction::transfer(&payer_pubkey, upload_address, lamports_required),
814 system_instruction::assign(upload_address, &loader_v4::id()),
815 instruction::set_program_length(
816 upload_address,
817 &authority_pubkey,
818 upload_account_length as u32,
819 &payer_pubkey,
820 ),
821 ]);
822 }
823 } else {
824 initial_instructions.append(&mut instruction::create_buffer(
825 &payer_pubkey,
826 upload_address,
827 lamports_required,
828 &authority_pubkey,
829 upload_account_length as u32,
830 &payer_pubkey,
831 ));
832 }
833
834 let mut write_messages = vec![];
836 if upload_signer_index.is_none() {
837 if upload_account.is_none() {
838 return Err(format!(
839 "No ELF was provided or uploaded to the account {upload_address:?}",
840 )
841 .into());
842 }
843 } else {
844 if upload_range.is_empty() {
845 return Err(format!("Attempting to upload empty range {upload_range:?}").into());
846 }
847 let first_write_message = Message::new(
848 &[instruction::write(
849 upload_address,
850 &authority_pubkey,
851 0,
852 Vec::new(),
853 )],
854 Some(&payer_pubkey),
855 );
856 let chunk_size = calculate_max_chunk_size(first_write_message);
857 for (chunk, i) in program_data[upload_range.clone()]
858 .chunks(chunk_size)
859 .zip(0usize..)
860 {
861 write_messages.push(vec![instruction::write(
862 upload_address,
863 &authority_pubkey,
864 (upload_range.start as u32).saturating_add(i.saturating_mul(chunk_size) as u32),
865 chunk.to_vec(),
866 )]);
867 }
868 }
869
870 let final_instructions = if buffer_address == Some(program_address) {
871 Vec::default()
873 } else if let Some(buffer_address) = buffer_address {
874 vec![
876 instruction::copy(
877 program_address,
878 &authority_pubkey,
879 buffer_address,
880 upload_range.start as u32,
881 0,
882 upload_range.len() as u32,
883 ),
884 instruction::deploy(program_address, &authority_pubkey),
885 instruction::set_program_length(buffer_address, &authority_pubkey, 0, &payer_pubkey),
886 ]
887 } else {
888 vec![instruction::deploy(program_address, &authority_pubkey)]
890 };
891
892 send_messages(
893 rpc_client,
894 config,
895 additional_cli_config,
896 auth_signer_index,
897 if initial_instructions.is_empty() {
898 Vec::default()
899 } else {
900 vec![initial_instructions]
901 },
902 write_messages,
903 if final_instructions.is_empty() {
904 Vec::default()
905 } else {
906 vec![final_instructions]
907 },
908 lamports_required.saturating_sub(existing_lamports),
909 config.output_format.formatted_string(&CliProgramId {
910 program_id: program_address.to_string(),
911 signature: None,
912 }),
913 )
914 .await
915}
916
917async fn process_retract_program(
918 rpc_client: Arc<RpcClient>,
919 config: &CliConfig<'_>,
920 additional_cli_config: &AdditionalCliConfig,
921 auth_signer_index: &SignerIndex,
922 program_address: &Pubkey,
923 close_program_entirely: bool,
924) -> ProcessResult {
925 let payer_pubkey = config.signers[0].pubkey();
926 let authority_pubkey = config.signers[*auth_signer_index].pubkey();
927
928 let Some(program_account) = rpc_client
929 .get_account_with_commitment(program_address, config.commitment)
930 .await?
931 .value
932 else {
933 return Err("Program account does not exist".into());
934 };
935 if !loader_v4::check_id(&program_account.owner) {
936 return Err(format!("{program_address} is not owned by loader-v4").into());
937 }
938
939 let mut instructions = Vec::default();
940 let retract_instruction =
941 build_retract_instruction(&program_account, program_address, &authority_pubkey)?;
942 if let Some(retract_instruction) = retract_instruction {
943 instructions.push(retract_instruction);
944 }
945 if close_program_entirely {
946 let set_program_length_instruction =
947 instruction::set_program_length(program_address, &authority_pubkey, 0, &payer_pubkey);
948 instructions.push(set_program_length_instruction);
949 } else if instructions.is_empty() {
950 return Err("Program is retracted already".into());
951 }
952
953 send_messages(
954 rpc_client,
955 config,
956 additional_cli_config,
957 auth_signer_index,
958 vec![instructions],
959 Vec::default(),
960 Vec::default(),
961 0,
962 config.output_format.formatted_string(&CliProgramId {
963 program_id: program_address.to_string(),
964 signature: None,
965 }),
966 )
967 .await
968}
969
970async fn process_transfer_authority_of_program(
971 rpc_client: Arc<RpcClient>,
972 config: &CliConfig<'_>,
973 additional_cli_config: &AdditionalCliConfig,
974 auth_signer_index: &SignerIndex,
975 new_auth_signer_index: &SignerIndex,
976 program_address: &Pubkey,
977) -> ProcessResult {
978 if let Some(program_account) = rpc_client
979 .get_account_with_commitment(program_address, config.commitment)
980 .await?
981 .value
982 {
983 if !loader_v4::check_id(&program_account.owner) {
984 return Err(format!("{program_address} is not owned by loader-v4").into());
985 }
986 } else {
987 return Err(format!("Unable to find the account {program_address}").into());
988 }
989
990 let authority_pubkey = config.signers[*auth_signer_index].pubkey();
991 let new_authority_pubkey = config.signers[*new_auth_signer_index].pubkey();
992
993 let messages = vec![vec![instruction::transfer_authority(
994 program_address,
995 &authority_pubkey,
996 &new_authority_pubkey,
997 )]];
998
999 send_messages(
1000 rpc_client,
1001 config,
1002 additional_cli_config,
1003 auth_signer_index,
1004 messages,
1005 Vec::default(),
1006 Vec::default(),
1007 0,
1008 config.output_format.formatted_string(&CliProgramId {
1009 program_id: program_address.to_string(),
1010 signature: None,
1011 }),
1012 )
1013 .await
1014}
1015
1016async fn process_finalize_program(
1017 rpc_client: Arc<RpcClient>,
1018 config: &CliConfig<'_>,
1019 additional_cli_config: &AdditionalCliConfig,
1020 auth_signer_index: &SignerIndex,
1021 next_version_signer_index: &SignerIndex,
1022 program_address: &Pubkey,
1023) -> ProcessResult {
1024 if let Some(program_account) = rpc_client
1025 .get_account_with_commitment(program_address, config.commitment)
1026 .await?
1027 .value
1028 {
1029 if !loader_v4::check_id(&program_account.owner) {
1030 return Err(format!("{program_address} is not owned by loader-v4").into());
1031 }
1032 } else {
1033 return Err(format!("Unable to find the account {program_address}").into());
1034 }
1035
1036 let authority_pubkey = config.signers[*auth_signer_index].pubkey();
1037 let next_version_pubkey = config.signers[*next_version_signer_index].pubkey();
1038
1039 let messages = vec![vec![instruction::finalize(
1040 program_address,
1041 &authority_pubkey,
1042 &next_version_pubkey,
1043 )]];
1044
1045 send_messages(
1046 rpc_client,
1047 config,
1048 additional_cli_config,
1049 auth_signer_index,
1050 messages,
1051 Vec::default(),
1052 Vec::default(),
1053 0,
1054 config.output_format.formatted_string(&CliProgramId {
1055 program_id: program_address.to_string(),
1056 signature: None,
1057 }),
1058 )
1059 .await
1060}
1061
1062async fn process_show(
1063 rpc_client: Arc<RpcClient>,
1064 config: &CliConfig<'_>,
1065 program_address: Option<Pubkey>,
1066 authority: Pubkey,
1067 all: bool,
1068) -> ProcessResult {
1069 if let Some(program_address) = program_address {
1070 if let Some(account) = rpc_client
1071 .get_account_with_commitment(&program_address, config.commitment)
1072 .await?
1073 .value
1074 {
1075 if loader_v4::check_id(&account.owner) {
1076 if let Ok(state) = solana_loader_v4_program::get_state(&account.data) {
1077 let status = match state.status {
1078 LoaderV4Status::Retracted => "retracted",
1079 LoaderV4Status::Deployed => "deployed",
1080 LoaderV4Status::Finalized => "finalized",
1081 };
1082 Ok(config.output_format.formatted_string(&CliProgramV4 {
1083 program_id: program_address.to_string(),
1084 owner: account.owner.to_string(),
1085 authority: state.authority_address_or_next_version.to_string(),
1086 last_deploy_slot: state.slot,
1087 data_len: account
1088 .data
1089 .len()
1090 .saturating_sub(LoaderV4State::program_data_offset()),
1091 status: status.to_string(),
1092 }))
1093 } else {
1094 Err(format!("{program_address} program state is invalid").into())
1095 }
1096 } else {
1097 Err(format!("{program_address} is not owned by loader-v4").into())
1098 }
1099 } else {
1100 Err(format!("Unable to find the account {program_address}").into())
1101 }
1102 } else {
1103 let authority_pubkey = if all { None } else { Some(authority) };
1104 let programs = get_programs(rpc_client, config, authority_pubkey).await?;
1105 Ok(config.output_format.formatted_string(&programs))
1106 }
1107}
1108
1109pub async fn process_dump(
1110 rpc_client: Arc<RpcClient>,
1111 config: &CliConfig<'_>,
1112 account_pubkey: Option<Pubkey>,
1113 output_location: &str,
1114) -> ProcessResult {
1115 if let Some(account_pubkey) = account_pubkey {
1116 if let Some(account) = rpc_client
1117 .get_account_with_commitment(&account_pubkey, config.commitment)
1118 .await?
1119 .value
1120 {
1121 if loader_v4::check_id(&account.owner) {
1122 let mut f = File::create(output_location)?;
1123 f.write_all(&account.data[LoaderV4State::program_data_offset()..])?;
1124 Ok(format!("Wrote program to {output_location}"))
1125 } else {
1126 Err(format!("{account_pubkey} is not owned by loader-v4").into())
1127 }
1128 } else {
1129 Err(format!("Unable to find the account {account_pubkey}").into())
1130 }
1131 } else {
1132 Err("No account specified".into())
1133 }
1134}
1135
1136#[deprecated(
1145 note = "Consider using async process_deploy_program() with nonblocking RpcClient for better \
1146 performance and resource usage"
1147)]
1148pub fn process_deploy_program_sync(
1149 rpc_client_blocking: Arc<solana_rpc_client::rpc_client::RpcClient>,
1150 config: &CliConfig<'_>,
1151 additional_cli_config: &AdditionalCliConfig,
1152 program_address: &Pubkey,
1153 buffer_address: Option<&Pubkey>,
1154 upload_signer_index: Option<&SignerIndex>,
1155 auth_signer_index: &SignerIndex,
1156 program_data: &[u8],
1157 upload_range: Range<Option<usize>>,
1158) -> ProcessResult {
1159 tokio::task::block_in_place(|| {
1160 tokio::runtime::Handle::current().block_on(async {
1161 let rpc_client_nonblocking = Arc::new(RpcClient::new_with_commitment(
1163 rpc_client_blocking.url(),
1164 rpc_client_blocking.commitment(),
1165 ));
1166
1167 process_deploy_program(
1168 rpc_client_nonblocking,
1169 config,
1170 additional_cli_config,
1171 program_address,
1172 buffer_address,
1173 upload_signer_index,
1174 auth_signer_index,
1175 program_data,
1176 upload_range,
1177 )
1178 .await
1179 })
1180 })
1181}
1182
1183#[deprecated(
1192 note = "Consider using async process_dump() with nonblocking RpcClient for better performance \
1193 and resource usage"
1194)]
1195pub fn process_dump_sync(
1196 rpc_client_blocking: Arc<solana_rpc_client::rpc_client::RpcClient>,
1197 config: &CliConfig<'_>,
1198 account_pubkey: Option<Pubkey>,
1199 output_location: &str,
1200) -> ProcessResult {
1201 tokio::task::block_in_place(|| {
1202 tokio::runtime::Handle::current().block_on(async {
1203 let rpc_client_nonblocking = Arc::new(RpcClient::new_with_commitment(
1205 rpc_client_blocking.url(),
1206 rpc_client_blocking.commitment(),
1207 ));
1208
1209 process_dump(
1210 rpc_client_nonblocking,
1211 config,
1212 account_pubkey,
1213 output_location,
1214 )
1215 .await
1216 })
1217 })
1218}
1219
1220#[allow(clippy::too_many_arguments)]
1221async fn send_messages(
1222 rpc_client: Arc<RpcClient>,
1223 config: &CliConfig<'_>,
1224 additional_cli_config: &AdditionalCliConfig,
1225 auth_signer_index: &SignerIndex,
1226 initial_messages: Vec<Vec<Instruction>>,
1227 write_messages: Vec<Vec<Instruction>>,
1228 final_messages: Vec<Vec<Instruction>>,
1229 balance_needed: u64,
1230 ok_result: String,
1231) -> ProcessResult {
1232 let payer_pubkey = config.signers[0].pubkey();
1233 let blockhash = additional_cli_config
1234 .blockhash_query
1235 .get_blockhash(&rpc_client, config.commitment)
1236 .await?;
1237 let compute_unit_config = ComputeUnitConfig {
1238 compute_unit_price: additional_cli_config.compute_unit_price,
1239 compute_unit_limit: ComputeUnitLimit::Simulated,
1240 };
1241 let simulate_messages = |message_prototypes: Vec<Vec<Instruction>>| async {
1242 let mut messages = Vec::with_capacity(message_prototypes.len());
1243 for instructions in message_prototypes.into_iter() {
1244 let mut message = Message::new_with_blockhash(
1245 &instructions.with_compute_unit_config(&compute_unit_config),
1246 Some(&payer_pubkey),
1247 &blockhash,
1248 );
1249 simulate_and_update_compute_unit_limit(
1250 &ComputeUnitLimit::Simulated,
1251 &rpc_client,
1252 &mut message,
1253 )
1254 .await?;
1255 messages.push(message);
1256 }
1257 Ok::<Vec<solana_message::Message>, Box<dyn std::error::Error>>(messages)
1258 };
1259 let initial_messages = simulate_messages(initial_messages).await?;
1260 let write_messages = simulate_messages(write_messages).await?;
1261 let final_messages = simulate_messages(final_messages).await?;
1262
1263 let mut fee = Saturating(0);
1264 for message in initial_messages.iter() {
1265 fee += rpc_client.get_fee_for_message(message).await?;
1266 }
1267 for message in final_messages.iter() {
1268 fee += rpc_client.get_fee_for_message(message).await?;
1269 }
1270 if let Some(message) = write_messages.first() {
1272 fee += rpc_client
1273 .get_fee_for_message(message)
1274 .await?
1275 .saturating_mul(write_messages.len() as u64);
1276 }
1277 check_account_for_spend_and_fee_with_commitment(
1278 &rpc_client,
1279 &payer_pubkey,
1280 balance_needed,
1281 fee.0,
1282 config.commitment,
1283 )
1284 .await?;
1285
1286 let send_or_return_message = |message: Message| async {
1287 let signers = (0..message.header.num_required_signatures)
1288 .map(|signer_index| {
1289 let key = message.account_keys[signer_index as usize];
1290 config
1291 .signers
1292 .iter()
1293 .find(|signer| signer.pubkey() == key)
1294 .unwrap()
1295 })
1296 .collect::<Vec<_>>();
1297 let mut tx = Transaction::new_unsigned(message);
1298 tx.try_sign(&signers, blockhash)?;
1299 if additional_cli_config.sign_only {
1300 return_signers_with_config(
1301 &tx,
1302 &config.output_format,
1303 &ReturnSignersConfig {
1304 dump_transaction_message: additional_cli_config.dump_transaction_message,
1305 },
1306 )
1307 } else {
1308 rpc_client
1309 .send_and_confirm_transaction_with_spinner_and_config(
1310 &tx,
1311 config.commitment,
1312 config.send_transaction_config,
1313 )
1314 .await
1315 .map_err(|err| format!("Failed to send message: {err}").into())
1316 .map(|_| String::new())
1317 }
1318 };
1319
1320 for message in initial_messages.into_iter() {
1321 let result = send_or_return_message(message).await?;
1322 if additional_cli_config.sign_only {
1323 return Ok(result);
1324 }
1325 }
1326
1327 if !write_messages.is_empty() {
1328 let connection_cache = {
1329 #[cfg(feature = "dev-context-only-utils")]
1330 let cache =
1331 ConnectionCache::new_quic_for_tests("connection_cache_cli_program_v4_quic", 1);
1332 #[cfg(not(feature = "dev-context-only-utils"))]
1333 let cache = ConnectionCache::new_quic("connection_cache_cli_program_v4_quic", 1);
1334 cache
1335 };
1336
1337 let transaction_errors = match connection_cache {
1338 ConnectionCache::Udp(cache) => {
1339 solana_tpu_client::nonblocking::tpu_client::TpuClient::new_with_connection_cache(
1340 rpc_client.clone(),
1341 &config.websocket_url,
1342 TpuClientConfig::default(),
1343 cache,
1344 )
1345 .await?
1346 .send_and_confirm_messages_with_spinner(
1347 &write_messages,
1348 &[config.signers[0], config.signers[*auth_signer_index]],
1349 )
1350 .await
1351 }
1352 ConnectionCache::Quic(cache) => {
1353 let tpu_client = if additional_cli_config.use_rpc {
1354 None
1355 } else {
1356 Some(
1358 solana_client::nonblocking::tpu_client::TpuClient::new_with_connection_cache(
1359 rpc_client.clone(),
1360 &config.websocket_url,
1361 TpuClientConfig::default(),
1362 cache,
1363 )
1364 .await
1365 .expect("Should return a valid tpu client"),
1366 )
1367 };
1368
1369 send_and_confirm_transactions_in_parallel_v2(
1370 rpc_client.clone(),
1371 tpu_client,
1372 &write_messages,
1373 &[config.signers[0], config.signers[*auth_signer_index]],
1374 SendAndConfirmConfigV2 {
1375 resign_txs_count: Some(5),
1376 with_spinner: true,
1377 rpc_send_transaction_config: config.send_transaction_config,
1378 },
1379 )
1380 .await
1381 }
1382 }
1383 .map_err(|err| format!("Data writes to account failed: {err}"))?
1384 .into_iter()
1385 .flatten()
1386 .collect::<Vec<_>>();
1387
1388 if !transaction_errors.is_empty() {
1389 for transaction_error in &transaction_errors {
1390 error!("{transaction_error:?}");
1391 }
1392 return Err(format!("{} write transactions failed", transaction_errors.len()).into());
1393 }
1394 }
1395
1396 for message in final_messages.into_iter() {
1397 let result = send_or_return_message(message).await?;
1398 if additional_cli_config.sign_only {
1399 return Ok(result);
1400 }
1401 }
1402
1403 Ok(ok_result)
1404}
1405
1406fn build_retract_instruction(
1407 account: &Account,
1408 buffer_address: &Pubkey,
1409 authority: &Pubkey,
1410) -> Result<Option<Instruction>, Box<dyn std::error::Error>> {
1411 if !loader_v4::check_id(&account.owner) {
1412 return Ok(None);
1413 }
1414
1415 if let Ok(LoaderV4State {
1416 slot: _,
1417 authority_address_or_next_version,
1418 status,
1419 }) = solana_loader_v4_program::get_state(&account.data)
1420 {
1421 if authority != authority_address_or_next_version {
1422 return Err(
1423 "Program authority does not match with the provided authority address".into(),
1424 );
1425 }
1426
1427 match status {
1428 Retracted => Ok(None),
1429 LoaderV4Status::Deployed => Ok(Some(instruction::retract(buffer_address, authority))),
1430 LoaderV4Status::Finalized => Err("Program is immutable".into()),
1431 }
1432 } else {
1433 Err("Program account's state could not be deserialized".into())
1434 }
1435}
1436
1437async fn build_set_program_length_instructions(
1438 rpc_client: Arc<RpcClient>,
1439 config: &CliConfig<'_>,
1440 auth_signer_index: &SignerIndex,
1441 account: &Account,
1442 buffer_address: &Pubkey,
1443 program_data_length: u32,
1444) -> Result<(Vec<Instruction>, u64), Box<dyn std::error::Error>> {
1445 let expected_account_data_len =
1446 LoaderV4State::program_data_offset().saturating_add(program_data_length as usize);
1447
1448 let lamports_required = rpc_client
1449 .get_minimum_balance_for_rent_exemption(expected_account_data_len)
1450 .await?;
1451
1452 if !loader_v4::check_id(&account.owner) {
1453 return Ok((Vec::default(), lamports_required));
1454 }
1455
1456 let payer_pubkey = config.signers[0].pubkey();
1457 let authority_pubkey = config.signers[*auth_signer_index].pubkey();
1458 let expected_account_data_len =
1459 LoaderV4State::program_data_offset().saturating_add(program_data_length as usize);
1460
1461 let lamports_required = rpc_client
1462 .get_minimum_balance_for_rent_exemption(expected_account_data_len)
1463 .await?;
1464
1465 if !loader_v4::check_id(&account.owner) {
1466 return Ok((Vec::default(), lamports_required));
1467 }
1468
1469 if !account.data.is_empty() {
1470 if let Ok(LoaderV4State {
1471 slot: _,
1472 authority_address_or_next_version,
1473 status,
1474 }) = solana_loader_v4_program::get_state(&account.data)
1475 {
1476 if &authority_pubkey != authority_address_or_next_version {
1477 return Err(
1478 "Program authority does not match with the provided authority address".into(),
1479 );
1480 }
1481
1482 if matches!(status, LoaderV4Status::Finalized) {
1483 return Err("Program is immutable".into());
1484 }
1485 } else {
1486 return Err("Program account's state could not be deserialized".into());
1487 }
1488 }
1489
1490 let set_program_length_instruction = instruction::set_program_length(
1491 buffer_address,
1492 &authority_pubkey,
1493 program_data_length,
1494 &payer_pubkey,
1495 );
1496
1497 match account.data.len().cmp(&expected_account_data_len) {
1498 Ordering::Less => {
1499 if account.lamports < lamports_required {
1500 let extra_lamports_required = lamports_required.saturating_sub(account.lamports);
1501 Ok((
1502 vec![
1503 system_instruction::transfer(
1504 &payer_pubkey,
1505 buffer_address,
1506 extra_lamports_required,
1507 ),
1508 set_program_length_instruction,
1509 ],
1510 extra_lamports_required,
1511 ))
1512 } else {
1513 Ok((vec![set_program_length_instruction], 0))
1514 }
1515 }
1516 Ordering::Equal => {
1517 if account.lamports < lamports_required {
1518 return Err("Program account has less lamports than required for its size".into());
1519 }
1520 Ok((vec![], 0))
1521 }
1522 Ordering::Greater => {
1523 if account.lamports < lamports_required {
1524 return Err("Program account has less lamports than required for its size".into());
1525 }
1526 Ok((vec![set_program_length_instruction], 0))
1527 }
1528 }
1529}
1530
1531async fn get_accounts_with_filter(
1532 rpc_client: Arc<RpcClient>,
1533 _config: &CliConfig<'_>,
1534 filters: Vec<RpcFilterType>,
1535 length: usize,
1536) -> Result<Vec<(Pubkey, UiAccount)>, Box<dyn std::error::Error>> {
1537 let results = rpc_client
1538 .get_program_ui_accounts_with_config(
1539 &loader_v4::id(),
1540 RpcProgramAccountsConfig {
1541 filters: Some(filters),
1542 account_config: RpcAccountInfoConfig {
1543 encoding: Some(UiAccountEncoding::Base64),
1544 data_slice: Some(UiDataSliceConfig { offset: 0, length }),
1545 ..RpcAccountInfoConfig::default()
1546 },
1547 ..RpcProgramAccountsConfig::default()
1548 },
1549 )
1550 .await?;
1551 Ok(results)
1552}
1553
1554async fn get_programs(
1555 rpc_client: Arc<RpcClient>,
1556 config: &CliConfig<'_>,
1557 authority_pubkey: Option<Pubkey>,
1558) -> Result<CliProgramsV4, Box<dyn std::error::Error>> {
1559 let filters = if let Some(authority_pubkey) = authority_pubkey {
1560 vec![
1561 (RpcFilterType::Memcmp(Memcmp::new_base58_encoded(
1562 size_of::<u64>(),
1563 authority_pubkey.as_ref(),
1564 ))),
1565 ]
1566 } else {
1567 vec![]
1568 };
1569
1570 let results = get_accounts_with_filter(
1571 rpc_client,
1572 config,
1573 filters,
1574 LoaderV4State::program_data_offset(),
1575 )
1576 .await?;
1577
1578 let mut programs = vec![];
1579 for (program, account) in results.iter() {
1580 let data_bytes = account.data.decode().expect(
1581 "It should be impossible at this point for the account data not to be decodable. \
1582 Ensure that the account was fetched using a binary encoding.",
1583 );
1584 if let Ok(state) = solana_loader_v4_program::get_state(data_bytes.as_slice()) {
1585 let status = match state.status {
1586 LoaderV4Status::Retracted => "retracted",
1587 LoaderV4Status::Deployed => "deployed",
1588 LoaderV4Status::Finalized => "finalized",
1589 };
1590 programs.push(CliProgramV4 {
1591 program_id: program.to_string(),
1592 owner: account.owner.to_string(),
1593 authority: state.authority_address_or_next_version.to_string(),
1594 last_deploy_slot: state.slot,
1595 status: status.to_string(),
1596 data_len: data_bytes
1597 .len()
1598 .saturating_sub(LoaderV4State::program_data_offset()),
1599 });
1600 } else {
1601 return Err(format!("Error parsing Program account {program}").into());
1602 }
1603 }
1604 Ok(CliProgramsV4 { programs })
1605}
1606
1607#[cfg(test)]
1608mod tests {
1609 use {
1610 super::*,
1611 crate::{clap_app::get_clap_app, cli::parse_command},
1612 serde_json::json,
1613 solana_keypair::{Keypair, keypair_from_seed, read_keypair_file, write_keypair_file},
1614 solana_rpc_client_api::{
1615 request::RpcRequest,
1616 response::{Response, RpcResponseContext},
1617 },
1618 std::collections::HashMap,
1619 };
1620
1621 fn program_authority() -> solana_keypair::Keypair {
1622 keypair_from_seed(&[3u8; 32]).unwrap()
1623 }
1624
1625 fn rpc_client_no_existing_program() -> RpcClient {
1626 RpcClient::new_mock("succeeds".to_string())
1627 }
1628
1629 fn rpc_client_with_program_data(data: &str, loader_is_owner: bool) -> RpcClient {
1630 let owner = if loader_is_owner {
1631 "LoaderV411111111111111111111111111111111111"
1632 } else {
1633 "Vote111111111111111111111111111111111111111"
1634 };
1635 let account_info_response = json!(Response {
1636 context: RpcResponseContext {
1637 slot: 1,
1638 api_version: None
1639 },
1640 value: json!({
1641 "data": [data, "base64"],
1642 "lamports": 42,
1643 "owner": owner,
1644 "executable": true,
1645 "rentEpoch": 1,
1646 }),
1647 });
1648 let mut mocks = HashMap::new();
1649 mocks.insert(RpcRequest::GetAccountInfo, account_info_response);
1650 RpcClient::new_mock_with_mocks("".to_string(), mocks)
1651 }
1652
1653 fn rpc_client_wrong_account_owner() -> RpcClient {
1654 rpc_client_with_program_data(
1655 "AAAAAAAAAADtSSjGKNHCxurpAziQWZVhKVknOlxj+TY2wUYUrIc30QAAAAAAAAAA",
1656 false,
1657 )
1658 }
1659
1660 fn rpc_client_wrong_authority() -> RpcClient {
1661 rpc_client_with_program_data(
1662 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
1663 true,
1664 )
1665 }
1666
1667 fn rpc_client_with_program_retracted() -> RpcClient {
1668 rpc_client_with_program_data(
1669 "AAAAAAAAAADtSSjGKNHCxurpAziQWZVhKVknOlxj+TY2wUYUrIc30QAAAAAAAAAA",
1670 true,
1671 )
1672 }
1673
1674 fn rpc_client_with_program_deployed() -> RpcClient {
1675 rpc_client_with_program_data(
1676 "AAAAAAAAAADtSSjGKNHCxurpAziQWZVhKVknOlxj+TY2wUYUrIc30QEAAAAAAAAA",
1677 true,
1678 )
1679 }
1680
1681 fn rpc_client_with_program_finalized() -> RpcClient {
1682 rpc_client_with_program_data(
1683 "AAAAAAAAAADtSSjGKNHCxurpAziQWZVhKVknOlxj+TY2wUYUrIc30QIAAAAAAAAA",
1684 true,
1685 )
1686 }
1687
1688 #[tokio::test]
1689 async fn test_deploy() {
1690 let mut config = CliConfig::default();
1691 let mut program_data = Vec::new();
1692 let mut file = File::open("tests/fixtures/noop.so").unwrap();
1693 file.read_to_end(&mut program_data).unwrap();
1694
1695 let payer = keypair_from_seed(&[1u8; 32]).unwrap();
1696 let program_signer = keypair_from_seed(&[2u8; 32]).unwrap();
1697 let authority_signer = program_authority();
1698
1699 config.signers.push(&payer);
1700 config.signers.push(&program_signer);
1701 config.signers.push(&authority_signer);
1702
1703 assert!(
1704 process_deploy_program(
1705 Arc::new(rpc_client_no_existing_program()),
1706 &config,
1707 &AdditionalCliConfig::default(),
1708 &program_signer.pubkey(),
1709 None,
1710 Some(&1),
1711 &2,
1712 &program_data,
1713 None..None,
1714 )
1715 .await
1716 .is_ok()
1717 );
1718
1719 assert!(
1720 process_deploy_program(
1721 Arc::new(rpc_client_no_existing_program()),
1722 &config,
1723 &AdditionalCliConfig::default(),
1724 &program_signer.pubkey(),
1725 Some(&program_signer.pubkey()),
1726 Some(&1),
1727 &2,
1728 &program_data,
1729 None..None,
1730 )
1731 .await
1732 .is_ok()
1733 );
1734
1735 assert!(
1736 process_deploy_program(
1737 Arc::new(rpc_client_wrong_account_owner()),
1738 &config,
1739 &AdditionalCliConfig::default(),
1740 &program_signer.pubkey(),
1741 None,
1742 Some(&1),
1743 &2,
1744 &program_data,
1745 None..None,
1746 )
1747 .await
1748 .is_err()
1749 );
1750
1751 assert!(
1752 process_deploy_program(
1753 Arc::new(rpc_client_with_program_deployed()),
1754 &config,
1755 &AdditionalCliConfig::default(),
1756 &program_signer.pubkey(),
1757 None,
1758 Some(&1),
1759 &2,
1760 &program_data,
1761 None..None,
1762 )
1763 .await
1764 .is_err()
1765 );
1766 }
1767
1768 #[tokio::test]
1769 async fn test_redeploy() {
1770 let mut config = CliConfig::default();
1771 let mut program_data = Vec::new();
1772 let mut file = File::open("tests/fixtures/noop.so").unwrap();
1773 file.read_to_end(&mut program_data).unwrap();
1774
1775 let payer = keypair_from_seed(&[1u8; 32]).unwrap();
1776 let program_address = Pubkey::new_unique();
1777 let authority_signer = program_authority();
1778
1779 config.signers.push(&payer);
1780 config.signers.push(&authority_signer);
1781
1782 assert!(
1784 process_deploy_program(
1785 Arc::new(rpc_client_no_existing_program()),
1786 &config,
1787 &AdditionalCliConfig::default(),
1788 &program_address,
1789 None,
1790 None,
1791 &1,
1792 &program_data,
1793 None..None,
1794 )
1795 .await
1796 .is_err()
1797 );
1798
1799 assert!(
1800 process_deploy_program(
1801 Arc::new(rpc_client_with_program_retracted()),
1802 &config,
1803 &AdditionalCliConfig::default(),
1804 &program_address,
1805 None,
1806 None,
1807 &1,
1808 &program_data,
1809 None..None,
1810 )
1811 .await
1812 .is_ok()
1813 );
1814
1815 assert!(
1816 process_deploy_program(
1817 Arc::new(rpc_client_with_program_deployed()),
1818 &config,
1819 &AdditionalCliConfig::default(),
1820 &program_address,
1821 None,
1822 None,
1823 &1,
1824 &program_data,
1825 None..None,
1826 )
1827 .await
1828 .is_ok()
1829 );
1830
1831 assert!(
1832 process_deploy_program(
1833 Arc::new(rpc_client_with_program_finalized()),
1834 &config,
1835 &AdditionalCliConfig::default(),
1836 &program_address,
1837 None,
1838 None,
1839 &1,
1840 &program_data,
1841 None..None,
1842 )
1843 .await
1844 .is_err()
1845 );
1846
1847 assert!(
1848 process_deploy_program(
1849 Arc::new(rpc_client_wrong_account_owner()),
1850 &config,
1851 &AdditionalCliConfig::default(),
1852 &program_address,
1853 None,
1854 None,
1855 &1,
1856 &program_data,
1857 None..None,
1858 )
1859 .await
1860 .is_err()
1861 );
1862
1863 assert!(
1864 process_deploy_program(
1865 Arc::new(rpc_client_wrong_authority()),
1866 &config,
1867 &AdditionalCliConfig::default(),
1868 &program_address,
1869 None,
1870 None,
1871 &1,
1872 &program_data,
1873 None..None,
1874 )
1875 .await
1876 .is_err()
1877 );
1878 }
1879
1880 #[tokio::test]
1881 async fn test_redeploy_from_source() {
1882 let mut config = CliConfig::default();
1883 let mut program_data = Vec::new();
1884 let mut file = File::open("tests/fixtures/noop.so").unwrap();
1885 file.read_to_end(&mut program_data).unwrap();
1886
1887 let payer = keypair_from_seed(&[1u8; 32]).unwrap();
1888 let buffer_signer = keypair_from_seed(&[2u8; 32]).unwrap();
1889 let program_address = Pubkey::new_unique();
1890 let authority_signer = program_authority();
1891
1892 config.signers.push(&payer);
1893 config.signers.push(&buffer_signer);
1894 config.signers.push(&authority_signer);
1895
1896 assert!(
1898 process_deploy_program(
1899 Arc::new(rpc_client_no_existing_program()),
1900 &config,
1901 &AdditionalCliConfig::default(),
1902 &program_address,
1903 Some(&buffer_signer.pubkey()),
1904 Some(&1),
1905 &2,
1906 &program_data,
1907 None..None,
1908 )
1909 .await
1910 .is_err()
1911 );
1912
1913 assert!(
1914 process_deploy_program(
1915 Arc::new(rpc_client_wrong_account_owner()),
1916 &config,
1917 &AdditionalCliConfig::default(),
1918 &program_address,
1919 Some(&buffer_signer.pubkey()),
1920 Some(&1),
1921 &2,
1922 &program_data,
1923 None..None,
1924 )
1925 .await
1926 .is_err()
1927 );
1928
1929 assert!(
1930 process_deploy_program(
1931 Arc::new(rpc_client_wrong_authority()),
1932 &config,
1933 &AdditionalCliConfig::default(),
1934 &program_address,
1935 Some(&buffer_signer.pubkey()),
1936 Some(&1),
1937 &2,
1938 &program_data,
1939 None..None,
1940 )
1941 .await
1942 .is_err()
1943 );
1944 }
1945
1946 #[tokio::test]
1947 async fn test_retract() {
1948 let mut config = CliConfig::default();
1949
1950 let payer = keypair_from_seed(&[1u8; 32]).unwrap();
1951 let program_signer = keypair_from_seed(&[2u8; 32]).unwrap();
1952 let authority_signer = program_authority();
1953
1954 config.signers.push(&payer);
1955 config.signers.push(&authority_signer);
1956
1957 for close_program_entirely in [false, true] {
1958 assert!(
1959 process_retract_program(
1960 Arc::new(rpc_client_no_existing_program()),
1961 &config,
1962 &AdditionalCliConfig::default(),
1963 &1,
1964 &program_signer.pubkey(),
1965 close_program_entirely,
1966 )
1967 .await
1968 .is_err()
1969 );
1970
1971 assert!(
1972 process_retract_program(
1973 Arc::new(rpc_client_with_program_retracted()),
1974 &config,
1975 &AdditionalCliConfig::default(),
1976 &1,
1977 &program_signer.pubkey(),
1978 close_program_entirely,
1979 )
1980 .await
1981 .is_ok()
1982 == close_program_entirely
1983 );
1984
1985 assert!(
1986 process_retract_program(
1987 Arc::new(rpc_client_with_program_deployed()),
1988 &config,
1989 &AdditionalCliConfig::default(),
1990 &1,
1991 &program_signer.pubkey(),
1992 close_program_entirely,
1993 )
1994 .await
1995 .is_ok()
1996 );
1997
1998 assert!(
1999 process_retract_program(
2000 Arc::new(rpc_client_with_program_finalized()),
2001 &config,
2002 &AdditionalCliConfig::default(),
2003 &1,
2004 &program_signer.pubkey(),
2005 close_program_entirely,
2006 )
2007 .await
2008 .is_err()
2009 );
2010
2011 assert!(
2012 process_retract_program(
2013 Arc::new(rpc_client_wrong_account_owner()),
2014 &config,
2015 &AdditionalCliConfig::default(),
2016 &1,
2017 &program_signer.pubkey(),
2018 close_program_entirely,
2019 )
2020 .await
2021 .is_err()
2022 );
2023
2024 assert!(
2025 process_retract_program(
2026 Arc::new(rpc_client_wrong_authority()),
2027 &config,
2028 &AdditionalCliConfig::default(),
2029 &1,
2030 &program_signer.pubkey(),
2031 close_program_entirely,
2032 )
2033 .await
2034 .is_err()
2035 );
2036 }
2037 }
2038
2039 #[tokio::test]
2040 async fn test_transfer_authority() {
2041 let mut config = CliConfig::default();
2042
2043 let payer = keypair_from_seed(&[1u8; 32]).unwrap();
2044 let program_signer = keypair_from_seed(&[2u8; 32]).unwrap();
2045 let authority_signer = program_authority();
2046 let new_authority_signer = program_authority();
2047
2048 config.signers.push(&payer);
2049 config.signers.push(&authority_signer);
2050 config.signers.push(&new_authority_signer);
2051
2052 assert!(
2053 process_transfer_authority_of_program(
2054 Arc::new(rpc_client_with_program_deployed()),
2055 &config,
2056 &AdditionalCliConfig::default(),
2057 &1,
2058 &2,
2059 &program_signer.pubkey(),
2060 )
2061 .await
2062 .is_ok()
2063 );
2064 }
2065
2066 #[tokio::test]
2067 async fn test_finalize() {
2068 let mut config = CliConfig::default();
2069
2070 let payer = keypair_from_seed(&[1u8; 32]).unwrap();
2071 let program_signer = keypair_from_seed(&[2u8; 32]).unwrap();
2072 let authority_signer = program_authority();
2073 let next_version_signer = keypair_from_seed(&[4u8; 32]).unwrap();
2074
2075 config.signers.push(&payer);
2076 config.signers.push(&authority_signer);
2077 config.signers.push(&next_version_signer);
2078
2079 assert!(
2080 process_finalize_program(
2081 Arc::new(rpc_client_with_program_deployed()),
2082 &config,
2083 &AdditionalCliConfig::default(),
2084 &1,
2085 &2,
2086 &program_signer.pubkey(),
2087 )
2088 .await
2089 .is_ok()
2090 );
2091 }
2092
2093 fn make_tmp_path(name: &str) -> String {
2094 let out_dir = std::env::var("FARF_DIR").unwrap_or_else(|_| "farf".to_string());
2095 let keypair = Keypair::new();
2096
2097 let path = format!("{}/tmp/{}-{}", out_dir, name, keypair.pubkey());
2098
2099 let _ignored = std::fs::remove_dir_all(&path);
2101 let _ignored = std::fs::remove_file(&path);
2103
2104 path
2105 }
2106
2107 #[test]
2108 #[allow(clippy::cognitive_complexity)]
2109 fn test_cli_parse_deploy() {
2110 let test_commands = get_clap_app("test", "desc", "version");
2111
2112 let default_keypair = Keypair::new();
2113 let keypair_file = make_tmp_path("keypair_file");
2114 write_keypair_file(&default_keypair, &keypair_file).unwrap();
2115 let default_signer = DefaultSigner::new("", &keypair_file);
2116
2117 let program_keypair = Keypair::new();
2118 let program_keypair_file = make_tmp_path("program_keypair_file");
2119 write_keypair_file(&program_keypair, &program_keypair_file).unwrap();
2120
2121 let buffer_keypair = Keypair::new();
2122 let buffer_keypair_file = make_tmp_path("buffer_keypair_file");
2123 write_keypair_file(&buffer_keypair, &buffer_keypair_file).unwrap();
2124
2125 let authority_keypair = Keypair::new();
2126 let authority_keypair_file = make_tmp_path("authority_keypair_file");
2127 write_keypair_file(&authority_keypair, &authority_keypair_file).unwrap();
2128
2129 let test_command = test_commands.clone().get_matches_from(vec![
2130 "test",
2131 "program-v4",
2132 "deploy",
2133 "/Users/test/program.so",
2134 "--program-keypair",
2135 &program_keypair_file,
2136 ]);
2137 assert_eq!(
2138 parse_command(&test_command, &default_signer, &mut None).unwrap(),
2139 CliCommandInfo {
2140 command: CliCommand::ProgramV4(ProgramV4CliCommand::Deploy {
2141 additional_cli_config: AdditionalCliConfig::default(),
2142 program_address: program_keypair.pubkey(),
2143 buffer_address: None,
2144 upload_signer_index: Some(1),
2145 authority_signer_index: 0,
2146 path_to_elf: Some("/Users/test/program.so".to_string()),
2147 upload_range: None..None,
2148 }),
2149 signers: vec![
2150 Box::new(read_keypair_file(&keypair_file).unwrap()),
2151 Box::new(read_keypair_file(&program_keypair_file).unwrap()),
2152 ],
2153 }
2154 );
2155
2156 let test_command = test_commands.clone().get_matches_from(vec![
2157 "test",
2158 "program-v4",
2159 "deploy",
2160 "/Users/test/program.so",
2161 "--buffer",
2162 &program_keypair_file,
2163 ]);
2164 assert_eq!(
2165 parse_command(&test_command, &default_signer, &mut None).unwrap(),
2166 CliCommandInfo {
2167 command: CliCommand::ProgramV4(ProgramV4CliCommand::Deploy {
2168 additional_cli_config: AdditionalCliConfig::default(),
2169 program_address: program_keypair.pubkey(),
2170 buffer_address: Some(program_keypair.pubkey()),
2171 upload_signer_index: Some(1),
2172 authority_signer_index: 0,
2173 path_to_elf: Some("/Users/test/program.so".to_string()),
2174 upload_range: None..None,
2175 }),
2176 signers: vec![
2177 Box::new(read_keypair_file(&keypair_file).unwrap()),
2178 Box::new(read_keypair_file(&program_keypair_file).unwrap()),
2179 ],
2180 }
2181 );
2182
2183 let test_command = test_commands.clone().get_matches_from(vec![
2184 "test",
2185 "program-v4",
2186 "deploy",
2187 "/Users/test/program.so",
2188 "--program-id",
2189 &program_keypair_file,
2190 "--buffer",
2191 &buffer_keypair_file,
2192 ]);
2193 assert_eq!(
2194 parse_command(&test_command, &default_signer, &mut None).unwrap(),
2195 CliCommandInfo {
2196 command: CliCommand::ProgramV4(ProgramV4CliCommand::Deploy {
2197 additional_cli_config: AdditionalCliConfig::default(),
2198 program_address: program_keypair.pubkey(),
2199 buffer_address: Some(buffer_keypair.pubkey()),
2200 upload_signer_index: Some(1),
2201 authority_signer_index: 0,
2202 path_to_elf: Some("/Users/test/program.so".to_string()),
2203 upload_range: None..None,
2204 }),
2205 signers: vec![
2206 Box::new(read_keypair_file(&keypair_file).unwrap()),
2207 Box::new(read_keypair_file(&buffer_keypair_file).unwrap()),
2208 ],
2209 }
2210 );
2211
2212 let test_command = test_commands.clone().get_matches_from(vec![
2213 "test",
2214 "program-v4",
2215 "deploy",
2216 "/Users/test/program.so",
2217 "--program-keypair",
2218 &program_keypair_file,
2219 "--authority",
2220 &authority_keypair_file,
2221 ]);
2222 assert_eq!(
2223 parse_command(&test_command, &default_signer, &mut None).unwrap(),
2224 CliCommandInfo {
2225 command: CliCommand::ProgramV4(ProgramV4CliCommand::Deploy {
2226 additional_cli_config: AdditionalCliConfig::default(),
2227 program_address: program_keypair.pubkey(),
2228 buffer_address: None,
2229 upload_signer_index: Some(1),
2230 authority_signer_index: 2,
2231 path_to_elf: Some("/Users/test/program.so".to_string()),
2232 upload_range: None..None,
2233 }),
2234 signers: vec![
2235 Box::new(read_keypair_file(&keypair_file).unwrap()),
2236 Box::new(read_keypair_file(&program_keypair_file).unwrap()),
2237 Box::new(read_keypair_file(&authority_keypair_file).unwrap()),
2238 ],
2239 }
2240 );
2241
2242 let test_command = test_commands.clone().get_matches_from(vec![
2243 "test",
2244 "program-v4",
2245 "deploy",
2246 "/Users/test/program.so",
2247 "--program-id",
2248 &program_keypair_file,
2249 "--authority",
2250 &authority_keypair_file,
2251 ]);
2252 assert_eq!(
2253 parse_command(&test_command, &default_signer, &mut None).unwrap(),
2254 CliCommandInfo {
2255 command: CliCommand::ProgramV4(ProgramV4CliCommand::Deploy {
2256 additional_cli_config: AdditionalCliConfig::default(),
2257 program_address: program_keypair.pubkey(),
2258 buffer_address: None,
2259 upload_signer_index: None,
2260 authority_signer_index: 1,
2261 path_to_elf: Some("/Users/test/program.so".to_string()),
2262 upload_range: None..None,
2263 }),
2264 signers: vec![
2265 Box::new(read_keypair_file(&keypair_file).unwrap()),
2266 Box::new(read_keypair_file(&authority_keypair_file).unwrap()),
2267 ],
2268 }
2269 );
2270
2271 let test_command = test_commands.clone().get_matches_from(vec![
2272 "test",
2273 "program-v4",
2274 "deploy",
2275 "/Users/test/program.so",
2276 "--program-id",
2277 &program_keypair_file,
2278 "--buffer",
2279 &buffer_keypair_file,
2280 "--authority",
2281 &authority_keypair_file,
2282 ]);
2283 assert_eq!(
2284 parse_command(&test_command, &default_signer, &mut None).unwrap(),
2285 CliCommandInfo {
2286 command: CliCommand::ProgramV4(ProgramV4CliCommand::Deploy {
2287 additional_cli_config: AdditionalCliConfig::default(),
2288 program_address: program_keypair.pubkey(),
2289 buffer_address: Some(buffer_keypair.pubkey()),
2290 upload_signer_index: Some(1),
2291 authority_signer_index: 2,
2292 path_to_elf: Some("/Users/test/program.so".to_string()),
2293 upload_range: None..None,
2294 }),
2295 signers: vec![
2296 Box::new(read_keypair_file(&keypair_file).unwrap()),
2297 Box::new(read_keypair_file(&buffer_keypair_file).unwrap()),
2298 Box::new(read_keypair_file(&authority_keypair_file).unwrap()),
2299 ],
2300 }
2301 );
2302
2303 let test_command = test_commands.clone().get_matches_from(vec![
2304 "test",
2305 "program-v4",
2306 "deploy",
2307 "--program-id",
2308 &program_keypair_file,
2309 "--buffer",
2310 &buffer_keypair_file,
2311 "--authority",
2312 &authority_keypair_file,
2313 ]);
2314 assert_eq!(
2315 parse_command(&test_command, &default_signer, &mut None).unwrap(),
2316 CliCommandInfo {
2317 command: CliCommand::ProgramV4(ProgramV4CliCommand::Deploy {
2318 additional_cli_config: AdditionalCliConfig::default(),
2319 program_address: program_keypair.pubkey(),
2320 buffer_address: Some(buffer_keypair.pubkey()),
2321 upload_signer_index: None,
2322 authority_signer_index: 2,
2323 path_to_elf: None,
2324 upload_range: None..None,
2325 }),
2326 signers: vec![
2327 Box::new(read_keypair_file(&keypair_file).unwrap()),
2328 Box::new(read_keypair_file(&buffer_keypair_file).unwrap()),
2329 Box::new(read_keypair_file(&authority_keypair_file).unwrap()),
2330 ],
2331 }
2332 );
2333
2334 let test_command = test_commands.clone().get_matches_from(vec![
2335 "test",
2336 "program-v4",
2337 "deploy",
2338 "/Users/test/program.so",
2339 "--start-offset",
2340 "16",
2341 "--end-offset",
2342 "32",
2343 "--program-id",
2344 &program_keypair_file,
2345 "--use-rpc",
2346 "--with-compute-unit-price",
2347 "1",
2348 ]);
2349 assert_eq!(
2350 parse_command(&test_command, &default_signer, &mut None).unwrap(),
2351 CliCommandInfo {
2352 command: CliCommand::ProgramV4(ProgramV4CliCommand::Deploy {
2353 additional_cli_config: AdditionalCliConfig {
2354 use_rpc: true,
2355 sign_only: false,
2356 dump_transaction_message: false,
2357 blockhash_query: BlockhashQuery::default(),
2358 compute_unit_price: Some(1),
2359 },
2360 program_address: program_keypair.pubkey(),
2361 buffer_address: None,
2362 upload_signer_index: None,
2363 authority_signer_index: 0,
2364 path_to_elf: Some("/Users/test/program.so".to_string()),
2365 upload_range: Some(16)..Some(32),
2366 }),
2367 signers: vec![Box::new(read_keypair_file(&keypair_file).unwrap()),],
2368 }
2369 );
2370 }
2371
2372 #[test]
2373 #[allow(clippy::cognitive_complexity)]
2374 fn test_cli_parse_retract() {
2375 let test_commands = get_clap_app("test", "desc", "version");
2376
2377 let default_keypair = Keypair::new();
2378 let keypair_file = make_tmp_path("keypair_file");
2379 write_keypair_file(&default_keypair, &keypair_file).unwrap();
2380 let default_signer = DefaultSigner::new("", &keypair_file);
2381
2382 let program_keypair = Keypair::new();
2383 let program_keypair_file = make_tmp_path("program_keypair_file");
2384 write_keypair_file(&program_keypair, &program_keypair_file).unwrap();
2385
2386 let authority_keypair = Keypair::new();
2387 let authority_keypair_file = make_tmp_path("authority_keypair_file");
2388 write_keypair_file(&authority_keypair, &authority_keypair_file).unwrap();
2389
2390 let test_command = test_commands.clone().get_matches_from(vec![
2391 "test",
2392 "program-v4",
2393 "retract",
2394 "--program-id",
2395 &program_keypair_file,
2396 ]);
2397 assert_eq!(
2398 parse_command(&test_command, &default_signer, &mut None).unwrap(),
2399 CliCommandInfo {
2400 command: CliCommand::ProgramV4(ProgramV4CliCommand::Retract {
2401 additional_cli_config: AdditionalCliConfig::default(),
2402 program_address: program_keypair.pubkey(),
2403 authority_signer_index: 0,
2404 close_program_entirely: false,
2405 }),
2406 signers: vec![Box::new(read_keypair_file(&keypair_file).unwrap()),],
2407 }
2408 );
2409
2410 let test_command = test_commands.clone().get_matches_from(vec![
2411 "test",
2412 "program-v4",
2413 "retract",
2414 "--program-id",
2415 &program_keypair_file,
2416 "--authority",
2417 &authority_keypair_file,
2418 "--close-program-entirely",
2419 ]);
2420 assert_eq!(
2421 parse_command(&test_command, &default_signer, &mut None).unwrap(),
2422 CliCommandInfo {
2423 command: CliCommand::ProgramV4(ProgramV4CliCommand::Retract {
2424 additional_cli_config: AdditionalCliConfig::default(),
2425 program_address: program_keypair.pubkey(),
2426 authority_signer_index: 1,
2427 close_program_entirely: true,
2428 }),
2429 signers: vec![
2430 Box::new(read_keypair_file(&keypair_file).unwrap()),
2431 Box::new(read_keypair_file(&authority_keypair_file).unwrap())
2432 ],
2433 }
2434 );
2435 }
2436
2437 #[test]
2438 #[allow(clippy::cognitive_complexity)]
2439 fn test_cli_parse_transfer_authority() {
2440 let test_commands = get_clap_app("test", "desc", "version");
2441
2442 let default_keypair = Keypair::new();
2443 let keypair_file = make_tmp_path("keypair_file");
2444 write_keypair_file(&default_keypair, &keypair_file).unwrap();
2445 let default_signer = DefaultSigner::new("", &keypair_file);
2446
2447 let program_keypair = Keypair::new();
2448 let program_keypair_file = make_tmp_path("program_keypair_file");
2449 write_keypair_file(&program_keypair, &program_keypair_file).unwrap();
2450
2451 let authority_keypair = Keypair::new();
2452 let authority_keypair_file = make_tmp_path("authority_keypair_file");
2453 write_keypair_file(&authority_keypair, &authority_keypair_file).unwrap();
2454
2455 let new_authority_keypair = Keypair::new();
2456 let new_authority_keypair_file = make_tmp_path("new_authority_keypair_file");
2457 write_keypair_file(&new_authority_keypair, &new_authority_keypair_file).unwrap();
2458
2459 let test_command = test_commands.clone().get_matches_from(vec![
2460 "test",
2461 "program-v4",
2462 "transfer-authority",
2463 "--program-id",
2464 &program_keypair_file,
2465 "--authority",
2466 &authority_keypair_file,
2467 "--new-authority",
2468 &new_authority_keypair_file,
2469 ]);
2470 assert_eq!(
2471 parse_command(&test_command, &default_signer, &mut None).unwrap(),
2472 CliCommandInfo {
2473 command: CliCommand::ProgramV4(ProgramV4CliCommand::TransferAuthority {
2474 additional_cli_config: AdditionalCliConfig::default(),
2475 program_address: program_keypair.pubkey(),
2476 authority_signer_index: 1,
2477 new_authority_signer_index: 2,
2478 }),
2479 signers: vec![
2480 Box::new(read_keypair_file(&keypair_file).unwrap()),
2481 Box::new(read_keypair_file(&authority_keypair_file).unwrap()),
2482 Box::new(read_keypair_file(&new_authority_keypair_file).unwrap()),
2483 ],
2484 }
2485 );
2486 }
2487
2488 #[test]
2489 #[allow(clippy::cognitive_complexity)]
2490 fn test_cli_parse_finalize() {
2491 let test_commands = get_clap_app("test", "desc", "version");
2492
2493 let default_keypair = Keypair::new();
2494 let keypair_file = make_tmp_path("keypair_file");
2495 write_keypair_file(&default_keypair, &keypair_file).unwrap();
2496 let default_signer = DefaultSigner::new("", &keypair_file);
2497
2498 let program_keypair = Keypair::new();
2499 let program_keypair_file = make_tmp_path("program_keypair_file");
2500 write_keypair_file(&program_keypair, &program_keypair_file).unwrap();
2501
2502 let authority_keypair = Keypair::new();
2503 let authority_keypair_file = make_tmp_path("authority_keypair_file");
2504 write_keypair_file(&authority_keypair, &authority_keypair_file).unwrap();
2505
2506 let next_version_keypair = Keypair::new();
2507 let next_version_keypair_file = make_tmp_path("next_version_keypair_file");
2508 write_keypair_file(&next_version_keypair, &next_version_keypair_file).unwrap();
2509
2510 let test_command = test_commands.clone().get_matches_from(vec![
2511 "test",
2512 "program-v4",
2513 "finalize",
2514 "--program-id",
2515 &program_keypair_file,
2516 "--authority",
2517 &authority_keypair_file,
2518 "--next-version",
2519 &next_version_keypair_file,
2520 ]);
2521 assert_eq!(
2522 parse_command(&test_command, &default_signer, &mut None).unwrap(),
2523 CliCommandInfo {
2524 command: CliCommand::ProgramV4(ProgramV4CliCommand::Finalize {
2525 additional_cli_config: AdditionalCliConfig::default(),
2526 program_address: program_keypair.pubkey(),
2527 authority_signer_index: 1,
2528 next_version_signer_index: 2,
2529 }),
2530 signers: vec![
2531 Box::new(read_keypair_file(&keypair_file).unwrap()),
2532 Box::new(read_keypair_file(&authority_keypair_file).unwrap()),
2533 Box::new(read_keypair_file(&next_version_keypair_file).unwrap()),
2534 ],
2535 }
2536 );
2537 }
2538}