Skip to main content

solana_cli/
program.rs

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