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