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