1use {
2 crate::{
3 checks::*,
4 cli::{
5 CliCommand, CliCommandInfo, CliConfig, CliError, ProcessResult,
6 log_instruction_custom_error,
7 },
8 compute_budget::{
9 ComputeUnitConfig, UpdateComputeUnitLimitResult, WithComputeUnitConfig,
10 simulate_and_update_compute_unit_limit,
11 },
12 feature::{CliFeatureStatus, status_from_account},
13 },
14 agave_feature_set::{FEATURE_NAMES, FeatureSet},
15 bip39::{Language, Mnemonic},
16 clap::{App, AppSettings, Arg, ArgMatches, SubCommand},
17 log::*,
18 solana_account::state_traits::StateMutWincode as _,
19 solana_account_decoder::{UiAccount, UiAccountEncoding, UiDataSliceConfig},
20 solana_clap_utils::{
21 self,
22 compute_budget::{ComputeUnitLimit, compute_unit_price_arg},
23 fee_payer::{FEE_PAYER_ARG, fee_payer_arg},
24 hidden_unless_forced,
25 input_parsers::*,
26 input_validators::*,
27 keypair::*,
28 offline::{DUMP_TRANSACTION_MESSAGE, OfflineArgs, SIGN_ONLY_ARG},
29 },
30 solana_cli_output::{
31 CliProgram, CliProgramAccountType, CliProgramAuthority, CliProgramBuffer, CliProgramId,
32 CliUpgradeableBuffer, CliUpgradeableBuffers, CliUpgradeableProgram,
33 CliUpgradeableProgramClosed, CliUpgradeableProgramExtended, CliUpgradeablePrograms,
34 ReturnSignersConfig, return_signers_with_config,
35 },
36 solana_client::send_and_confirm_transactions_in_parallel::{
37 SendAndConfirmConfigV3, SendTransport, send_and_confirm_transactions_in_parallel_v3,
38 },
39 solana_commitment_config::CommitmentConfig,
40 solana_instruction::Instruction,
41 solana_instruction_error::InstructionError,
42 solana_keypair::{Keypair, keypair_from_seed, read_keypair_file},
43 solana_loader_v3_interface::{
44 get_program_data_address,
45 instruction::{self as loader_v3_instruction, MINIMUM_EXTEND_PROGRAM_BYTES},
46 state::UpgradeableLoaderState,
47 },
48 solana_message::{Message, VersionedMessage},
49 solana_net_utils::bind_to_unspecified,
50 solana_packet::PACKET_DATA_SIZE,
51 solana_program_runtime::{
52 execution_budget::SVMTransactionExecutionBudget, invoke_context::InvokeContext,
53 },
54 solana_pubkey::Pubkey,
55 solana_remote_wallet::remote_wallet::RemoteWalletManager,
56 solana_rpc_client::nonblocking::rpc_client::RpcClient,
57 solana_rpc_client_api::{
58 client_error::ErrorKind as ClientErrorKind,
59 config::{RpcAccountInfoConfig, RpcProgramAccountsConfig},
60 filter::{Memcmp, RpcFilterType},
61 request::MAX_MULTIPLE_ACCOUNTS,
62 },
63 solana_rpc_client_nonce_utils::nonblocking::blockhash_query::BlockhashQuery,
64 solana_sbpf::{
65 elf::{ElfError, Executable, get_sbpf_version},
66 error::EbpfError,
67 program::SBPFVersion,
68 verifier::{LocalVerifier, RequisiteVerifier},
69 vm::Config,
70 },
71 solana_sdk_ids::{bpf_loader, bpf_loader_deprecated, bpf_loader_upgradeable, compute_budget},
72 solana_signature::Signature,
73 solana_signer::Signer,
74 solana_syscalls::create_program_runtime_environment,
75 solana_system_interface::{MAX_PERMITTED_DATA_LENGTH, error::SystemError},
76 solana_tpu_client_next::{
77 ClientBuilder, connection_workers_scheduler::NonblockingBroadcaster,
78 node_address_service::LeaderTpuCacheServiceConfig,
79 websocket_node_address_service::WebsocketNodeAddressService,
80 },
81 solana_transaction::{Transaction, versioned::VersionedTransaction},
82 solana_transaction_error::TransactionError,
83 std::{
84 fs::File,
85 io::{Read, Write},
86 mem::size_of,
87 num::{NonZeroUsize, Saturating},
88 path::PathBuf,
89 rc::Rc,
90 str::FromStr,
91 sync::Arc,
92 time::Duration,
93 },
94 tokio_util::sync::CancellationToken,
95};
96
97pub const CLOSE_PROGRAM_WARNING: &str = "WARNING! Closed programs cannot be recreated at the same \
98 program id. Once a program is closed, it can never be \
99 invoked again. To proceed with closing, rerun the \
100 `close` command with the `--bypass-warning` flag";
101
102const CHECK_INTERVAL: Duration = Duration::from_secs(1);
105const SEND_INTERVAL: Duration = Duration::from_millis(5);
108
109#[derive(Debug, PartialEq, Eq)]
110pub enum ProgramCliCommand {
111 Deploy {
112 program_location: Option<String>,
113 fee_payer_signer_index: SignerIndex,
114 program_signer_index: Option<SignerIndex>,
115 program_pubkey: Option<Pubkey>,
116 buffer_signer_index: Option<SignerIndex>,
117 buffer_pubkey: Option<Pubkey>,
118 upgrade_authority_signer_index: SignerIndex,
119 is_final: bool,
120 max_len: Option<usize>,
121 skip_fee_check: bool,
122 compute_unit_price: Option<u64>,
123 max_sign_attempts: usize,
124 auto_extend: bool,
125 use_rpc: bool,
126 skip_feature_verification: bool,
127 },
128 Upgrade {
129 fee_payer_signer_index: SignerIndex,
130 program_pubkey: Pubkey,
131 buffer_pubkey: Pubkey,
132 upgrade_authority_signer_index: SignerIndex,
133 sign_only: bool,
134 dump_transaction_message: bool,
135 blockhash_query: BlockhashQuery,
136 skip_feature_verification: bool,
137 },
138 WriteBuffer {
139 program_location: String,
140 fee_payer_signer_index: SignerIndex,
141 buffer_signer_index: Option<SignerIndex>,
142 buffer_pubkey: Option<Pubkey>,
143 buffer_authority_signer_index: SignerIndex,
144 max_len: Option<usize>,
145 skip_fee_check: bool,
146 compute_unit_price: Option<u64>,
147 max_sign_attempts: usize,
148 use_rpc: bool,
149 skip_feature_verification: bool,
150 },
151 SetBufferAuthority {
152 buffer_pubkey: Pubkey,
153 buffer_authority_index: Option<SignerIndex>,
154 new_buffer_authority: Pubkey,
155 },
156 SetUpgradeAuthority {
157 program_pubkey: Pubkey,
158 upgrade_authority_index: Option<SignerIndex>,
159 new_upgrade_authority: Option<Pubkey>,
160 sign_only: bool,
161 dump_transaction_message: bool,
162 blockhash_query: BlockhashQuery,
163 },
164 SetUpgradeAuthorityChecked {
165 program_pubkey: Pubkey,
166 upgrade_authority_index: SignerIndex,
167 new_upgrade_authority_index: SignerIndex,
168 sign_only: bool,
169 dump_transaction_message: bool,
170 blockhash_query: BlockhashQuery,
171 },
172 Show {
173 account_pubkey: Option<Pubkey>,
174 authority_pubkey: Pubkey,
175 get_programs: bool,
176 get_buffers: bool,
177 all: bool,
178 use_lamports_unit: bool,
179 },
180 Dump {
181 account_pubkey: Option<Pubkey>,
182 output_location: String,
183 },
184 Close {
185 account_pubkey: Option<Pubkey>,
186 recipient_pubkey: Pubkey,
187 authority_index: SignerIndex,
188 use_lamports_unit: bool,
189 bypass_warning: bool,
190 },
191 ExtendProgram {
192 program_pubkey: Pubkey,
193 payer_signer_index: SignerIndex,
194 additional_bytes: u32,
195 },
196}
197
198pub trait ProgramSubCommands {
199 fn program_subcommands(self) -> Self;
200}
201
202impl ProgramSubCommands for App<'_, '_> {
203 fn program_subcommands(self) -> Self {
204 self.subcommand(
205 SubCommand::with_name("program")
206 .about("Program management")
207 .setting(AppSettings::SubcommandRequiredElseHelp)
208 .arg(
209 Arg::with_name("skip_fee_check")
210 .long("skip-fee-check")
211 .hidden(hidden_unless_forced())
212 .takes_value(false)
213 .global(true),
214 )
215 .subcommand(
216 SubCommand::with_name("deploy")
217 .about("Deploy an upgradeable program")
218 .arg(
219 Arg::with_name("program_location")
220 .index(1)
221 .value_name("PROGRAM_FILEPATH")
222 .takes_value(true)
223 .help("/path/to/program.so"),
224 )
225 .arg(fee_payer_arg())
226 .arg(
227 Arg::with_name("buffer")
228 .long("buffer")
229 .value_name("BUFFER_SIGNER")
230 .takes_value(true)
231 .validator(is_valid_signer)
232 .help(
233 "Intermediate buffer account to write data to, which can be \
234 used to resume a failed deploy [default: random address]",
235 ),
236 )
237 .arg(
238 Arg::with_name("upgrade_authority")
239 .long("upgrade-authority")
240 .value_name("UPGRADE_AUTHORITY_SIGNER")
241 .takes_value(true)
242 .validator(is_valid_signer)
243 .help(
244 "Upgrade authority [default: the default configured keypair]",
245 ),
246 )
247 .arg(pubkey!(
248 Arg::with_name("program_id")
249 .long("program-id")
250 .value_name("PROGRAM_ID"),
251 "Executable program; must be a signer for initial deploys, can be an \
252 address for upgrades [default: address of keypair at \
253 /path/to/program-keypair.json if present, otherwise a random \
254 address]."
255 ))
256 .arg(
257 Arg::with_name("final")
258 .long("final")
259 .help("The program will not be upgradeable"),
260 )
261 .arg(
262 Arg::with_name("max_len")
263 .long("max-len")
264 .value_name("max_len")
265 .takes_value(true)
266 .required(false)
267 .help(
268 "Maximum length of the upgradeable program [default: the \
269 length of the original deployed program]",
270 ),
271 )
272 .arg(
273 Arg::with_name("allow_excessive_balance")
274 .long("allow-excessive-deploy-account-balance")
275 .hidden(hidden_unless_forced())
276 .takes_value(false)
277 .help(
278 "Use the designated program id even if the account already \
279 holds a large balance of SOL (Obsolete)",
280 ),
281 )
282 .arg(
283 Arg::with_name("max_sign_attempts")
284 .long("max-sign-attempts")
285 .takes_value(true)
286 .validator(is_parsable::<u64>)
287 .default_value("5")
288 .help(
289 "Maximum number of attempts to sign or resign transactions \
290 after blockhash expiration. If any transactions sent during \
291 the program deploy are still unconfirmed after the initially \
292 chosen recent blockhash expires, those transactions will be \
293 resigned with a new recent blockhash and resent. Use this \
294 setting to adjust the maximum number of transaction signing \
295 iterations. Each blockhash is valid for about 60 seconds, \
296 which means using the default value of 5 will lead to \
297 sending transactions for at least 5 minutes or until all \
298 transactions are confirmed,whichever comes first.",
299 ),
300 )
301 .arg(Arg::with_name("use_rpc").long("use-rpc").help(
302 "Send write transactions to the configured RPC instead of validator \
303 TPUs",
304 ))
305 .arg(compute_unit_price_arg())
306 .arg(
307 Arg::with_name("no_auto_extend")
308 .long("no-auto-extend")
309 .takes_value(false)
310 .help("Don't automatically extend the program's data account size"),
311 )
312 .arg(
313 Arg::with_name("skip_feature_verify")
314 .long("skip-feature-verify")
315 .takes_value(false)
316 .help(
317 "Don't verify program against the activated feature set. This \
318 setting means a program containing a syscall not yet active \
319 on mainnet will succeed local verification, but fail during \
320 the last step of deployment.",
321 ),
322 ),
323 )
324 .subcommand(
325 SubCommand::with_name("upgrade")
326 .about("Upgrade an upgradeable program")
327 .arg(pubkey!(
328 Arg::with_name("buffer")
329 .index(1)
330 .required(true)
331 .value_name("BUFFER_PUBKEY"),
332 "Intermediate buffer account with new program data"
333 ))
334 .arg(pubkey!(
335 Arg::with_name("program_id")
336 .index(2)
337 .required(true)
338 .value_name("PROGRAM_ID"),
339 "Executable program's address (pubkey)"
340 ))
341 .arg(fee_payer_arg())
342 .arg(
343 Arg::with_name("upgrade_authority")
344 .long("upgrade-authority")
345 .value_name("UPGRADE_AUTHORITY_SIGNER")
346 .takes_value(true)
347 .validator(is_valid_signer)
348 .help(
349 "Upgrade authority [default: the default configured keypair]",
350 ),
351 )
352 .arg(
353 Arg::with_name("skip_feature_verify")
354 .long("skip-feature-verify")
355 .takes_value(false)
356 .help(
357 "Don't verify program against the activated feature set. This \
358 setting means a program containing a syscall not yet active \
359 on mainnet will succeed local verification, but fail during \
360 the last step of deployment.",
361 ),
362 )
363 .offline_args(),
364 )
365 .subcommand(
366 SubCommand::with_name("write-buffer")
367 .about("Writes a program into a buffer account")
368 .arg(
369 Arg::with_name("program_location")
370 .index(1)
371 .value_name("PROGRAM_FILEPATH")
372 .takes_value(true)
373 .required(true)
374 .help("/path/to/program.so"),
375 )
376 .arg(fee_payer_arg())
377 .arg(
378 Arg::with_name("buffer")
379 .long("buffer")
380 .value_name("BUFFER_SIGNER")
381 .takes_value(true)
382 .validator(is_valid_signer)
383 .help(
384 "Buffer account to write data into [default: random address]",
385 ),
386 )
387 .arg(
388 Arg::with_name("buffer_authority")
389 .long("buffer-authority")
390 .value_name("BUFFER_AUTHORITY_SIGNER")
391 .takes_value(true)
392 .validator(is_valid_signer)
393 .help("Buffer authority [default: the default configured keypair]"),
394 )
395 .arg(
396 Arg::with_name("max_len")
397 .long("max-len")
398 .value_name("max_len")
399 .takes_value(true)
400 .required(false)
401 .help(
402 "Maximum length of the upgradeable program [default: the \
403 length of the original deployed program]",
404 ),
405 )
406 .arg(
407 Arg::with_name("max_sign_attempts")
408 .long("max-sign-attempts")
409 .takes_value(true)
410 .validator(is_parsable::<u64>)
411 .default_value("5")
412 .help(
413 "Maximum number of attempts to sign or resign transactions \
414 after blockhash expiration. If any transactions sent during \
415 the program deploy are still unconfirmed after the initially \
416 chosen recent blockhash expires, those transactions will be \
417 resigned with a new recent blockhash and resent. Use this \
418 setting to adjust the maximum number of transaction signing \
419 iterations. Each blockhash is valid for about 60 seconds, \
420 which means using the default value of 5 will lead to \
421 sending transactions for at least 5 minutes or until all \
422 transactions are confirmed,whichever comes first.",
423 ),
424 )
425 .arg(Arg::with_name("use_rpc").long("use-rpc").help(
426 "Send transactions to the configured RPC instead of validator TPUs",
427 ))
428 .arg(compute_unit_price_arg())
429 .arg(
430 Arg::with_name("skip_feature_verify")
431 .long("skip-feature-verify")
432 .takes_value(false)
433 .help(
434 "Don't verify program against the activated feature set. This \
435 setting means a program containing a syscall not yet active \
436 on mainnet will succeed local verification, but fail during \
437 the last step of deployment.",
438 ),
439 ),
440 )
441 .subcommand(
442 SubCommand::with_name("set-buffer-authority")
443 .about("Set a new buffer authority")
444 .arg(
445 Arg::with_name("buffer")
446 .index(1)
447 .value_name("BUFFER_PUBKEY")
448 .takes_value(true)
449 .required(true)
450 .help("Public key of the buffer"),
451 )
452 .arg(
453 Arg::with_name("buffer_authority")
454 .long("buffer-authority")
455 .value_name("BUFFER_AUTHORITY_SIGNER")
456 .takes_value(true)
457 .validator(is_valid_signer)
458 .help("Buffer authority [default: the default configured keypair]"),
459 )
460 .arg(pubkey!(
461 Arg::with_name("new_buffer_authority")
462 .long("new-buffer-authority")
463 .value_name("NEW_BUFFER_AUTHORITY")
464 .required(true),
465 "New buffer authority."
466 )),
467 )
468 .subcommand(
469 SubCommand::with_name("set-upgrade-authority")
470 .about("Set a new program authority")
471 .arg(
472 Arg::with_name("program_id")
473 .index(1)
474 .value_name("PROGRAM_ADDRESS")
475 .takes_value(true)
476 .required(true)
477 .help("Address of the program to upgrade"),
478 )
479 .arg(
480 Arg::with_name("upgrade_authority")
481 .long("upgrade-authority")
482 .value_name("UPGRADE_AUTHORITY_SIGNER")
483 .takes_value(true)
484 .validator(is_valid_signer)
485 .help(
486 "Upgrade authority [default: the default configured keypair]",
487 ),
488 )
489 .arg(
490 Arg::with_name("new_upgrade_authority")
491 .long("new-upgrade-authority")
492 .value_name("NEW_UPGRADE_AUTHORITY")
493 .required_unless("final")
494 .takes_value(true)
495 .help(
496 "New upgrade authority (keypair or pubkey). It is strongly \
497 recommended to pass in a keypair to prevent mistakes in \
498 setting the upgrade authority. You can opt out of this \
499 behavior by passing \
500 --skip-new-upgrade-authority-signer-check if you are really \
501 confident that you are setting the correct authority. \
502 Alternatively, If you wish to make the program immutable, \
503 you should ignore this arg and pass the --final flag.",
504 ),
505 )
506 .arg(
507 Arg::with_name("final")
508 .long("final")
509 .conflicts_with("new_upgrade_authority")
510 .help("The program will not be upgradeable"),
511 )
512 .arg(
513 Arg::with_name("skip_new_upgrade_authority_signer_check")
514 .long("skip-new-upgrade-authority-signer-check")
515 .requires("new_upgrade_authority")
516 .takes_value(false)
517 .help(
518 "Set this flag if you don't want the new authority to sign \
519 the set-upgrade-authority transaction.",
520 ),
521 )
522 .offline_args(),
523 )
524 .subcommand(
525 SubCommand::with_name("show")
526 .about("Display information about a buffer or program")
527 .arg(
528 Arg::with_name("account")
529 .index(1)
530 .value_name("ACCOUNT_ADDRESS")
531 .takes_value(true)
532 .help("Address of the buffer or program to show"),
533 )
534 .arg(
535 Arg::with_name("programs")
536 .long("programs")
537 .conflicts_with("account")
538 .conflicts_with("buffers")
539 .required_unless_one(&["account", "buffers"])
540 .help("Show every upgradeable program that matches the authority"),
541 )
542 .arg(
543 Arg::with_name("buffers")
544 .long("buffers")
545 .conflicts_with("account")
546 .conflicts_with("programs")
547 .required_unless_one(&["account", "programs"])
548 .help("Show every upgradeable buffer that matches the authority"),
549 )
550 .arg(
551 Arg::with_name("all")
552 .long("all")
553 .conflicts_with("account")
554 .conflicts_with("buffer_authority")
555 .help("Show accounts for all authorities"),
556 )
557 .arg(pubkey!(
558 Arg::with_name("buffer_authority")
559 .long("buffer-authority")
560 .value_name("AUTHORITY")
561 .conflicts_with("all"),
562 "Authority [default: the default configured keypair]."
563 ))
564 .arg(
565 Arg::with_name("lamports")
566 .long("lamports")
567 .takes_value(false)
568 .help("Display balance in lamports instead of SOL"),
569 ),
570 )
571 .subcommand(
572 SubCommand::with_name("dump")
573 .about("Write the program data to a file")
574 .arg(
575 Arg::with_name("account")
576 .index(1)
577 .value_name("ACCOUNT_ADDRESS")
578 .takes_value(true)
579 .required(true)
580 .help("Address of the buffer or program"),
581 )
582 .arg(
583 Arg::with_name("output_location")
584 .index(2)
585 .value_name("OUTPUT_FILEPATH")
586 .takes_value(true)
587 .required(true)
588 .help("/path/to/program.so"),
589 ),
590 )
591 .subcommand(
592 SubCommand::with_name("close")
593 .about("Close a program or buffer account and withdraw all lamports")
594 .arg(
595 Arg::with_name("account")
596 .index(1)
597 .value_name("ACCOUNT_ADDRESS")
598 .takes_value(true)
599 .help("Address of the program or buffer account to close"),
600 )
601 .arg(
602 Arg::with_name("buffers")
603 .long("buffers")
604 .conflicts_with("account")
605 .required_unless("account")
606 .help("Close all buffer accounts that match the authority"),
607 )
608 .arg(
609 Arg::with_name("authority")
610 .long("authority")
611 .alias("buffer-authority")
612 .value_name("AUTHORITY_SIGNER")
613 .takes_value(true)
614 .validator(is_valid_signer)
615 .help(
616 "Upgrade or buffer authority [default: the default configured \
617 keypair]",
618 ),
619 )
620 .arg(pubkey!(
621 Arg::with_name("recipient_account")
622 .long("recipient")
623 .value_name("RECIPIENT_ADDRESS"),
624 "Recipient of closed account's lamports [default: the default \
625 configured keypair]."
626 ))
627 .arg(
628 Arg::with_name("lamports")
629 .long("lamports")
630 .takes_value(false)
631 .help("Display balance in lamports instead of SOL"),
632 )
633 .arg(
634 Arg::with_name("bypass_warning")
635 .long("bypass-warning")
636 .takes_value(false)
637 .help("Bypass the permanent program closure warning"),
638 ),
639 )
640 .subcommand(
641 SubCommand::with_name("extend")
642 .about(
643 "Extend the length of an upgradeable program to deploy larger programs",
644 )
645 .arg(
646 Arg::with_name("program_id")
647 .index(1)
648 .value_name("PROGRAM_ID")
649 .takes_value(true)
650 .required(true)
651 .validator(is_valid_pubkey)
652 .help("Address of the program to extend"),
653 )
654 .arg(
655 Arg::with_name("additional_bytes")
656 .index(2)
657 .value_name("ADDITIONAL_BYTES")
658 .takes_value(true)
659 .required(true)
660 .validator(is_parsable::<u32>)
661 .help(
662 "Number of bytes that will be allocated for the program's \
663 data account",
664 ),
665 )
666 .arg(
667 Arg::with_name("payer")
668 .long("payer")
669 .value_name("PAYER_SIGNER")
670 .takes_value(true)
671 .validator(is_valid_signer)
672 .help(
673 "Payer for the additional rent [default: the default \
674 configured keypair]",
675 ),
676 ),
677 ),
678 )
679 .subcommand(
680 SubCommand::with_name("deploy")
681 .about(
682 "Deploy has been removed. Use `solana program deploy` instead to deploy \
683 upgradeable programs",
684 )
685 .setting(AppSettings::Hidden),
686 )
687 }
688}
689
690pub fn parse_program_subcommand(
691 matches: &ArgMatches<'_>,
692 default_signer: &DefaultSigner,
693 wallet_manager: &mut Option<Rc<RemoteWalletManager>>,
694) -> Result<CliCommandInfo, CliError> {
695 let (subcommand, sub_matches) = matches.subcommand();
696 let matches_skip_fee_check = matches.is_present("skip_fee_check");
697 let sub_matches_skip_fee_check = sub_matches
698 .map(|m| m.is_present("skip_fee_check"))
699 .unwrap_or(false);
700 let skip_fee_check = matches_skip_fee_check || sub_matches_skip_fee_check;
701
702 let response = match (subcommand, sub_matches) {
703 ("deploy", Some(matches)) => {
704 let (fee_payer, fee_payer_pubkey) =
705 signer_of(matches, FEE_PAYER_ARG.name, wallet_manager)?;
706
707 let mut bulk_signers = vec![
708 Some(default_signer.signer_from_path(matches, wallet_manager)?),
709 fee_payer, ];
711
712 let program_location = matches
713 .value_of("program_location")
714 .map(|location| location.to_string());
715
716 let buffer_pubkey = if let Ok((buffer_signer, Some(buffer_pubkey))) =
717 signer_of(matches, "buffer", wallet_manager)
718 {
719 bulk_signers.push(buffer_signer);
720 Some(buffer_pubkey)
721 } else {
722 pubkey_of_signer(matches, "buffer", wallet_manager)?
723 };
724
725 let program_pubkey = if let Ok((program_signer, Some(program_pubkey))) =
726 signer_of(matches, "program_id", wallet_manager)
727 {
728 bulk_signers.push(program_signer);
729 Some(program_pubkey)
730 } else {
731 pubkey_of_signer(matches, "program_id", wallet_manager)?
732 };
733
734 let (upgrade_authority, upgrade_authority_pubkey) =
735 signer_of(matches, "upgrade_authority", wallet_manager)?;
736 bulk_signers.push(upgrade_authority);
737
738 let max_len = value_of(matches, "max_len");
739
740 let signer_info =
741 default_signer.generate_unique_signers(bulk_signers, matches, wallet_manager)?;
742
743 let compute_unit_price = value_of(matches, "compute_unit_price");
744 let max_sign_attempts = value_of(matches, "max_sign_attempts").unwrap();
745
746 let auto_extend = !matches.is_present("no_auto_extend");
747
748 let skip_feature_verify = matches.is_present("skip_feature_verify");
749
750 CliCommandInfo {
751 command: CliCommand::Program(ProgramCliCommand::Deploy {
752 program_location,
753 fee_payer_signer_index: signer_info.index_of(fee_payer_pubkey).unwrap(),
754 program_signer_index: signer_info.index_of_or_none(program_pubkey),
755 program_pubkey,
756 buffer_signer_index: signer_info.index_of_or_none(buffer_pubkey),
757 buffer_pubkey,
758 upgrade_authority_signer_index: signer_info
759 .index_of(upgrade_authority_pubkey)
760 .unwrap(),
761 is_final: matches.is_present("final"),
762 max_len,
763 skip_fee_check,
764 compute_unit_price,
765 max_sign_attempts,
766 use_rpc: matches.is_present("use_rpc"),
767 auto_extend,
768 skip_feature_verification: skip_feature_verify,
769 }),
770 signers: signer_info.signers,
771 }
772 }
773 ("upgrade", Some(matches)) => {
774 let sign_only = matches.is_present(SIGN_ONLY_ARG.name);
775 let dump_transaction_message = matches.is_present(DUMP_TRANSACTION_MESSAGE.name);
776 let blockhash_query = BlockhashQuery::new_from_matches(matches);
777 let buffer_pubkey = pubkey_of_signer(matches, "buffer", wallet_manager)
778 .unwrap()
779 .unwrap();
780 let program_pubkey = pubkey_of_signer(matches, "program_id", wallet_manager)
781 .unwrap()
782 .unwrap();
783
784 let (fee_payer, fee_payer_pubkey) =
785 signer_of(matches, FEE_PAYER_ARG.name, wallet_manager)?;
786
787 let mut bulk_signers = vec![
788 fee_payer, ];
790
791 let (upgrade_authority, upgrade_authority_pubkey) =
792 signer_of(matches, "upgrade_authority", wallet_manager)?;
793 bulk_signers.push(upgrade_authority);
794
795 let signer_info =
796 default_signer.generate_unique_signers(bulk_signers, matches, wallet_manager)?;
797
798 let skip_feature_verify = matches.is_present("skip_feature_verify");
799
800 CliCommandInfo {
801 command: CliCommand::Program(ProgramCliCommand::Upgrade {
802 fee_payer_signer_index: signer_info.index_of(fee_payer_pubkey).unwrap(),
803 program_pubkey,
804 buffer_pubkey,
805 upgrade_authority_signer_index: signer_info
806 .index_of(upgrade_authority_pubkey)
807 .unwrap(),
808 sign_only,
809 dump_transaction_message,
810 blockhash_query,
811 skip_feature_verification: skip_feature_verify,
812 }),
813 signers: signer_info.signers,
814 }
815 }
816 ("write-buffer", Some(matches)) => {
817 let (fee_payer, fee_payer_pubkey) =
818 signer_of(matches, FEE_PAYER_ARG.name, wallet_manager)?;
819
820 let mut bulk_signers = vec![
821 Some(default_signer.signer_from_path(matches, wallet_manager)?),
822 fee_payer, ];
824
825 let buffer_pubkey = if let Ok((buffer_signer, Some(buffer_pubkey))) =
826 signer_of(matches, "buffer", wallet_manager)
827 {
828 bulk_signers.push(buffer_signer);
829 Some(buffer_pubkey)
830 } else {
831 pubkey_of_signer(matches, "buffer", wallet_manager)?
832 };
833
834 let (buffer_authority, buffer_authority_pubkey) =
835 signer_of(matches, "buffer_authority", wallet_manager)?;
836 bulk_signers.push(buffer_authority);
837
838 let max_len = value_of(matches, "max_len");
839
840 let signer_info =
841 default_signer.generate_unique_signers(bulk_signers, matches, wallet_manager)?;
842
843 let compute_unit_price = value_of(matches, "compute_unit_price");
844 let max_sign_attempts = value_of(matches, "max_sign_attempts").unwrap();
845 let skip_feature_verify = matches.is_present("skip_feature_verify");
846
847 CliCommandInfo {
848 command: CliCommand::Program(ProgramCliCommand::WriteBuffer {
849 program_location: matches.value_of("program_location").unwrap().to_string(),
850 fee_payer_signer_index: signer_info.index_of(fee_payer_pubkey).unwrap(),
851 buffer_signer_index: signer_info.index_of_or_none(buffer_pubkey),
852 buffer_pubkey,
853 buffer_authority_signer_index: signer_info
854 .index_of(buffer_authority_pubkey)
855 .unwrap(),
856 max_len,
857 skip_fee_check,
858 compute_unit_price,
859 max_sign_attempts,
860 use_rpc: matches.is_present("use_rpc"),
861 skip_feature_verification: skip_feature_verify,
862 }),
863 signers: signer_info.signers,
864 }
865 }
866 ("set-buffer-authority", Some(matches)) => {
867 let buffer_pubkey = pubkey_of(matches, "buffer").unwrap();
868
869 let (buffer_authority_signer, buffer_authority_pubkey) =
870 signer_of(matches, "buffer_authority", wallet_manager)?;
871 let new_buffer_authority =
872 pubkey_of_signer(matches, "new_buffer_authority", wallet_manager)?.unwrap();
873
874 let signer_info = default_signer.generate_unique_signers(
875 vec![
876 Some(default_signer.signer_from_path(matches, wallet_manager)?),
877 buffer_authority_signer,
878 ],
879 matches,
880 wallet_manager,
881 )?;
882
883 CliCommandInfo {
884 command: CliCommand::Program(ProgramCliCommand::SetBufferAuthority {
885 buffer_pubkey,
886 buffer_authority_index: signer_info.index_of(buffer_authority_pubkey),
887 new_buffer_authority,
888 }),
889 signers: signer_info.signers,
890 }
891 }
892 ("set-upgrade-authority", Some(matches)) => {
893 let sign_only = matches.is_present(SIGN_ONLY_ARG.name);
894 let dump_transaction_message = matches.is_present(DUMP_TRANSACTION_MESSAGE.name);
895 let blockhash_query = BlockhashQuery::new_from_matches(matches);
896 let (upgrade_authority_signer, upgrade_authority_pubkey) =
897 signer_of(matches, "upgrade_authority", wallet_manager)?;
898 let program_pubkey = pubkey_of(matches, "program_id").unwrap();
899 let is_final = matches.is_present("final");
900 let new_upgrade_authority = if is_final {
901 None
902 } else {
903 pubkey_of_signer(matches, "new_upgrade_authority", wallet_manager)?
904 };
905
906 let mut signers = vec![
907 Some(default_signer.signer_from_path(matches, wallet_manager)?),
908 upgrade_authority_signer,
909 ];
910
911 if !is_final && !matches.is_present("skip_new_upgrade_authority_signer_check") {
912 let (new_upgrade_authority_signer, _) =
913 signer_of(matches, "new_upgrade_authority", wallet_manager)?;
914 signers.push(new_upgrade_authority_signer);
915 }
916
917 let signer_info =
918 default_signer.generate_unique_signers(signers, matches, wallet_manager)?;
919
920 if matches.is_present("skip_new_upgrade_authority_signer_check") || is_final {
921 CliCommandInfo {
922 command: CliCommand::Program(ProgramCliCommand::SetUpgradeAuthority {
923 program_pubkey,
924 upgrade_authority_index: signer_info.index_of(upgrade_authority_pubkey),
925 new_upgrade_authority,
926 sign_only,
927 dump_transaction_message,
928 blockhash_query,
929 }),
930 signers: signer_info.signers,
931 }
932 } else {
933 CliCommandInfo {
934 command: CliCommand::Program(ProgramCliCommand::SetUpgradeAuthorityChecked {
935 program_pubkey,
936 upgrade_authority_index: signer_info
937 .index_of(upgrade_authority_pubkey)
938 .expect("upgrade authority is missing from signers"),
939 new_upgrade_authority_index: signer_info
940 .index_of(new_upgrade_authority)
941 .expect("new upgrade authority is missing from signers"),
942 sign_only,
943 dump_transaction_message,
944 blockhash_query,
945 }),
946 signers: signer_info.signers,
947 }
948 }
949 }
950 ("show", Some(matches)) => {
951 let authority_pubkey = if let Some(authority_pubkey) =
952 pubkey_of_signer(matches, "buffer_authority", wallet_manager)?
953 {
954 authority_pubkey
955 } else {
956 default_signer
957 .signer_from_path(matches, wallet_manager)?
958 .pubkey()
959 };
960
961 CliCommandInfo::without_signers(CliCommand::Program(ProgramCliCommand::Show {
962 account_pubkey: pubkey_of(matches, "account"),
963 authority_pubkey,
964 get_programs: matches.is_present("programs"),
965 get_buffers: matches.is_present("buffers"),
966 all: matches.is_present("all"),
967 use_lamports_unit: matches.is_present("lamports"),
968 }))
969 }
970 ("dump", Some(matches)) => {
971 CliCommandInfo::without_signers(CliCommand::Program(ProgramCliCommand::Dump {
972 account_pubkey: pubkey_of(matches, "account"),
973 output_location: matches.value_of("output_location").unwrap().to_string(),
974 }))
975 }
976 ("close", Some(matches)) => {
977 let account_pubkey = if matches.is_present("buffers") {
978 None
979 } else {
980 pubkey_of(matches, "account")
981 };
982
983 let recipient_pubkey = if let Some(recipient_pubkey) =
984 pubkey_of_signer(matches, "recipient_account", wallet_manager)?
985 {
986 recipient_pubkey
987 } else {
988 default_signer
989 .signer_from_path(matches, wallet_manager)?
990 .pubkey()
991 };
992
993 let (authority_signer, authority_pubkey) =
994 signer_of(matches, "authority", wallet_manager)?;
995
996 let signer_info = default_signer.generate_unique_signers(
997 vec![
998 Some(default_signer.signer_from_path(matches, wallet_manager)?),
999 authority_signer,
1000 ],
1001 matches,
1002 wallet_manager,
1003 )?;
1004
1005 CliCommandInfo {
1006 command: CliCommand::Program(ProgramCliCommand::Close {
1007 account_pubkey,
1008 recipient_pubkey,
1009 authority_index: signer_info.index_of(authority_pubkey).unwrap(),
1010 use_lamports_unit: matches.is_present("lamports"),
1011 bypass_warning: matches.is_present("bypass_warning"),
1012 }),
1013 signers: signer_info.signers,
1014 }
1015 }
1016 ("extend", Some(matches)) => {
1017 let program_pubkey = pubkey_of(matches, "program_id").unwrap();
1018 let additional_bytes = value_of(matches, "additional_bytes").unwrap();
1019 let (payer_signer, payer_pubkey) = signer_of(matches, "payer", wallet_manager)?;
1020
1021 let signer_info = default_signer.generate_unique_signers(
1022 vec![
1023 Some(default_signer.signer_from_path(matches, wallet_manager)?),
1024 payer_signer,
1025 ],
1026 matches,
1027 wallet_manager,
1028 )?;
1029
1030 CliCommandInfo {
1031 command: CliCommand::Program(ProgramCliCommand::ExtendProgram {
1032 program_pubkey,
1033 payer_signer_index: signer_info.index_of(payer_pubkey).unwrap(),
1034 additional_bytes,
1035 }),
1036 signers: signer_info.signers,
1037 }
1038 }
1039 _ => unreachable!(),
1040 };
1041 Ok(response)
1042}
1043
1044pub async fn process_program_subcommand(
1045 rpc_client: Arc<RpcClient>,
1046 config: &CliConfig<'_>,
1047 program_subcommand: &ProgramCliCommand,
1048) -> ProcessResult {
1049 match program_subcommand {
1050 ProgramCliCommand::Deploy {
1051 program_location,
1052 fee_payer_signer_index,
1053 program_signer_index,
1054 program_pubkey,
1055 buffer_signer_index,
1056 buffer_pubkey,
1057 upgrade_authority_signer_index,
1058 is_final,
1059 max_len,
1060 skip_fee_check,
1061 compute_unit_price,
1062 max_sign_attempts,
1063 auto_extend,
1064 use_rpc,
1065 skip_feature_verification,
1066 } => {
1067 process_program_deploy(
1068 rpc_client,
1069 config,
1070 program_location,
1071 *fee_payer_signer_index,
1072 *program_signer_index,
1073 *program_pubkey,
1074 *buffer_signer_index,
1075 *buffer_pubkey,
1076 *upgrade_authority_signer_index,
1077 *is_final,
1078 *max_len,
1079 *skip_fee_check,
1080 *compute_unit_price,
1081 *max_sign_attempts,
1082 *auto_extend,
1083 *use_rpc,
1084 *skip_feature_verification,
1085 )
1086 .await
1087 }
1088 ProgramCliCommand::Upgrade {
1089 fee_payer_signer_index,
1090 program_pubkey,
1091 buffer_pubkey,
1092 upgrade_authority_signer_index,
1093 sign_only,
1094 dump_transaction_message,
1095 blockhash_query,
1096 skip_feature_verification,
1097 } => {
1098 process_program_upgrade(
1099 rpc_client,
1100 config,
1101 *fee_payer_signer_index,
1102 *program_pubkey,
1103 *buffer_pubkey,
1104 *upgrade_authority_signer_index,
1105 *sign_only,
1106 *dump_transaction_message,
1107 blockhash_query,
1108 *skip_feature_verification,
1109 )
1110 .await
1111 }
1112 ProgramCliCommand::WriteBuffer {
1113 program_location,
1114 fee_payer_signer_index,
1115 buffer_signer_index,
1116 buffer_pubkey,
1117 buffer_authority_signer_index,
1118 max_len,
1119 skip_fee_check,
1120 compute_unit_price,
1121 max_sign_attempts,
1122 use_rpc,
1123 skip_feature_verification,
1124 } => {
1125 process_write_buffer(
1126 rpc_client,
1127 config,
1128 program_location,
1129 *fee_payer_signer_index,
1130 *buffer_signer_index,
1131 *buffer_pubkey,
1132 *buffer_authority_signer_index,
1133 *max_len,
1134 *skip_fee_check,
1135 *compute_unit_price,
1136 *max_sign_attempts,
1137 *use_rpc,
1138 *skip_feature_verification,
1139 )
1140 .await
1141 }
1142 ProgramCliCommand::SetBufferAuthority {
1143 buffer_pubkey,
1144 buffer_authority_index,
1145 new_buffer_authority,
1146 } => {
1147 process_set_authority(
1148 &rpc_client,
1149 config,
1150 None,
1151 Some(*buffer_pubkey),
1152 *buffer_authority_index,
1153 Some(*new_buffer_authority),
1154 false,
1155 false,
1156 &BlockhashQuery::default(),
1157 )
1158 .await
1159 }
1160 ProgramCliCommand::SetUpgradeAuthority {
1161 program_pubkey,
1162 upgrade_authority_index,
1163 new_upgrade_authority,
1164 sign_only,
1165 dump_transaction_message,
1166 blockhash_query,
1167 } => {
1168 process_set_authority(
1169 &rpc_client,
1170 config,
1171 Some(*program_pubkey),
1172 None,
1173 *upgrade_authority_index,
1174 *new_upgrade_authority,
1175 *sign_only,
1176 *dump_transaction_message,
1177 blockhash_query,
1178 )
1179 .await
1180 }
1181 ProgramCliCommand::SetUpgradeAuthorityChecked {
1182 program_pubkey,
1183 upgrade_authority_index,
1184 new_upgrade_authority_index,
1185 sign_only,
1186 dump_transaction_message,
1187 blockhash_query,
1188 } => {
1189 process_set_authority_checked(
1190 &rpc_client,
1191 config,
1192 *program_pubkey,
1193 *upgrade_authority_index,
1194 *new_upgrade_authority_index,
1195 *sign_only,
1196 *dump_transaction_message,
1197 blockhash_query,
1198 )
1199 .await
1200 }
1201 ProgramCliCommand::Show {
1202 account_pubkey,
1203 authority_pubkey,
1204 get_programs,
1205 get_buffers,
1206 all,
1207 use_lamports_unit,
1208 } => {
1209 process_show(
1210 &rpc_client,
1211 config,
1212 *account_pubkey,
1213 *authority_pubkey,
1214 *get_programs,
1215 *get_buffers,
1216 *all,
1217 *use_lamports_unit,
1218 )
1219 .await
1220 }
1221 ProgramCliCommand::Dump {
1222 account_pubkey,
1223 output_location,
1224 } => process_dump(&rpc_client, config, *account_pubkey, output_location).await,
1225 ProgramCliCommand::Close {
1226 account_pubkey,
1227 recipient_pubkey,
1228 authority_index,
1229 use_lamports_unit,
1230 bypass_warning,
1231 } => {
1232 process_close(
1233 &rpc_client,
1234 config,
1235 *account_pubkey,
1236 *recipient_pubkey,
1237 *authority_index,
1238 *use_lamports_unit,
1239 *bypass_warning,
1240 )
1241 .await
1242 }
1243 ProgramCliCommand::ExtendProgram {
1244 program_pubkey,
1245 payer_signer_index,
1246 additional_bytes,
1247 } => {
1248 process_extend_program(
1249 &rpc_client,
1250 config,
1251 *program_pubkey,
1252 *payer_signer_index,
1253 *additional_bytes,
1254 )
1255 .await
1256 }
1257 }
1258}
1259
1260fn get_default_program_keypair(program_location: &Option<String>) -> Keypair {
1261 if let Some(program_location) = program_location {
1262 let mut keypair_file = PathBuf::new();
1263 keypair_file.push(program_location);
1264 let mut filename = keypair_file.file_stem().unwrap().to_os_string();
1265 filename.push("-keypair");
1266 keypair_file.set_file_name(filename);
1267 keypair_file.set_extension("json");
1268 if let Ok(keypair) = read_keypair_file(keypair_file.to_str().unwrap()) {
1269 keypair
1270 } else {
1271 Keypair::new()
1272 }
1273 } else {
1274 Keypair::new()
1275 }
1276}
1277
1278#[allow(clippy::too_many_arguments)]
1280async fn process_program_deploy(
1281 rpc_client: Arc<RpcClient>,
1282 config: &CliConfig<'_>,
1283 program_location: &Option<String>,
1284 fee_payer_signer_index: SignerIndex,
1285 program_signer_index: Option<SignerIndex>,
1286 program_pubkey: Option<Pubkey>,
1287 buffer_signer_index: Option<SignerIndex>,
1288 buffer_pubkey: Option<Pubkey>,
1289 upgrade_authority_signer_index: SignerIndex,
1290 is_final: bool,
1291 max_len: Option<usize>,
1292 skip_fee_check: bool,
1293 compute_unit_price: Option<u64>,
1294 max_sign_attempts: usize,
1295 auto_extend: bool,
1296 use_rpc: bool,
1297 skip_feature_verification: bool,
1298) -> ProcessResult {
1299 let fee_payer_signer = config.signers[fee_payer_signer_index];
1300 let upgrade_authority_signer = config.signers[upgrade_authority_signer_index];
1301
1302 let (buffer_words, buffer_mnemonic, buffer_keypair) = create_ephemeral_keypair()?;
1303 let (buffer_provided, buffer_signer, buffer_pubkey) = if let Some(i) = buffer_signer_index {
1304 (true, Some(config.signers[i]), config.signers[i].pubkey())
1305 } else if let Some(pubkey) = buffer_pubkey {
1306 (true, None, pubkey)
1307 } else {
1308 (
1309 false,
1310 Some(&buffer_keypair as &dyn Signer),
1311 buffer_keypair.pubkey(),
1312 )
1313 };
1314
1315 let default_program_keypair = get_default_program_keypair(program_location);
1316 let (program_signer, program_pubkey) = if let Some(i) = program_signer_index {
1317 (Some(config.signers[i]), config.signers[i].pubkey())
1318 } else if let Some(program_pubkey) = program_pubkey {
1319 (None, program_pubkey)
1320 } else {
1321 (
1322 Some(&default_program_keypair as &dyn Signer),
1323 default_program_keypair.pubkey(),
1324 )
1325 };
1326
1327 let do_initial_deploy = if let Some(account) = rpc_client
1328 .get_account_with_commitment(&program_pubkey, config.commitment)
1329 .await?
1330 .value
1331 {
1332 if account.owner != bpf_loader_upgradeable::id() {
1333 return Err(format!(
1334 "Account {program_pubkey} is not an upgradeable program or already in use"
1335 )
1336 .into());
1337 }
1338
1339 if !account.executable {
1340 true
1342 } else if let Ok(UpgradeableLoaderState::Program {
1343 programdata_address,
1344 }) = account.state()
1345 {
1346 if let Some(account) = rpc_client
1347 .get_account_with_commitment(&programdata_address, config.commitment)
1348 .await?
1349 .value
1350 {
1351 if let Ok(UpgradeableLoaderState::ProgramData {
1352 slot: _,
1353 upgrade_authority_address: program_authority_pubkey,
1354 }) = account.state()
1355 {
1356 if program_authority_pubkey.is_none() {
1357 return Err(
1358 format!("Program {program_pubkey} is no longer upgradeable").into()
1359 );
1360 }
1361 if program_authority_pubkey != Some(upgrade_authority_signer.pubkey()) {
1362 return Err(format!(
1363 "Program's authority {:?} does not match authority provided {:?}",
1364 program_authority_pubkey,
1365 upgrade_authority_signer.pubkey(),
1366 )
1367 .into());
1368 }
1369 false
1371 } else {
1372 return Err(format!(
1373 "Program {program_pubkey} has been closed, use a new Program Id"
1374 )
1375 .into());
1376 }
1377 } else {
1378 return Err(format!(
1379 "Program {program_pubkey} has been closed, use a new Program Id"
1380 )
1381 .into());
1382 }
1383 } else {
1384 return Err(format!("{program_pubkey} is not an upgradeable program").into());
1385 }
1386 } else {
1387 true
1389 };
1390
1391 let feature_set = if skip_feature_verification {
1392 FeatureSet::all_enabled()
1393 } else {
1394 fetch_feature_set(&rpc_client).await?
1395 };
1396
1397 let (program_data, program_len, buffer_program_data) =
1398 if let Some(program_location) = program_location {
1399 let program_data = read_and_verify_elf(program_location, feature_set)?;
1400 let program_len = program_data.len();
1401
1402 let buffer_program_data = if buffer_provided {
1403 fetch_buffer_program_data(
1404 &rpc_client,
1405 config,
1406 Some(program_len),
1407 buffer_pubkey,
1408 upgrade_authority_signer.pubkey(),
1409 )
1410 .await?
1411 } else {
1412 None
1413 };
1414
1415 (program_data, program_len, buffer_program_data)
1416 } else if buffer_provided {
1417 let buffer_program_data = fetch_verified_buffer_program_data(
1418 &rpc_client,
1419 config,
1420 buffer_pubkey,
1421 upgrade_authority_signer.pubkey(),
1422 feature_set,
1423 )
1424 .await?;
1425
1426 (vec![], buffer_program_data.len(), Some(buffer_program_data))
1427 } else {
1428 return Err("Program location required if buffer not supplied".into());
1429 };
1430
1431 let program_data_max_len = if let Some(len) = max_len {
1432 if program_len > len {
1433 return Err(
1434 "Max length specified not large enough to accommodate desired program".into(),
1435 );
1436 }
1437 len
1438 } else {
1439 program_len
1440 };
1441
1442 let min_rent_exempt_program_data_balance = rpc_client
1443 .get_minimum_balance_for_rent_exemption(UpgradeableLoaderState::size_of_programdata(
1444 program_data_max_len,
1445 ))
1446 .await?;
1447
1448 if do_initial_deploy && program_signer.is_none() {
1449 return Err("Initial deployments require a keypair be provided for the program id".into());
1450 }
1451 if !buffer_provided {
1452 report_ephemeral_mnemonic(buffer_words, buffer_mnemonic, &buffer_pubkey);
1455 }
1456 let result = if do_initial_deploy {
1457 do_process_program_deploy(
1458 rpc_client.clone(),
1459 config,
1460 &program_data,
1461 program_len,
1462 program_data_max_len,
1463 min_rent_exempt_program_data_balance,
1464 fee_payer_signer,
1465 &[program_signer.unwrap(), upgrade_authority_signer],
1466 buffer_signer,
1467 &buffer_pubkey,
1468 buffer_program_data,
1469 upgrade_authority_signer,
1470 skip_fee_check,
1471 compute_unit_price,
1472 max_sign_attempts,
1473 use_rpc,
1474 )
1475 .await
1476 } else {
1477 do_process_program_upgrade(
1478 rpc_client.clone(),
1479 config,
1480 &program_data,
1481 program_len,
1482 min_rent_exempt_program_data_balance,
1483 fee_payer_signer,
1484 &program_pubkey,
1485 upgrade_authority_signer,
1486 &buffer_pubkey,
1487 buffer_signer,
1488 buffer_program_data,
1489 skip_fee_check,
1490 compute_unit_price,
1491 max_sign_attempts,
1492 auto_extend,
1493 use_rpc,
1494 )
1495 .await
1496 };
1497 if result.is_ok() && is_final {
1498 process_set_authority(
1499 &rpc_client,
1500 config,
1501 Some(program_pubkey),
1502 None,
1503 Some(upgrade_authority_signer_index),
1504 None,
1505 false,
1506 false,
1507 &BlockhashQuery::default(),
1508 )
1509 .await?;
1510 }
1511 result
1512}
1513
1514async fn fetch_verified_buffer_program_data(
1515 rpc_client: &RpcClient,
1516 config: &CliConfig<'_>,
1517 buffer_pubkey: Pubkey,
1518 buffer_authority: Pubkey,
1519 feature_set: FeatureSet,
1520) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
1521 let Some(buffer_program_data) =
1522 fetch_buffer_program_data(rpc_client, config, None, buffer_pubkey, buffer_authority)
1523 .await?
1524 else {
1525 return Err(format!("Buffer account {buffer_pubkey} not found").into());
1526 };
1527
1528 verify_elf(&buffer_program_data, feature_set).map_err(|err| {
1529 format!("Buffer account {buffer_pubkey} has invalid program data: {err:?}")
1530 })?;
1531
1532 Ok(buffer_program_data)
1533}
1534
1535async fn fetch_buffer_program_data(
1536 rpc_client: &RpcClient,
1537 config: &CliConfig<'_>,
1538 min_program_len: Option<usize>,
1539 buffer_pubkey: Pubkey,
1540 buffer_authority: Pubkey,
1541) -> Result<Option<Vec<u8>>, Box<dyn std::error::Error>> {
1542 let Some(mut account) = rpc_client
1543 .get_account_with_commitment(&buffer_pubkey, config.commitment)
1544 .await?
1545 .value
1546 else {
1547 return Ok(None);
1548 };
1549
1550 if !bpf_loader_upgradeable::check_id(&account.owner) {
1551 return Err(format!(
1552 "Buffer account {buffer_pubkey} is not owned by the BPF Upgradeable Loader",
1553 )
1554 .into());
1555 }
1556
1557 if let Ok(UpgradeableLoaderState::Buffer { authority_address }) = account.state() {
1558 if authority_address.is_none() {
1559 return Err(format!("Buffer {buffer_pubkey} is immutable").into());
1560 }
1561 if authority_address != Some(buffer_authority) {
1562 return Err(format!(
1563 "Buffer's authority {authority_address:?} does not match authority provided \
1564 {buffer_authority}"
1565 )
1566 .into());
1567 }
1568 } else {
1569 return Err(format!("{buffer_pubkey} is not an upgradeable loader buffer account").into());
1570 }
1571
1572 if let Some(min_program_len) = min_program_len {
1573 let min_buffer_data_len = UpgradeableLoaderState::size_of_buffer(min_program_len);
1574 if account.data.len() < min_buffer_data_len {
1575 return Err(format!(
1576 "Buffer account data size ({}) is smaller than the minimum size ({})",
1577 account.data.len(),
1578 min_buffer_data_len
1579 )
1580 .into());
1581 }
1582 }
1583
1584 let buffer_program_data = account
1585 .data
1586 .split_off(UpgradeableLoaderState::size_of_buffer_metadata());
1587
1588 Ok(Some(buffer_program_data))
1589}
1590
1591#[allow(clippy::too_many_arguments)]
1593async fn process_program_upgrade(
1594 rpc_client: Arc<RpcClient>,
1595 config: &CliConfig<'_>,
1596 fee_payer_signer_index: SignerIndex,
1597 program_id: Pubkey,
1598 buffer_pubkey: Pubkey,
1599 upgrade_authority_signer_index: SignerIndex,
1600 sign_only: bool,
1601 dump_transaction_message: bool,
1602 blockhash_query: &BlockhashQuery,
1603 skip_feature_verification: bool,
1604) -> ProcessResult {
1605 let fee_payer_signer = config.signers[fee_payer_signer_index];
1606 let upgrade_authority_signer = config.signers[upgrade_authority_signer_index];
1607
1608 let blockhash = blockhash_query
1609 .get_blockhash(&rpc_client, config.commitment)
1610 .await?;
1611 let message = Message::new_with_blockhash(
1612 &[loader_v3_instruction::upgrade(
1613 &program_id,
1614 &buffer_pubkey,
1615 &upgrade_authority_signer.pubkey(),
1616 &fee_payer_signer.pubkey(),
1617 )],
1618 Some(&fee_payer_signer.pubkey()),
1619 &blockhash,
1620 );
1621
1622 if sign_only {
1623 let mut tx = Transaction::new_unsigned(message);
1624 let signers = &[fee_payer_signer, upgrade_authority_signer];
1625 tx.try_partial_sign(signers, blockhash)?;
1628 return_signers_with_config(
1629 &tx,
1630 &config.output_format,
1631 &ReturnSignersConfig {
1632 dump_transaction_message,
1633 },
1634 )
1635 } else {
1636 let feature_set = if skip_feature_verification {
1637 FeatureSet::all_enabled()
1638 } else {
1639 fetch_feature_set(&rpc_client).await?
1640 };
1641
1642 fetch_verified_buffer_program_data(
1643 &rpc_client,
1644 config,
1645 buffer_pubkey,
1646 upgrade_authority_signer.pubkey(),
1647 feature_set,
1648 )
1649 .await?;
1650
1651 let message = VersionedMessage::Legacy(message);
1652 let fee = rpc_client.get_fee_for_versioned_message(&message).await?;
1653 check_account_for_spend_and_fee_with_commitment(
1654 &rpc_client,
1655 &fee_payer_signer.pubkey(),
1656 0,
1657 fee,
1658 config.commitment,
1659 )
1660 .await?;
1661 let signers = &[fee_payer_signer, upgrade_authority_signer];
1662 let tx = VersionedTransaction::try_new(message, &dedup_signers(signers))?;
1663 let final_tx_sig = rpc_client
1664 .send_and_confirm_transaction_with_spinner_and_config(
1665 &tx,
1666 config.commitment,
1667 config.send_transaction_config,
1668 )
1669 .await
1670 .map_err(|e| format!("Upgrading program failed: {e}"))?;
1671 let program_id = CliProgramId {
1672 program_id: program_id.to_string(),
1673 signature: Some(final_tx_sig.to_string()),
1674 };
1675 Ok(config.output_format.formatted_string(&program_id))
1676 }
1677}
1678
1679#[allow(clippy::too_many_arguments)]
1680async fn process_write_buffer(
1681 rpc_client: Arc<RpcClient>,
1682 config: &CliConfig<'_>,
1683 program_location: &str,
1684 fee_payer_signer_index: SignerIndex,
1685 buffer_signer_index: Option<SignerIndex>,
1686 buffer_pubkey: Option<Pubkey>,
1687 buffer_authority_signer_index: SignerIndex,
1688 max_len: Option<usize>,
1689 skip_fee_check: bool,
1690 compute_unit_price: Option<u64>,
1691 max_sign_attempts: usize,
1692 use_rpc: bool,
1693 skip_feature_verification: bool,
1694) -> ProcessResult {
1695 let fee_payer_signer = config.signers[fee_payer_signer_index];
1696 let buffer_authority = config.signers[buffer_authority_signer_index];
1697
1698 let feature_set = if skip_feature_verification {
1699 FeatureSet::all_enabled()
1700 } else {
1701 fetch_feature_set(&rpc_client).await?
1702 };
1703
1704 let program_data = read_and_verify_elf(program_location, feature_set)?;
1705 let program_len = program_data.len();
1706
1707 let (words, mnemonic, buffer_keypair) = create_ephemeral_keypair()?;
1709 let (buffer_signer, buffer_pubkey) = if let Some(i) = buffer_signer_index {
1710 (Some(config.signers[i]), config.signers[i].pubkey())
1711 } else if let Some(pubkey) = buffer_pubkey {
1712 (None, pubkey)
1713 } else {
1714 (
1715 Some(&buffer_keypair as &dyn Signer),
1716 buffer_keypair.pubkey(),
1717 )
1718 };
1719
1720 let buffer_program_data = fetch_buffer_program_data(
1721 &rpc_client,
1722 config,
1723 Some(program_len),
1724 buffer_pubkey,
1725 buffer_authority.pubkey(),
1726 )
1727 .await?;
1728
1729 let buffer_data_max_len = if let Some(len) = max_len {
1730 len
1731 } else {
1732 program_data.len()
1733 };
1734 let min_rent_exempt_program_buffer_balance = rpc_client
1735 .get_minimum_balance_for_rent_exemption(UpgradeableLoaderState::size_of_buffer(
1736 buffer_data_max_len,
1737 ))
1738 .await?;
1739
1740 let result = do_process_write_buffer(
1741 rpc_client,
1742 config,
1743 &program_data,
1744 program_data.len(),
1745 min_rent_exempt_program_buffer_balance,
1746 fee_payer_signer,
1747 buffer_signer,
1748 &buffer_pubkey,
1749 buffer_program_data,
1750 buffer_authority,
1751 skip_fee_check,
1752 compute_unit_price,
1753 max_sign_attempts,
1754 use_rpc,
1755 )
1756 .await;
1757 if result.is_err() && buffer_signer_index.is_none() && buffer_signer.is_some() {
1758 report_ephemeral_mnemonic(words, mnemonic, &buffer_pubkey);
1759 }
1760 result
1761}
1762
1763async fn process_set_authority(
1764 rpc_client: &RpcClient,
1765 config: &CliConfig<'_>,
1766 program_pubkey: Option<Pubkey>,
1767 buffer_pubkey: Option<Pubkey>,
1768 authority: Option<SignerIndex>,
1769 new_authority: Option<Pubkey>,
1770 sign_only: bool,
1771 dump_transaction_message: bool,
1772 blockhash_query: &BlockhashQuery,
1773) -> ProcessResult {
1774 let authority_signer = if let Some(index) = authority {
1775 config.signers[index]
1776 } else {
1777 return Err("Set authority requires the current authority".into());
1778 };
1779
1780 trace!("Set a new authority");
1781 let blockhash = blockhash_query
1782 .get_blockhash(rpc_client, config.commitment)
1783 .await?;
1784
1785 let mut tx = if let Some(ref pubkey) = program_pubkey {
1786 Transaction::new_unsigned(Message::new(
1787 &[loader_v3_instruction::set_upgrade_authority(
1788 pubkey,
1789 &authority_signer.pubkey(),
1790 new_authority.as_ref(),
1791 )],
1792 Some(&config.signers[0].pubkey()),
1793 ))
1794 } else if let Some(pubkey) = buffer_pubkey {
1795 if let Some(ref new_authority) = new_authority {
1796 Transaction::new_unsigned(Message::new(
1797 &[loader_v3_instruction::set_buffer_authority(
1798 &pubkey,
1799 &authority_signer.pubkey(),
1800 new_authority,
1801 )],
1802 Some(&config.signers[0].pubkey()),
1803 ))
1804 } else {
1805 return Err("Buffer authority cannot be None".into());
1806 }
1807 } else {
1808 return Err("Program or Buffer not provided".into());
1809 };
1810
1811 let signers = &[config.signers[0], authority_signer];
1812
1813 if sign_only {
1814 tx.try_partial_sign(signers, blockhash)?;
1815 return_signers_with_config(
1816 &tx,
1817 &config.output_format,
1818 &ReturnSignersConfig {
1819 dump_transaction_message,
1820 },
1821 )
1822 } else {
1823 tx.try_sign(signers, blockhash)?;
1824 rpc_client
1825 .send_and_confirm_transaction_with_spinner_and_config(
1826 &tx,
1827 config.commitment,
1828 config.send_transaction_config,
1829 )
1830 .await
1831 .map_err(|e| format!("Setting authority failed: {e}"))?;
1832
1833 let authority = CliProgramAuthority {
1834 authority: new_authority
1835 .map(|pubkey| pubkey.to_string())
1836 .unwrap_or_else(|| "none".to_string()),
1837 account_type: if program_pubkey.is_some() {
1838 CliProgramAccountType::Program
1839 } else {
1840 CliProgramAccountType::Buffer
1841 },
1842 };
1843 Ok(config.output_format.formatted_string(&authority))
1844 }
1845}
1846
1847async fn process_set_authority_checked(
1848 rpc_client: &RpcClient,
1849 config: &CliConfig<'_>,
1850 program_pubkey: Pubkey,
1851 authority_index: SignerIndex,
1852 new_authority_index: SignerIndex,
1853 sign_only: bool,
1854 dump_transaction_message: bool,
1855 blockhash_query: &BlockhashQuery,
1856) -> ProcessResult {
1857 let authority_signer = config.signers[authority_index];
1858 let new_authority_signer = config.signers[new_authority_index];
1859
1860 trace!("Set a new (checked) authority");
1861 let blockhash = blockhash_query
1862 .get_blockhash(rpc_client, config.commitment)
1863 .await?;
1864
1865 let mut tx = Transaction::new_unsigned(Message::new(
1866 &[loader_v3_instruction::set_upgrade_authority_checked(
1867 &program_pubkey,
1868 &authority_signer.pubkey(),
1869 &new_authority_signer.pubkey(),
1870 )],
1871 Some(&config.signers[0].pubkey()),
1872 ));
1873
1874 let signers = &[config.signers[0], authority_signer, new_authority_signer];
1875 if sign_only {
1876 tx.try_partial_sign(signers, blockhash)?;
1877 return_signers_with_config(
1878 &tx,
1879 &config.output_format,
1880 &ReturnSignersConfig {
1881 dump_transaction_message,
1882 },
1883 )
1884 } else {
1885 tx.try_sign(signers, blockhash)?;
1886 rpc_client
1887 .send_and_confirm_transaction_with_spinner_and_config(
1888 &tx,
1889 config.commitment,
1890 config.send_transaction_config,
1891 )
1892 .await
1893 .map_err(|e| format!("Setting authority failed: {e}"))?;
1894
1895 let authority = CliProgramAuthority {
1896 authority: new_authority_signer.pubkey().to_string(),
1897 account_type: CliProgramAccountType::Program,
1898 };
1899 Ok(config.output_format.formatted_string(&authority))
1900 }
1901}
1902
1903const ACCOUNT_TYPE_SIZE: usize = 4;
1904const SLOT_SIZE: usize = size_of::<u64>();
1905const OPTION_SIZE: usize = 1;
1906const PUBKEY_LEN: usize = 32;
1907
1908fn ui_account_payload_len(ui_account: &UiAccount, metadata_len: usize) -> usize {
1909 ui_account
1910 .space
1911 .and_then(|space| usize::try_from(space).ok())
1912 .unwrap_or_default()
1913 .saturating_sub(metadata_len)
1914}
1915
1916async fn get_buffers(
1917 rpc_client: &RpcClient,
1918 authority_pubkey: Option<Pubkey>,
1919 use_lamports_unit: bool,
1920) -> Result<CliUpgradeableBuffers, Box<dyn std::error::Error>> {
1921 let mut filters = vec![RpcFilterType::Memcmp(Memcmp::new_base58_encoded(
1922 0,
1923 &[1, 0, 0, 0],
1924 ))];
1925 if let Some(authority_pubkey) = authority_pubkey {
1926 filters.push(RpcFilterType::Memcmp(Memcmp::new_base58_encoded(
1927 ACCOUNT_TYPE_SIZE,
1928 &[1],
1929 )));
1930 filters.push(RpcFilterType::Memcmp(Memcmp::new_base58_encoded(
1931 ACCOUNT_TYPE_SIZE + OPTION_SIZE,
1932 authority_pubkey.as_ref(),
1933 )));
1934 }
1935
1936 let results = get_accounts_with_filter(
1937 rpc_client,
1938 filters,
1939 ACCOUNT_TYPE_SIZE + OPTION_SIZE + PUBKEY_LEN,
1940 )
1941 .await?;
1942
1943 let mut buffers = vec![];
1944 for (address, ui_account) in results.iter() {
1945 let account = ui_account.to_account().expect(
1946 "It should be impossible at this point for the account data not to be decodable. \
1947 Ensure that the account was fetched using a binary encoding.",
1948 );
1949 if let Ok(UpgradeableLoaderState::Buffer { authority_address }) = account.state() {
1950 buffers.push(CliUpgradeableBuffer {
1951 address: address.to_string(),
1952 authority: authority_address
1953 .map(|pubkey| pubkey.to_string())
1954 .unwrap_or_else(|| "none".to_string()),
1955 data_len: ui_account_payload_len(
1956 ui_account,
1957 UpgradeableLoaderState::size_of_buffer_metadata(),
1958 ),
1959 lamports: account.lamports,
1960 use_lamports_unit,
1961 });
1962 } else {
1963 return Err(format!("Error parsing Buffer account {address}").into());
1964 }
1965 }
1966 Ok(CliUpgradeableBuffers {
1967 buffers,
1968 use_lamports_unit,
1969 })
1970}
1971
1972async fn get_programs(
1973 rpc_client: &RpcClient,
1974 authority_pubkey: Option<Pubkey>,
1975 use_lamports_unit: bool,
1976) -> Result<CliUpgradeablePrograms, Box<dyn std::error::Error>> {
1977 let mut filters = vec![RpcFilterType::Memcmp(Memcmp::new_base58_encoded(
1978 0,
1979 &[3, 0, 0, 0],
1980 ))];
1981 if let Some(authority_pubkey) = authority_pubkey {
1982 filters.push(RpcFilterType::Memcmp(Memcmp::new_base58_encoded(
1983 ACCOUNT_TYPE_SIZE + SLOT_SIZE,
1984 &[1],
1985 )));
1986 filters.push(RpcFilterType::Memcmp(Memcmp::new_base58_encoded(
1987 ACCOUNT_TYPE_SIZE + SLOT_SIZE + OPTION_SIZE,
1988 authority_pubkey.as_ref(),
1989 )));
1990 }
1991
1992 let results = get_accounts_with_filter(
1993 rpc_client,
1994 filters,
1995 ACCOUNT_TYPE_SIZE + SLOT_SIZE + OPTION_SIZE + PUBKEY_LEN,
1996 )
1997 .await?;
1998
1999 let mut programs = vec![];
2000 for (programdata_address, programdata_ui_account) in results.iter() {
2001 let programdata_account = programdata_ui_account.to_account().expect(
2002 "It should be impossible at this point for the account data not to be decodable. \
2003 Ensure that the account was fetched using a binary encoding.",
2004 );
2005 if let Ok(UpgradeableLoaderState::ProgramData {
2006 slot,
2007 upgrade_authority_address,
2008 }) = programdata_account.state()
2009 {
2010 let mut bytes = vec![2, 0, 0, 0];
2011 bytes.extend_from_slice(programdata_address.as_ref());
2012 let filters = vec![RpcFilterType::Memcmp(Memcmp::new_base58_encoded(0, &bytes))];
2013
2014 let results = get_accounts_with_filter(rpc_client, filters, 0).await?;
2015 if results.len() != 1 {
2016 return Err(format!(
2017 "Error: More than one Program associated with ProgramData account \
2018 {programdata_address}"
2019 )
2020 .into());
2021 }
2022 programs.push(CliUpgradeableProgram {
2023 program_id: results[0].0.to_string(),
2024 owner: programdata_account.owner.to_string(),
2025 programdata_address: programdata_address.to_string(),
2026 authority: upgrade_authority_address
2027 .map(|pubkey| pubkey.to_string())
2028 .unwrap_or_else(|| "none".to_string()),
2029 last_deploy_slot: slot,
2030 data_len: ui_account_payload_len(
2031 programdata_ui_account,
2032 UpgradeableLoaderState::size_of_programdata_metadata(),
2033 ),
2034 lamports: programdata_account.lamports,
2035 use_lamports_unit,
2036 });
2037 } else {
2038 return Err(format!("Error parsing ProgramData account {programdata_address}").into());
2039 }
2040 }
2041 Ok(CliUpgradeablePrograms {
2042 programs,
2043 use_lamports_unit,
2044 })
2045}
2046
2047async fn get_accounts_with_filter(
2048 rpc_client: &RpcClient,
2049 filters: Vec<RpcFilterType>,
2050 length: usize,
2051) -> Result<Vec<(Pubkey, UiAccount)>, Box<dyn std::error::Error>> {
2052 let results = rpc_client
2053 .get_program_ui_accounts_with_config(
2054 &bpf_loader_upgradeable::id(),
2055 RpcProgramAccountsConfig {
2056 filters: Some(filters),
2057 account_config: RpcAccountInfoConfig {
2058 encoding: Some(UiAccountEncoding::Base64),
2059 data_slice: Some(UiDataSliceConfig { offset: 0, length }),
2060 ..RpcAccountInfoConfig::default()
2061 },
2062 ..RpcProgramAccountsConfig::default()
2063 },
2064 )
2065 .await?;
2066 Ok(results)
2067}
2068
2069async fn process_show(
2070 rpc_client: &RpcClient,
2071 config: &CliConfig<'_>,
2072 account_pubkey: Option<Pubkey>,
2073 authority_pubkey: Pubkey,
2074 programs: bool,
2075 buffers: bool,
2076 all: bool,
2077 use_lamports_unit: bool,
2078) -> ProcessResult {
2079 if let Some(account_pubkey) = account_pubkey {
2080 if let Some(account) = rpc_client
2081 .get_account_with_commitment(&account_pubkey, config.commitment)
2082 .await?
2083 .value
2084 {
2085 if account.owner == bpf_loader::id() || account.owner == bpf_loader_deprecated::id() {
2086 Ok(config.output_format.formatted_string(&CliProgram {
2087 program_id: account_pubkey.to_string(),
2088 owner: account.owner.to_string(),
2089 data_len: account.data.len(),
2090 }))
2091 } else if account.owner == bpf_loader_upgradeable::id() {
2092 if let Ok(UpgradeableLoaderState::Program {
2093 programdata_address,
2094 }) = account.state()
2095 {
2096 if let Some(programdata_account) = rpc_client
2097 .get_account_with_commitment(&programdata_address, config.commitment)
2098 .await?
2099 .value
2100 {
2101 if let Ok(UpgradeableLoaderState::ProgramData {
2102 upgrade_authority_address,
2103 slot,
2104 }) = programdata_account.state()
2105 {
2106 Ok(config
2107 .output_format
2108 .formatted_string(&CliUpgradeableProgram {
2109 program_id: account_pubkey.to_string(),
2110 owner: account.owner.to_string(),
2111 programdata_address: programdata_address.to_string(),
2112 authority: upgrade_authority_address
2113 .map(|pubkey| pubkey.to_string())
2114 .unwrap_or_else(|| "none".to_string()),
2115 last_deploy_slot: slot,
2116 data_len: programdata_account.data.len().saturating_sub(
2117 UpgradeableLoaderState::size_of_programdata_metadata(),
2118 ),
2119 lamports: programdata_account.lamports,
2120 use_lamports_unit,
2121 }))
2122 } else {
2123 Err(format!("Program {account_pubkey} has been closed").into())
2124 }
2125 } else {
2126 Err(format!("Program {account_pubkey} has been closed").into())
2127 }
2128 } else if let Ok(UpgradeableLoaderState::Buffer { authority_address }) =
2129 account.state()
2130 {
2131 Ok(config
2132 .output_format
2133 .formatted_string(&CliUpgradeableBuffer {
2134 address: account_pubkey.to_string(),
2135 authority: authority_address
2136 .map(|pubkey| pubkey.to_string())
2137 .unwrap_or_else(|| "none".to_string()),
2138 data_len: account
2139 .data
2140 .len()
2141 .saturating_sub(UpgradeableLoaderState::size_of_buffer_metadata()),
2142 lamports: account.lamports,
2143 use_lamports_unit,
2144 }))
2145 } else {
2146 Err(format!(
2147 "{account_pubkey} is not an upgradeable loader Buffer or Program account"
2148 )
2149 .into())
2150 }
2151 } else {
2152 Err(format!("{account_pubkey} is not an SBF program").into())
2153 }
2154 } else {
2155 Err(format!("Unable to find the account {account_pubkey}").into())
2156 }
2157 } else if programs {
2158 let authority_pubkey = if all { None } else { Some(authority_pubkey) };
2159 let programs = get_programs(rpc_client, authority_pubkey, use_lamports_unit).await?;
2160 Ok(config.output_format.formatted_string(&programs))
2161 } else if buffers {
2162 let authority_pubkey = if all { None } else { Some(authority_pubkey) };
2163 let buffers = get_buffers(rpc_client, authority_pubkey, use_lamports_unit).await?;
2164 Ok(config.output_format.formatted_string(&buffers))
2165 } else {
2166 Err("Invalid parameters".to_string().into())
2167 }
2168}
2169
2170async fn process_dump(
2171 rpc_client: &RpcClient,
2172 config: &CliConfig<'_>,
2173 account_pubkey: Option<Pubkey>,
2174 output_location: &str,
2175) -> ProcessResult {
2176 if let Some(account_pubkey) = account_pubkey {
2177 if let Some(account) = rpc_client
2178 .get_account_with_commitment(&account_pubkey, config.commitment)
2179 .await?
2180 .value
2181 {
2182 if account.owner == bpf_loader::id() || account.owner == bpf_loader_deprecated::id() {
2183 let mut f = File::create(output_location)?;
2184 f.write_all(&account.data)?;
2185 Ok(format!("Wrote program to {output_location}"))
2186 } else if account.owner == bpf_loader_upgradeable::id() {
2187 if let Ok(UpgradeableLoaderState::Program {
2188 programdata_address,
2189 }) = account.state()
2190 {
2191 if let Some(programdata_account) = rpc_client
2192 .get_account_with_commitment(&programdata_address, config.commitment)
2193 .await?
2194 .value
2195 {
2196 if let Ok(UpgradeableLoaderState::ProgramData { .. }) =
2197 programdata_account.state()
2198 {
2199 let offset = UpgradeableLoaderState::size_of_programdata_metadata();
2200 let program_data = &programdata_account.data[offset..];
2201 let mut f = File::create(output_location)?;
2202 f.write_all(program_data)?;
2203 Ok(format!("Wrote program to {output_location}"))
2204 } else {
2205 Err(format!("Program {account_pubkey} has been closed").into())
2206 }
2207 } else {
2208 Err(format!("Program {account_pubkey} has been closed").into())
2209 }
2210 } else if let Ok(UpgradeableLoaderState::Buffer { .. }) = account.state() {
2211 let offset = UpgradeableLoaderState::size_of_buffer_metadata();
2212 let program_data = &account.data[offset..];
2213 let mut f = File::create(output_location)?;
2214 f.write_all(program_data)?;
2215 Ok(format!("Wrote program to {output_location}"))
2216 } else {
2217 Err(format!(
2218 "{account_pubkey} is not an upgradeable loader buffer or program account"
2219 )
2220 .into())
2221 }
2222 } else {
2223 Err(format!("{account_pubkey} is not an SBF program").into())
2224 }
2225 } else {
2226 Err(format!("Unable to find the account {account_pubkey}").into())
2227 }
2228 } else {
2229 Err("No account specified".into())
2230 }
2231}
2232
2233async fn close(
2234 rpc_client: &RpcClient,
2235 config: &CliConfig<'_>,
2236 account_pubkey: &Pubkey,
2237 recipient_pubkey: &Pubkey,
2238 authority_signer: &dyn Signer,
2239 program_pubkey: Option<&Pubkey>,
2240) -> Result<(), Box<dyn std::error::Error>> {
2241 let blockhash = rpc_client.get_latest_blockhash().await?;
2242
2243 let mut tx = Transaction::new_unsigned(Message::new(
2244 &[loader_v3_instruction::close_any(
2245 account_pubkey,
2246 recipient_pubkey,
2247 Some(&authority_signer.pubkey()),
2248 program_pubkey,
2249 )],
2250 Some(&config.signers[0].pubkey()),
2251 ));
2252
2253 tx.try_sign(&[config.signers[0], authority_signer], blockhash)?;
2254 let result = rpc_client
2255 .send_and_confirm_transaction_with_spinner_and_config(
2256 &tx,
2257 config.commitment,
2258 config.send_transaction_config,
2259 )
2260 .await;
2261 if let Err(err) = result {
2262 if let ClientErrorKind::TransactionError(TransactionError::InstructionError(
2263 _,
2264 InstructionError::InvalidInstructionData,
2265 )) = err.kind()
2266 {
2267 return Err("Closing a buffer account is not supported by the cluster".into());
2268 } else if let ClientErrorKind::TransactionError(TransactionError::InstructionError(
2269 _,
2270 InstructionError::InvalidArgument,
2271 )) = err.kind()
2272 {
2273 return Err("Closing a program account is not supported by the cluster".into());
2274 } else {
2275 return Err(format!("Close failed: {err}").into());
2276 }
2277 }
2278 Ok(())
2279}
2280
2281async fn process_close(
2282 rpc_client: &RpcClient,
2283 config: &CliConfig<'_>,
2284 account_pubkey: Option<Pubkey>,
2285 recipient_pubkey: Pubkey,
2286 authority_index: SignerIndex,
2287 use_lamports_unit: bool,
2288 bypass_warning: bool,
2289) -> ProcessResult {
2290 let authority_signer = config.signers[authority_index];
2291
2292 if let Some(account_pubkey) = account_pubkey {
2293 if let Some(account) = rpc_client
2294 .get_account_with_commitment(&account_pubkey, config.commitment)
2295 .await?
2296 .value
2297 {
2298 match account.state() {
2299 Ok(UpgradeableLoaderState::Buffer { authority_address }) => {
2300 if authority_address != Some(authority_signer.pubkey()) {
2301 return Err(format!(
2302 "Buffer account authority {:?} does not match {:?}",
2303 authority_address,
2304 Some(authority_signer.pubkey())
2305 )
2306 .into());
2307 } else {
2308 close(
2309 rpc_client,
2310 config,
2311 &account_pubkey,
2312 &recipient_pubkey,
2313 authority_signer,
2314 None,
2315 )
2316 .await?;
2317 }
2318 Ok(config
2319 .output_format
2320 .formatted_string(&CliUpgradeableBuffers {
2321 buffers: vec![CliUpgradeableBuffer {
2322 address: account_pubkey.to_string(),
2323 authority: authority_address
2324 .map(|pubkey| pubkey.to_string())
2325 .unwrap_or_else(|| "none".to_string()),
2326 data_len: 0,
2327 lamports: account.lamports,
2328 use_lamports_unit,
2329 }],
2330 use_lamports_unit,
2331 }))
2332 }
2333 Ok(UpgradeableLoaderState::Program {
2334 programdata_address: programdata_pubkey,
2335 }) => {
2336 if let Some(account) = rpc_client
2337 .get_account_with_commitment(&programdata_pubkey, config.commitment)
2338 .await?
2339 .value
2340 {
2341 if let Ok(UpgradeableLoaderState::ProgramData {
2342 slot: _,
2343 upgrade_authority_address: authority_pubkey,
2344 }) = account.state()
2345 {
2346 if authority_pubkey != Some(authority_signer.pubkey()) {
2347 Err(format!(
2348 "Program authority {:?} does not match {:?}",
2349 authority_pubkey,
2350 Some(authority_signer.pubkey())
2351 )
2352 .into())
2353 } else {
2354 if !bypass_warning {
2355 return Err(String::from(CLOSE_PROGRAM_WARNING).into());
2356 }
2357 close(
2358 rpc_client,
2359 config,
2360 &programdata_pubkey,
2361 &recipient_pubkey,
2362 authority_signer,
2363 Some(&account_pubkey),
2364 )
2365 .await?;
2366 Ok(config.output_format.formatted_string(
2367 &CliUpgradeableProgramClosed {
2368 program_id: account_pubkey.to_string(),
2369 lamports: account.lamports,
2370 use_lamports_unit,
2371 },
2372 ))
2373 }
2374 } else {
2375 Err(format!("Program {account_pubkey} has been closed").into())
2376 }
2377 } else {
2378 Err(format!("Program {account_pubkey} has been closed").into())
2379 }
2380 }
2381 _ => Err(format!("{account_pubkey} is not a Program or Buffer account").into()),
2382 }
2383 } else {
2384 Err(format!("Unable to find the account {account_pubkey}").into())
2385 }
2386 } else {
2387 let buffers = get_buffers(
2388 rpc_client,
2389 Some(authority_signer.pubkey()),
2390 use_lamports_unit,
2391 )
2392 .await?;
2393
2394 let mut closed = vec![];
2395 for buffer in buffers.buffers.iter() {
2396 match close(
2397 rpc_client,
2398 config,
2399 &Pubkey::from_str(&buffer.address)?,
2400 &recipient_pubkey,
2401 authority_signer,
2402 None,
2403 )
2404 .await
2405 {
2406 Ok(()) => {
2407 closed.push(buffer.clone());
2408 }
2409 Err(err) => {
2410 eprintln!("Failed to close buffer {}: {}", buffer.address, err);
2411 }
2412 }
2413 }
2414
2415 Ok(config
2416 .output_format
2417 .formatted_string(&CliUpgradeableBuffers {
2418 buffers: closed,
2419 use_lamports_unit,
2420 }))
2421 }
2422}
2423
2424async fn process_extend_program(
2425 rpc_client: &RpcClient,
2426 config: &CliConfig<'_>,
2427 program_pubkey: Pubkey,
2428 payer_signer_index: SignerIndex,
2429 additional_bytes: u32,
2430) -> ProcessResult {
2431 let fee_payer_pubkey = config.signers[0].pubkey();
2432 let payer_signer = config.signers[payer_signer_index];
2433 let payer_pubkey = payer_signer.pubkey();
2434
2435 if additional_bytes == 0 {
2436 return Err("Additional bytes must be greater than zero".into());
2437 }
2438
2439 let program_account = match rpc_client
2440 .get_account_with_commitment(&program_pubkey, config.commitment)
2441 .await?
2442 .value
2443 {
2444 Some(program_account) => Ok(program_account),
2445 None => Err(format!("Unable to find program {program_pubkey}")),
2446 }?;
2447
2448 if !bpf_loader_upgradeable::check_id(&program_account.owner) {
2449 return Err(format!("Account {program_pubkey} is not an upgradeable program").into());
2450 }
2451
2452 let programdata_pubkey = match program_account.state() {
2453 Ok(UpgradeableLoaderState::Program {
2454 programdata_address: programdata_pubkey,
2455 }) => Ok(programdata_pubkey),
2456 _ => Err(format!(
2457 "Account {program_pubkey} is not an upgradeable program"
2458 )),
2459 }?;
2460
2461 let programdata_account = match rpc_client
2462 .get_account_with_commitment(&programdata_pubkey, config.commitment)
2463 .await?
2464 .value
2465 {
2466 Some(programdata_account) => Ok(programdata_account),
2467 None => Err(format!("Program {program_pubkey} is closed")),
2468 }?;
2469
2470 let upgrade_authority_address = match programdata_account.state() {
2471 Ok(UpgradeableLoaderState::ProgramData {
2472 slot: _,
2473 upgrade_authority_address,
2474 }) => Ok(upgrade_authority_address),
2475 _ => Err(format!("Program {program_pubkey} is closed")),
2476 }?;
2477
2478 upgrade_authority_address
2479 .ok_or_else(|| format!("Program {program_pubkey} is not upgradeable"))?;
2480
2481 let blockhash = rpc_client.get_latest_blockhash().await?;
2482 let feature_set = fetch_feature_set(rpc_client).await?;
2483 let feature_snapshot = feature_set.snapshot();
2484
2485 if feature_snapshot.loader_v3_minimum_extend_program_size {
2486 let current_len = programdata_account.data.len();
2492 let headroom = (MAX_PERMITTED_DATA_LENGTH as usize).saturating_sub(current_len);
2493 if additional_bytes < MINIMUM_EXTEND_PROGRAM_BYTES
2494 && (additional_bytes as usize) != headroom
2495 {
2496 let err_msg = if (headroom as u32) < MINIMUM_EXTEND_PROGRAM_BYTES {
2497 format!(
2498 "Program is {headroom} bytes from maximum size, but {additional_bytes} were \
2499 requested. Please re-run the command with {headroom} additional bytes."
2500 )
2501 } else {
2502 format!(
2503 "ExtendProgram requires a minimum of {MINIMUM_EXTEND_PROGRAM_BYTES} \
2504 additional bytes or to extend to maximum size, but only {additional_bytes} \
2505 were requested"
2506 )
2507 };
2508 return Err(err_msg.into());
2509 }
2510 }
2511
2512 let instruction = loader_v3_instruction::extend_program(
2513 &program_pubkey,
2514 Some(&payer_pubkey),
2515 additional_bytes,
2516 );
2517 let mut tx = Transaction::new_unsigned(Message::new(&[instruction], Some(&fee_payer_pubkey)));
2518
2519 tx.try_sign(&[config.signers[0], payer_signer], blockhash)?;
2520 let result = rpc_client
2521 .send_and_confirm_transaction_with_spinner_and_config(
2522 &tx,
2523 config.commitment,
2524 config.send_transaction_config,
2525 )
2526 .await;
2527 if let Err(err) = result {
2528 if let ClientErrorKind::TransactionError(TransactionError::InstructionError(
2529 _,
2530 InstructionError::InvalidInstructionData,
2531 )) = err.kind()
2532 {
2533 return Err("Extending a program is not supported by the cluster".into());
2534 } else {
2535 return Err(format!("Extend program failed: {err}").into());
2536 }
2537 }
2538
2539 Ok(config
2540 .output_format
2541 .formatted_string(&CliUpgradeableProgramExtended {
2542 program_id: program_pubkey.to_string(),
2543 additional_bytes,
2544 }))
2545}
2546
2547pub fn calculate_max_chunk_size(baseline_msg: Message) -> usize {
2548 let tx_size = wincode::serialized_size(&Transaction {
2549 signatures: vec![
2550 Signature::default();
2551 baseline_msg.header.num_required_signatures as usize
2552 ],
2553 message: baseline_msg,
2554 })
2555 .unwrap() as usize;
2556 PACKET_DATA_SIZE.saturating_sub(tx_size).saturating_sub(1)
2558}
2559
2560#[allow(clippy::too_many_arguments)]
2561async fn do_process_program_deploy(
2562 rpc_client: Arc<RpcClient>,
2563 config: &CliConfig<'_>,
2564 program_data: &[u8], program_len: usize,
2566 program_data_max_len: usize,
2567 min_rent_exempt_program_data_balance: u64,
2568 fee_payer_signer: &dyn Signer,
2569 program_signers: &[&dyn Signer],
2570 buffer_signer: Option<&dyn Signer>,
2571 buffer_pubkey: &Pubkey,
2572 buffer_program_data: Option<Vec<u8>>,
2573 buffer_authority_signer: &dyn Signer,
2574 skip_fee_check: bool,
2575 compute_unit_price: Option<u64>,
2576 max_sign_attempts: usize,
2577 use_rpc: bool,
2578) -> ProcessResult {
2579 let blockhash = rpc_client.get_latest_blockhash().await?;
2580 let compute_unit_limit = ComputeUnitLimit::Simulated;
2581
2582 let (initial_instructions, balance_needed, buffer_program_data) =
2583 if let Some(buffer_program_data) = buffer_program_data {
2584 (vec![], 0, buffer_program_data)
2585 } else {
2586 (
2587 loader_v3_instruction::create_buffer(
2588 &fee_payer_signer.pubkey(),
2589 buffer_pubkey,
2590 &buffer_authority_signer.pubkey(),
2591 min_rent_exempt_program_data_balance,
2592 program_len,
2593 )?,
2594 min_rent_exempt_program_data_balance,
2595 vec![0; program_len],
2596 )
2597 };
2598
2599 let initial_message = if !initial_instructions.is_empty() {
2600 Some(Message::new_with_blockhash(
2601 &initial_instructions.with_compute_unit_config(&ComputeUnitConfig {
2602 compute_unit_price,
2603 compute_unit_limit,
2604 }),
2605 Some(&fee_payer_signer.pubkey()),
2606 &blockhash,
2607 ))
2608 } else {
2609 None
2610 };
2611
2612 let create_msg = |offset: u32, bytes: Vec<u8>| {
2614 let instruction = loader_v3_instruction::write(
2615 buffer_pubkey,
2616 &buffer_authority_signer.pubkey(),
2617 offset,
2618 bytes,
2619 );
2620
2621 let instructions = vec![instruction].with_compute_unit_config(&ComputeUnitConfig {
2622 compute_unit_price,
2623 compute_unit_limit,
2624 });
2625 Message::new_with_blockhash(&instructions, Some(&fee_payer_signer.pubkey()), &blockhash)
2626 };
2627
2628 let mut write_messages = vec![];
2629 let chunk_size = calculate_max_chunk_size(create_msg(0, Vec::new()));
2630 for (chunk, i) in program_data.chunks(chunk_size).zip(0usize..) {
2631 let offset = i.saturating_mul(chunk_size);
2632 if chunk != &buffer_program_data[offset..offset.saturating_add(chunk.len())] {
2633 write_messages.push(VersionedMessage::Legacy(create_msg(
2634 offset as u32,
2635 chunk.to_vec(),
2636 )));
2637 }
2638 }
2639
2640 let final_message = {
2642 #[allow(deprecated)]
2643 let instructions = loader_v3_instruction::deploy_with_max_program_len(
2644 &fee_payer_signer.pubkey(),
2645 &program_signers[0].pubkey(),
2646 buffer_pubkey,
2647 &program_signers[1].pubkey(),
2648 rpc_client
2649 .get_minimum_balance_for_rent_exemption(UpgradeableLoaderState::size_of_program())
2650 .await?,
2651 program_data_max_len,
2652 )?
2653 .with_compute_unit_config(&ComputeUnitConfig {
2654 compute_unit_price,
2655 compute_unit_limit,
2656 });
2657
2658 Some(Message::new_with_blockhash(
2659 &instructions,
2660 Some(&fee_payer_signer.pubkey()),
2661 &blockhash,
2662 ))
2663 };
2664
2665 let initial_message = initial_message.map(VersionedMessage::Legacy);
2666 let final_message = final_message.map(VersionedMessage::Legacy);
2667
2668 if !skip_fee_check {
2669 check_payer(
2670 &rpc_client,
2671 config,
2672 fee_payer_signer.pubkey(),
2673 balance_needed,
2674 &initial_message,
2675 &write_messages,
2676 &final_message,
2677 )
2678 .await?;
2679 }
2680
2681 let final_tx_sig = send_deploy_messages(
2682 rpc_client,
2683 config,
2684 initial_message,
2685 write_messages,
2686 final_message,
2687 fee_payer_signer,
2688 buffer_signer,
2689 Some(buffer_authority_signer),
2690 Some(program_signers),
2691 max_sign_attempts,
2692 use_rpc,
2693 &compute_unit_limit,
2694 )
2695 .await?;
2696
2697 let program_id = CliProgramId {
2698 program_id: program_signers[0].pubkey().to_string(),
2699 signature: final_tx_sig.as_ref().map(ToString::to_string),
2700 };
2701 Ok(config.output_format.formatted_string(&program_id))
2702}
2703
2704#[allow(clippy::too_many_arguments)]
2705async fn do_process_write_buffer(
2706 rpc_client: Arc<RpcClient>,
2707 config: &CliConfig<'_>,
2708 program_data: &[u8], program_len: usize,
2710 min_rent_exempt_program_buffer_balance: u64,
2711 fee_payer_signer: &dyn Signer,
2712 buffer_signer: Option<&dyn Signer>,
2713 buffer_pubkey: &Pubkey,
2714 buffer_program_data: Option<Vec<u8>>,
2715 buffer_authority_signer: &dyn Signer,
2716 skip_fee_check: bool,
2717 compute_unit_price: Option<u64>,
2718 max_sign_attempts: usize,
2719 use_rpc: bool,
2720) -> ProcessResult {
2721 let blockhash = rpc_client.get_latest_blockhash().await?;
2722 let compute_unit_limit = ComputeUnitLimit::Simulated;
2723
2724 let (initial_instructions, balance_needed, buffer_program_data) =
2725 if let Some(buffer_program_data) = buffer_program_data {
2726 (vec![], 0, buffer_program_data)
2727 } else {
2728 (
2729 loader_v3_instruction::create_buffer(
2730 &fee_payer_signer.pubkey(),
2731 buffer_pubkey,
2732 &buffer_authority_signer.pubkey(),
2733 min_rent_exempt_program_buffer_balance,
2734 program_len,
2735 )?,
2736 min_rent_exempt_program_buffer_balance,
2737 vec![0; program_len],
2738 )
2739 };
2740
2741 let initial_message = if !initial_instructions.is_empty() {
2742 Some(Message::new_with_blockhash(
2743 &initial_instructions.with_compute_unit_config(&ComputeUnitConfig {
2744 compute_unit_price,
2745 compute_unit_limit,
2746 }),
2747 Some(&fee_payer_signer.pubkey()),
2748 &blockhash,
2749 ))
2750 } else {
2751 None
2752 };
2753
2754 let create_msg = |offset: u32, bytes: Vec<u8>| {
2756 let instruction = loader_v3_instruction::write(
2757 buffer_pubkey,
2758 &buffer_authority_signer.pubkey(),
2759 offset,
2760 bytes,
2761 );
2762
2763 let instructions = vec![instruction].with_compute_unit_config(&ComputeUnitConfig {
2764 compute_unit_price,
2765 compute_unit_limit,
2766 });
2767 Message::new_with_blockhash(&instructions, Some(&fee_payer_signer.pubkey()), &blockhash)
2768 };
2769
2770 let mut write_messages = vec![];
2771 let chunk_size = calculate_max_chunk_size(create_msg(0, Vec::new()));
2772 for (chunk, i) in program_data.chunks(chunk_size).zip(0usize..) {
2773 let offset = i.saturating_mul(chunk_size);
2774 if chunk != &buffer_program_data[offset..offset.saturating_add(chunk.len())] {
2775 write_messages.push(VersionedMessage::Legacy(create_msg(
2776 offset as u32,
2777 chunk.to_vec(),
2778 )));
2779 }
2780 }
2781
2782 let initial_message = initial_message.map(VersionedMessage::Legacy);
2783
2784 if !skip_fee_check {
2785 check_payer(
2786 &rpc_client,
2787 config,
2788 fee_payer_signer.pubkey(),
2789 balance_needed,
2790 &initial_message,
2791 &write_messages,
2792 &None,
2793 )
2794 .await?;
2795 }
2796
2797 let _final_tx_sig = send_deploy_messages(
2798 rpc_client,
2799 config,
2800 initial_message,
2801 write_messages,
2802 None,
2803 fee_payer_signer,
2804 buffer_signer,
2805 Some(buffer_authority_signer),
2806 None,
2807 max_sign_attempts,
2808 use_rpc,
2809 &compute_unit_limit,
2810 )
2811 .await?;
2812
2813 let buffer = CliProgramBuffer {
2814 buffer: buffer_pubkey.to_string(),
2815 };
2816 Ok(config.output_format.formatted_string(&buffer))
2817}
2818
2819#[allow(clippy::too_many_arguments)]
2820async fn do_process_program_upgrade(
2821 rpc_client: Arc<RpcClient>,
2822 config: &CliConfig<'_>,
2823 program_data: &[u8], program_len: usize,
2825 min_rent_exempt_program_data_balance: u64,
2826 fee_payer_signer: &dyn Signer,
2827 program_id: &Pubkey,
2828 upgrade_authority: &dyn Signer,
2829 buffer_pubkey: &Pubkey,
2830 buffer_signer: Option<&dyn Signer>,
2831 buffer_program_data: Option<Vec<u8>>,
2832 skip_fee_check: bool,
2833 compute_unit_price: Option<u64>,
2834 max_sign_attempts: usize,
2835 auto_extend: bool,
2836 use_rpc: bool,
2837) -> ProcessResult {
2838 let blockhash = rpc_client.get_latest_blockhash().await?;
2839 let compute_unit_limit = ComputeUnitLimit::Simulated;
2840
2841 let (initial_message, write_messages, balance_needed) = if let Some(buffer_signer) =
2842 buffer_signer
2843 {
2844 let (mut initial_instructions, balance_needed, buffer_program_data) =
2845 if let Some(buffer_program_data) = buffer_program_data {
2846 (vec![], 0, buffer_program_data)
2847 } else {
2848 (
2849 loader_v3_instruction::create_buffer(
2850 &fee_payer_signer.pubkey(),
2851 &buffer_signer.pubkey(),
2852 &upgrade_authority.pubkey(),
2853 min_rent_exempt_program_data_balance,
2854 program_len,
2855 )?,
2856 min_rent_exempt_program_data_balance,
2857 vec![0; program_len],
2858 )
2859 };
2860
2861 if auto_extend {
2862 extend_program_data_if_needed(
2863 &mut initial_instructions,
2864 &rpc_client,
2865 config.commitment,
2866 &fee_payer_signer.pubkey(),
2867 program_id,
2868 program_len,
2869 )
2870 .await?;
2871 }
2872
2873 let initial_message = if !initial_instructions.is_empty() {
2874 Some(Message::new_with_blockhash(
2875 &initial_instructions.with_compute_unit_config(&ComputeUnitConfig {
2876 compute_unit_price,
2877 compute_unit_limit: ComputeUnitLimit::Simulated,
2878 }),
2879 Some(&fee_payer_signer.pubkey()),
2880 &blockhash,
2881 ))
2882 } else {
2883 None
2884 };
2885
2886 let buffer_signer_pubkey = buffer_signer.pubkey();
2887 let upgrade_authority_pubkey = upgrade_authority.pubkey();
2888 let create_msg = |offset: u32, bytes: Vec<u8>| {
2889 let instructions = vec![loader_v3_instruction::write(
2890 &buffer_signer_pubkey,
2891 &upgrade_authority_pubkey,
2892 offset,
2893 bytes,
2894 )]
2895 .with_compute_unit_config(&ComputeUnitConfig {
2896 compute_unit_price,
2897 compute_unit_limit,
2898 });
2899 Message::new_with_blockhash(&instructions, Some(&fee_payer_signer.pubkey()), &blockhash)
2900 };
2901
2902 let mut write_messages = vec![];
2904 let chunk_size = calculate_max_chunk_size(create_msg(0, Vec::new()));
2905 for (chunk, i) in program_data.chunks(chunk_size).zip(0usize..) {
2906 let offset = i.saturating_mul(chunk_size);
2907 if chunk != &buffer_program_data[offset..offset.saturating_add(chunk.len())] {
2908 write_messages.push(VersionedMessage::Legacy(create_msg(
2909 offset as u32,
2910 chunk.to_vec(),
2911 )));
2912 }
2913 }
2914
2915 (initial_message, write_messages, balance_needed)
2916 } else {
2917 (None, vec![], 0)
2918 };
2919
2920 let final_instructions = vec![loader_v3_instruction::upgrade(
2922 program_id,
2923 buffer_pubkey,
2924 &upgrade_authority.pubkey(),
2925 &fee_payer_signer.pubkey(),
2926 )]
2927 .with_compute_unit_config(&ComputeUnitConfig {
2928 compute_unit_price,
2929 compute_unit_limit,
2930 });
2931 let final_message = Message::new_with_blockhash(
2932 &final_instructions,
2933 Some(&fee_payer_signer.pubkey()),
2934 &blockhash,
2935 );
2936 let final_message = Some(final_message);
2937
2938 let initial_message = initial_message.map(VersionedMessage::Legacy);
2939 let final_message = final_message.map(VersionedMessage::Legacy);
2940
2941 if !skip_fee_check {
2942 check_payer(
2943 &rpc_client,
2944 config,
2945 fee_payer_signer.pubkey(),
2946 balance_needed,
2947 &initial_message,
2948 &write_messages,
2949 &final_message,
2950 )
2951 .await?;
2952 }
2953
2954 let final_tx_sig = send_deploy_messages(
2955 rpc_client,
2956 config,
2957 initial_message,
2958 write_messages,
2959 final_message,
2960 fee_payer_signer,
2961 buffer_signer,
2962 Some(upgrade_authority),
2963 Some(&[upgrade_authority]),
2964 max_sign_attempts,
2965 use_rpc,
2966 &compute_unit_limit,
2967 )
2968 .await?;
2969
2970 let program_id = CliProgramId {
2971 program_id: program_id.to_string(),
2972 signature: final_tx_sig.as_ref().map(ToString::to_string),
2973 };
2974 Ok(config.output_format.formatted_string(&program_id))
2975}
2976
2977async fn extend_program_data_if_needed(
2980 initial_instructions: &mut Vec<Instruction>,
2981 rpc_client: &RpcClient,
2982 commitment: CommitmentConfig,
2983 fee_payer: &Pubkey,
2984 program_id: &Pubkey,
2985 program_len: usize,
2986) -> Result<(), Box<dyn std::error::Error>> {
2987 let program_data_address = get_program_data_address(program_id);
2988
2989 let Some(program_data_account) = rpc_client
2990 .get_account_with_commitment(&program_data_address, commitment)
2991 .await?
2992 .value
2993 else {
2994 return Ok(());
2996 };
2997
2998 let upgrade_authority_address = match program_data_account.state() {
2999 Ok(UpgradeableLoaderState::ProgramData {
3000 slot: _,
3001 upgrade_authority_address,
3002 }) => Ok(upgrade_authority_address),
3003 _ => Err(format!("Program {program_id} is closed")),
3004 }?;
3005
3006 upgrade_authority_address.ok_or_else(|| format!("Program {program_id} is not upgradeable"))?;
3007
3008 let required_len = UpgradeableLoaderState::size_of_programdata(program_len);
3009 let max_permitted_data_length = usize::try_from(MAX_PERMITTED_DATA_LENGTH).unwrap();
3010 if required_len > max_permitted_data_length {
3011 let max_program_len = max_permitted_data_length
3012 .saturating_sub(UpgradeableLoaderState::size_of_programdata(0));
3013 return Err(format!(
3014 "New program ({program_id}) data account is too big: {required_len}.\nMaximum program \
3015 size: {max_program_len}.",
3016 )
3017 .into());
3018 }
3019
3020 let current_len = program_data_account.data.len();
3021 let additional_bytes = required_len.saturating_sub(current_len);
3022 if additional_bytes == 0 {
3023 return Ok(());
3025 }
3026
3027 let mut additional_bytes =
3028 u32::try_from(additional_bytes).expect("`u32` is big enough to hold an account size");
3029
3030 let feature_set = fetch_feature_set(rpc_client).await?;
3031 let feature_snapshot = feature_set.snapshot();
3032
3033 if feature_snapshot.loader_v3_minimum_extend_program_size {
3034 let headroom =
3038 u32::try_from(max_permitted_data_length.saturating_sub(current_len)).unwrap();
3039 additional_bytes = additional_bytes.max(MINIMUM_EXTEND_PROGRAM_BYTES.min(headroom));
3040 }
3041
3042 let instruction =
3043 loader_v3_instruction::extend_program(program_id, Some(fee_payer), additional_bytes);
3044 initial_instructions.push(instruction);
3045
3046 Ok(())
3047}
3048
3049fn read_and_verify_elf(
3050 program_location: &str,
3051 feature_set: FeatureSet,
3052) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
3053 let mut file = File::open(program_location)
3054 .map_err(|err| format!("Unable to open program file: {err}"))?;
3055 let mut program_data = Vec::new();
3056 file.read_to_end(&mut program_data)
3057 .map_err(|err| format!("Unable to read program file: {err}"))?;
3058
3059 verify_elf(&program_data, feature_set)?;
3060
3061 Ok(program_data)
3062}
3063
3064fn verify_elf(
3065 program_data: &[u8],
3066 feature_set: FeatureSet,
3067) -> Result<(), Box<dyn std::error::Error>> {
3068 let program_runtime_environment = create_program_runtime_environment(
3070 &feature_set.runtime_features(),
3071 &SVMTransactionExecutionBudget::new_with_defaults(
3072 feature_set.snapshot().raise_cpi_nesting_limit_to_8,
3073 ),
3074 true,
3075 false,
3076 )
3077 .unwrap();
3078 let config = program_runtime_environment.get_config();
3079 let executable = Executable::<InvokeContext>::from_elf(
3080 program_data,
3081 Arc::clone(&*program_runtime_environment),
3082 )
3083 .map_err(|err| explain_elf_error(&err, program_data, config))?;
3084
3085 executable
3086 .verify::<RequisiteVerifier>()
3087 .map_err(|err| explain_elf_error(&err, program_data, config))?;
3088
3089 executable
3091 .verify::<LocalVerifier>()
3092 .map_err(|err| explain_elf_error(&err, program_data, config).into())
3093}
3094
3095fn explain_elf_error(err: &EbpfError, program_data: &[u8], config: &Config) -> String {
3098 let EbpfError::ElfError(ElfError::UnsupportedSBPFVersion) = err else {
3102 return format!("{err} (local pre-flight)");
3103 };
3104
3105 fn sbpf_version_label(version: SBPFVersion) -> &'static str {
3106 match version {
3107 SBPFVersion::V0 => "v0",
3108 SBPFVersion::V1 => "v1",
3109 SBPFVersion::V2 => "v2",
3110 SBPFVersion::V3 => "v3",
3111 SBPFVersion::V4 => "v4",
3112 SBPFVersion::Reserved => "reserved",
3113 }
3114 }
3115
3116 let min = sbpf_version_label(*config.enabled_sbpf_versions.start());
3117 let max = sbpf_version_label(*config.enabled_sbpf_versions.end());
3118 match get_sbpf_version(program_data) {
3119 Ok(version) => {
3120 let detected = sbpf_version_label(version);
3121 format!(
3122 "program targets SBPF {detected}, which this CLI does not have enabled (enabled: \
3123 {min}–{max}). Rebuild the program targeting {max}."
3124 )
3125 }
3126 Err(error) => format!("could not read SBPF version: {error} (local pre-flight)"),
3127 }
3128}
3129
3130async fn check_payer(
3131 rpc_client: &RpcClient,
3132 config: &CliConfig<'_>,
3133 fee_payer_pubkey: Pubkey,
3134 balance_needed: u64,
3135 initial_message: &Option<VersionedMessage>,
3136 write_messages: &[VersionedMessage],
3137 final_message: &Option<VersionedMessage>,
3138) -> Result<(), Box<dyn std::error::Error>> {
3139 let mut fee = Saturating(0);
3140 if let Some(message) = initial_message {
3141 fee += rpc_client.get_fee_for_versioned_message(message).await?;
3142 }
3143 if let Some(message) = write_messages.first() {
3145 fee += rpc_client
3146 .get_fee_for_versioned_message(message)
3147 .await?
3148 .saturating_mul(write_messages.len() as u64);
3149 }
3150 if let Some(message) = final_message {
3151 fee += rpc_client.get_fee_for_versioned_message(message).await?;
3152 }
3153 check_account_for_spend_and_fee_with_commitment(
3154 rpc_client,
3155 &fee_payer_pubkey,
3156 balance_needed,
3157 fee.0,
3158 config.commitment,
3159 )
3160 .await?;
3161 Ok(())
3162}
3163
3164fn dedup_signers<'a>(signers: &[&'a dyn Signer]) -> Vec<&'a dyn Signer> {
3165 let mut seen = Vec::with_capacity(signers.len());
3166 signers
3167 .iter()
3168 .filter(|signer| {
3169 let pubkey = signer.pubkey();
3170 let is_new = !seen.contains(&pubkey);
3171 if is_new {
3172 seen.push(pubkey);
3173 }
3174 is_new
3175 })
3176 .copied()
3177 .collect()
3178}
3179
3180#[allow(clippy::too_many_arguments)]
3181async fn send_deploy_messages(
3182 rpc_client: Arc<RpcClient>,
3183 config: &CliConfig<'_>,
3184 initial_message: Option<VersionedMessage>,
3185 write_messages: Vec<VersionedMessage>,
3186 final_message: Option<VersionedMessage>,
3187 fee_payer_signer: &dyn Signer,
3188 initial_signer: Option<&dyn Signer>,
3189 write_signer: Option<&dyn Signer>,
3190 final_signers: Option<&[&dyn Signer]>,
3191 max_sign_attempts: usize,
3192 use_rpc: bool,
3193 compute_unit_limit: &ComputeUnitLimit,
3194) -> Result<Option<Signature>, Box<dyn std::error::Error>> {
3195 let into_legacy = |message| match message {
3196 VersionedMessage::Legacy(message) => message,
3197 _ => unreachable!("program deployment constructs legacy messages"),
3198 };
3199 let initial_message = initial_message.map(into_legacy);
3200 let mut write_messages = write_messages
3201 .into_iter()
3202 .map(into_legacy)
3203 .collect::<Vec<_>>();
3204 let final_message = final_message.map(into_legacy);
3205 if let Some(mut message) = initial_message {
3206 if let Some(initial_signer) = initial_signer {
3207 trace!("Preparing the required accounts");
3208 simulate_and_update_compute_unit_limit(compute_unit_limit, &rpc_client, &mut message)
3209 .await?;
3210 let mut initial_transaction = Transaction::new_unsigned(message.clone());
3211 let blockhash = rpc_client.get_latest_blockhash().await?;
3212
3213 if message.header.num_required_signatures == 3 {
3218 initial_transaction.try_sign(
3219 &[fee_payer_signer, initial_signer, write_signer.unwrap()],
3220 blockhash,
3221 )?;
3222 } else if message.header.num_required_signatures == 2 {
3223 initial_transaction.try_sign(&[fee_payer_signer, initial_signer], blockhash)?;
3224 } else {
3225 initial_transaction.try_sign(&[fee_payer_signer], blockhash)?;
3226 }
3227 let result = rpc_client
3228 .send_and_confirm_transaction_with_spinner_and_config(
3229 &initial_transaction,
3230 config.commitment,
3231 config.send_transaction_config,
3232 )
3233 .await;
3234 log_instruction_custom_error::<SystemError>(result, config)
3235 .map_err(|err| format!("Account allocation failed: {err}"))?;
3236 } else {
3237 return Err("Buffer account not created yet, must provide a key pair".into());
3238 }
3239 }
3240
3241 if !write_messages.is_empty()
3242 && let Some(write_signer) = write_signer
3243 {
3244 trace!("Writing program data");
3245
3246 {
3250 let mut message = write_messages[0].clone();
3251 if let UpdateComputeUnitLimitResult::UpdatedInstructionIndex(ix_index) =
3252 simulate_and_update_compute_unit_limit(
3253 compute_unit_limit,
3254 &rpc_client,
3255 &mut message,
3256 )
3257 .await?
3258 {
3259 for msg in &mut write_messages {
3260 assert_eq!(msg.program_id(ix_index), Some(&compute_budget::id()));
3266 msg.instructions[ix_index]
3267 .data
3268 .clone_from(&message.instructions[ix_index].data);
3269 }
3270 }
3271
3272 let cancel_token = CancellationToken::new();
3273 let (transport, node_address_service, _tpu_client) = if use_rpc {
3275 (
3276 SendTransport::Rpc(config.send_transaction_config),
3277 None,
3278 None,
3279 )
3280 } else {
3281 let (provider, service) = WebsocketNodeAddressService::run(
3282 rpc_client.clone(),
3283 config.websocket_url.clone(),
3284 LeaderTpuCacheServiceConfig::default(),
3285 cancel_token.child_token(),
3286 )
3287 .await?;
3288
3289 let bind_socket = bind_to_unspecified()?;
3290
3291 let (transaction_sender, client) = ClientBuilder::new(Box::new(provider))
3292 .cancel_token(cancel_token.clone())
3293 .bind_socket(bind_socket)
3294 .broadcaster(NonblockingBroadcaster)
3295 .build()
3296 .expect("Failed to build TPU client");
3297 (
3298 SendTransport::Tpu(transaction_sender),
3299 Some(service),
3300 Some(client),
3301 )
3302 };
3303
3304 let versioned_write_messages = write_messages.into_iter().map(VersionedMessage::Legacy);
3305
3306 let transaction_errors_result = send_and_confirm_transactions_in_parallel_v3(
3307 rpc_client.clone(),
3308 transport,
3309 versioned_write_messages,
3310 &dedup_signers(&[fee_payer_signer, write_signer]),
3311 SendAndConfirmConfigV3 {
3312 with_spinner: true,
3313 max_sign_attempts: NonZeroUsize::new(max_sign_attempts)
3314 .ok_or("--max-sign-attempts must be at least 1")?,
3315 check_interval: CHECK_INTERVAL,
3316 send_interval: SEND_INTERVAL,
3317 },
3318 )
3319 .await
3320 .map_err(|err| format!("Data writes to account failed: {err}"));
3321
3322 cancel_token.cancel();
3323 if let Some(node_address_service) = node_address_service
3324 && let Err(err) = node_address_service.shutdown().await
3325 {
3326 error!("Failed to shut down WebSocket node address service: {err}");
3327 }
3328
3329 let transaction_errors = transaction_errors_result?
3330 .into_iter()
3331 .flatten()
3332 .collect::<Vec<_>>();
3333
3334 if !transaction_errors.is_empty() {
3335 for transaction_error in &transaction_errors {
3336 error!("{transaction_error:?}");
3337 }
3338 return Err(
3339 format!("{} write transactions failed", transaction_errors.len()).into(),
3340 );
3341 }
3342 }
3343 }
3344
3345 if let Some(mut message) = final_message
3346 && let Some(final_signers) = final_signers
3347 {
3348 trace!("Deploying program");
3349
3350 simulate_and_update_compute_unit_limit(compute_unit_limit, &rpc_client, &mut message)
3351 .await?;
3352 let mut final_tx = Transaction::new_unsigned(message);
3353 let blockhash = rpc_client.get_latest_blockhash().await?;
3354 let mut signers = final_signers.to_vec();
3355 signers.push(fee_payer_signer);
3356 final_tx.try_sign(&signers, blockhash)?;
3357 let result = rpc_client
3358 .send_and_confirm_transaction_with_spinner_and_config(
3359 &final_tx,
3360 config.commitment,
3361 config.send_transaction_config,
3362 )
3363 .await
3364 .map_err(|e| format!("Deploying program failed: {e}"))?;
3365 return Ok(Some(result));
3366 }
3367
3368 Ok(None)
3369}
3370
3371fn create_ephemeral_keypair()
3372-> Result<(usize, bip39::Mnemonic, Keypair), Box<dyn std::error::Error>> {
3373 const WORDS: usize = 12;
3374 let mnemonic = Mnemonic::generate_in(Language::English, WORDS)?;
3375 let seed = mnemonic.to_seed("");
3376 let new_keypair = keypair_from_seed(&seed)?;
3377
3378 Ok((WORDS, mnemonic, new_keypair))
3379}
3380
3381fn report_ephemeral_mnemonic(words: usize, mnemonic: bip39::Mnemonic, ephemeral_pubkey: &Pubkey) {
3382 let phrase = mnemonic.to_string();
3383 let divider = String::from_utf8(vec![b'='; phrase.len()]).unwrap();
3384 eprintln!("{divider}\nRecover the intermediate account's ephemeral keypair file with");
3385 eprintln!("`solana-keygen recover` and the following {words}-word seed phrase:");
3386 eprintln!("{divider}\n{phrase}\n{divider}");
3387 eprintln!("To resume a deploy, pass the recovered keypair as the");
3388 eprintln!("[BUFFER_SIGNER] to `solana program deploy` or `solana program write-buffer'.");
3389 eprintln!("Or to recover the account's lamports, use:");
3390 eprintln!("{divider}\nsolana program close {ephemeral_pubkey}\n{divider}");
3391}
3392
3393async fn fetch_feature_set(
3394 rpc_client: &RpcClient,
3395) -> Result<FeatureSet, Box<dyn std::error::Error>> {
3396 let mut feature_set = FeatureSet::default();
3397 for feature_ids in FEATURE_NAMES
3398 .keys()
3399 .cloned()
3400 .collect::<Vec<Pubkey>>()
3401 .chunks(MAX_MULTIPLE_ACCOUNTS)
3402 {
3403 rpc_client
3404 .get_multiple_accounts(feature_ids)
3405 .await?
3406 .into_iter()
3407 .zip(feature_ids)
3408 .for_each(|(account, feature_id)| {
3409 let activation_slot = account.and_then(status_from_account);
3410
3411 if let Some(CliFeatureStatus::Active(slot)) = activation_slot {
3412 feature_set.activate(feature_id, slot);
3413 }
3414 });
3415 }
3416
3417 Ok(feature_set)
3418}
3419
3420#[cfg(test)]
3421mod tests {
3422 use {
3423 super::*,
3424 crate::{
3425 clap_app::get_clap_app,
3426 cli::{parse_command, process_command},
3427 },
3428 serde_json::Value,
3429 solana_cli_output::OutputFormat,
3430 solana_hash::Hash,
3431 solana_keypair::write_keypair_file,
3432 solana_sbpf::elf_parser::consts::{
3433 EI_OSABI, ELFCLASS64, ELFDATA2LSB, ELFMAG, ELFOSABI_NONE, EV_CURRENT,
3434 },
3435 };
3436
3437 fn make_tmp_path(name: &str) -> String {
3438 let out_dir = std::env::var("FARF_DIR").unwrap_or_else(|_| "farf".to_string());
3439 let keypair = Keypair::new();
3440
3441 let path = format!("{}/tmp/{}-{}", out_dir, name, keypair.pubkey());
3442
3443 let _ignored = std::fs::remove_dir_all(&path);
3445 let _ignored = std::fs::remove_file(&path);
3447
3448 path
3449 }
3450
3451 #[test]
3452 #[allow(clippy::cognitive_complexity)]
3453 fn test_cli_parse_deploy() {
3454 let test_commands = get_clap_app("test", "desc", "version");
3455
3456 let default_keypair = Keypair::new();
3457 let keypair_file = make_tmp_path("keypair_file");
3458 write_keypair_file(&default_keypair, &keypair_file).unwrap();
3459 let default_signer = DefaultSigner::new("", &keypair_file);
3460
3461 let test_command = test_commands.clone().get_matches_from(vec![
3462 "test",
3463 "program",
3464 "deploy",
3465 "/Users/test/program.so",
3466 ]);
3467 assert_eq!(
3468 parse_command(&test_command, &default_signer, &mut None).unwrap(),
3469 CliCommandInfo {
3470 command: CliCommand::Program(ProgramCliCommand::Deploy {
3471 program_location: Some("/Users/test/program.so".to_string()),
3472 fee_payer_signer_index: 0,
3473 buffer_signer_index: None,
3474 buffer_pubkey: None,
3475 program_signer_index: None,
3476 program_pubkey: None,
3477 upgrade_authority_signer_index: 0,
3478 is_final: false,
3479 max_len: None,
3480 skip_fee_check: false,
3481 compute_unit_price: None,
3482 max_sign_attempts: 5,
3483 auto_extend: true,
3484 use_rpc: false,
3485 skip_feature_verification: false,
3486 }),
3487 signers: vec![Box::new(read_keypair_file(&keypair_file).unwrap())],
3488 }
3489 );
3490
3491 let test_command = test_commands.clone().get_matches_from(vec![
3492 "test",
3493 "program",
3494 "deploy",
3495 "/Users/test/program.so",
3496 "--max-len",
3497 "42",
3498 ]);
3499 assert_eq!(
3500 parse_command(&test_command, &default_signer, &mut None).unwrap(),
3501 CliCommandInfo {
3502 command: CliCommand::Program(ProgramCliCommand::Deploy {
3503 program_location: Some("/Users/test/program.so".to_string()),
3504 fee_payer_signer_index: 0,
3505 buffer_signer_index: None,
3506 buffer_pubkey: None,
3507 program_signer_index: None,
3508 program_pubkey: None,
3509 upgrade_authority_signer_index: 0,
3510 is_final: false,
3511 max_len: Some(42),
3512 skip_fee_check: false,
3513 compute_unit_price: None,
3514 max_sign_attempts: 5,
3515 auto_extend: true,
3516 use_rpc: false,
3517 skip_feature_verification: false,
3518 }),
3519 signers: vec![Box::new(read_keypair_file(&keypair_file).unwrap())],
3520 }
3521 );
3522
3523 let buffer_keypair = Keypair::new();
3524 let buffer_keypair_file = make_tmp_path("buffer_keypair_file");
3525 write_keypair_file(&buffer_keypair, &buffer_keypair_file).unwrap();
3526 let test_command = test_commands.clone().get_matches_from(vec![
3527 "test",
3528 "program",
3529 "deploy",
3530 "--buffer",
3531 &buffer_keypair_file,
3532 ]);
3533 assert_eq!(
3534 parse_command(&test_command, &default_signer, &mut None).unwrap(),
3535 CliCommandInfo {
3536 command: CliCommand::Program(ProgramCliCommand::Deploy {
3537 program_location: None,
3538 fee_payer_signer_index: 0,
3539 buffer_signer_index: Some(1),
3540 buffer_pubkey: Some(buffer_keypair.pubkey()),
3541 program_signer_index: None,
3542 program_pubkey: None,
3543 upgrade_authority_signer_index: 0,
3544 is_final: false,
3545 max_len: None,
3546 skip_fee_check: false,
3547 compute_unit_price: None,
3548 max_sign_attempts: 5,
3549 auto_extend: true,
3550 use_rpc: false,
3551 skip_feature_verification: false,
3552 }),
3553 signers: vec![
3554 Box::new(read_keypair_file(&keypair_file).unwrap()),
3555 Box::new(read_keypair_file(&buffer_keypair_file).unwrap()),
3556 ],
3557 }
3558 );
3559
3560 let program_pubkey = Pubkey::new_unique();
3561 let test = test_commands.clone().get_matches_from(vec![
3562 "test",
3563 "program",
3564 "deploy",
3565 "/Users/test/program.so",
3566 "--program-id",
3567 &program_pubkey.to_string(),
3568 ]);
3569 assert_eq!(
3570 parse_command(&test, &default_signer, &mut None).unwrap(),
3571 CliCommandInfo {
3572 command: CliCommand::Program(ProgramCliCommand::Deploy {
3573 program_location: Some("/Users/test/program.so".to_string()),
3574 fee_payer_signer_index: 0,
3575 buffer_signer_index: None,
3576 buffer_pubkey: None,
3577 program_signer_index: None,
3578 program_pubkey: Some(program_pubkey),
3579 upgrade_authority_signer_index: 0,
3580 is_final: false,
3581 max_len: None,
3582 skip_fee_check: false,
3583 compute_unit_price: None,
3584 max_sign_attempts: 5,
3585 auto_extend: true,
3586 use_rpc: false,
3587 skip_feature_verification: false,
3588 }),
3589 signers: vec![Box::new(read_keypair_file(&keypair_file).unwrap())],
3590 }
3591 );
3592
3593 let program_keypair = Keypair::new();
3594 let program_keypair_file = make_tmp_path("program_keypair_file");
3595 write_keypair_file(&program_keypair, &program_keypair_file).unwrap();
3596 let test = test_commands.clone().get_matches_from(vec![
3597 "test",
3598 "program",
3599 "deploy",
3600 "/Users/test/program.so",
3601 "--program-id",
3602 &program_keypair_file,
3603 ]);
3604 assert_eq!(
3605 parse_command(&test, &default_signer, &mut None).unwrap(),
3606 CliCommandInfo {
3607 command: CliCommand::Program(ProgramCliCommand::Deploy {
3608 program_location: Some("/Users/test/program.so".to_string()),
3609 fee_payer_signer_index: 0,
3610 buffer_signer_index: None,
3611 buffer_pubkey: None,
3612 program_signer_index: Some(1),
3613 program_pubkey: Some(program_keypair.pubkey()),
3614 upgrade_authority_signer_index: 0,
3615 is_final: false,
3616 max_len: None,
3617 skip_fee_check: false,
3618 compute_unit_price: None,
3619 max_sign_attempts: 5,
3620 auto_extend: true,
3621 use_rpc: false,
3622 skip_feature_verification: false,
3623 }),
3624 signers: vec![
3625 Box::new(read_keypair_file(&keypair_file).unwrap()),
3626 Box::new(read_keypair_file(&program_keypair_file).unwrap()),
3627 ],
3628 }
3629 );
3630
3631 let authority_keypair = Keypair::new();
3632 let authority_keypair_file = make_tmp_path("authority_keypair_file");
3633 write_keypair_file(&authority_keypair, &authority_keypair_file).unwrap();
3634 let test_command = test_commands.clone().get_matches_from(vec![
3635 "test",
3636 "program",
3637 "deploy",
3638 "/Users/test/program.so",
3639 "--upgrade-authority",
3640 &authority_keypair_file,
3641 ]);
3642 assert_eq!(
3643 parse_command(&test_command, &default_signer, &mut None).unwrap(),
3644 CliCommandInfo {
3645 command: CliCommand::Program(ProgramCliCommand::Deploy {
3646 program_location: Some("/Users/test/program.so".to_string()),
3647 fee_payer_signer_index: 0,
3648 buffer_signer_index: None,
3649 buffer_pubkey: None,
3650 program_signer_index: None,
3651 program_pubkey: None,
3652 upgrade_authority_signer_index: 1,
3653 is_final: false,
3654 max_len: None,
3655 skip_fee_check: false,
3656 compute_unit_price: None,
3657 max_sign_attempts: 5,
3658 auto_extend: true,
3659 use_rpc: false,
3660 skip_feature_verification: false,
3661 }),
3662 signers: vec![
3663 Box::new(read_keypair_file(&keypair_file).unwrap()),
3664 Box::new(read_keypair_file(&authority_keypair_file).unwrap()),
3665 ],
3666 }
3667 );
3668
3669 let test_command = test_commands.clone().get_matches_from(vec![
3670 "test",
3671 "program",
3672 "deploy",
3673 "/Users/test/program.so",
3674 "--final",
3675 ]);
3676 assert_eq!(
3677 parse_command(&test_command, &default_signer, &mut None).unwrap(),
3678 CliCommandInfo {
3679 command: CliCommand::Program(ProgramCliCommand::Deploy {
3680 program_location: Some("/Users/test/program.so".to_string()),
3681 fee_payer_signer_index: 0,
3682 buffer_signer_index: None,
3683 buffer_pubkey: None,
3684 program_signer_index: None,
3685 program_pubkey: None,
3686 upgrade_authority_signer_index: 0,
3687 is_final: true,
3688 max_len: None,
3689 skip_fee_check: false,
3690 compute_unit_price: None,
3691 max_sign_attempts: 5,
3692 auto_extend: true,
3693 use_rpc: false,
3694 skip_feature_verification: false,
3695 }),
3696 signers: vec![Box::new(read_keypair_file(&keypair_file).unwrap())],
3697 }
3698 );
3699
3700 let test_command = test_commands.clone().get_matches_from(vec![
3701 "test",
3702 "program",
3703 "deploy",
3704 "/Users/test/program.so",
3705 "--max-sign-attempts",
3706 "1",
3707 ]);
3708 assert_eq!(
3709 parse_command(&test_command, &default_signer, &mut None).unwrap(),
3710 CliCommandInfo {
3711 command: CliCommand::Program(ProgramCliCommand::Deploy {
3712 program_location: Some("/Users/test/program.so".to_string()),
3713 fee_payer_signer_index: 0,
3714 buffer_signer_index: None,
3715 buffer_pubkey: None,
3716 program_signer_index: None,
3717 program_pubkey: None,
3718 upgrade_authority_signer_index: 0,
3719 is_final: false,
3720 max_len: None,
3721 skip_fee_check: false,
3722 compute_unit_price: None,
3723 max_sign_attempts: 1,
3724 auto_extend: true,
3725 use_rpc: false,
3726 skip_feature_verification: false,
3727 }),
3728 signers: vec![Box::new(read_keypair_file(&keypair_file).unwrap())],
3729 }
3730 );
3731
3732 let test_command = test_commands.clone().get_matches_from(vec![
3733 "test",
3734 "program",
3735 "deploy",
3736 "/Users/test/program.so",
3737 "--use-rpc",
3738 ]);
3739 assert_eq!(
3740 parse_command(&test_command, &default_signer, &mut None).unwrap(),
3741 CliCommandInfo {
3742 command: CliCommand::Program(ProgramCliCommand::Deploy {
3743 program_location: Some("/Users/test/program.so".to_string()),
3744 fee_payer_signer_index: 0,
3745 buffer_signer_index: None,
3746 buffer_pubkey: None,
3747 program_signer_index: None,
3748 program_pubkey: None,
3749 upgrade_authority_signer_index: 0,
3750 is_final: false,
3751 max_len: None,
3752 skip_fee_check: false,
3753 compute_unit_price: None,
3754 max_sign_attempts: 5,
3755 auto_extend: true,
3756 use_rpc: true,
3757 skip_feature_verification: false,
3758 }),
3759 signers: vec![Box::new(read_keypair_file(&keypair_file).unwrap())],
3760 }
3761 );
3762
3763 let test_command = test_commands.clone().get_matches_from(vec![
3764 "test",
3765 "program",
3766 "deploy",
3767 "/Users/test/program.so",
3768 "--skip-feature-verify",
3769 ]);
3770 assert_eq!(
3771 parse_command(&test_command, &default_signer, &mut None).unwrap(),
3772 CliCommandInfo {
3773 command: CliCommand::Program(ProgramCliCommand::Deploy {
3774 program_location: Some("/Users/test/program.so".to_string()),
3775 fee_payer_signer_index: 0,
3776 buffer_signer_index: None,
3777 buffer_pubkey: None,
3778 program_signer_index: None,
3779 program_pubkey: None,
3780 upgrade_authority_signer_index: 0,
3781 is_final: false,
3782 max_len: None,
3783 skip_fee_check: false,
3784 compute_unit_price: None,
3785 max_sign_attempts: 5,
3786 auto_extend: true,
3787 use_rpc: false,
3788 skip_feature_verification: true,
3789 }),
3790 signers: vec![Box::new(read_keypair_file(&keypair_file).unwrap())],
3791 }
3792 );
3793 }
3794
3795 #[test]
3796 fn test_cli_parse_upgrade() {
3797 let test_commands = get_clap_app("test", "desc", "version");
3798
3799 let default_keypair = Keypair::new();
3800 let keypair_file = make_tmp_path("keypair_file");
3801 write_keypair_file(&default_keypair, &keypair_file).unwrap();
3802 let default_signer = DefaultSigner::new("", &keypair_file);
3803
3804 let program_key = Pubkey::new_unique();
3805 let buffer_key = Pubkey::new_unique();
3806 let test_command = test_commands.clone().get_matches_from(vec![
3807 "test",
3808 "program",
3809 "upgrade",
3810 format!("{buffer_key}").as_str(),
3811 format!("{program_key}").as_str(),
3812 "--skip-feature-verify",
3813 ]);
3814 assert_eq!(
3815 parse_command(&test_command, &default_signer, &mut None).unwrap(),
3816 CliCommandInfo {
3817 command: CliCommand::Program(ProgramCliCommand::Upgrade {
3818 fee_payer_signer_index: 0,
3819 program_pubkey: program_key,
3820 buffer_pubkey: buffer_key,
3821 upgrade_authority_signer_index: 0,
3822 sign_only: false,
3823 dump_transaction_message: false,
3824 blockhash_query: BlockhashQuery::default(),
3825 skip_feature_verification: true,
3826 }),
3827 signers: vec![Box::new(read_keypair_file(&keypair_file).unwrap())],
3828 }
3829 );
3830 }
3831
3832 #[test]
3833 #[allow(clippy::cognitive_complexity)]
3834 fn test_cli_parse_write_buffer() {
3835 let test_commands = get_clap_app("test", "desc", "version");
3836
3837 let default_keypair = Keypair::new();
3838 let keypair_file = make_tmp_path("keypair_file");
3839 write_keypair_file(&default_keypair, &keypair_file).unwrap();
3840 let default_signer = DefaultSigner::new("", &keypair_file);
3841
3842 let test_command = test_commands.clone().get_matches_from(vec![
3844 "test",
3845 "program",
3846 "write-buffer",
3847 "/Users/test/program.so",
3848 ]);
3849 assert_eq!(
3850 parse_command(&test_command, &default_signer, &mut None).unwrap(),
3851 CliCommandInfo {
3852 command: CliCommand::Program(ProgramCliCommand::WriteBuffer {
3853 program_location: "/Users/test/program.so".to_string(),
3854 fee_payer_signer_index: 0,
3855 buffer_signer_index: None,
3856 buffer_pubkey: None,
3857 buffer_authority_signer_index: 0,
3858 max_len: None,
3859 skip_fee_check: false,
3860 compute_unit_price: None,
3861 max_sign_attempts: 5,
3862 use_rpc: false,
3863 skip_feature_verification: false,
3864 }),
3865 signers: vec![Box::new(read_keypair_file(&keypair_file).unwrap())],
3866 }
3867 );
3868
3869 let test_command = test_commands.clone().get_matches_from(vec![
3871 "test",
3872 "program",
3873 "write-buffer",
3874 "/Users/test/program.so",
3875 "--max-len",
3876 "42",
3877 ]);
3878 assert_eq!(
3879 parse_command(&test_command, &default_signer, &mut None).unwrap(),
3880 CliCommandInfo {
3881 command: CliCommand::Program(ProgramCliCommand::WriteBuffer {
3882 program_location: "/Users/test/program.so".to_string(),
3883 fee_payer_signer_index: 0,
3884 buffer_signer_index: None,
3885 buffer_pubkey: None,
3886 buffer_authority_signer_index: 0,
3887 max_len: Some(42),
3888 skip_fee_check: false,
3889 compute_unit_price: None,
3890 max_sign_attempts: 5,
3891 use_rpc: false,
3892 skip_feature_verification: false,
3893 }),
3894 signers: vec![Box::new(read_keypair_file(&keypair_file).unwrap())],
3895 }
3896 );
3897
3898 let buffer_keypair = Keypair::new();
3900 let buffer_keypair_file = make_tmp_path("buffer_keypair_file");
3901 write_keypair_file(&buffer_keypair, &buffer_keypair_file).unwrap();
3902 let test_command = test_commands.clone().get_matches_from(vec![
3903 "test",
3904 "program",
3905 "write-buffer",
3906 "/Users/test/program.so",
3907 "--buffer",
3908 &buffer_keypair_file,
3909 ]);
3910 assert_eq!(
3911 parse_command(&test_command, &default_signer, &mut None).unwrap(),
3912 CliCommandInfo {
3913 command: CliCommand::Program(ProgramCliCommand::WriteBuffer {
3914 program_location: "/Users/test/program.so".to_string(),
3915 fee_payer_signer_index: 0,
3916 buffer_signer_index: Some(1),
3917 buffer_pubkey: Some(buffer_keypair.pubkey()),
3918 buffer_authority_signer_index: 0,
3919 max_len: None,
3920 skip_fee_check: false,
3921 compute_unit_price: None,
3922 max_sign_attempts: 5,
3923 use_rpc: false,
3924 skip_feature_verification: false,
3925 }),
3926 signers: vec![
3927 Box::new(read_keypair_file(&keypair_file).unwrap()),
3928 Box::new(read_keypair_file(&buffer_keypair_file).unwrap()),
3929 ],
3930 }
3931 );
3932
3933 let authority_keypair = Keypair::new();
3935 let authority_keypair_file = make_tmp_path("authority_keypair_file");
3936 write_keypair_file(&authority_keypair, &authority_keypair_file).unwrap();
3937 let test_command = test_commands.clone().get_matches_from(vec![
3938 "test",
3939 "program",
3940 "write-buffer",
3941 "/Users/test/program.so",
3942 "--buffer-authority",
3943 &authority_keypair_file,
3944 ]);
3945 assert_eq!(
3946 parse_command(&test_command, &default_signer, &mut None).unwrap(),
3947 CliCommandInfo {
3948 command: CliCommand::Program(ProgramCliCommand::WriteBuffer {
3949 program_location: "/Users/test/program.so".to_string(),
3950 fee_payer_signer_index: 0,
3951 buffer_signer_index: None,
3952 buffer_pubkey: None,
3953 buffer_authority_signer_index: 1,
3954 max_len: None,
3955 skip_fee_check: false,
3956 compute_unit_price: None,
3957 max_sign_attempts: 5,
3958 use_rpc: false,
3959 skip_feature_verification: false,
3960 }),
3961 signers: vec![
3962 Box::new(read_keypair_file(&keypair_file).unwrap()),
3963 Box::new(read_keypair_file(&authority_keypair_file).unwrap()),
3964 ],
3965 }
3966 );
3967
3968 let buffer_keypair = Keypair::new();
3970 let buffer_keypair_file = make_tmp_path("buffer_keypair_file");
3971 write_keypair_file(&buffer_keypair, &buffer_keypair_file).unwrap();
3972 let authority_keypair = Keypair::new();
3973 let authority_keypair_file = make_tmp_path("authority_keypair_file");
3974 write_keypair_file(&authority_keypair, &authority_keypair_file).unwrap();
3975 let test_command = test_commands.clone().get_matches_from(vec![
3976 "test",
3977 "program",
3978 "write-buffer",
3979 "/Users/test/program.so",
3980 "--buffer",
3981 &buffer_keypair_file,
3982 "--buffer-authority",
3983 &authority_keypair_file,
3984 ]);
3985 assert_eq!(
3986 parse_command(&test_command, &default_signer, &mut None).unwrap(),
3987 CliCommandInfo {
3988 command: CliCommand::Program(ProgramCliCommand::WriteBuffer {
3989 program_location: "/Users/test/program.so".to_string(),
3990 fee_payer_signer_index: 0,
3991 buffer_signer_index: Some(1),
3992 buffer_pubkey: Some(buffer_keypair.pubkey()),
3993 buffer_authority_signer_index: 2,
3994 max_len: None,
3995 skip_fee_check: false,
3996 compute_unit_price: None,
3997 max_sign_attempts: 5,
3998 use_rpc: false,
3999 skip_feature_verification: false,
4000 }),
4001 signers: vec![
4002 Box::new(read_keypair_file(&keypair_file).unwrap()),
4003 Box::new(read_keypair_file(&buffer_keypair_file).unwrap()),
4004 Box::new(read_keypair_file(&authority_keypair_file).unwrap()),
4005 ],
4006 }
4007 );
4008
4009 let test_command = test_commands.clone().get_matches_from(vec![
4011 "test",
4012 "program",
4013 "write-buffer",
4014 "/Users/test/program.so",
4015 "--max-sign-attempts",
4016 "10",
4017 ]);
4018 assert_eq!(
4019 parse_command(&test_command, &default_signer, &mut None).unwrap(),
4020 CliCommandInfo {
4021 command: CliCommand::Program(ProgramCliCommand::WriteBuffer {
4022 program_location: "/Users/test/program.so".to_string(),
4023 fee_payer_signer_index: 0,
4024 buffer_signer_index: None,
4025 buffer_pubkey: None,
4026 buffer_authority_signer_index: 0,
4027 max_len: None,
4028 skip_fee_check: false,
4029 compute_unit_price: None,
4030 max_sign_attempts: 10,
4031 use_rpc: false,
4032 skip_feature_verification: false
4033 }),
4034 signers: vec![Box::new(read_keypair_file(&keypair_file).unwrap())],
4035 }
4036 );
4037
4038 let test_command = test_commands.clone().get_matches_from(vec![
4040 "test",
4041 "program",
4042 "write-buffer",
4043 "/Users/test/program.so",
4044 "--skip-feature-verify",
4045 ]);
4046 assert_eq!(
4047 parse_command(&test_command, &default_signer, &mut None).unwrap(),
4048 CliCommandInfo {
4049 command: CliCommand::Program(ProgramCliCommand::WriteBuffer {
4050 program_location: "/Users/test/program.so".to_string(),
4051 fee_payer_signer_index: 0,
4052 buffer_signer_index: None,
4053 buffer_pubkey: None,
4054 buffer_authority_signer_index: 0,
4055 max_len: None,
4056 skip_fee_check: false,
4057 compute_unit_price: None,
4058 max_sign_attempts: 5,
4059 use_rpc: false,
4060 skip_feature_verification: true,
4061 }),
4062 signers: vec![Box::new(read_keypair_file(&keypair_file).unwrap())],
4063 }
4064 );
4065 }
4066
4067 #[test]
4068 #[allow(clippy::cognitive_complexity)]
4069 fn test_cli_parse_set_upgrade_authority() {
4070 let test_commands = get_clap_app("test", "desc", "version");
4071
4072 let default_keypair = Keypair::new();
4073 let keypair_file = make_tmp_path("keypair_file");
4074 write_keypair_file(&default_keypair, &keypair_file).unwrap();
4075 let default_signer = DefaultSigner::new("", &keypair_file);
4076
4077 let program_pubkey = Pubkey::new_unique();
4078 let new_authority_pubkey = Pubkey::new_unique();
4079 let blockhash = Hash::new_unique();
4080
4081 let test_command = test_commands.clone().get_matches_from(vec![
4082 "test",
4083 "program",
4084 "set-upgrade-authority",
4085 &program_pubkey.to_string(),
4086 "--new-upgrade-authority",
4087 &new_authority_pubkey.to_string(),
4088 "--skip-new-upgrade-authority-signer-check",
4089 "--sign-only",
4090 "--dump-transaction-message",
4091 "--blockhash",
4092 blockhash.to_string().as_str(),
4093 ]);
4094 assert_eq!(
4095 parse_command(&test_command, &default_signer, &mut None).unwrap(),
4096 CliCommandInfo {
4097 command: CliCommand::Program(ProgramCliCommand::SetUpgradeAuthority {
4098 program_pubkey,
4099 upgrade_authority_index: Some(0),
4100 new_upgrade_authority: Some(new_authority_pubkey),
4101 sign_only: true,
4102 dump_transaction_message: true,
4103 blockhash_query: BlockhashQuery::new(Some(blockhash), true, None),
4104 }),
4105 signers: vec![Box::new(read_keypair_file(&keypair_file).unwrap())],
4106 }
4107 );
4108
4109 let program_pubkey = Pubkey::new_unique();
4110 let new_authority_pubkey = Keypair::new();
4111 let new_authority_pubkey_file = make_tmp_path("authority_keypair_file");
4112 write_keypair_file(&new_authority_pubkey, &new_authority_pubkey_file).unwrap();
4113 let test_command = test_commands.clone().get_matches_from(vec![
4114 "test",
4115 "program",
4116 "set-upgrade-authority",
4117 &program_pubkey.to_string(),
4118 "--new-upgrade-authority",
4119 &new_authority_pubkey_file,
4120 "--skip-new-upgrade-authority-signer-check",
4121 ]);
4122 assert_eq!(
4123 parse_command(&test_command, &default_signer, &mut None).unwrap(),
4124 CliCommandInfo {
4125 command: CliCommand::Program(ProgramCliCommand::SetUpgradeAuthority {
4126 program_pubkey,
4127 upgrade_authority_index: Some(0),
4128 new_upgrade_authority: Some(new_authority_pubkey.pubkey()),
4129 sign_only: false,
4130 dump_transaction_message: false,
4131 blockhash_query: BlockhashQuery::default(),
4132 }),
4133 signers: vec![Box::new(read_keypair_file(&keypair_file).unwrap())],
4134 }
4135 );
4136
4137 let blockhash = Hash::new_unique();
4138 let program_pubkey = Pubkey::new_unique();
4139 let new_authority_pubkey = Keypair::new();
4140 let new_authority_pubkey_file = make_tmp_path("authority_keypair_file");
4141 write_keypair_file(&new_authority_pubkey, &new_authority_pubkey_file).unwrap();
4142 let test_command = test_commands.clone().get_matches_from(vec![
4143 "test",
4144 "program",
4145 "set-upgrade-authority",
4146 &program_pubkey.to_string(),
4147 "--new-upgrade-authority",
4148 &new_authority_pubkey_file,
4149 "--sign-only",
4150 "--dump-transaction-message",
4151 "--blockhash",
4152 blockhash.to_string().as_str(),
4153 ]);
4154 assert_eq!(
4155 parse_command(&test_command, &default_signer, &mut None).unwrap(),
4156 CliCommandInfo {
4157 command: CliCommand::Program(ProgramCliCommand::SetUpgradeAuthorityChecked {
4158 program_pubkey,
4159 upgrade_authority_index: 0,
4160 new_upgrade_authority_index: 1,
4161 sign_only: true,
4162 dump_transaction_message: true,
4163 blockhash_query: BlockhashQuery::new(Some(blockhash), true, None),
4164 }),
4165 signers: vec![
4166 Box::new(read_keypair_file(&keypair_file).unwrap()),
4167 Box::new(read_keypair_file(&new_authority_pubkey_file).unwrap()),
4168 ],
4169 }
4170 );
4171
4172 let program_pubkey = Pubkey::new_unique();
4173 let new_authority_pubkey = Keypair::new();
4174 let new_authority_pubkey_file = make_tmp_path("authority_keypair_file");
4175 write_keypair_file(&new_authority_pubkey, new_authority_pubkey_file).unwrap();
4176 let test_command = test_commands.clone().get_matches_from(vec![
4177 "test",
4178 "program",
4179 "set-upgrade-authority",
4180 &program_pubkey.to_string(),
4181 "--final",
4182 ]);
4183 assert_eq!(
4184 parse_command(&test_command, &default_signer, &mut None).unwrap(),
4185 CliCommandInfo {
4186 command: CliCommand::Program(ProgramCliCommand::SetUpgradeAuthority {
4187 program_pubkey,
4188 upgrade_authority_index: Some(0),
4189 new_upgrade_authority: None,
4190 sign_only: false,
4191 dump_transaction_message: false,
4192 blockhash_query: BlockhashQuery::default(),
4193 }),
4194 signers: vec![Box::new(read_keypair_file(&keypair_file).unwrap())],
4195 }
4196 );
4197
4198 let program_pubkey = Pubkey::new_unique();
4199 let authority = Keypair::new();
4200 let authority_keypair_file = make_tmp_path("authority_keypair_file");
4201 write_keypair_file(&authority, &authority_keypair_file).unwrap();
4202 let test_command = test_commands.clone().get_matches_from(vec![
4203 "test",
4204 "program",
4205 "set-upgrade-authority",
4206 &program_pubkey.to_string(),
4207 "--upgrade-authority",
4208 &authority_keypair_file,
4209 "--final",
4210 ]);
4211 assert_eq!(
4212 parse_command(&test_command, &default_signer, &mut None).unwrap(),
4213 CliCommandInfo {
4214 command: CliCommand::Program(ProgramCliCommand::SetUpgradeAuthority {
4215 program_pubkey,
4216 upgrade_authority_index: Some(1),
4217 new_upgrade_authority: None,
4218 sign_only: false,
4219 dump_transaction_message: false,
4220 blockhash_query: BlockhashQuery::default(),
4221 }),
4222 signers: vec![
4223 Box::new(read_keypair_file(&keypair_file).unwrap()),
4224 Box::new(read_keypair_file(&authority_keypair_file).unwrap()),
4225 ],
4226 }
4227 );
4228 }
4229
4230 #[test]
4231 #[allow(clippy::cognitive_complexity)]
4232 fn test_cli_parse_set_buffer_authority() {
4233 let test_commands = get_clap_app("test", "desc", "version");
4234
4235 let default_keypair = Keypair::new();
4236 let keypair_file = make_tmp_path("keypair_file");
4237 write_keypair_file(&default_keypair, &keypair_file).unwrap();
4238 let default_signer = DefaultSigner::new("", &keypair_file);
4239
4240 let buffer_pubkey = Pubkey::new_unique();
4241 let new_authority_pubkey = Pubkey::new_unique();
4242 let test_command = test_commands.clone().get_matches_from(vec![
4243 "test",
4244 "program",
4245 "set-buffer-authority",
4246 &buffer_pubkey.to_string(),
4247 "--new-buffer-authority",
4248 &new_authority_pubkey.to_string(),
4249 ]);
4250 assert_eq!(
4251 parse_command(&test_command, &default_signer, &mut None).unwrap(),
4252 CliCommandInfo {
4253 command: CliCommand::Program(ProgramCliCommand::SetBufferAuthority {
4254 buffer_pubkey,
4255 buffer_authority_index: Some(0),
4256 new_buffer_authority: new_authority_pubkey,
4257 }),
4258 signers: vec![Box::new(read_keypair_file(&keypair_file).unwrap())],
4259 }
4260 );
4261
4262 let buffer_pubkey = Pubkey::new_unique();
4263 let new_authority_keypair = Keypair::new();
4264 let new_authority_keypair_file = make_tmp_path("authority_keypair_file");
4265 write_keypair_file(&new_authority_keypair, &new_authority_keypair_file).unwrap();
4266 let test_command = test_commands.clone().get_matches_from(vec![
4267 "test",
4268 "program",
4269 "set-buffer-authority",
4270 &buffer_pubkey.to_string(),
4271 "--new-buffer-authority",
4272 &new_authority_keypair_file,
4273 ]);
4274 assert_eq!(
4275 parse_command(&test_command, &default_signer, &mut None).unwrap(),
4276 CliCommandInfo {
4277 command: CliCommand::Program(ProgramCliCommand::SetBufferAuthority {
4278 buffer_pubkey,
4279 buffer_authority_index: Some(0),
4280 new_buffer_authority: new_authority_keypair.pubkey(),
4281 }),
4282 signers: vec![Box::new(read_keypair_file(&keypair_file).unwrap())],
4283 }
4284 );
4285 }
4286
4287 #[test]
4288 #[allow(clippy::cognitive_complexity)]
4289 fn test_cli_parse_show() {
4290 let test_commands = get_clap_app("test", "desc", "version");
4291
4292 let default_keypair = Keypair::new();
4293 let keypair_file = make_tmp_path("keypair_file");
4294 write_keypair_file(&default_keypair, &keypair_file).unwrap();
4295 let default_signer = DefaultSigner::new("", &keypair_file);
4296
4297 let buffer_pubkey = Pubkey::new_unique();
4299 let authority_keypair = Keypair::new();
4300 let authority_keypair_file = make_tmp_path("authority_keypair_file");
4301 write_keypair_file(&authority_keypair, &authority_keypair_file).unwrap();
4302
4303 let test_command = test_commands.clone().get_matches_from(vec![
4304 "test",
4305 "program",
4306 "show",
4307 &buffer_pubkey.to_string(),
4308 ]);
4309 assert_eq!(
4310 parse_command(&test_command, &default_signer, &mut None).unwrap(),
4311 CliCommandInfo::without_signers(CliCommand::Program(ProgramCliCommand::Show {
4312 account_pubkey: Some(buffer_pubkey),
4313 authority_pubkey: default_keypair.pubkey(),
4314 get_programs: false,
4315 get_buffers: false,
4316 all: false,
4317 use_lamports_unit: false,
4318 }))
4319 );
4320
4321 let test_command = test_commands.clone().get_matches_from(vec![
4322 "test",
4323 "program",
4324 "show",
4325 "--programs",
4326 "--all",
4327 "--lamports",
4328 ]);
4329 assert_eq!(
4330 parse_command(&test_command, &default_signer, &mut None).unwrap(),
4331 CliCommandInfo::without_signers(CliCommand::Program(ProgramCliCommand::Show {
4332 account_pubkey: None,
4333 authority_pubkey: default_keypair.pubkey(),
4334 get_programs: true,
4335 get_buffers: false,
4336 all: true,
4337 use_lamports_unit: true,
4338 }))
4339 );
4340
4341 let test_command = test_commands.clone().get_matches_from(vec![
4342 "test",
4343 "program",
4344 "show",
4345 "--buffers",
4346 "--all",
4347 "--lamports",
4348 ]);
4349 assert_eq!(
4350 parse_command(&test_command, &default_signer, &mut None).unwrap(),
4351 CliCommandInfo::without_signers(CliCommand::Program(ProgramCliCommand::Show {
4352 account_pubkey: None,
4353 authority_pubkey: default_keypair.pubkey(),
4354 get_programs: false,
4355 get_buffers: true,
4356 all: true,
4357 use_lamports_unit: true,
4358 }))
4359 );
4360
4361 let test_command = test_commands.clone().get_matches_from(vec![
4362 "test",
4363 "program",
4364 "show",
4365 "--buffers",
4366 "--buffer-authority",
4367 &authority_keypair.pubkey().to_string(),
4368 ]);
4369 assert_eq!(
4370 parse_command(&test_command, &default_signer, &mut None).unwrap(),
4371 CliCommandInfo::without_signers(CliCommand::Program(ProgramCliCommand::Show {
4372 account_pubkey: None,
4373 authority_pubkey: authority_keypair.pubkey(),
4374 get_programs: false,
4375 get_buffers: true,
4376 all: false,
4377 use_lamports_unit: false,
4378 }))
4379 );
4380
4381 let test_command = test_commands.clone().get_matches_from(vec![
4382 "test",
4383 "program",
4384 "show",
4385 "--buffers",
4386 "--buffer-authority",
4387 &authority_keypair_file,
4388 ]);
4389 assert_eq!(
4390 parse_command(&test_command, &default_signer, &mut None).unwrap(),
4391 CliCommandInfo::without_signers(CliCommand::Program(ProgramCliCommand::Show {
4392 account_pubkey: None,
4393 authority_pubkey: authority_keypair.pubkey(),
4394 get_programs: false,
4395 get_buffers: true,
4396 all: false,
4397 use_lamports_unit: false,
4398 }))
4399 );
4400 }
4401
4402 #[test]
4403 #[allow(clippy::cognitive_complexity)]
4404 fn test_cli_parse_close() {
4405 let test_commands = get_clap_app("test", "desc", "version");
4406
4407 let default_keypair = Keypair::new();
4408 let keypair_file = make_tmp_path("keypair_file");
4409 write_keypair_file(&default_keypair, &keypair_file).unwrap();
4410 let default_signer = DefaultSigner::new("", &keypair_file);
4411
4412 let buffer_pubkey = Pubkey::new_unique();
4414 let recipient_pubkey = Pubkey::new_unique();
4415 let authority_keypair = Keypair::new();
4416 let authority_keypair_file = make_tmp_path("authority_keypair_file");
4417
4418 let test_command = test_commands.clone().get_matches_from(vec![
4419 "test",
4420 "program",
4421 "close",
4422 &buffer_pubkey.to_string(),
4423 ]);
4424 assert_eq!(
4425 parse_command(&test_command, &default_signer, &mut None).unwrap(),
4426 CliCommandInfo {
4427 command: CliCommand::Program(ProgramCliCommand::Close {
4428 account_pubkey: Some(buffer_pubkey),
4429 recipient_pubkey: default_keypair.pubkey(),
4430 authority_index: 0,
4431 use_lamports_unit: false,
4432 bypass_warning: false,
4433 }),
4434 signers: vec![Box::new(read_keypair_file(&keypair_file).unwrap())],
4435 }
4436 );
4437
4438 write_keypair_file(&authority_keypair, &authority_keypair_file).unwrap();
4440 let test_command = test_commands.clone().get_matches_from(vec![
4441 "test",
4442 "program",
4443 "close",
4444 &buffer_pubkey.to_string(),
4445 "--bypass-warning",
4446 ]);
4447 assert_eq!(
4448 parse_command(&test_command, &default_signer, &mut None).unwrap(),
4449 CliCommandInfo {
4450 command: CliCommand::Program(ProgramCliCommand::Close {
4451 account_pubkey: Some(buffer_pubkey),
4452 recipient_pubkey: default_keypair.pubkey(),
4453 authority_index: 0,
4454 use_lamports_unit: false,
4455 bypass_warning: true,
4456 }),
4457 signers: vec![Box::new(read_keypair_file(&keypair_file).unwrap())],
4458 }
4459 );
4460
4461 write_keypair_file(&authority_keypair, &authority_keypair_file).unwrap();
4463 let test_command = test_commands.clone().get_matches_from(vec![
4464 "test",
4465 "program",
4466 "close",
4467 &buffer_pubkey.to_string(),
4468 "--buffer-authority",
4469 &authority_keypair_file,
4470 ]);
4471 assert_eq!(
4472 parse_command(&test_command, &default_signer, &mut None).unwrap(),
4473 CliCommandInfo {
4474 command: CliCommand::Program(ProgramCliCommand::Close {
4475 account_pubkey: Some(buffer_pubkey),
4476 recipient_pubkey: default_keypair.pubkey(),
4477 authority_index: 1,
4478 use_lamports_unit: false,
4479 bypass_warning: false,
4480 }),
4481 signers: vec![
4482 Box::new(read_keypair_file(&keypair_file).unwrap()),
4483 Box::new(read_keypair_file(&authority_keypair_file).unwrap()),
4484 ],
4485 }
4486 );
4487
4488 let test_command = test_commands.clone().get_matches_from(vec![
4490 "test",
4491 "program",
4492 "close",
4493 &buffer_pubkey.to_string(),
4494 "--recipient",
4495 &recipient_pubkey.to_string(),
4496 ]);
4497 assert_eq!(
4498 parse_command(&test_command, &default_signer, &mut None).unwrap(),
4499 CliCommandInfo {
4500 command: CliCommand::Program(ProgramCliCommand::Close {
4501 account_pubkey: Some(buffer_pubkey),
4502 recipient_pubkey,
4503 authority_index: 0,
4504 use_lamports_unit: false,
4505 bypass_warning: false,
4506 }),
4507 signers: vec![Box::new(read_keypair_file(&keypair_file).unwrap()),],
4508 }
4509 );
4510
4511 let test_command = test_commands.clone().get_matches_from(vec![
4513 "test",
4514 "program",
4515 "close",
4516 "--buffers",
4517 "--lamports",
4518 ]);
4519 assert_eq!(
4520 parse_command(&test_command, &default_signer, &mut None).unwrap(),
4521 CliCommandInfo {
4522 command: CliCommand::Program(ProgramCliCommand::Close {
4523 account_pubkey: None,
4524 recipient_pubkey: default_keypair.pubkey(),
4525 authority_index: 0,
4526 use_lamports_unit: true,
4527 bypass_warning: false,
4528 }),
4529 signers: vec![Box::new(read_keypair_file(&keypair_file).unwrap()),],
4530 }
4531 );
4532 }
4533
4534 #[test]
4535 fn test_cli_parse_extend_program() {
4536 let test_commands = get_clap_app("test", "desc", "version");
4537
4538 let default_keypair = Keypair::new();
4539 let keypair_file = make_tmp_path("keypair_file");
4540 write_keypair_file(&default_keypair, &keypair_file).unwrap();
4541 let default_signer = DefaultSigner::new("", &keypair_file);
4542
4543 let program_pubkey = Pubkey::new_unique();
4545 let additional_bytes = 100;
4546
4547 let test_command = test_commands.clone().get_matches_from(vec![
4548 "test",
4549 "program",
4550 "extend",
4551 &program_pubkey.to_string(),
4552 &additional_bytes.to_string(),
4553 ]);
4554 assert_eq!(
4555 parse_command(&test_command, &default_signer, &mut None).unwrap(),
4556 CliCommandInfo {
4557 command: CliCommand::Program(ProgramCliCommand::ExtendProgram {
4558 program_pubkey,
4559 payer_signer_index: 0,
4560 additional_bytes
4561 }),
4562 signers: vec![Box::new(read_keypair_file(&keypair_file).unwrap())],
4563 }
4564 );
4565
4566 let payer_keypair = Keypair::new();
4568 let payer_keypair_file = make_tmp_path("payer_keypair_file");
4569 write_keypair_file(&payer_keypair, &payer_keypair_file).unwrap();
4570 let test_command = test_commands.clone().get_matches_from(vec![
4571 "test",
4572 "program",
4573 "extend",
4574 &program_pubkey.to_string(),
4575 &additional_bytes.to_string(),
4576 "--payer",
4577 &payer_keypair_file,
4578 ]);
4579 assert_eq!(
4580 parse_command(&test_command, &default_signer, &mut None).unwrap(),
4581 CliCommandInfo {
4582 command: CliCommand::Program(ProgramCliCommand::ExtendProgram {
4583 program_pubkey,
4584 payer_signer_index: 1,
4585 additional_bytes
4586 }),
4587 signers: vec![
4588 Box::new(read_keypair_file(&keypair_file).unwrap()),
4589 Box::new(read_keypair_file(&payer_keypair_file).unwrap()),
4590 ],
4591 }
4592 );
4593 }
4594
4595 #[tokio::test]
4596 async fn test_cli_keypair_file() {
4597 agave_logger::setup();
4598
4599 let default_keypair = Keypair::new();
4600 let program_pubkey = Keypair::new();
4601 let deploy_path = make_tmp_path("deploy");
4602 let mut program_location = PathBuf::from(deploy_path.clone());
4603 program_location.push("noop");
4604 program_location.set_extension("so");
4605 let mut pathbuf = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
4606 pathbuf.push("tests");
4607 pathbuf.push("fixtures");
4608 pathbuf.push("noop");
4609 pathbuf.set_extension("so");
4610 let program_keypair_location = program_location.with_file_name("noop-keypair.json");
4611 std::fs::create_dir_all(deploy_path).unwrap();
4612 std::fs::copy(pathbuf, program_location.as_os_str()).unwrap();
4613 write_keypair_file(&program_pubkey, program_keypair_location).unwrap();
4614
4615 let config = CliConfig {
4616 rpc_client: Some(Arc::new(RpcClient::new_mock("".to_string()))),
4617 command: CliCommand::Program(ProgramCliCommand::Deploy {
4618 program_location: Some(program_location.to_str().unwrap().to_string()),
4619 fee_payer_signer_index: 0,
4620 buffer_signer_index: None,
4621 buffer_pubkey: None,
4622 program_signer_index: None,
4623 program_pubkey: None,
4624 upgrade_authority_signer_index: 0,
4625 is_final: false,
4626 max_len: None,
4627 skip_fee_check: false,
4628 compute_unit_price: None,
4629 max_sign_attempts: 5,
4630 auto_extend: true,
4631 use_rpc: false,
4632 skip_feature_verification: true,
4633 }),
4634 signers: vec![&default_keypair],
4635 output_format: OutputFormat::JsonCompact,
4636 ..CliConfig::default()
4637 };
4638
4639 let result = process_command(&config).await;
4640 let json: Value = serde_json::from_str(&result.unwrap()).unwrap();
4641 let program_id = json
4642 .as_object()
4643 .unwrap()
4644 .get("programId")
4645 .unwrap()
4646 .as_str()
4647 .unwrap();
4648
4649 assert_eq!(
4650 program_id.parse::<Pubkey>().unwrap(),
4651 program_pubkey.pubkey()
4652 );
4653 }
4654
4655 fn fake_elf(e_flags: u32) -> Vec<u8> {
4658 let mut bytes = vec![0u8; 64];
4659 bytes[..4].copy_from_slice(&ELFMAG);
4660 bytes[4] = ELFCLASS64;
4661 bytes[5] = ELFDATA2LSB;
4662 bytes[6] = EV_CURRENT as u8;
4663 bytes[EI_OSABI as usize] = ELFOSABI_NONE;
4664 bytes[48..52].copy_from_slice(&e_flags.to_le_bytes());
4665 bytes
4666 }
4667
4668 fn deploy_config(versions: std::ops::RangeInclusive<SBPFVersion>) -> Config {
4669 Config {
4670 enabled_sbpf_versions: versions,
4671 ..Config::default()
4672 }
4673 }
4674
4675 #[test]
4676 fn test_explain_unsupported_sbpf_version() {
4677 let elf = fake_elf(1); let config = deploy_config(SBPFVersion::V3..=SBPFVersion::V3);
4679 let err = EbpfError::ElfError(ElfError::UnsupportedSBPFVersion);
4680 let msg = explain_elf_error(&err, &elf, &config);
4681 assert!(msg.contains("SBPF v1"), "got: {msg}");
4682 assert!(msg.contains("enabled: v3"), "got: {msg}");
4683 }
4684}