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