Skip to main content

solana_cli/
program.rs

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