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