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