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